BÀI VIẾT // [DEVOPS] 10 Bash Script Đơn Giản Để Tự Động Hóa Quy Trình DevOps
10 Bash Script Đơn Giản Để Tự Động Hóa Quy Trình DevOps

10 Bash Script Đơn Giản Để Tự Động Hóa Quy Trình DevOps

Tuyển tập 10 Bash script nhỏ mà hữu ích cho DevOps: backup, dọn tmp, giám sát disk, restart service, batch rename, xoay log, nén archive, kiểm tra SSL, ping hàng loạt và đồng bộ thư mục.

DEVOPS BASH

Vì sao nên sưu tầm script nhỏ?

Trong series Bash, chúng ta đã học từ biến, điều kiện, vòng lặp đến error handling, cronbest practice. Nhưng giá trị thực sự của Bash nằm ở những script nhỏ chạy mỗi ngày: backup đêm, dọn disk đầy, restart service crash lúc 2 giờ sáng.

Bài viết này tổng hợp 10 script gọn nhẹ, đúng chất DevOps. Mỗi script dưới 30 dòng, tuân thủ set -euo pipefail, quote biến đầy đủ, và có thể gắn vào cron ngay. Đây là sườn ý tưởng tham khảo từ cộng đồng, được viết lại hoàn toàn theo chuẩn của series.


Sao lưu thư mục theo ngày

Nhu cầu cơ bản nhất: sao lưu thư mục config hoặc data mỗi đêm.

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
16
17
#!/usr/bin/env bash
set -euo pipefail

SOURCE_DIR="${1:-/etc/myapp}"
BACKUP_ROOT="${2:-/var/backups/myapp}"
KEEP_DAYS="${KEEP_DAYS:-7}"

DATE_TAG="$(date +%Y-%m-%d)"
DEST_DIR="${BACKUP_ROOT}/${DATE_TAG}"

mkdir -p "$DEST_DIR"
tar -czf "${DEST_DIR}/backup.tar.gz" -C "$(dirname "$SOURCE_DIR")" "$(basename "$SOURCE_DIR")"

echo "Backup completed: ${DEST_DIR}/backup.tar.gz"

# Xóa bản backup cũ hơn KEEP_DAYS ngày
find "$BACKUP_ROOT" -maxdepth 1 -type d -mtime "+${KEEP_DAYS}" -exec rm -rf {} +

Giải thích:

  • tar -czf nén toàn bộ thư mục thành một file duy nhất, dễ copy đi nơi khác.
  • -C kết hợp dirname / basename để tránh lưu absolute path trong archive.
  • find -mtime tự dọn backup cũ, tránh đầy disk — lỗi kinh điển khi chỉ backup mà quên dọn.
  • Nhận SOURCE_DIR qua tham số thay vì hardcode, tái sử dụng cho nhiều app.

Dọn file tạm và cache cũ

Thư mục /tmp và thư mục cache phình to là nguyên nhân phổ biến gây đầy disk.

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
16
#!/usr/bin/env bash
set -euo pipefail

TARGET_DIR="${1:-/tmp}"
OLDER_THAN_DAYS="${2:-7}"

if [[ ! -d "$TARGET_DIR" ]]; then
  echo "ERROR: directory not found: $TARGET_DIR" >&2
  exit 1
fi

echo "Cleaning files older than ${OLDER_THAN_DAYS} days in ${TARGET_DIR}..."
find "$TARGET_DIR" -type f -mtime "+${OLDER_THAN_DAYS}" -print -delete

echo "Cleanup done."
du -sh "$TARGET_DIR"

Giải thích:

  • find -mtime +7 -delete chỉ xóa file cũ, không đụng đến file đang dùng.
  • -print trước -delete để có log những gì đã xóa, tiện audit.
  • Kiểm tra -d trước khi xóa để tránh typo xóa nhầm thư mục.
  • Chạy khô trước với -print thay vì -delete khi thử nghiệm trên production.

Giám sát dung lượng disk

Phiên bản gọn của script monitoring trong bài monitoring: kiểm tra và cảnh báo khi vượt ngưỡng.

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
#!/usr/bin/env bash
set -euo pipefail

THRESHOLD="${1:-80}"
MOUNT_POINT="${2:-/}"

USAGE=$(df --output=pcent "$MOUNT_POINT" | tail -1 | tr -dc '0-9')

