[클라이언트] --FTP(21/2121)+Passive Data Port--> [SFTPGo Pod] --S3 API--> [AIStor]
|
[메타데이터 DB: SQLite/PostgreSQL]
먼저 결정해야 할 것 3가지
ClusterIP Service로는 동작하지 않습니다. → NodePort(범위 지정) 또는 hostNetwork/hostPort 방식이 필요합니다. (가능하다면 SFTP가 NAT/방화벽 친화적이라 더 낫지만, 요구사항이 FTP이므로 이 가이드는 FTP 기준으로 작성합니다.)Secret으로 관리하고, ConfigMap이나 평문 env로 노출하지 않습니다.kubectl create namespace sftpgo
AIStor 접속 정보 Secret
kubectl -n sftpgo create secret generic aistor-s3-credentials \
--from-literal=access_key='<AISTOR_ACCESS_KEY>' \
--from-literal=access_secret='<AISTOR_SECRET_KEY>'
SFTPGo 초기 관리자 계정 Secret
kubectl -n sftpgo create secret generic sftpgo-admin-credentials \
--from-literal=admin_user='admin' \
--from-literal=admin_password='<강력한-비밀번호>'
SFTPGo는 기본적으로 env var(SFTPGO_섹션__키 형태)로 대부분 설정을 오버라이드할 수 있습니다. FTP 활성화 + passive IP 설정이 핵심입니다.
# configmap.yaml
apiVersion: v1
kind: ConfigMap
metadata:
name: sftpgo-config
namespace: sftpgo
data:
SFTPGO_FTPD__BINDINGS__0__PORT: "2121"
SFTPGO_FTPD__BINDINGS__0__FORCE_PASSIVE_IP: "<노드의 외부 접근 가능 IP 또는 LB IP>"
SFTPGO_FTPD__PASSIVE_PORT_RANGE__START: "50000"
SFTPGO_FTPD__PASSIVE_PORT_RANGE__END: "50100"
# 데이터 제공자 (기본 SQLite 유지 시 아래 생략 가능)
SFTPGO_DATA_PROVIDER__DRIVER: "sqlite"
SFTPGO_DATA_PROVIDER__NAME: "/srv/sftpgo/data/sftpgo.db"
# 최초 admin 자동 생성 (선택)
SFTPGO_DEFAULT_ADMIN__USERNAME: "admin"
FORCE_PASSIVE_IP는 FTP passive mode의 핵심입니다. 클라이언트가 데이터 채널을 열 때 서버가 "이 IP로 접속해"라고 알려주는 값인데, Pod 내부 IP를 그대로 주면 외부에서 접근이 불가능합니다. NodePort를 쓴다면 해당 Node의 외부 IP, LoadBalancer를 쓴다면 그 LB의 IP를 지정해야 합니다.
# deployment.yaml
apiVersion: apps/v1
kind: Deployment
metadata:
name: sftpgo
namespace: sftpgo
spec:
replicas: 1 # SQLite 사용 시 반드시 1 유지
strategy:
type: Recreate # PVC 단일 마운트이므로 롤링 업데이트 대신 Recreate
selector:
matchLabels:
app: sftpgo
template:
metadata:
labels:
app: sftpgo
spec:
containers:
- name: sftpgo
image: drakkan/sftpgo:v2.6.7 # 배포 시점 최신 안정 버전으로 확인 후 지정 권장
envFrom:
- configMapRef:
name: sftpgo-config
env:
- name: SFTPGO_DEFAULT_ADMIN__PASSWORD
valueFrom:
secretKeyRef:
name: sftpgo-admin-credentials
key: admin_password
ports:
- containerPort: 8080 # Web/REST API
- containerPort: 2022 # SFTP (필요 없으면 제거 가능)
- containerPort: 2121 # FTP 제어 채널
- containerPort: 50000 # Passive 포트 range 시작
# K8s ports는 단일 포트만 명시 가능하므로 range는 Service/방화벽에서 처리
volumeMounts:
- name: sftpgo-data
mountPath: /srv/sftpgo/data
resources:
requests:
cpu: "250m"
memory: "256Mi"
limits:
cpu: "1"
memory: "1Gi"
livenessProbe:
httpGet:
path: /healthz
port: 8080
initialDelaySeconds: 10
periodSeconds: 15
volumes:
- name: sftpgo-data
persistentVolumeClaim:
claimName: sftpgo-data-pvc
---
apiVersion: v1
kind: PersistentVolumeClaim
metadata:
name: sftpgo-data-pvc
namespace: sftpgo
spec:
accessModes: ["ReadWriteOnce"]
resources:
requests:
storage: 5Gi
# service.yaml
apiVersion: v1
kind: Service
metadata:
name: sftpgo
namespace: sftpgo
spec:
type: NodePort
selector:
app: sftpgo
ports:
- name: web
port: 8080
targetPort: 8080
- name: ftp-control
port: 2121
targetPort: 2121
nodePort: 30121
- name: passive-50000
port: 50000
targetPort: 50000
nodePort: 30000
# NodePort Service는 포트를 하나씩 나열해야 함 — range 전체(50000~50100)를
# 매핑하려면 101개 port 항목이 필요하거나, 아래 "옵션 B(hostNetwork)"가 현실적입니다.
중요: 표준 K8s Service는 포트 range를 한 번에 지정할 수 없습니다(각 포트를 개별 선언해야 함). Passive port를 50~100개 단위로 열려면 YAML이 매우 길어집니다. 실무에서는 아래 옵션 B(hostNetwork)를 더 많이 사용합니다.
# deployment.yaml 의 spec.template.spec 에 추가
spec:
hostNetwork: true
dnsPolicy: ClusterFirstWithHostNet
containers:
- name: sftpgo
ports:
- containerPort: 2121
hostPort: 2121
- containerPort: 8080
hostPort: 8080
# passive range는 컨테이너 내부 -e 로 전달한 50000-50100과
# 노드 자체의 방화벽 정책만 맞으면 hostNetwork 특성상 그대로 통과됩니다.
hostNetwork: true를 쓰면 Pod가 노드의 네트워크 스택을 직접 사용하므로 passive port range가 별도 매핑 없이 그대로 열립니다. 다만 해당 노드에는 SFTPGo Pod가 1개만 뜰 수 있고(포트 충돌), 노드 방화벽에서 50000-50100/tcp를 직접 열어야 합니다.FORCE_PASSIVE_IP는 그 노드의 IP로 지정합니다.nodeSelector로 특정 노드에 고정 배치하는 것을 권장합니다. nodeSelector:
sftpgo-node: "true"
관리자 계정이 뜬 뒤, REST API로 "AIStor를 백엔드로 쓰는 FTP 업로드 전용 사용자"를 자동 생성하는 Job입니다.
# init-user-job.yaml
apiVersion: batch/v1
kind: Job
metadata:
name: sftpgo-init-user
namespace: sftpgo
spec:
template:
spec:
restartPolicy: OnFailure
containers:
- name: init-user
image: curlimages/curl:8.10.1
env:
- name: ADMIN_USER
valueFrom: { secretKeyRef: { name: sftpgo-admin-credentials, key: admin_user } }
- name: ADMIN_PASS
valueFrom: { secretKeyRef: { name: sftpgo-admin-credentials, key: admin_password } }
- name: S3_ACCESS_KEY
valueFrom: { secretKeyRef: { name: aistor-s3-credentials, key: access_key } }
- name: S3_ACCESS_SECRET
valueFrom: { secretKeyRef: { name: aistor-s3-credentials, key: access_secret } }
command: ["/bin/sh", "-c"]
args:
- |
set -e
BASE=http://sftpgo.sftpgo.svc.cluster.local:8080/api/v2
# 1) 관리자 토큰 발급
TOKEN=$(curl -s -u "$ADMIN_USER:$ADMIN_PASS" "$BASE/token" | sed -n 's/.*"access_token":"\([^"]*\)".*/\1/p')
# 2) FTP 업로드용 사용자 생성 (S3 필터시스템, AIStor 엔드포인트 연결)
curl -s -X POST "$BASE/users" \
-H "Authorization: Bearer $TOKEN" \
-H "Content-Type: application/json" \
-d '{
"username": "gpu-uploader",
"password": "'"<업로드-계정-비밀번호>"'",
"status": 1,
"permissions": { "/": ["*"] },
"filesystem": {
"provider": 1,
"s3config": {
"bucket": "<AISTOR_BUCKET_NAME>",
"region": "us-east-1",
"access_key": "'"$S3_ACCESS_KEY"'",
"access_secret": "'"$S3_ACCESS_SECRET"'",
"endpoint": "https://<AISTOR_ENDPOINT_HOST>:<PORT>",
"force_path_style": true,
"key_prefix": "gpu-node-uploads/"
}
}
}'
S3 config에서 특히 신경 써야 할 값
endpoint: AIStor의 S3 API 엔드포인트 (예: https://aistor.internal.example.com:9000). AWS S3가 아니므로 반드시 명시.force_path_style: true: AIStor/MinIO 계열은 대부분 virtual-hosted style이 아닌 path-style(https://endpoint/bucket/key)을 요구합니다. 빠뜨리면 버킷 인식 실패가 흔한 오류입니다.region: AIStor가 특정 region 문자열을 요구하지 않더라도 SDK가 빈 값을 거부하는 경우가 있어 us-east-1 등 임의 값을 넣는 것이 안전합니다.key_prefix: 이 사용자가 버킷 내 특정 경로 하위에서만 동작하게 제한하고 싶을 때 사용 (일종의 chroot). SFTPGO_FTPD__BINDINGS__0__TLS_MODE: "1" # 1=explicit, 2=implicit
이 경우 인증서/키를 Secret으로 마운트하고 SFTPGO_FTPD__CERTIFICATE_FILE, SFTPGO_FTPD__CERTIFICATE_KEY_FILE 환경변수로 경로를 지정합니다.
PutObject, ListBucket 등)으로 발급하는 것을 권장합니다.kubectl apply -f configmap.yaml
kubectl apply -f deployment.yaml
kubectl apply -f service.yaml
kubectl -n sftpgo rollout status deployment/sftpgo
kubectl apply -f init-user-job.yaml
kubectl -n sftpgo logs job/sftpgo-init-user
테스트 체크리스트
kubectl -n sftpgo get pods — Pod Running 상태 확인http://<node-ip>:8080/web/admin)lftp -u gpu-uploader,<비밀번호> ftp://<FORCE_PASSIVE_IP>:2121
put testfile.binmc ls/aws s3 ls로 직접 검증)SFTPGO_DATA_PROVIDER__DRIVER=postgresql 등), StatefulSet 대신 다수의 stateless Deployment 운용 가능./metrics 엔드포인트를 제공하므로 업로드 처리량/실패율을 모니터링 스택에 편입해두면 이후 Phase 4/5(Storage 연동 테스트, 성능 테스트)에서 유용합니다.