

기계 번역으로 제공되는 번역입니다. 제공된 번역과 원본 영어의 내용이 상충하는 경우에는 영어 버전이 우선합니다.

# 독립 실행형 로그인 노드를 AWS PCS의 여러 클러스터에 연결
<a name="multi-cluster-login-script"></a>

`pcs-multi-cluster-login-configure.sh` 스크립트는 단일 독립 실행형 로그인 노드에서 여러 Slurm `sackd` 데몬을 구성하는 자동화된 방법을 제공합니다. 이를 통해 로그인 노드가 여러 클러스터와 통신할 수 있습니다. 스크립트는 다음 작업을 자동화합니다.
+  AWS PCS API 작업을 사용하여 클러스터 정보 가져오기
+ base64로 인코딩된 Slurm 인증 키에 대한 프롬프트
+ 클러스터 인증 키를 사용하여 Slurm JWKS 파일을 생성합니다.
+ 클러스터 엔드포인트 및 포트로 `sackd` 서비스를 구성합니다.
+ 클러스터별 데몬에 대한 `systemd` 서비스 파일을 생성합니다`sackd`.
+ 클러스터 환경 설정을 위한 활성화 스크립트를 생성합니다.
+ `sackd` 서비스를 활성화하고 시작합니다.

**참고**  
이 스크립트에는 Slurm 버전 25.05 이상이 필요합니다.

Slurm은 인스턴스에 이미 설치되어 있어야 합니다(수동 프로세스의 [3단계](working-with_login-nodes_standalone_install-slurm.md)에 해당). 인스턴스는 대상 클러스터의 엔드포인트에 도달할 수 있어야 합니다. 스크립트는 수동 구성 프로세스에서 [4단계](working-with_login-nodes_standalone_get-secret.md)와 [5단계](working-with_login-nodes_standalone_configure-connection.md)의 동일한 작업을 수행합니다. 클러스터 정보를 자동으로 가져오고, `sackd` 서비스를 구성하고, 필요한 `systemd` 서비스 파일을 생성하고, 사용자가 클러스터 상호 작용을 위해 셸 환경을 구성하는 데 사용할 수 있는 활성화 스크립트를 생성합니다.

**Topics**
+ [AWS PCS 다중 클러스터 로그인 노드 구성 스크립트의 사전 조건](multi-cluster-login-script-prerequisites.md)
+ [AWS PCS 다중 클러스터 로그인 노드 구성 스크립트 코드](multi-cluster-login-script-code.md)
+ [AWS PCS 다중 클러스터 로그인 노드 구성 스크립트 사용](multi-cluster-login-script-usage.md)

# AWS PCS 다중 클러스터 로그인 노드 구성 스크립트의 사전 조건
<a name="multi-cluster-login-script-prerequisites"></a>

## 시스템 요구 사항
<a name="system-requirements"></a>
+ 가 `systemd` 지원되는 Linux OS
+ 시스템 구성에 대한 루트 권한

## 필수 명령 및 패키지
<a name="required-commands"></a>
+ `bash` – 쉘 인터프리터(버전 4.0 이상)
+ `curl` - AWS IMDS v2 메타데이터 검색의 경우
+ `jq` - AWS API 응답을 구문 분석하기 위한 JSON 프로세서
+ `aws` – AWS CLI v2 - AWS PCS API 작업 실행 및 Secrets Manager 액세스
+ `systemctl` - `systemd` 서비스 관리
+ `find` - 파일 시스템 검색 유틸리티
+ `grep` - 텍스트 패턴 일치
+ `sed` - 텍스트 조작을 위한 스트림 편집기
+ `sort` - 텍스트 정렬 유틸리티
+ `tail` - 파일의 마지막 줄을 표시합니다.
+ `mkdir` - 디렉터리 생성
+ `chmod` - 파일 권한 변경
+ `chown` - 파일 소유권 변경
+ `ldconfig` - 동적 링커 구성

## AWS 요구 사항
<a name="aws-requirements"></a>
+ Slurm 버전 25.05 이상을 실행하는 AWS PCS 클러스터
+ AWS 구성된 자격 증명(IAM 역할, 자격 증명 파일 또는 환경 변수를 통해)
+ 다음에 대한 권한:
  + `pcs:GetCluster`
  + `secretsmanager:GetSecretValue` (대체 보안 암호를 사용하는 경우)

## 시스템 사용자 및 그룹
<a name="system-users-groups"></a>
+ `slurm` 사용자와 그룹이 시스템에 있어야 합니다.