if (( USAGE > THRESHOLD )); then
  echo "WARNING: disk usage on ${MOUNT_POINT} is ${USAGE}% (threshold: ${THRESHOLD}%)" >&2
  exit 1
else
  echo "OK: disk usage on ${MOUNT_POINT} is ${USAGE}%"
fi

Giải thích:

  • df --output=pcent lấy đúng cột phần trăm, ổn định hơn parse df mặc định.
  • tr -dc '0-9' bóc số khỏi chuỗi 82%.
  • Exit code 1 khi vượt ngưỡng để cron hoặc pipeline phát hiện lỗi.
  • Gắn vào cron mỗi 30 phút, hoặc gọi webhook khi exit code khác 0.

Tự động restart service khi crash

Kịch bản on-call kinh điển: service dừng mà không ai biết.

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
16
17
18
19
#!/usr/bin/env bash
set -euo pipefail

SERVICE="${1:-myapp.service}"

if systemctl is-active --quiet "$SERVICE"; then
  echo "OK: ${SERVICE} is running."
else
  echo "ALERT: ${SERVICE} is down. Restarting..."
  sudo systemctl restart "$SERVICE"

  sleep 5
  if systemctl is-active --quiet "$SERVICE"; then
    echo "RECOVERED: ${SERVICE} restarted successfully."
  else
    echo "FAILED: ${SERVICE} still down after restart." >&2
    exit 1
  fi
fi

Giải thích:

  • systemctl is-active --quiet kiểm tra trạng thái bằng exit code, không cần parse text.
  • Restart xong phải verify lại sau vài giây, tránh báo cáo sai là đã phục hồi.
  • Đặt trong cron mỗi 5 phút cho service quan trọng, kết hợp log vào file để truy vết.
  • Với nhiều server, bọc vòng lặp SSH như trong bài SSH.

Đổi tên file hàng loạt

Khi deploy hoặc xoay log, ta thường cần đổi tên hàng loạt file theo pattern.

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
16
17
18
19
#!/usr/bin/env bash
set -euo pipefail

TARGET_DIR="${1:-.}"
PATTERN="${2:-*.log}"
SUFFIX="$(date +%Y%m%d)"

shopt -s nullglob
files=( "$TARGET_DIR"/$PATTERN )

