#!/bin/bash
# SSH 병렬 명령 실행 스크립트
# 사용법: ./ssh_parallel_exec.sh "실행할_명령어"
# 설정 변수들 (환경변수로 오버라이드 가능)
SSH_HOSTS="${SSH_HOSTS:-host1.example.com host2.example.com host3.example.com}"
SSH_USER="${SSH_USER:-$(whoami)}"
SSH_KEY="${SSH_KEY:-$HOME/.ssh/id_rsa}"
SSH_PORT="${SSH_PORT:-22}"
SSH_TIMEOUT="${SSH_TIMEOUT:-30}"
MAX_PARALLEL="${MAX_PARALLEL:-10}"
# 색상 코드
RED='\033[0;31m'
GREEN='\033[0;32m'
YELLOW='\033[1;33m'
BLUE='\033[0;34m'
NC='\033[0m' # No Color
# 사용법 출력 함수
usage() {
cat << EOF
사용법: $0 "실행할_명령어" [옵션]
환경변수:
SSH_HOSTS - 접속할 호스트 목록 (공백으로 구분)
SSH_USER - SSH 사용자명 (기본값: 현재 사용자)
SSH_KEY - SSH 개인키 파일 경로 (기본값: ~/.ssh/id_rsa)
SSH_PORT - SSH 포트 (기본값: 22)
SSH_TIMEOUT - SSH 연결 타임아웃 (기본값: 30초)
MAX_PARALLEL - 최대 병렬 연결 수 (기본값: 10)
옵션:
-h, --help - 이 도움말 출력
-v, --verbose - 상세 출력 모드
-s, --serial - 순차 실행 (병렬 실행 안함)
-c, --continue - 오류 발생 시에도 계속 진행
예제:
# 기본 사용
$0 "uptime"
# 환경변수 설정 후 사용
export SSH_HOSTS="server1.com server2.com server3.com"
export SSH_USER="admin"
$0 "df -h"
# 한 줄로 실행
SSH_HOSTS="node1 node2 node3" SSH_USER="root" $0 "systemctl status nginx"
EOF
}
# 로그 함수들
log_info() {
echo -e "${BLUE}[INFO]${NC} $1" >&2
}
log_success() {
echo -e "${GREEN}[SUCCESS]${NC} $1" >&2
}
log_warning() {
echo -e "${YELLOW}[WARNING]${NC} $1" >&2
}
log_error() {
echo -e "${RED}[ERROR]${NC} $1" >&2
}
# SSH 연결 테스트 함수
test_ssh_connection() {
local host="$1"
local user="$2"
local key="$3"
local port="$4"
local timeout="$5"
ssh -i "$key" -p "$port" -o ConnectTimeout="$timeout" \
-o StrictHostKeyChecking=no -o BatchMode=yes \
"$user@$host" "echo 'SSH connection test successful'" >/dev/null 2>&1
return $?
}
# 단일 호스트에서 명령 실행 함수
execute_on_host() {
local host="$1"
local user="$2"
local key="$3"
local port="$4"
local timeout="$5"
local command="$6"
local verbose="$7"
local continue_on_error="$8"
local output_file=$(mktemp)
local error_file=$(mktemp)
# 호스트 헤더 출력
echo "=========================================="
echo "HOST: $host"
echo "TIME: $(date '+%Y-%m-%d %H:%M:%S')"
echo "=========================================="
# SSH 연결 테스트
if [[ "$verbose" == "true" ]]; then
log_info "Testing SSH connection to $host..."
fi
if ! test_ssh_connection "$host" "$user" "$key" "$port" "$timeout"; then
log_error "Cannot connect to $host"
echo -e "${RED}FAILED: SSH connection failed${NC}"
echo ""
rm -f "$output_file" "$error_file"
return 1
fi
# 명령 실행
if [[ "$verbose" == "true" ]]; then
log_info "Executing command on $host: $command"
fi
ssh -i "$key" -p "$port" -o ConnectTimeout="$timeout" \
-o StrictHostKeyChecking=no -o BatchMode=yes \
"$user@$host" "$command" > "$output_file" 2> "$error_file"
local exit_code=$?
# 결과 출력
if [[ $exit_code -eq 0 ]]; then
echo -e "${GREEN}SUCCESS${NC}"
cat "$output_file"
else
echo -e "${RED}FAILED (Exit Code: $exit_code)${NC}"
if [[ -s "$error_file" ]]; then
echo -e "${RED}Error Output:${NC}"
cat "$error_file"
fi
if [[ -s "$output_file" ]]; then
echo -e "${YELLOW}Standard Output:${NC}"
cat "$output_file"
fi
fi
echo ""
rm -f "$output_file" "$error_file"
return $exit_code
}
# 메인 실행 함수
main() {
local command=""
local verbose="false"
local serial="false"
local continue_on_error="false"
# 인수 파싱
while [[ $# -gt 0 ]]; do
case $1 in
-h|--help)
usage
exit 0
;;
-v|--verbose)
verbose="true"
shift
;;
-s|--serial)
serial="true"
shift
;;
-c|--continue)
continue_on_error="true"
shift
;;
*)
if [[ -z "$command" ]]; then
command="$1"
else
log_error "Unknown option: $1"
usage
exit 1
fi
shift
;;
esac
done
# 명령어 확인
if [[ -z "$command" ]]; then
log_error "실행할 명령어를 지정해주세요."
usage
exit 1
fi
# SSH 키 파일 존재 확인
if [[ ! -f "$SSH_KEY" ]]; then
log_error "SSH key file not found: $SSH_KEY"
exit 1
fi
# 호스트 목록을 배열로 변환
IFS=' ' read -ra HOSTS <<< "$SSH_HOSTS"
if [[ ${#HOSTS[@]} -eq 0 ]]; then
log_error "호스트 목록이 비어있습니다."
exit 1
fi
log_info "호스트 목록 파일: $host_file"
log_info "실행할 명령어: $command"
log_info "대상 호스트 수: ${#HOSTS[@]}"
log_info "SSH 사용자: $SSH_USER"
log_info "실행 모드: $([ "$serial" == "true" ] && echo "순차 실행" || echo "병렬 실행")"
if [[ "$verbose" == "true" ]]; then
log_info "SSH 키: $SSH_KEY"
log_info "SSH 포트: $SSH_PORT"
log_info "타임아웃: ${SSH_TIMEOUT}초"
log_info "대상 호스트: ${HOSTS[*]}"
fi
echo ""
local success_count=0
local failed_hosts=()
local pids=()
# 명령 실행
for host in "${HOSTS[@]}"; do
if [[ "$serial" == "true" ]]; then
# 순차 실행
if execute_on_host "$host" "$SSH_USER" "$SSH_KEY" "$SSH_PORT" "$SSH_TIMEOUT" "$command" "$verbose" "$continue_on_error"; then
((success_count++))
else
failed_hosts+=("$host")
if [[ "$continue_on_error" != "true" ]]; then
log_error "호스트 $host에서 실패했습니다. 중단합니다."
break
fi
fi
else
# 병렬 실행
while [[ ${#pids[@]} -ge $MAX_PARALLEL ]]; do
# 완료된 프로세스 정리
for i in "${!pids[@]}"; do
if ! kill -0 "${pids[i]}" 2>/dev/null; then
unset pids[i]
fi
done
pids=("${pids[@]}") # 배열 재정렬
sleep 0.1
done
# 백그라운드에서 실행
execute_on_host "$host" "$SSH_USER" "$SSH_KEY" "$SSH_PORT" "$SSH_TIMEOUT" "$command" "$verbose" "$continue_on_error" &
pids+=($!)
fi
done
# 병렬 실행의 경우 모든 프로세스 완료 대기
if [[ "$serial" != "true" ]]; then
for pid in "${pids[@]}"; do
if wait "$pid"; then
((success_count++))
fi
done
fi
# 결과 요약
echo "=========================================="
echo "실행 완료 요약"
echo "=========================================="
log_success "성공: $success_count/${#HOSTS[@]} 호스트"
if [[ ${#failed_hosts[@]} -gt 0 ]]; then
log_error "실패한 호스트: ${failed_hosts[*]}"
exit 1
else
log_success "모든 호스트에서 성공적으로 실행되었습니다!"
exit 0
fi
}
# 스크립트 실행
main "$@"