
회사에서 "다운로드한 데이터셋 S3에 올려달라"라는 요청을 받고, S3가 뭔지부터 찾아보며 삽질한 기록을 남긴다. 나처럼 AWS를 처음 만지는 분들에게 또 까먹고 버벅거릴 미래의 나에게 도움이 되길 바라며 마주했던 모든 에러까지 전부 정리해 봤다.
0. 상황
대용량 데이터셋(약 몇백기가, 파일 몇만개)을 로컬에 다운로드하고 있었는데, 이걸 회사 AWS S3에 올려달라는 요청을 받았다. 문제는 내가 S3를 한 번도 안 써봤다는 것...!
1. S3란 무엇인가
s3는 용량 무제한 클라우드 외장하드 + 웹 API에 가깝다는 걸 알게 되었다.
- EC2 같은 서버처럼 안에서 프로그램이 돌아가는 게 아니라, 파일을 넣고(put) 꺼내는(get) 저장소다.
- 요금은 서버처럼 "켜져 있는 시간"이 아니라 저장한 용량과 전송량 기준으로 매겨진다.
- 버킷(bucket) 이 최상위 컨테이너다. 폴더 트리의 루트라고 생각하면 된다.
S3에는 사실 "폴더"가 없다
이게 제일 신기했던 부분이었다. S3는 파일 시스템이 아니라 거대한 키-값 저장소다. Dataset/Images/feature.bin이라는 건 폴더 안의 폴더 안의 파일이 아니라, 이름에 슬래시(/)가 포함된 하나의 긴 파일 이름(키) 이다. 콘솔이 슬래시를 기준으로 폴더처럼 보여주는 것뿐이다.
실무적으로 의미하는 것:
- 빈 폴더를 미리 만들어둘 필요가 전혀 없다. 업로드 경로에 슬래시만 포함되면 "폴더"는 자동으로 생겨 보인다.
- 폴더 안의 파일을 전부 지우면 폴더도 같이 사라져 보인다. 진짜 폴더가 아니니까.
- 파일 "이름 변경"이나 "이동"은 사실 복사 후 삭제로 처리된다.
2. 버킷을 마음대로 만들어도 되나?
결론부터: 기술적으로는 되지만, 먼저 팀에 확인하는 게 맞다.
- 회사 계정에는 보통 용도별 버킷과 네이밍 규칙이 이미 잡혀 있다. 기존 버킷의 지정된 경로(prefix)에 올리는 게 정답인 경우가 많다.
- 인프라를 코드로 관리하는 회사라면, 콘솔에서 수동으로 만든 버킷은 코드와 실제 상태가 어긋나서 관리 사각지대가 되기 쉽다.
- 버킷 이름은 한번 만들면 변경이 불가능하다.
나는 요청자에게 확인했더니 "ap-northeast-2에 dataset이라는 이름으로 만들어달라"는 답을 받아서 직접 생성하게 됐다.
3. 버킷 만들기 (콘솔)
S3 콘솔에서 → 버킷 만들기 버튼을 클릭하면 된다. 설정은 거의 전부 기본값으로 가면 된다.
| 버킷 유형 | 범용 | 기본값. 특별한 이유 없으면 이걸로 |
| 리전 | ap-northeast-2 (서울) | 기존 버킷들과 통일. ap-northeast-1은 도쿄니까 주의 |
| 네임스페이스 | 글로벌 네임스페이스 | "계정 리전 네임스페이스(권장)"이 있지만 신생 방식. 기존 버킷들과의 일관성을 위해 표준 방식 유지 |
| 객체 소유권 | ACL 비활성화됨(권장) | 요즘 표준. IAM/버킷 정책으로만 권한 관리 |
| 퍼블릭 액세스 차단 | 모든 퍼블릭 액세스 차단 (기본값 유지) | 내부 데이터가 인터넷에 공개될 이유가 없다 |
| 버전 관리 | 비활성화 | 켜면 덮어쓴 이전 버전까지 전부 과금된다. 한번 올리고 잘 안 바뀌는 데이터셋에는 불필요 |
| 암호화 | SSE-S3 (기본값) | 그대로 |
한 가지 유의점: 글로벌 네임스페이스에서는 버킷 이름이 전 세계 모든 AWS 계정을 통틀어 유일해야 한다. dataset처럼 일반적인 이름은 이미 누가 쓰고 있을 수 있다.
4. 업로드 방법 선택: 콘솔 vs CLI
버킷을 만들면 주황색 업로드 버튼으로 웹에서 바로 올릴 수 있다. 폴더를 드래그하면 폴더 구조도 그대로 유지된다.
하지만 몇만개가 넘는 대용량 데이터를 콘솔로 올리는 건 무리라는 걸 깨달았다:
- 업로드 내내 브라우저 탭을 살려둬야 한다. 컴퓨터가 잠들거나, 와이파이가 잠깐 끊기거나, 콘솔 세션이 만료되면(회사 SSO 세션은 보통 몇 시간짜리) 그대로 실패한다.
- 파일이 수만 개면 브라우저가 하나씩 처리하느라 CLI보다 훨씬 느리다.
- 일부만 실패했을 때 실패분만 골라 재시도하는 게 사실상 불가능하다.
반면 CLI의 aws s3 sync는:
- 이미 올라간 파일은 건너뛴다 → 중간에 끊겨도 같은 명령을 재실행하면 이어서 올라간다.
- 작은 파일 수만 개도 병렬 처리해서 빠르다.
- -exclude 옵션으로 맥이 폴더마다 만드는 쓰레기 파일 .DS_Store를 걸러낼 수 있다.
5. AWS CLI 설치와 인증
설치(맥북 기준)
brew install awscli
aws --version # 버전 나오면 성공
인증의 함정: 콘솔 비밀번호 ≠ CLI 인증
여기서 처음 헤맸다. AWS 인증은 두 종류다:
- 아이디/비밀번호 → 웹 콘솔 로그인용
- 액세스 키 (Access Key ID + Secret Access Key) → CLI용
관리자에게 받은 건 콘솔 로그인 정보뿐이었으므로, 액세스 키를 따로 만들어야 했다.
에러 1: 액세스 키 섹션이 안 보임.
콘솔 우측 상단 계정 이름 → 보안 자격 증명 에 가면 액세스 키 섹션이 있어야 하는데, 그 자리에 빨간 에러만 떠 있었다:
User: arn:aws:iam::************:user/my-user is not authorized to perform:
iam:ListAccessKeys ... no identity-based policy allows the action
원인: 내 계정에 S3 권한은 있었지만(버킷 생성/업로드 가능), IAM 권한(액세스 키 생성·조회)은 없었다. AWS 권한은 서비스별로 따로 부여되기 때문에 이런 조합이 흔하다. "S3 작업만 할 사람이니 S3 권한만 주자"는 세팅.
해결: 관리자에게 요청해서 권한을 부여 받았다. 권한이 열리면 보안 자격 증명 페이지에서 액세스 키 만들기 → 사용 사례 CLI 선택 → 생성 순으로 액세스 키를 만들 수 있다.
Secret Access Key는 생성 화면을 벗어나면 다시 볼 수 없다. csv로 받아두거나 화면을 띄워둔 채 바로 등록해야 한다.
CLI에 키 등록
aws configure
# Access Key ID / Secret Access Key 입력
# Default region: ap-northeast-2
# output format: 엔터
aws sts get-caller-identity # 내 ARN이 나오면 인증 성공
에러 2: 업로드했더니 AccessDenied - MFA의 벽
인증까지 확인하고 드디어 업로드를 실행했는데 아래처럼 MFA 관련 에러가 발생했다.
fatal error: An error occurred (AccessDenied) when calling the ListObjectsV2
operation: User: arn:aws:iam::************:user/my-user is not authorized to
perform: s3:ListBucket ... with an explicit deny in an identity-based policy:
arn:aws:iam::************:policy/company-enforce-mfa
원인: 에러 끝의 정책 이름(enforce-mfa)이 힌트였다. 회사에서 "MFA 없이 들어온 요청은 전부 거부" 하도록 강제해둔 것이다. 콘솔에서는 로그인할 때 MFA를 거치니 문제없었지만, 액세스 키는 MFA 없이 쓰는 장기 자격 증명이라 막힌 것이다.
해결: MFA 코드로 12시간짜리 임시 세션을 발급받아서 쓴다. aws sts get-session-token에 MFA 6자리를 넘기면 임시 자격 증명 3종(Access Key / Secret / Session Token)이 나오고, 이걸 환경 변수로 등록하면 된다.
매번 손으로 하기 귀찮으니 셸 함수로 만들어 ~/.zshrc에 넣어두면 편하다:
company-mfa() {
local code="$1"
if [ -z "$code" ]; then
echo "usage: company-mfa <MFA 6자리>"
return 1
fi
local creds
creds=$(aws sts get-session-token \
--profile my-longterm \
--serial-number arn:aws:iam::계정ID:mfa/디바이스이름 \
--token-code "$code" \
--duration-seconds 43200 \
--query 'Credentials.[AccessKeyId,SecretAccessKey,SessionToken,Expiration]' \
--output text) || return 1
export AWS_ACCESS_KEY_ID=$(echo "$creds" | cut -f1)
export AWS_SECRET_ACCESS_KEY=$(echo "$creds" | cut -f2)
export AWS_SESSION_TOKEN=$(echo "$creds" | cut -f3)
export AWS_DEFAULT_REGION=ap-northeast-2
echo "MFA 세션 활성화 — 만료: $(echo "$creds" | cut -f4)"
}
함수가 참조하는 프로파일 이름으로 장기 키를 등록해둬야 한다.
aws configure --profile my-longterm # 아까 만든 액세스 키 등록
사용법:
source ~/.zshrc # 함수 반영 (최초 1회)
company-mfa 123456 # 인증 앱의 실제 6자리
# → "MFA 세션 활성화 — 만료: ..." 나오면 성공
주의할 점:
- 임시 자격 증명은 환경 변수라서, 함수를 실행한 그 터미널 창에서만 유효하다. 새 창을 열면 다시 실행해야 한다.
- 12시간 뒤 세션이 만료되면 ExpiredToken 에러가 난다. 새 코드로 함수를 재실행하고 sync를 다시 돌리면 멈춘 지점부터 이어진다.
6. 업로드
데이터셋 폴더들이 있는 상위 디렉터리로 이동해서 아래 명령어를 수행해준다:
company-mfa 123456 # MFA 세션 활성화 (같은 창에서!)
caffeinate -i aws s3 sync . s3://dataset/ --exclude "*.DS_Store"
한 줄에 담긴 것들:
- caffeinate -i — 업로드가 끝날 때까지 맥이 잠들지 않게 한다 (뚜껑은 열어둘 것, 전원 연결 권장)
- sync . — 현재 디렉터리의 폴더 구조를 그대로 미러링. TVR/FeatureData/feature.bin이 버킷에서도 같은 경로가 된다
- -exclude "*.DS_Store" — 맥 시스템 쓰레기 파일 제외
중간에 끊기면 같은 명령을 그대로 재실행하면 안 올라간 파일만 이어서 올라간다. 이게 sync의 최대 장점이다.
검증
# S3 쪽 총 파일 수와 용량
aws s3 ls s3://dataset/ --recursive --summarize --human-readable | tail -3
# 로컬 쪽 파일 수 (.DS_Store 제외)
find . -type f ! -name ".DS_Store" | wc -l
두 숫자가 일치하면 업로드가 모두 정상적으로 완료 된 것이다.
요청자에게 s3://dataset/경로 형태로 알려주면 끝이다.
정리: 삽질에서 배운 것들
- S3는 서버가 아니라 저장소다. 폴더도 사실은 없다. 슬래시가 든 긴 파일 이름이 있을 뿐.
- 버킷은 마음대로 만들지 말고 팀에 먼저 물어보자. 네이밍 규칙과 인프라 코드가 있을 수 있다.
- 대용량은 무조건 CLI + sync. 브라우저 업로드는 수만 개 파일 앞에서 무력하다.
- 콘솔 비밀번호와 CLI 액세스 키는 별개다. 그리고 액세스 키를 만들 IAM 권한이 없을 수도 있다.
- S3 권한과 IAM 권한은 서비스별로 따로 논다. 버킷을 만들 수 있어도 액세스 키는 못 만들 수 있다.
- explicit deny + 정책 이름에 mfa가 보이면 → MFA 임시 세션(sts get-session-token)이 답이다.
- 임시 세션은 환경 변수라 그 터미널 창에서만 유효하다. 12시간 만료되면 재발급 후 sync 재실행.
- 복붙한 코드가 이상한 에러를 내면 보이지 않는 특수 문자를 의심하자.
- caffeinate -i 는 밤샘 업로드의 친구다.
- Secret Access Key와 비밀번호는 절대 채팅에 평문으로 남기지 말 것.