if (( ${#files[@]} == 0 )); then
  echo "No files matched: ${TARGET_DIR}/${PATTERN}"
  exit 0
fi

for file in "${files[@]}"; do
  mv -- "$file" "${file}.${SUFFIX}"
  echo "Renamed: $file -> ${file}.${SUFFIX}"
done

Giải thích:

  • nullglob để glob không khớp thì trả về rỗng thay vì chuỗi pattern thô.
  • "${files[@]}" quote đúng cách để xử lý tên file có khoảng trắng.
  • mv -- tránh nhầm tên file bắt đầu bằng - thành option.
  • Hậu tố ngày giúp sắp xếp và tìm kiếm dễ dàng.

Xoay log thủ công

Trước khi dùng logrotate hệ thống, hiểu cơ chế xoay log bằng Bash giúp debug dễ hơn.

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
#!/usr/bin/env bash
set -euo pipefail

LOG_FILE="${1:-/var/log/myapp/app.log}"
MAX_SIZE_MB="${2:-100}"
KEEP_FILES="${3:-5}"

if [[ ! -f "$LOG_FILE" ]]; then
  echo "Log file not found: $LOG_FILE" >&2
  exit 1
fi

size_mb=$(du -m "$LOG_FILE" | cut -f1)

if (( size_mb < MAX_SIZE_MB )); then
  echo "OK: ${LOG_FILE} is ${size_mb}MB, no rotation needed."
  exit 0
fi

# Xoay: app.log.4 -> app.log.5, ..., app.log -> app.log.1
for (( i=KEEP_FILES-1; i>=1; i-- )); do
  if [[ -f "${LOG_FILE}.${i}" ]]; then
    mv -- "${LOG_FILE}.${i}" "${LOG_FILE}.$((i+1))"
  fi
done

mv -- "$LOG_FILE" "${LOG_FILE}.1"
touch "$LOG_FILE"

echo "Rotated: ${LOG_FILE} (${size_mb}MB)"

Giải thích:

  • du -m lấy kích thước theo MB để so sánh ngưỡng.
  • Vòng lặp ngược giữ tối đa KEEP_FILES bản, bản cũ nhất tự bị ghi đè.
  • touch tạo file log mới ngay để app không ghi vào file đã di chuyển.
  • Với app ghi log liên tục, nên gửi signal reload cho app sau khi xoay.

Nén archive cho log cũ

Sau khi xoay log, nén các bản cũ để tiết kiệm disk.

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
#!/usr/bin/env bash
set -euo pipefail

LOG_DIR="${1:-/var/log/myapp}"
OLDER_THAN_DAYS="${2:-7}"

find "$LOG_DIR" -type f -name "*.log.*" -mtime "+${OLDER_THAN_DAYS}" ! -name "*.gz" -print0 |
  while IFS= read -r -d '' logfile; do
    gzip -9 "$logfile"
    echo "Compressed: ${logfile}.gz"
  done

du -sh "$LOG_DIR"

Giải thích:

  • ! -name "*.gz" tránh nén lại file đã nén.
  • -print0 kết hợp read -d '' xử lý an toàn tên file có khoảng trắng hoặc ký tự đặc biệt.
  • gzip -9 mức nén cao nhất, phù hợp cho log text.
  • Chạy weekly qua cron, sau bước xoay log.

Kiểm tra hạn chứng chỉ SSL

Chứng chỉ hết hạn mà quên renew là sự cố dễ tránh nhất.

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
16
17
18
19
20
21
22
#!/usr/bin/env bash
set -euo pipefail

DOMAIN="${1:-<your-domain>}"
WARN_DAYS="${2:-14}"

expiry=$(echo | openssl s_client -servername "$DOMAIN" -connect "${DOMAIN}:443" 2>/dev/null \
  | openssl x509 -noout -enddate | cut -d= -f2)

expiry_epoch=$(date -d "$expiry" +%s)
now_epoch=$(date +%s)
days_left=$(( (expiry_epoch - now_epoch) / 86400 ))

if (( days_left < 0 )); then
  echo "CRITICAL: certificate for ${DOMAIN} already expired!" >&2
  exit 2
elif (( days_left <= WARN_DAYS )); then
  echo "WARNING: certificate for ${DOMAIN} expires in ${days_left} days (${expiry})." >&2
  exit 1
else
  echo "OK: certificate for ${DOMAIN} valid for ${days_left} more days."
fi

Giải thích:

  • openssl s_client -connect lấy chain chứng chỉ thật từ server đang chạy.
  • -servername (SNI) bắt buộc khi một IP phục vụ nhiều domain.
  • Đổi ngày hết hạn sang epoch để tính số ngày còn lại chính xác.
  • Exit code phân biệt WARNINGCRITICAL để hệ thống cảnh báo routing đúng kênh.

Ping kiểm tra hàng loạt host

Khi quản lý nhiều server, cần kiểm tra nhanh host nào còn sống.

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
16
17
18
19
20
#!/usr/bin/env bash
set -euo pipefail

HOSTS_FILE="${1:-hosts.txt}"

if [[ ! -f "$HOSTS_FILE" ]]; then
  echo "ERROR: hosts file not found: $HOSTS_FILE" >&2
  exit 1
fi

while IFS= read -r host || [[ -n "$host" ]]; do
  # Bỏ dòng trống và comment
  [[ -z "$host" || "$host" =~ ^# ]] && continue

  if ping -c 2 -W 2 "$host" &>/dev/null; then
    echo "UP:   $host"
  else
    echo "DOWN: $host" >&2
  fi
done < "$HOSTS_FILE"

File hosts.txt mẫu:

1
2
3
4
# Danh sách server của team
webserver-01
webserver-02
db-primary

Giải thích:

  • ping -c 2 -W 2 gửi 2 gói, timeout 2 giây mỗi gói — đủ nhanh cho hàng chục host.
  • Đọc file theo dòng với while read, bỏ qua comment # để document trực tiếp trong file.
  • || [[ -n "$host" ]] xử lý dòng cuối thiếu newline.
  • Nâng cấp tiếp theo: chạy song song với &wait như trong bài parallel.

Đồng bộ thư mục với rsync

Sao chép thư mục giữa hai máy hoặc backup tăng dần (incremental) nhanh hơn cp nhiều.

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
16
17
18
#!/usr/bin/env bash
set -euo pipefail

SOURCE_DIR="${1:-/srv/data/}"
DEST="${2:-backup-host:/srv/backup/data/}"
LOG_FILE="${LOG_FILE:-/var/log/sync-data.log}"

if [[ ! -d "${SOURCE_DIR%/}" && ! -e "$SOURCE_DIR" ]]; then
  echo "ERROR: source not found: $SOURCE_DIR" >&2
  exit 1
fi

rsync -avz --delete \
  --exclude='.cache/' \
  --exclude='*.tmp' \
  "$SOURCE_DIR" "$DEST" 2>&1 | tee -a "$LOG_FILE"

echo "Sync completed at $(date '+%Y-%m-%d %H:%M:%S')" | tee -a "$LOG_FILE"

Giải thích:

  • -a giữ permission, owner, timestamp; -z nén trên đường truyền; -v log chi tiết.
  • --delete đồng bộ xóa — file đã xóa ở nguồn cũng xóa ở đích, giữ hai bên giống hệt nhau.
  • --exclude bỏ qua cache và file tạm, giảm dung lượng đáng kể.
  • Dấu / cuối SOURCE_DIR rất quan trọng trong rsync: có / là copy nội dung, không có là copy cả thư mục.

Cách chạy và lên lịch

Cấp quyền và chạy thử

1
2
3
4
5
6
7
8
9
chmod +x backup.sh cleanup.sh
./backup.sh /etc/myapp /var/backups/myapp

# Chạy debug khi có lỗi
bash -x backup.sh /etc/myapp /var/backups/myapp

# Kiểm tra cú pháp trước khi đưa lên server
bash -n *.sh
shellcheck *.sh

Gắn vào cron

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
# Mở crontab
crontab -e

# Backup mỗi đêm lúc 2h sáng
0 2 * * * /opt/scripts/backup.sh /etc/myapp /var/backups/myapp >> /var/log/backup.log 2>&1

# Kiểm tra service mỗi 5 phút
*/5 * * * * /opt/scripts/restart-service.sh myapp.service >> /var/log/service-check.log 2>&1

# Kiểm tra SSL mỗi sáng
0 8 * * * /opt/scripts/check-ssl.sh example.com 14 >> /var/log/ssl-check.log 2>&1

Lưu ý về cron đã phân tích kỹ trong bài cron: luôn dùng absolute path, khai báo PATH và biến môi trường đầy đủ, redirect cả stdout và stderr ra log.


Ghi chú triển khai

  • Đừng hardcode secret: Mọi script trên đều nhận tham số hoặc biến môi trường. Không nhúng password, token, webhook URL vào file. Dùng file .env với chmod 600source khi cần.
  • Luôn quote biến: "$SOURCE_DIR", "${files[@]}" — quy tắc từ bài best practice. Script nhỏ càng dễ chủ quan, càng dễ dính lỗi word splitting khi gặp tên file có dấu cách.
  • Fail nhanh, báo rõ: Dùng set -euo pipefail, validate input đầu script, trả exit code có nghĩa để cron và pipeline phát hiện lỗi. Script chạy xong lặng lẽ không có nghĩa là chạy đúng.
  • Chạy khô trước trên production: Với script xóa (cleanup, find -delete) và đồng bộ (rsync --delete), lần đầu chạy với -print hoặc --dry-run, xác nhận danh sách file rồi mới cho xóa thật.
  • Một script một việc: Mỗi file trên chỉ làm một task. Khi cần chuỗi nhiều bước, viết script điều phối gọi từng script con, thay vì nhồi tất cả vào một file dài khó test.
  • Lint trước khi commit: Chạy shellcheckbash -n cho cả 10 script. Đưa vào pre-commit hoặc CI để team giữ chuẩn thống nhất.

Lời kết

Mười script trên không có gì cao siêu — backup, dọn tmp, check disk, restart service, rename, xoay log, nén archive, check SSL, ping host, sync folder. Nhưng cộng lại, chúng tiết kiệm hàng giờ làm việc thủ công mỗi tuần và giảm trực tiếp sự cố trực đêm.

Hãy bắt đầu bằng một script duy nhất: chọn nỗi đau lớn nhất của team hiện tại, deploy script đó qua cron, quan sát một tuần, rồi mới thêm script tiếp theo. Khi các script nhỏ đã ổn định, bạn sẽ có nền tảng vững chắc để tiến tới Bash trong CI/CDmonitoring tập trung.

Đừng để automation nằm trên giấy — hãy chạy script đầu tiên ngay hôm nay.

THẢO LUẬN & BÌNH LUẬN