SFTP 파일 수집, 내보내기와 이전 연결 전환
SFTP 커넥터는 SSH 기반 파일 전송으로 원격 폴더와 Foundry 데이터셋 사이에서 파일을 옮깁니다. 원천 탐색, 대량 가져오기, 증분 수집과 파일 내보내기는 일반 제공(GA)입니다. Export tasks는 이전 SFTP 소스에서만 사용하는 Sunset 기능입니다.
파일 형식은 그대로 유지되며 스키마를 자동 적용하지 않습니다. 출력 데이터셋에 스키마를 설정하거나 하위 변환에서 해석합니다. 파일 크기 자체의 제한은 없지만 이틀 넘게 실행되는 Foundry 동기화는 중단됩니다. 네트워크 부담을 줄이려면 작은 파일과 실행당 제한된 파일 수를 사용하고 더 자주 동기화합니다.
새 소스와 인증
Data Connection의 +New Source에서 SFTP를 선택합니다. 온프레미스 서버는 Foundry 워커와 에이전트 프록시 연결이 권장됩니다.
사용자 이름과 비밀번호 또는 private key로 인증합니다. 키는 RSA 또는 OpenSSH 형식을 사용할 수 있으며 서버의 authorized key가 같은 사용자에 대해 구성되어 있어야 합니다. 접속 사용자는 연결 루트에 접근하고 그 아래의 모든 대상 파일과 디렉터리를 읽고 나열할 권한이 필요합니다.
연결이 실패하면 Foundry 밖의 SFTP 클라이언트에서 같은 인증 정보와 서버 설정으로 접근 가능한지 확인합니다.
네트워크와 서버 신뢰
서버 호스트 이름과 해석되는 IP에 외부 통신 정책을 추가합니다. 하나의 도메인이 여러 도메인 또는 서버로 해석되면 관련 도메인과 IP를 모두 허용해야 합니다. UNIX 환경에서 dig로 실제 도메인의 해석 결과를 확인할 수 있습니다.
dig <domain> +short
에이전트를 사용하면 에이전트 호스트에서 SFTP 서버까지 연결할 수 있어야 하며 방화벽도 허용해야 합니다. 포트와 호스트 이름 검증 경로에 따라 연결 정지나 시간 초과가 생기면 적용한 외부 통신 정책 목록과 함께 Palantir 지원에 문의합니다.
서버 SSH 공개 키
서버 키는 관리자가 제공하는 값을 사용하는 것이 권장됩니다. 서버에 도달할 수 있는 Linux 환경에서는 다음 형식으로 RSA 키를 수집할 수도 있습니다. 수집한 키는 별도로 신뢰할 수 있는 경로에서 확인합니다.
ssh-keyscan -t rsa -p {port} {hostname} | awk '{print $3}'
Connection details의 Host key에 이 값을 설정합니다. Accept any host key를 켜면 키 검증을 생략할 수 있지만 안전하지 않으므로 권장하지 않습니다.
연결 속성
| 항목 | 필수 여부 | 기본값 | 의미 |
|---|---|---|---|
Hostname | 필수 | 미지정 | 서버 도메인 이름 또는 IP입니다. |
Port | 필수 | 미지정 | 서버의 실제 SFTP 포트입니다. 일반적으로 22를 사용합니다. |
Root directory | 필수 | 미지정 | 연결 요청의 기준 디렉터리입니다. |
Username | 필수 | 미지정 | SFTP 사용자 이름입니다. |
Password | 선택 | 미지정 | 비밀번호 인증에 사용합니다. |
Private key | 선택 | 미지정 | RSA 또는 OpenSSH 형식의 인증 키입니다. |
Host key | 필수 | 미지정 | 신뢰하는 서버 공개 키입니다. |
Accept any host key | 선택 | false | 서버 키 검증을 생략할지 지정합니다. |
Proxy | 선택 | Direct | 프록시 연결을 구성합니다. |
Timeout | 선택 | 0 | 연결 수립과 각 명령의 서버 응답을 기다리는 한도입니다. |
Maximum concurrent connections | 선택 | 실행 환경이 지원하는 최대 병렬 업로드 수 | 서버 부하와 동시 연결 한도에 맞춥니다. 실행 환경의 상한보다 높여도 효과가 없습니다. |
| Connection settings | 선택 | 미지정 | 키 교환, 암호와 인증 알고리즘의 협상을 조정합니다. |
Logs | 선택 | Info | 선택한 수준 이상의 로그를 기록합니다. Error를 고르면 Error와 Fatal을 기록합니다. |
Extension Negotiation | 선택 | 미지정 | 서버와 교환하는 확장 협상 메시지를 조정합니다. |
Timeout의 0은 각 서버 응답을 무기한 기다린다는 뜻입니다. 서버가 계속 응답하면 타임아웃이 발생하지 않으므로 작은 값을 정해도 큰 파일의 전체 전송 시간을 직접 제한하지 않습니다.
알고리즘 협상
| 항목 | 의미 |
|---|---|
Key exchange algorithms | 키 교환 알고리즘 목록입니다. 최소 하나를 선택합니다. |
Ciphers | 암호화에 사용할 암호 목록입니다. |
Message Authentication Code (MAC) | 메시지 인증 코드 유형 목록입니다. |
Host key types | 허용할 서버 호스트 키 유형입니다. |
Public key types | 공개 키 사용자 인증에 사용할 알고리즘입니다. |
자동 협상에 호환성 문제가 있으면 서버가 지원하는 알고리즘과 맞춰 조정합니다. 확장은 보통 켜 두는 것이 권장됩니다. 확장 협상 때문에 연결이 실패하면 필요한 최소 항목만 끄며 모든 확장을 한꺼번에 비활성화하지 않습니다.
수집과 파일 내보내기
수집은 파일 기반 동기화 인터페이스를 사용합니다. 내보내기는 소스의 내보내기를 활성화한 뒤 새 내보내기로 구성합니다.
Foundry 데이터셋의 파일 경로에는 spark/ 접두사가 붙는 경우가 많습니다. 그대로 내보내면 서버에 spark 폴더를 만들려고 하므로 디렉터리 생성 권한이 없으면 실패합니다. 내보내기 전에 경로를 재작성하여 원하는 출력 구조를 만듭니다.
코드에서 파일 업로드
다음 예제는 입력 데이터셋의 XLSX 파일을 바이트 그대로 업로드합니다. Ed25519 사용자 private key와 별도로 확인한 RSA 서버 호스트 키를 사용하며, 해당 서버가 이 키 유형을 제공해야 합니다. 소스의 create_socket으로 연결 소켓을 만들고 등록한 서버 키를 검증합니다.
Python 저장소에 paramiko, pandas를 추가합니다. 소스 RID, 입력과 출력 RID, 호스트, 포트, 사용자, RSA 서버 공개 키와 대상 폴더를 실제 값으로 지정합니다. 출력은 원래 파일 이름과 업로드한 원격 경로의 대응 표입니다.
from transforms.api import Output, Input, TransformOutput, TransformInput, transform
from transforms.external.systems import external_systems, Source, ResolvedSource
import base64
import io
import pandas as pd
import paramiko
@external_systems(sftp_source=Source("<source_rid>"))
@transform(
output_file=Output("<output_dataset_rid>"),
xlsx_files=Input("<input_dataset_rid>"),
)
def compute(
sftp_source: ResolvedSource,
output_file: TransformOutput,
xlsx_files: TransformInput,
) -> None:
hostname = "<hostname>"
port = 22
username = "<username>"
expected_rsa_host_key = "<base64_rsa_host_key>"
private_key = paramiko.Ed25519Key.from_private_key(
io.StringIO(sftp_source.get_secret("privateKeyString"))
)
client = paramiko.SSHClient()
host_key_name = hostname if port == 22 else f"[{hostname}]:{port}"
client.get_host_keys().add(
host_key_name,
"ssh-rsa",
paramiko.RSAKey(data=base64.b64decode(expected_rsa_host_key)),
)
client.set_missing_host_key_policy(paramiko.RejectPolicy())
proxy_socket = sftp_source.create_socket(hostname, port)
remote_paths = []
try:
client.connect(
hostname, port=port, username=username, pkey=private_key,
sock=proxy_socket, allow_agent=False, look_for_keys=False,
)
sftp = client.open_sftp()
try:
fs = xlsx_files.filesystem()
for item in fs.ls():
remote_path = get_remote_path(item.path)
with fs.open(item.path, "rb") as source_file:
with sftp.file(remote_path, "wb") as remote_file:
remote_file.write(source_file.read())
remote_paths.append({"file_name": item.path, "remote_path": remote_path})
finally:
sftp.close()
finally:
client.close()
proxy_socket.close()
output_file.write_pandas(
pd.DataFrame.from_records(remote_paths, columns=["file_name", "remote_path"])
)
def get_remote_path(file_path):
remote_target_folder = "<example_target_folder>"
clean_file_path = file_path.replace(" ", "_")
return f"{remote_target_folder}/{clean_file_path}"
대상 경로에 필요한 디렉터리는 미리 존재하고 사용자에게 쓰기 권한이 있어야 합니다. 파일 이름의 공백은 밑줄로 바꾸므로 변환 후 이름이 충돌하지 않는 입력을 사용합니다. wb로 파일을 열기 때문에 같은 원격 파일이 있으면 내용을 교체합니다.
기존 SFTP 소스에서 전환
이전 SFTP에서 새 커넥터로 자동 이전하는 기능은 없습니다. 새 소스를 만들고 기존 구성을 수동으로 옮깁니다.
- 기존 소스에서 비밀번호를 다시 읽을 수 없습니다. 값을 잃었다면 서버 관리자에게 요청합니다.
- 새 소스는
privateKeyFile,privateKeyPassphrase를 지원하지 않습니다. private key 내용을 소스 설정에 직접 입력합니다. - 새 소스는
knownHostsFile을 지원하지 않습니다. 서버의 Base64 호스트 키를 소스의 Host key 설정에 지정합니다.
이전 소스의 구성 형식
기존 구성을 유지보수할 때에는 + New source의 Advanced에서 Custom source를 사용하며 유형은 magritte-sftp여야 합니다. 다음 이름, 경로, 포트와 키 내용은 구성 형식을 보여 주는 예시입니다.
type: magritte-sftp
hostname: my.host.name
port: 22
username: username
password: '{{password}}'
rootDirectory: /home/palantir/sftp
base64Hostkey: >-
FULL CONTENTS OF THE HOST KEY
unsafeAcceptAllHostKeys: false
privateKeyFile: location/of/private/key/file
privateKeyPassphrase: '{{passphrase}}'
proxyConfiguration:
host: hostname.of.proxy
port: 8080
type: HTTP
credentials:
username: proxyUsername
password: '{{proxyPassword}}'
timeout: 0
maxConcurrentConnections: 10
sessionParams:
customKex:
- "ecdh-sha2-nistp256"
- "ecdh-sha2-nistp384"
knownHostsFile: /path/to/known_hosts_file
프록시 유형에는 HTTP, HTTPS 또는 SOCKS를 사용할 수 있습니다. 새 소스와 이전 YAML의 설정 필드를 혼용하지 않습니다.
이전 Export task
새 내보내기는 현재 SFTP 커넥터의 파일 내보내기로 만듭니다. 다음은 기존 Export task를 유지보수하기 위한 구성입니다.
프로젝트에서 대상 소스를 오른쪽 클릭하고 Create Data Connection Task를 선택합니다. 왼쪽 패널의 Source가 올바른지 확인하고 inputDataset이라는 Input과 outputDataset이라는 Output을 추가합니다. 출력 데이터셋은 실행, 예약과 모니터링에 사용합니다. 패널의 표시 이름은 YAML에서 사용하는 이름을 대신하지 않습니다.
| 속성 | 필수 여부 | 의미 |
|---|---|---|
directoryPath | 필수 | 쓸 디렉터리이며 끝에 /가 있어야 합니다. |
excludePaths | 선택 | 내보내지 않을 파일 이름의 정규식 목록입니다. |
rewritePaths | 선택 | 정규식과 치환값으로 출력 경로를 바꿉니다. |
uploadConfirmation | 선택 | exportedFiles이면 출력 데이터셋에 내보낸 파일 목록을 씁니다. |
createTransactionFolders | 선택 | 트랜잭션 커밋 시각을 바탕으로 트랜잭션마다 고유한 하위 폴더를 만듭니다. |
incrementalType | 선택 | incremental이면 이전 내보내기 이후의 트랜잭션만 처리합니다. |
flagFile | 선택 | 빌드의 파일 복사가 끝났음을 알리는 빈 파일을 씁니다. |
spanMultipleViews | 선택 | true이면 한 빌드에서 여러 트랜잭션을 내보냅니다. false이면 하나씩 내보내며 증분일 때 가장 오래된 트랜잭션부터 처리합니다. |
경로 재작성
첫 번째로 파일 이름과 일치하는 정규식 키의 캡처 그룹을 치환값으로 바꿉니다. 치환값에는 다음 메타데이터 표현을 넣을 수 있습니다.
| 표현 | 치환 내용 |
|---|---|
${dt:javaDateExpression} | 파일을 내보내는 시각을 Java DateTimeFormatter 패턴으로 표시합니다. |
${transaction} | 파일이 포함된 Foundry 트랜잭션 ID입니다. |
${dataset} | 파일이 포함된 Foundry 데이터셋 ID입니다. |
가상 파일 spark/file_name, 트랜잭션 transaction_id, 데이터셋 dataset_id에 정규식 fi.*ame와 치환값 file_${dt:DD-MM-YYYY}-${transaction}-${dataset}_end를 적용한 출력 예시는 spark/file_79-03-2023-transaction_id-dataset_id_end입니다. 날짜 패턴의 대소문자는 그대로 의미를 가지므로 다른 날짜 표기와 혼동하지 않습니다.
완료 표시 파일
flag file은 빌드에서 내보내는 모든 데이터 복사가 끝난 뒤 생성하는 빈 파일입니다. 기본적으로 directoryPath에 쓰며 createTransactionFolders를 켜면 데이터가 쓰인 각 폴더에 만듭니다.
예를 들어 이름이 confirmation.txt이면 모든 파일 내보내기가 끝난 시점에 필요한 표시 파일을 함께 씁니다. 개별 하위 폴더가 끝날 때마다 기록하는 것은 아닙니다. 서버 데이터 파일이 표시 파일보다 새로우면 이전 내보내기가 성공하지 않았거나 현재 내보내기가 진행 중일 수 있습니다.
type: export-sftp-task
directoryPath: export-directory/subdirectory/
directoryPath는 연결 루트에 대한 상대 경로이며 서버에 미리 존재해야 합니다. 루트가 /home/palantir/sftp이고 상대 경로가 export-directory/이면 실제 대상은 /home/palantir/sftp/export-directory/입니다. 구성 후 Save를 선택합니다.
로그와 문제 확인
Data Connections의 Logs에서 원천 탐색과 빌드 로그를 확인하고 jschLogMessage를 검색합니다.
Borrowing connection for ls failed 안에 SocketException: Connection reset이 있으면 Connection established, Remote version string이 기록되었는지 확인합니다. 두 항목이 없으면 외부 통신 정책, 방화벽과 서버의 유입 연결 허용을 조사합니다.
연결이 멈춘 채 진행되지 않으면 Timeout을 1000 같은 작은 값으로 조정해 어느 명령에서 기다리는지 로그를 얻을 수 있습니다. 디렉터리 목록이나 파일 읽기의 일부 단계가 지연될 수 있습니다. 기존 에이전트 실행 경로에 프록시 부하 분산기가 있으면 연속 요청의 출발 IP와 연결 경로가 일관되는지도 확인합니다.
다음은 연결 협상과 공개 키 인증이 완료되는 로그 형식의 예시입니다. 호스트와 버전 값은 환경마다 다릅니다.
jschLogMessage: Connecting to <HOSTNAME> port 2232
jschLogMessage: Connection established
jschLogMessage: Remote version string: SSH-2.0-OpenSSH_8.4p1 Debian-5+deb11u1
jschLogMessage: Local version string: SSH-2.0-JSCH_0.2.12
jschLogMessage: CheckCiphers: chacha20-poly1305@openssh.com
jschLogMessage: CheckKexes: curve25519-sha256,curve25519-sha256@libssh.org,curve448-sha512
jschLogMessage: CheckSignatures: ssh-ed25519,ssh-ed448
jschLogMessage: server_host_key proposal before known_host reordering is: ssh-ed25519,ecdsa-sha2-nistp256,ecdsa-sha2-nistp384,ecdsa-sha2-nistp521,rsa-sha2-512,rsa-sha2-256,ssh-dss,ssh-rsa
jschLogMessage: server_host_key proposal after known_host reordering is: ssh-ed25519,rsa-sha2-512,rsa-sha2-256,ssh-rsa,ecdsa-sha2-nistp256,ecdsa-sha2-nistp384,ecdsa-sha2-nistp521,ssh-dss
jschLogMessage: SSH_MSG_KEXINIT sent
jschLogMessage: SSH_MSG_KEXINIT received
jschLogMessage: server proposal: KEX algorithms: curve25519-sha256,curve25519-sha256@libssh.org,ecdh-sha2-nistp256,ecdh-sha2-nistp384,ecdh-sha2-nistp521,diffie-hellman-group-exchange-sha256,diffie-hellman-group16-sha512,diffie-hellman-group18-sha512,diffie-hellman-group14-sha256
jschLogMessage: server proposal: host key algorithms: ssh-ed25519,rsa-sha2-512,rsa-sha2-256,ssh-rsa
jschLogMessage: server proposal: ciphers c2s: chacha20-poly1305@openssh.com,aes128-ctr,aes192-ctr,aes256-ctr,aes128-gcm@openssh.com,aes256-gcm@openssh.com
jschLogMessage: server proposal: ciphers s2c: chacha20-poly1305@openssh.com,aes128-ctr,aes192-ctr,aes256-ctr,aes128-gcm@openssh.com,aes256-gcm@openssh.com
jschLogMessage: server proposal: MACs c2s: umac-64-etm@openssh.com,umac-128-etm@openssh.com,hmac-sha2-256-etm@openssh.com,hmac-sha2-512-etm@openssh.com,hmac-sha1-etm@openssh.com,umac-64@openssh.com,umac-128@openssh.com,hmac-sha2-256,hmac-sha2-512,hmac-sha1
jschLogMessage: server proposal: MACs s2c: umac-64-etm@openssh.com,umac-128-etm@openssh.com,hmac-sha2-256-etm@openssh.com,hmac-sha2-512-etm@openssh.com,hmac-sha1-etm@openssh.com,umac-64@openssh.com,umac-128@openssh.com,hmac-sha2-256,hmac-sha2-512,hmac-sha1
jschLogMessage: server proposal: compression c2s: none,zlib@openssh.com
jschLogMessage: server proposal: compression s2c: none,zlib@openssh.com
jschLogMessage: server proposal: languages c2s:
jschLogMessage: server proposal: languages s2c:
jschLogMessage: client proposal: KEX algorithms: curve25519-sha256,curve25519-sha256@libssh.org,ecdh-sha2-nistp256,ecdh-sha2-nistp384,ecdh-sha2-nistp521,diffie-hellman-group-exchange-sha256,diffie-hellman-group16-sha512,diffie-hellman-group18-sha512,diffie-hellman-group14-sha256,diffie-hellman-group14-sha1,diffie-hellman-group-exchange-sha1,diffie-hellman-group1-sha1,ext-info-c
jschLogMessage: client proposal: host key algorithms: ssh-ed25519,rsa-sha2-512,rsa-sha2-256,ssh-rsa,ecdsa-sha2-nistp256,ecdsa-sha2-nistp384,ecdsa-sha2-nistp521,ssh-dss
jschLogMessage: client proposal: ciphers c2s: aes128-ctr,aes192-ctr,aes256-ctr,aes128-gcm@openssh.com,aes256-gcm@openssh.com,aes128-cbc,3des-ctr,3des-cbc,blowfish-cbc,aes192-cbc,aes256-cbc
jschLogMessage: client proposal: ciphers s2c: aes128-ctr,aes192-ctr,aes256-ctr,aes128-gcm@openssh.com,aes256-gcm@openssh.com,aes128-cbc,3des-ctr,3des-cbc,blowfish-cbc,aes192-cbc,aes256-cbc
jschLogMessage: client proposal: MACs c2s: hmac-sha2-256-etm@openssh.com,hmac-sha2-512-etm@openssh.com,hmac-sha1-etm@openssh.com,hmac-sha2-256,hmac-sha2-512,hmac-sha1,hmac-md5,hmac-sha1-96,hmac-md5-96
jschLogMessage: client proposal: MACs s2c: hmac-sha2-256-etm@openssh.com,hmac-sha2-512-etm@openssh.com,hmac-sha1-etm@openssh.com,hmac-sha2-256,hmac-sha2-512,hmac-sha1,hmac-md5,hmac-sha1-96,hmac-md5-96
jschLogMessage: client proposal: compression c2s: none
jschLogMessage: client proposal: compression s2c: none
jschLogMessage: client proposal: languages c2s:
jschLogMessage: client proposal: languages s2c:
jschLogMessage: kex: algorithm: curve25519-sha256
jschLogMessage: kex: host key algorithm: ssh-ed25519
jschLogMessage: kex: server->client cipher: aes128-ctr MAC: hmac-sha2-256-etm@openssh.com compression: none
jschLogMessage: kex: client->server cipher: aes128-ctr MAC: hmac-sha2-256-etm@openssh.com compression: none
jschLogMessage: SSH_MSG_KEX_ECDH_INIT sent
jschLogMessage: expecting SSH_MSG_KEX_ECDH_REPLY
jschLogMessage: ssh_eddsa_verify: ssh-ed25519 signature true
jschLogMessage: Host '[<HOSTNAME>]:2232' is known and matches the EDDSA host key
jschLogMessage: SSH_MSG_NEWKEYS sent
jschLogMessage: SSH_MSG_NEWKEYS received
jschLogMessage: SSH_MSG_SERVICE_REQUEST sent
jschLogMessage: SSH_MSG_EXT_INFO received
jschLogMessage: server-sig-algs=<ssh-ed25519,sk-ssh-ed25519@openssh.com,ssh-rsa,rsa-sha2-256,rsa-sha2-512,ssh-dss,ecdsa-sha2-nistp256,ecdsa-sha2-nistp384,ecdsa-sha2-nistp521,sk-ecdsa-sha2-nistp256@openssh.com,webauthn-sk-ecdsa-sha2-nistp256@openssh.com>
jschLogMessage: SSH_MSG_SERVICE_ACCEPT received
jschLogMessage: Authentications that can continue: publickey,keyboard-interactive,password
jschLogMessage: Next authentication method: publickey
jschLogMessage: PubkeyAcceptedAlgorithms = ssh-ed25519,ecdsa-sha2-nistp256,ecdsa-sha2-nistp384,ecdsa-sha2-nistp521,rsa-sha2-512,rsa-sha2-256,ssh-rsa
jschLogMessage: PubkeyAcceptedAlgorithms in server-sig-algs = [ssh-ed25519, ecdsa-sha2-nistp256, ecdsa-sha2-nistp384, ecdsa-sha2-nistp521, rsa-sha2-512, rsa-sha2-256, ssh-rsa]
jschLogMessage: rsa-sha2-512 preauth success
jschLogMessage: rsa-sha2-512 auth success
jschLogMessage: Authentication succeeded (publickey).
이 예시의 서버 호스트 키 알고리즘은 ssh-ed25519, rsa-sha2-512, rsa-sha2-256, ssh-rsa입니다. ssh-ed25519 협상에 문제가 있으면 서버가 함께 지원하는 rsa-sha2-512를 Host key types에서 선택하는 식으로 호환성을 조정할 수 있습니다.