Tự động hóa SSH với Bash: Quản lý multi-server hiệu quả trong DevOps
Hướng dẫn tự động hóa SSH với Bash để quản lý multi-server: ssh-keygen, ssh-copy-id, ~/.ssh/config, scp, rsync, chạy lệnh từ xa song song với & và wait.
Trong bài viết trước, chúng ta đã tìm hiểu cách giám sát tài nguyên hệ thống và can thiệp tiến trình ngay trên một server duy nhất. Nhưng trong thực tế DevOps, bạn hiếm khi chỉ quản lý một máy. Khi hạ tầng mở rộng đến 10, 50 hay thậm chí hàng trăm server, việc SSH thủ công vào từng máy để chạy lệnh là một ác mộng — chậm, dễ sai và không thể lặp lại.
Tự động hóa SSH bằng Bash giải quyết vấn đề này: bạn viết một lần, chạy trên mọi server, kết quả đồng nhất và có thể tích hợp vào pipeline CI/CD hoặc cron job. Bài viết này sẽ hướng dẫn bạn thiết lập SSH key-based authentication, cấu hình ~/.ssh/config để quản lý nhiều server, truyền file an toàn với scp và rsync, cùng một script thực hành chạy lệnh song song trên nhiều server.
Thiết lập SSH key-based authentication
SSH key-based authentication loại bỏ việc nhập mật khẩu每次 khi kết nối — đây là yêu cầu bắt buộc cho bất kỳ script tự động hóa nào.
Tạo SSH key pair
1
2
3
4
5
# Tạo key RSA 4096-bitssh-keygen -t rsa -b 4096 -f ~/.ssh/id_rsa_deploy -C "deploy@automation"# Hoặc dùng Ed25519 (nhanh hơn, an toàn hơn)ssh-keygen -t ed25519 -f ~/.ssh/id_ed25519_deploy -C "deploy@automation"
Lưu ý: Nếu bạn dùng key cho mục đích tự động hóa, hãy đặt passphrase rỗng (nhấn Enter khi được hỏi) hoặc dùng ssh-agent để quản lý passphrase. Tuy nhiên, trong môi trường CI/CD, nên dùng key không passphrase và bảo vệ key bằng quyền truy cập filesystem.
Copy key sang server
1
2
3
4
5
6
# Dùng ssh-copy-id (cách chuẩn)ssh-copy-id -i ~/.ssh/id_ed25519_deploy.pub user@server1
ssh-copy-id -i ~/.ssh/id_ed25519_deploy.pub user@server2
# Hoặc copy thủ công nếu ssh-copy-id không khả dụngcat ~/.ssh/id_ed25519_deploy.pub | ssh user@server1 "mkdir -p ~/.ssh && cat >> ~/.ssh/authorized_keys"
Kiểm tra kết nối
1
2
# Test không cần mật khẩussh -i ~/.ssh/id_ed25519_deploy user@server1 "echo 'SSH connection successful'"
Cấu hình ~/.ssh/config
File ~/.ssh/config giúp bạn truy cập server bằng tên ngắn thay vì nhớ địa chỉ IP và username:
# ~/.ssh/config
# Server staging
Host staging-web
HostName 192.168.1.10
User deploy
IdentityFile ~/.ssh/id_ed25519_deploy
Port 22
Host staging-db
HostName 192.168.1.11
User deploy
IdentityFile ~/.ssh/id_ed25519_deploy
Port 22
# Server production
Host prod-web
HostName 10.0.0.10
User deploy
IdentityFile ~/.ssh/id_ed25519_deploy
Port 2222
Host prod-db
HostName 10.0.0.11
User deploy
IdentityFile ~/.ssh/id_ed25519_deploy
Port 2222
# Global defaults
Host *
ServerAliveInterval 60
ServerAliveCountMax 3
StrictHostKeyChecking accept-new
LogLevel ERROR
Sau khi cấu hình, bạn có thể truy cập server bằng tên host ngắn:
1
2
3
4
5
# Thay vì: ssh [email protected]ssh staging-web "df -h /"# Hoặc dùng alias trong Bashecho"alias prod-web='ssh prod-web'" >> ~/.bashrc
Giải thích các cấu hình quan trọng:
ServerAliveInterval 60: Gửi keepalive mỗi 60 giây để tránh bị ngắt kết nối.
ServerAliveCountMax 3: Ngắt kết nối sau 3 lần keepalive thất bại.
StrictHostKeyChecking accept-new: Tự động chấp nhận host key mới mà không hỏi.
LogLevel ERROR: Chỉ hiển thị lỗi, giảm verbosity khi chạy script.
Chạy lệnh từ xa
Lệnh cơ bản
1
2
3
4
5
6
7
8
# Chạy một lệnh trên serverssh user@server1 "uptime"# Chạy nhiều lệnhssh user@server1 "cd /opt/app && git pull && systemctl restart myapp"# Chạy lệnh với sudossh user@server1 "sudo systemctl status nginx"
Truyền biến môi trường
1
2
3
4
5
6
7
8
9
10
11
# Truyền biến vào phiên SSHDEPLOY_ENV="staging"APP_VERSION="v2.1.0" ssh user@server1 "echo \$DEPLOY_ENV \$APP_VERSION"# An toàn hơn: dùng heredocssh user@server1 << 'REMOTE_SCRIPT'
export DEPLOY_ENV="staging"
cd /opt/app
git pull origin main
npm install --production
systemctl restart myapp
REMOTE_SCRIPT
Xử lý lỗi khi chạy remote
1
2
3
4
5
6
7
# Kiểm tra exit codeif ssh user@server1 "systemctl status nginx" 2>/dev/null;thenecho"Nginx is running on server1"elseecho"Nginx is down on server1 — attempting restart" ssh user@server1 "sudo systemctl restart nginx"fi
Truyền file giữa các server
scp — Secure Copy
1
2
3
4
5
6
7
8
9
10
11
# Copy file từ local sang remotescp /local/path/config.yml user@server1:/opt/app/config.yml
# Copy thư mục (recursive)scp -r /local/path/dist/ user@server1:/opt/app/dist/
# Copy từ remote sang localscp user@server1:/var/log/app.log /local/logs/
# Copy giữa hai server (qua local trung gian)scp user@server1:/data/backup.sql user@server2:/data/restore.sql
rsync — Remote Sync
rsync mạnh hơn scp vì hỗ trợ đồng bộ chỉ các file thay đổi (incremental sync):
1
2
3
4
5
6
7
8
9
10
11
# Đồng bộ thư mụcdist lên serverrsync -avz --progress /local/path/dist/ user@server1:/opt/app/dist/
# rsync qua SSH với port tùy chỉnhrsync -avz -e "ssh -p 2222" /local/path/ user@server1:/opt/app/
# Chỉ sync file đã thay đổi trong 1 ngàyrsync -avz --max-age=1 /local/path/logs/ user@server1:/backup/logs/
# Dry-run trước khi thực hiệnrsync -avzn --delete /local/path/ user@server1:/opt/app/
Giải thích flags:
-a: Archive mode (giữ quyền file, symlink, timestamp).
-v: Verbose — hiển thị file đang được sync.
-z: Nén dữ liệu khi truyền qua mạng.
--delete: Xóa file ở destination không có trong source.
--progress: Hiển thị tiến trình truyền file.
Lặp qua nhiều server
Vòng lặp cơ bản
1
2
3
4
5
6
SERVERS=("web-01""web-02""web-03")for server in "${SERVERS[@]}";doecho"=== $server ===" ssh "deploy@${server}""hostname && uptime && df -h / | tail -1"done
SERVERS=("web-01""web-02""web-03""db-01""db-02")MAX_PARALLEL=3run_on_server(){localserver="$1"echo"[START] $server" ssh "deploy@${server}""sudo apt update && sudo apt upgrade -y" 2>/dev/null
localstatus=$?echo"[DONE] $server (exit: $status)"return$status}# Chạy song song với giới hạn concurrencypids=()for server in "${SERVERS[@]}";do run_on_server "$server"&pids+=($!)# Giới hạn số tiến trình chạy đồng thờiif[[${#pids[@]} -ge $MAX_PARALLEL]];thenwait"${pids[0]}"pids=("${pids[@]:1}")fidone# Đợi tất cả tiến trình còn lại hoàn thànhfor pid in "${pids[@]}";dowait"$pid"doneecho"All servers updated."
Giải thích:
run_on_server "$server" &: Chạy hàm trong background.
pids+=($!): Lưu PID của tiến trình background.
wait "${pids[0]}": Đợi tiến trình đầu tiên hoàn thành trước khi chạy tiến trình mới.
MAX_PARALLEL=3: Giới hạn tối đa 3 server chạy cùng lúc.
Bảo mật SSH key: Đặt quyền chmod 600 cho private key và chmod 644 cho public key. Không bao giờ commit key vào repository. Trong CI/CD, lưu key dưới dạng secret và mount vào file tạm thời.
Host key verification: Ở第一次 kết nối, SSH sẽ hỏi xác nhận host key. Trong script tự động, dùng StrictHostKeyChecking accept-new trong ~/.ssh/config hoặc flag -o StrictHostKeyChecking=accept-new để tự động chấp nhận.
Giới hạn concurrency: Chạy quá nhiều SSH session cùng lúc có thể gây overload cho network hoặc server nguồn. Luôn thiết lập MAX_PARALLEL phù hợp với hạ tầng.
Timeout kết nối: Sử dụng -o ConnectTimeout=10 và -o BatchMode=yes để tránh script treo vô hạn khi server không phản hồi.
Rollback strategy: Luôn có kế hoạch rollback trước khi deploy hàng loạt. Script ví dụ trên chỉ deploy — trong thực tế, bạn nên thêm bước snapshot hoặc backup trước khi thay đổi.
Lời kết
Tự động hóa SSH bằng Bash không chỉ giúp bạn tiết kiệm thời gian mà còn đảm bảo tính nhất quán khi quản lý nhiều server. Từ việc thiết lập key-based authentication, cấu hình ~/.ssh/config để truy cập nhanh, sử dụng scp/rsync để truyền file, đến việc chạy lệnh song song với & và wait — tất cả đều có thể kết hợp trong một script Bash gọn gàng.
Ở bài tiếp theo, chúng ta sẽ khám phá cách kết hợp Bash và Docker: viết wrapper script để quản lý container, tự động restart khi crash, và deploy stack với docker compose.