## Slurm 설치
<a name="slurm-installation"></a>
+ Slurm은 AWS PCS Slurm 설치 관리자 패키지와 동일한 위치에 설치해야 합니다.

  ```
  /opt/aws/pcs/scheduler/slurm-version
  ```

# AWS PCS 다중 클러스터 로그인 노드 구성 스크립트 코드
<a name="multi-cluster-login-script-code"></a>

다음 소스 코드를 다음 이름의 파일에 저장합니다.

```
pcs-multi-cluster-login-configure.sh
```

## 스크립트 소스 코드
<a name="multi-cluster-login-script-code-content"></a>

```
#!/bin/bash
# Copyright Amazon.com, Inc. or its affiliates. All Rights Reserved.

# AWS PCS Multi-Cluster Standalone Login Node Configuration Script
# 
# This script configures AWS Parallel Computing Service (PCS) multi-cluster stand alone login nodes
# by setting up the Slurm authentication and credential kiosk daemon (sackd)
# for connecting to remote PCS clusters.
#
# Prerequisites:
# - AWS CLI configured with appropriate permissions
# - Slurm version 25.05 or later
# - Root privileges for system configuration
# - Network connectivity to AWS PCS endpoints


set -eo pipefail

# Function to display usage
usage() {
    echo "Usage: $0 --cluster-identifier <cluster-identifier> [--endpoint-url <endpoint-url>]"
    echo "       $0 -h|--help"
}

# Function to display help
help() {
    echo "AWS PCS Multi-Cluster Standalone Login Node Configuration Script"
    echo "==============================================="
    echo
    echo "This script configures multi-cluster standalone login node for AWS Parallel Computing Service (PCS)"
    echo "by setting up the Slurm authentication and credential kiosk daemon (sackd)."
    echo
    usage
    echo
    echo "Options:"
    echo "  --cluster-identifier <id>    AWS PCS cluster identifier (required)"
    echo "  --endpoint-url <url>         Custom PCS endpoint URL (optional)"
    echo "  -h, --help                   Show this help message"
    echo
    echo "Examples:"
    echo "  $0 --cluster-identifier my-pcs-cluster"
    echo
    echo "Note: This script requires root privileges and Slurm version 25.05 or later."
}

# Function to retrieve authentication key
get_auth_key() {
    if [ "$ALTERNATE_SECRET_RETRIEVAL" = "true" ]; then
        echo "Retrieving authentication key from AWS Secrets Manager..." >&2
        local auth_key_arn=$(echo "$CLUSTER_INFO" | jq -r '.cluster.slurmConfiguration.authKey.secretArn')
        local auth_key_version=$(echo "$CLUSTER_INFO" | jq -r '.cluster.slurmConfiguration.authKey.secretVersion')
        
        if [ "$auth_key_arn" = "null" ] || [ "$auth_key_version" = "null" ]; then
            echo "Error: Auth key information not found in cluster configuration" >&2
            exit 1
        fi
        
        if ! aws secretsmanager get-secret-value --secret-id "$auth_key_arn" --version-id "$auth_key_version" --query SecretString --output text --region "$REGION" 2>/dev/null; then
            echo "Error: Failed to retrieve auth key from Secrets Manager" >&2
            exit 1
        fi
    else
        echo "Please enter the base64-encoded Slurm authentication key:" >&2
        echo -n "Base64 of the Slurm secret key: " >&2
        local key
        read -rs key
        echo >&2
        echo "$key"
    fi
}

# Function to get next available SACKD port
get_next_sackd_port() {
    local exclude_file="$1"
    local port=6918
    local used_ports=()
    
    # Get all currently used SACKD ports into an array
    while IFS= read -r line; do
        used_ports+=("$line")
    done < <(find /etc/sysconfig -name "sackd-pcs-*" ! -path "$exclude_file" \
             -exec grep SACKD_PORT= '{}' ';' 2>/dev/null | \
             sed 's/.*SACKD_PORT=//' | sort -n)
    
    # Loop through used ports to find first available port
    for used_port in "${used_ports[@]}"; do
        if [ "$port" -lt "$used_port" ]; then
            break
        elif [ "$port" -eq "$used_port" ]; then
            ((port++))
        fi
    done
    
    echo "$port"
}

# Function to configure cluster
configure_cluster() {
    mkdir -p /etc/slurm
    SLURM_JWKS_FILE="/etc/slurm/slurm-${CLUSTER_NAME}.jwks"
    echo '{"keys":[{"alg":"HS256","kty":"oct","kid":"key-'"${CLUSTER_ID}"'","k":"'"${BASE64_SLURM_KEY}"'"}]}' | jq -c '.' > "${SLURM_JWKS_FILE}"
    
    chmod 0600 "$SLURM_JWKS_FILE"
    chown slurm:slurm "$SLURM_JWKS_FILE"
    
    SLURM_INSTALL_PATH="/opt/aws/pcs/scheduler/slurm-${SLURM_VERSION}"
    
    SACKD_RUNTIME_DIRECTORY="/run/slurm-${CLUSTER_NAME}"
    mkdir -p "${SACKD_RUNTIME_DIRECTORY}"
    chown slurm:slurm "${SACKD_RUNTIME_DIRECTORY}"
    
    mkdir -p /etc/sysconfig
    SACKD_SERVICE_NAME="sackd-pcs-${CLUSTER_NAME}"
    SACKD_SERVICE_ENV="/etc/sysconfig/${SACKD_SERVICE_NAME}"
    SACKD_PORT=$(get_next_sackd_port "$SACKD_SERVICE_ENV")
    cat > "${SACKD_SERVICE_ENV}" << EOF
SACKD_OPTIONS='--conf-server=$ENDPOINTS'
SLURM_SACK_JWKS='$SLURM_JWKS_FILE'
RUNTIME_DIRECTORY='$SACKD_RUNTIME_DIRECTORY'
SACKD_PORT=$SACKD_PORT
EOF
    
    SACKD_SERVICE_PATH="/etc/systemd/system/${SACKD_SERVICE_NAME}.service"
    
    cat << EOF > "$SACKD_SERVICE_PATH"
[Unit]
Description=Slurm auth and cred kiosk daemon
After=network-online.target remote-fs.target
Wants=network-online.target
ConditionPathExists=${SACKD_SERVICE_ENV}

[Service]
Type=notify
EnvironmentFile=${SACKD_SERVICE_ENV}
User=slurm
Group=slurm
RuntimeDirectory=slurm-${CLUSTER_NAME}
RuntimeDirectoryMode=0755
ExecStart=${SLURM_INSTALL_PATH}/sbin/sackd --systemd \$SACKD_OPTIONS
ExecReload=/bin/kill -HUP \$MAINPID
KillMode=process
LimitNOFILE=131072
LimitMEMLOCK=infinity
LimitSTACK=infinity

[Install]
WantedBy=multi-user.target
EOF
    
    chown root:root "$SACKD_SERVICE_PATH"
    chmod 0644 "$SACKD_SERVICE_PATH"
    systemctl daemon-reload && systemctl enable "$SACKD_SERVICE_NAME"
    systemctl restart "$SACKD_SERVICE_NAME"
    
    ACTIVATE_SCRIPT="activate-pcs-${CLUSTER_NAME}"
    cat > "$ACTIVATE_SCRIPT" << EOF
# Activate script for Slurm cluster ${CLUSTER_NAME}

# Add Slurm paths
export PATH="${SLURM_INSTALL_PATH}/bin:\$PATH"
export MANPATH="${SLURM_INSTALL_PATH}/share/man:\$MANPATH"
export LD_LIBRARY_PATH="${SLURM_INSTALL_PATH}/lib:\$LD_LIBRARY_PATH"
ldconfig

# Set Slurm configuration
export SLURM_CONF="/run/slurm-${CLUSTER_NAME}/conf/slurm.conf"
export PCS_CLUSTER_NAME="${CLUSTER_NAME}"
export PCS_CLUSTER_IDENTIFIER="${CLUSTER_IDENTIFIER}"
export PCS_CLUSTER_ID="${CLUSTER_ID}"

echo "Activated PCS cluster environment: ${CLUSTER_NAME}"

# Deactivate function
function deactivate-pcs-${CLUSTER_NAME}() {
    export PATH="\$(echo "\$PATH" | sed -e "s|${SLURM_INSTALL_PATH}/bin:||g" -e "s|:${SLURM_INSTALL_PATH}/bin||g" -e "s|^${SLURM_INSTALL_PATH}/bin\$||")"
    export MANPATH="\$(echo "\$MANPATH" | sed -e "s|${SLURM_INSTALL_PATH}/share/man:||g" -e "s|:${SLURM_INSTALL_PATH}/share/man||g" -e "s|^${SLURM_INSTALL_PATH}/share/man\$||")"
    export LD_LIBRARY_PATH="\$(echo "\$LD_LIBRARY_PATH" | sed -e "s|${SLURM_INSTALL_PATH}/lib:||g" -e "s|:${SLURM_INSTALL_PATH}/lib||g" -e "s|^${SLURM_INSTALL_PATH}/lib\$||")"
    unset SLURM_CONF
    unset PCS_CLUSTER_NAME
    unset PCS_CLUSTER_IDENTIFIER
    unset PCS_CLUSTER_ID
    unset -f deactivate-pcs-${CLUSTER_NAME}
    ldconfig
    echo "Deactivated PCS cluster environment: ${CLUSTER_NAME}"
}

export -f deactivate-pcs-${CLUSTER_NAME}

EOF
}

# Main function
main() {
    # Parse arguments
    CLUSTER_IDENTIFIER=""
    PCS_ENDPOINT_URL=""
    
    while [ "$1" != "" ]; do
        case $1 in
            --cluster-identifier)
                shift
                CLUSTER_IDENTIFIER="$1"
                ;;
            --endpoint-url)
                shift
                PCS_ENDPOINT_URL="--endpoint-url $1"
                ;;
            -h|--help)
                help
                exit 0
                ;;
            *)
                echo "Invalid argument: $1" >&2
                usage >&2
                exit 1
                ;;
        esac
        shift
    done
    
    # Validate required arguments
    if [ -z "$CLUSTER_IDENTIFIER" ]; then
        echo "Error: --cluster-identifier is required" >&2
        usage >&2
        exit 1
    fi
    
    # Validate running as root
    if [ "$EUID" -ne 0 ]; then
        echo "Error: This script must be run as root" >&2
        exit 1
    fi
    
    # Validate required commands are available
    for cmd in aws jq curl; do
        if ! command -v "$cmd" &> /dev/null; then
            echo "Error: Required command '$cmd' not found" >&2
            exit 1
        fi
    done
    
    # Get the region name from IMDS v2 with error handling (try IPv6 first, fallback to IPv4)
    echo "Retrieving AWS region from instance metadata..."
    # Try IPv6 IMDS endpoint first (fd00:ec2::254) with fast timeout (1s connect, 2s total)
    # If IPv6 fails, fallback to IPv4 IMDS endpoint (169.254.169.254)
    IMDS_ENDPOINT="http://[fd00:ec2::254]"
    if ! TOKEN=$(curl -s -X PUT "${IMDS_ENDPOINT}/latest/api/token" -H "X-aws-ec2-metadata-token-ttl-seconds: 21600" --connect-timeout 1 --max-time 2 2>/dev/null); then
        IMDS_ENDPOINT="http://169.254.169.254"
        if ! TOKEN=$(curl -s -X PUT "${IMDS_ENDPOINT}/latest/api/token" -H "X-aws-ec2-metadata-token-ttl-seconds: 21600" --max-time 5); then
            echo "Error: Failed to retrieve IMDS token. Ensure this script is running on an EC2 instance." >&2
            exit 1
        fi
    fi
    
    if ! REGION=$(curl -s -H "X-aws-ec2-metadata-token: $TOKEN" "${IMDS_ENDPOINT}/latest/dynamic/instance-identity/document" --max-time 5 | jq -r '.region'); then
        echo "Error: Failed to retrieve AWS region from instance metadata" >&2
        exit 1
    fi
    
    echo "Detected AWS region: $REGION"
    
    # Retrieve cluster information from AWS PCS
    echo "Retrieving cluster information for: $CLUSTER_IDENTIFIER"
    # shellcheck disable=SC2086
    if ! CLUSTER_INFO=$(aws pcs get-cluster --region "$REGION" --cluster-identifier "$CLUSTER_IDENTIFIER" $PCS_ENDPOINT_URL 2>/dev/null); then
        echo "Error: Failed to retrieve cluster information. Check cluster identifier and AWS permissions." >&2
        exit 1
    fi
    
    CLUSTER_ID=$(echo "$CLUSTER_INFO" | jq -r '.cluster.id')
    CLUSTER_NAME="$(echo "$CLUSTER_INFO" | jq -r '.cluster.name')"
    SLURM_VERSION=$(echo "$CLUSTER_INFO" | jq -r '.cluster.scheduler.version')
    SLURM_VERSION=${SLURM_VERSION#Slurm_}
    
    # Check if Slurm version is >= 25.05
    # shellcheck disable=SC2072
    if [[ "$SLURM_VERSION" < "25.05" ]]; then
        echo "Error: This script requires Slurm version 25.05 or later. Found version: $SLURM_VERSION" >&2
        exit 1
    fi
    
    ENDPOINTS=$(echo "$CLUSTER_INFO" | jq -r '.cluster.endpoints[] | select(.type == "SLURMCTLD") | (if .privateIpAddress != "" then .privateIpAddress else "[" + .ipv6Address + "]" end) + ":" + .port' | tr '\n' ',' | sed 's/,$//')
    
    # Get BASE64_SLURM_KEY
    BASE64_SLURM_KEY=$(get_auth_key)
    
    if [ -z "$BASE64_SLURM_KEY" ]; then
        echo "Error: base64 Slurm key cannot be empty" >&2
        exit 1
    fi
    
    configure_cluster
    
    # Final configuration summary
    echo "========================================"
    echo "Configuration completed successfully!"
    echo "========================================"
    echo "Cluster Name: $CLUSTER_NAME"
    echo "Cluster ID: $CLUSTER_ID"
    echo "Slurm Version: $SLURM_VERSION"
    echo "Service Name: $SACKD_SERVICE_NAME"
    echo "SACKD Port: $SACKD_PORT"
    echo
    echo "To activate this cluster environment, run:"
    echo "  source ./$ACTIVATE_SCRIPT"
    echo
    echo "To deactivate this cluster environment, run:"
    echo "  deactivate-pcs-${CLUSTER_NAME}"
    echo
    echo "To check service status:"
    echo "  systemctl status $SACKD_SERVICE_NAME"
    echo
    echo "To view service logs:"
    echo "  journalctl -u $SACKD_SERVICE_NAME -f"
}

# Exit if being sourced for testing
[[ "${BASH_SOURCE[0]}" != "${0}" ]] && return

# Execute main function
main "$@"
```

