temp0904a

Young-Kyoo Kim·2025년 9월 4일
#!/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 "$@"

0개의 댓글