# AWS PCS 다중 클러스터 로그인 노드 구성 스크립트 사용
<a name="multi-cluster-login-script-usage"></a>

## 스크립트 실행
<a name="running-script"></a>

**구성 스크립트를 실행하려면**

1. [스크립트의 내용을](multi-cluster-login-script-code.md#multi-cluster-login-script-code-content) 다음과 같은 파일에 저장합니다.

   ```
   pcs-multi-cluster-login-configure.sh
   ```

1. 실행 가능하도록 설정합니다.

   ```
   chmod +x pcs-multi-cluster-login-configure.sh
   ```

1. 스크립트를 실행합니다.

   ```
   ./pcs-multi-cluster-login-configure.sh --cluster-identifier cluster-name
   ```

## 클러스터 상호 작용 환경
<a name="activation-script-usage"></a>

구성에 성공하면 스크립트는 현재 디렉터리에 클러스터별 활성화 스크립트를 생성합니다. 스크립트의 이름은 입니다`activate-pcs-cluster-name`. 활성화 스크립트는 대상 클러스터와 상호 작용하는 데 필요한 환경 변수 및 경로를 구성합니다.

**클러스터 환경을 활성화하려면**
+ `source` 명령을 사용하여 활성화 스크립트 실행

  ```
  source ./activate-pcs-cluster-name
  ```  
**Example**  

  ```
  # Activate cluster environment for cluster 'my-cluster'
  source ./activate-pcs-my-cluster
  
  # Now you can use Slurm commands
  sinfo
  squeue
  sbatch my-job.sh
  ```

**활성화 스크립트가 수행하는 작업**
+ 클러스터의 구성을 가리키도록 `SLURM_CONF` 환경 변수를 설정합니다.
+ 클러스터의 Slurm 바이너리`PATH`를 포함하도록를 업데이트합니다.
+ 기타 필요한 Slurm 환경 변수(`MANPATH`, `LD_LIBRARY_PATH`)를 구성합니다.
+  AWS PCS 클러스터 식별 변수를 설정합니다.
+ 대상 AWS PCS 클러스터와의 원활한 상호 작용을 활성화합니다.

**클러스터 환경을 비활성화하려면**
+ 비활성화 명령을 실행합니다.

  ```
  deactivate-pcs-cluster-name
  ```  
**Example**  

  ```
  # After activating a cluster
  source ./activate-pcs-my-cluster
  
  # Work with the cluster
  sinfo
  
  # Deactivate when done
  deactivate-pcs-my-cluster
  ```

**비활성화 명령이 수행하는 작업**
+ 원래 `PATH` 환경 변수를 복원합니다.
+ 클러스터별 Slurm 환경 변수를 설정 해제합니다.
+ 쉘 환경을 사전 활성화 상태로 되돌립니다.

**참고**  
활성화는 세션별로 다르며 클러스터와 상호 작용하려는 쉘 세션에서 소싱되어야 합니다.