본 콘텐츠의 이미지 및 내용은 AI로 생성되었습니다.
본 콘텐츠의 이미지 및 내용을 무단으로 복제, 배포, 수정하여 사용할 경우 저작권법에 의해 법적 제재를 받을 수 있습니다.
이미지 로딩 중...
AI Generated
2025. 11. 2. · 26 Views
GCP 트러블슈팅 완벽 가이드
Google Cloud Platform에서 발생하는 다양한 문제를 효과적으로 해결하는 방법을 배웁니다. 실시간 로그 분석, 성능 모니터링, 네트워크 문제 해결 등 실무에서 바로 활용할 수 있는 트러블슈팅 기법을 제공합니다.
목차
- Cloud Logging으로 로그 분석하기
- Cloud Monitoring 메트릭 활용
- Error Reporting으로 에러 추적
- Cloud Trace로 성능 병목 찾기
- VPC 네트워크 문제 해결
- IAM 권한 문제 디버깅
- Compute Engine 인스턴스 문제 해결
- Cloud Storage 액세스 문제
1. Cloud Logging으로 로그 분석하기
시작하며
여러분의 GCP 애플리케이션이 갑자기 500 에러를 반환하기 시작했습니다. 사용자들은 불만을 제기하고, 여러분은 급하게 문제를 찾아야 하는 상황입니다.
하지만 수천 개의 로그 중에서 정확히 어떤 부분이 문제인지 찾기가 쉽지 않습니다. 이런 상황은 실제 운영 환경에서 매일 발생합니다.
로그가 너무 많아서 중요한 에러 메시지를 놓치거나, 여러 서비스에 분산된 로그를 추적하느라 시간을 낭비하게 됩니다. 바로 이럴 때 필요한 것이 Cloud Logging입니다.
GCP의 중앙 집중식 로깅 시스템을 활용하면 수백만 개의 로그에서 원하는 정보를 몇 초 만에 찾아낼 수 있습니다.
개요
간단히 말해서, Cloud Logging은 GCP의 모든 서비스와 애플리케이션 로그를 한곳에 모아 검색하고 분석할 수 있는 강력한 도구입니다. 실무에서 애플리케이션을 운영하다 보면 여러 서비스에서 발생하는 로그를 통합 관리해야 합니다.
Cloud Run, App Engine, GKE, Compute Engine 등 다양한 서비스의 로그가 자동으로 수집되어 하나의 인터페이스에서 조회할 수 있습니다. 예를 들어, 사용자가 결제 실패를 신고했을 때 프론트엔드부터 백엔드, 데이터베이스까지 전체 요청 흐름을 추적할 수 있습니다.
기존에는 각 서버에 SSH로 접속해서 tail -f로 로그를 확인했다면, 이제는 웹 콘솔이나 API를 통해 실시간으로 모든 로그를 검색할 수 있습니다. Cloud Logging의 핵심 특징은 강력한 필터링 기능, 실시간 스트리밍, 그리고 다른 GCP 서비스와의 긴밀한 통합입니다.
이러한 특징들은 문제 발생 시 평균 해결 시간(MTTR)을 크게 단축시켜줍니다.
코드 예제
from google.cloud import logging
# Cloud Logging 클라이언트 초기화
client = logging.Client()
# 최근 1시간 동안 발생한 ERROR 레벨 이상의 로그 필터링
filter_str = '''
severity >= ERROR
timestamp >= "2025-01-06T10:00:00Z"
resource.type = "cloud_run_revision"
'''
# 로그 엔트리 조회 (최대 100개)
for entry in client.list_entries(filter_=filter_str, max_results=100):
print(f"시간: {entry.timestamp}")
print(f"심각도: {entry.severity}")
print(f"메시지: {entry.payload}")
# 에러 발생 리소스 정보 출력
print(f"서비스: {entry.resource.labels.get('service_name')}")
print("-" * 50)
설명
이것이 하는 일: Cloud Logging API를 사용하여 특정 조건에 맞는 로그를 프로그래밍 방식으로 조회하고, 문제가 발생한 시점과 위치를 정확히 파악합니다. 첫 번째로, logging.Client()로 클라이언트를 초기화합니다.
이 클라이언트는 자동으로 애플리케이션 기본 인증 정보(ADC)를 사용하여 GCP 프로젝트에 연결됩니다. 로컬 개발 환경에서는 gcloud auth application-default login으로 인증하고, 프로덕션에서는 서비스 계정을 사용하면 됩니다.
그 다음으로, 필터 쿼리를 작성합니다. Cloud Logging은 구조화된 쿼리 언어를 지원하며, severity(심각도), timestamp(시간), resource.type(리소스 유형) 등 다양한 필드로 조건을 설정할 수 있습니다.
여기서는 ERROR 이상의 심각도를 가진 최근 1시간 내의 Cloud Run 로그만 필터링합니다. 이렇게 하면 수백만 개의 로그 중에서 실제로 문제가 있는 로그만 추출할 수 있습니다.
마지막으로, list_entries() 메서드가 실행되면서 필터 조건에 맞는 로그를 반환합니다. 각 로그 엔트리는 타임스탬프, 심각도, 메시지 본문, 리소스 정보를 포함하고 있어 어떤 서비스의 어느 인스턴스에서 문제가 발생했는지 정확히 파악할 수 있습니다.
여러분이 이 코드를 사용하면 수동으로 로그를 검색하는 대신 자동화된 스크립트로 문제를 감지하고 알림을 보낼 수 있습니다. 예를 들어, 이 코드를 Cloud Functions에 배포하고 Cloud Scheduler로 주기적으로 실행하면 실시간 모니터링 시스템을 구축할 수 있습니다.
또한 로그 데이터를 BigQuery로 내보내 장기적인 패턴 분석도 가능합니다.
실전 팁
💡 로그 기반 메트릭을 생성하여 특정 에러 패턴이 반복될 때 자동으로 알림을 받도록 설정하세요. 예를 들어 "database connection failed"가 5분 내에 10회 이상 발생하면 Slack으로 알림을 보낼 수 있습니다.
💡 로그 보관 비용을 절약하려면 로그 라우터(Log Router)를 설정하여 중요한 로그만 장기 보관하고 나머지는 7일 후 자동 삭제되도록 하세요. DEBUG 레벨 로그는 보통 단기 보관으로 충분합니다.
💡 jsonPayload 필드에 구조화된 데이터를 로깅하면 필터링과 분석이 훨씬 쉬워집니다. 단순 문자열 대신 {"user_id": "123", "action": "purchase", "amount": 50} 같은 형태로 로그를 남기세요.
💡 로그를 검색할 때는 시간 범위를 최대한 좁히세요. 넓은 시간 범위로 검색하면 쿼리 속도가 느려지고 비용도 증가합니다. 먼저 최근 1시간으로 검색하고, 필요하면 점진적으로 범위를 넓히는 것이 효율적입니다.
💡 Cloud Logging의 로그 탐색기(Logs Explorer)에서 쿼리를 작성하고 테스트한 후, "Show query" 버튼으로 필터 문자열을 복사하여 코드에 사용하면 실수를 줄일 수 있습니다.
2. Cloud Monitoring 메트릭 활용
시작하며
여러분의 웹 애플리케이션이 점점 느려지고 있습니다. 사용자들은 페이지 로딩이 오래 걸린다고 불평하지만, 정확히 어떤 리소스가 부족한지 알 수 없습니다.
CPU일까요, 메모리일까요, 아니면 네트워크 대역폭일까요? 이런 문제는 리소스 사용량을 실시간으로 모니터링하지 않으면 발견하기 어렵습니다.
문제가 심각해진 후에야 알게 되면 이미 많은 사용자에게 영향을 미친 뒤입니다. 바로 이럴 때 필요한 것이 Cloud Monitoring입니다.
GCP 리소스의 성능 메트릭을 수집하고 시각화하여 문제가 발생하기 전에 미리 감지할 수 있습니다.
개요
간단히 말해서, Cloud Monitoring은 GCP 리소스의 CPU, 메모리, 디스크, 네트워크 등 다양한 메트릭을 수집하고 분석하는 시스템입니다. 실무에서는 애플리케이션의 건강 상태를 지속적으로 추적해야 합니다.
Cloud Monitoring을 사용하면 Compute Engine 인스턴스의 CPU 사용률, Cloud SQL의 커넥션 수, Load Balancer의 요청 레이턴시 등 모든 것을 한눈에 볼 수 있습니다. 예를 들어, 블랙 프라이데이 같은 트래픽 급증 상황에서 Auto Scaling이 제대로 작동하는지 실시간으로 확인할 수 있습니다.
기존에는 각 서버에 Nagios나 Prometheus를 설치하고 관리해야 했다면, 이제는 GCP가 자동으로 메트릭을 수집하고 저장해줍니다. Cloud Monitoring의 핵심 특징은 자동 메트릭 수집, 커스텀 메트릭 지원, 그리고 알림 정책 설정입니다.
이러한 특징들은 운영팀이 24시간 모니터를 감시하지 않아도 문제를 놓치지 않게 해줍니다.
코드 예제
from google.cloud import monitoring_v3
from datetime import datetime, timedelta
# Monitoring 클라이언트 초기화
client = monitoring_v3.MetricServiceClient()
project_name = f"projects/your-project-id"
# 최근 1시간 동안의 CPU 사용률 조회
interval = monitoring_v3.TimeInterval({
"end_time": datetime.utcnow(),
"start_time": datetime.utcnow() - timedelta(hours=1),
})
# CPU 사용률 메트릭 쿼리
results = client.list_time_series(
request={
"name": project_name,
"filter": 'metric.type = "compute.googleapis.com/instance/cpu/utilization"',
"interval": interval,
"view": monitoring_v3.ListTimeSeriesRequest.TimeSeriesView.FULL,
}
)
# 인스턴스별 평균 CPU 사용률 계산
for result in results:
instance_name = result.resource.labels['instance_id']
values = [point.value.double_value for point in result.points]
avg_cpu = sum(values) / len(values) * 100
print(f"인스턴스 {instance_name}: 평균 CPU {avg_cpu:.2f}%")
# CPU 사용률이 80% 이상이면 경고
if avg_cpu > 80:
print(f"⚠️ 경고: {instance_name}의 CPU 사용률이 높습니다!")
설명
이것이 하는 일: Cloud Monitoring API를 통해 특정 시간 범위 동안의 메트릭 데이터를 조회하고, 각 인스턴스의 리소스 사용률을 분석하여 문제가 있는 리소스를 찾아냅니다. 첫 번째로, MetricServiceClient를 생성하여 Monitoring API에 연결합니다.
이 클라이언트를 통해 수백 가지의 사전 정의된 메트릭과 여러분이 직접 만든 커스텀 메트릭에 접근할 수 있습니다. 프로젝트 이름은 반드시 projects/프로젝트ID 형식이어야 합니다.
그 다음으로, 시간 범위를 정의합니다. TimeInterval은 메트릭을 조회할 시작 시간과 종료 시간을 지정합니다.
여기서는 최근 1시간의 데이터를 조회하지만, 실무에서는 더 긴 기간의 데이터를 조회하여 트렌드를 분석할 수도 있습니다. 메트릭 필터에서 compute.googleapis.com/instance/cpu/utilization은 Compute Engine 인스턴스의 CPU 사용률을 의미하며, GCP의 각 서비스마다 고유한 메트릭 타입이 있습니다.
마지막으로, list_time_series()가 실행되면서 조건에 맞는 시계열 데이터를 반환합니다. 각 시계열은 여러 데이터 포인트(points)를 포함하고 있으며, 각 포인트는 특정 시점의 값을 나타냅니다.
평균을 계산하여 전체 기간 동안의 CPU 사용 패턴을 파악하고, 임계값을 초과하면 경고를 출력합니다. 여러분이 이 코드를 사용하면 수동으로 콘솔을 확인하는 대신 자동화된 대시보드나 알림 시스템을 구축할 수 있습니다.
예를 들어, 이 스크립트를 Cloud Run Job으로 실행하고 CPU 사용률이 높은 인스턴스를 발견하면 자동으로 PagerDuty나 Slack으로 알림을 보낼 수 있습니다. 또한 메트릭 데이터를 시간별, 일별로 집계하여 용량 계획(capacity planning)에 활용할 수 있습니다.
실전 팁
💡 알림 정책(Alerting Policy)을 설정할 때는 단일 스파이크보다는 지속적인 패턴을 감지하도록 하세요. 예를 들어 "CPU 사용률이 80% 이상인 상태가 5분 이상 지속"처럼 조건을 설정하면 일시적인 부하로 인한 오탐을 줄일 수 있습니다.
💡 커스텀 메트릭을 활용하여 비즈니스 특화 지표를 모니터링하세요. 예를 들어 "분당 결제 성공 건수", "장바구니 이탈률" 같은 메트릭을 직접 정의하면 기술적 문제뿐만 아니라 비즈니스 영향도 추적할 수 있습니다.
💡 대시보드를 만들 때는 관련 메트릭을 함께 배치하세요. CPU, 메모리, 디스크 I/O를 한 화면에 보여주면 리소스 간 상관관계를 파악하기 쉽습니다. 예를 들어 메모리가 부족해지면 디스크 스왑이 증가하는 패턴을 발견할 수 있습니다.
💡 메트릭 데이터의 보관 기간을 고려하세요. Cloud Monitoring은 기본적으로 6주간 데이터를 보관하지만, 장기 분석이 필요하면 BigQuery로 내보내기를 설정하여 수년간의 데이터를 저장할 수 있습니다.
💡 여러 프로젝트를 관리한다면 Metrics Scope를 활용하여 하나의 대시보드에서 모든 프로젝트의 메트릭을 통합 조회하세요. 마이크로서비스 아키텍처에서 특히 유용합니다.
3. Error Reporting으로 에러 추적
시작하며
여러분의 프로덕션 애플리케이션에서 간헐적으로 에러가 발생하고 있습니다. 로그를 확인해보니 같은 에러가 하루에 수십 번씩 반복되는데, 매번 다른 사용자에게 발생합니다.
어떤 패턴이 있는지, 얼마나 많은 사용자가 영향을 받았는지 파악하기가 어렵습니다. 이런 상황은 특히 대규모 서비스에서 흔합니다.
수백만 개의 로그 중에서 같은 유형의 에러를 찾아 그룹화하고, 영향 범위를 분석하는 것은 많은 시간과 노력이 필요합니다. 바로 이럴 때 필요한 것이 Error Reporting입니다.
GCP가 자동으로 에러를 감지하고, 스택 트레이스를 분석하여 같은 유형의 에러끼리 그룹화해줍니다.
개요
간단히 말해서, Error Reporting은 애플리케이션에서 발생하는 예외와 에러를 자동으로 수집하고 분류하여 시각화해주는 서비스입니다. 실무에서 애플리케이션을 운영하다 보면 다양한 에러가 발생합니다.
NullPointerException, ConnectionTimeout, 403 Forbidden 등 수많은 에러가 뒤섞여 있을 때, Error Reporting은 스택 트레이스를 분석하여 같은 원인의 에러를 하나로 묶어줍니다. 예를 들어, 동일한 코드 라인에서 발생한 NullPointerException은 사용자가 다르더라도 하나의 에러 그룹으로 표시됩니다.
기존에는 로그를 일일이 읽으면서 비슷한 에러를 수동으로 찾아야 했다면, 이제는 Error Reporting이 자동으로 분류하고 발생 빈도, 영향받은 사용자 수, 처음 발생 시간과 마지막 발생 시간을 알려줍니다. Error Reporting의 핵심 특징은 자동 에러 그룹화, 실시간 알림, 그리고 스택 트레이스 분석입니다.
이러한 특징들은 개발팀이 가장 중요한 에러를 우선순위로 처리할 수 있게 해줍니다.
코드 예제
from google.cloud import error_reporting
# Error Reporting 클라이언트 초기화
client = error_reporting.Client()
try:
# 의도적으로 에러를 발생시키는 코드 (실제로는 비즈니스 로직)
user_id = None
result = process_payment(user_id) # user_id가 None이면 에러 발생
except Exception as e:
# 에러를 Error Reporting에 자동으로 전송
client.report_exception()
# 추가 컨텍스트 정보와 함께 에러 리포팅
client.report(
f"결제 처리 실패: {str(e)}",
http_context={
"method": "POST",
"url": "/api/payment",
"user_agent": "Mozilla/5.0...",
"remote_ip": "203.0.113.1"
},
user="user_12345"
)
print(f"에러가 Error Reporting에 기록되었습니다: {e}")
설명
이것이 하는 일: 애플리케이션에서 발생한 예외를 Error Reporting 서비스로 전송하여 자동으로 분류하고, 에러의 영향 범위와 발생 패턴을 분석할 수 있게 합니다. 첫 번째로, error_reporting.Client()로 클라이언트를 초기화합니다.
이 클라이언트는 현재 실행 중인 GCP 환경(Cloud Run, App Engine, GKE 등)을 자동으로 감지하고, 해당 서비스의 컨텍스트 정보를 에러 리포트에 포함시킵니다. 별도 설정 없이도 어떤 서비스, 어떤 버전에서 에러가 발생했는지 추적됩니다.
그 다음으로, try-except 블록에서 에러를 잡습니다. report_exception()을 호출하면 현재 스택 트레이스가 자동으로 캡처되어 전송됩니다.
더 상세한 정보가 필요하면 report() 메서드를 사용하여 HTTP 요청 정보, 사용자 ID, 커스텀 메시지 등을 추가할 수 있습니다. 이렇게 하면 단순히 "에러가 발생했다"가 아니라 "어떤 사용자가 어떤 요청을 했을 때 에러가 발생했는지" 정확히 알 수 있습니다.
마지막으로, Error Reporting 대시보드에서 이 에러는 스택 트레이스를 기반으로 다른 유사한 에러들과 자동으로 그룹화됩니다. 예를 들어, 같은 process_payment 함수에서 발생한 모든 NullPointerException은 하나의 그룹으로 묶여서 "이 에러가 지난 24시간 동안 327번 발생했고, 189명의 사용자에게 영향을 미쳤습니다"라는 식으로 표시됩니다.
여러분이 이 코드를 사용하면 에러 발생을 실시간으로 추적하고, 새로운 유형의 에러가 나타나면 즉시 알림을 받을 수 있습니다. Error Reporting은 Jira, GitHub Issues와 통합되므로 에러를 발견하면 버튼 클릭 하나로 이슈 티켓을 생성할 수 있습니다.
또한 에러 발생 빈도 그래프를 보면 특정 배포 후 에러가 급증했는지, 아니면 오래된 버그인지 즉시 파악할 수 있습니다.
실전 팁
💡 에러 메시지에 버전 정보를 포함시키세요. 예를 들어 서비스 버전이나 라이브러리 버전을 함께 기록하면, 특정 버전에서만 발생하는 에러를 쉽게 찾을 수 있습니다.
💡 PII(개인 식별 정보)가 에러 메시지에 포함되지 않도록 주의하세요. 사용자 이메일이나 비밀번호가 스택 트레이스에 노출되면 보안 문제가 발생합니다. 대신 user_id 같은 익명화된 식별자를 사용하세요.
💡 에러 그룹의 "Resolve" 기능을 활용하세요. 수정한 에러를 Resolved로 표시하면, 같은 에러가 다시 발생할 때 "Regression Detected" 알림을 받아 회귀 버그를 빠르게 감지할 수 있습니다.
💡 에러의 심각도를 구분하세요. 모든 예외를 Error Reporting에 보내면 중요한 에러가 묻힐 수 있습니다. 치명적인 에러(결제 실패, 데이터 손실 등)와 경미한 에러(UI 렌더링 오류 등)를 구분하여 우선순위를 정하세요.
💡 Error Reporting의 알림 정책을 설정할 때는 "새로운 에러 발생"과 "기존 에러 급증" 두 가지 시나리오를 모두 커버하세요. 완전히 새로운 에러는 최근 배포와 관련이 있을 가능성이 높고, 기존 에러의 급증은 인프라 문제나 외부 서비스 장애를 의미할 수 있습니다.
4. Cloud Trace로 성능 병목 찾기
시작하며
여러분의 API가 평소에는 200ms 안에 응답하는데, 특정 요청은 5초 이상 걸립니다. 로그를 확인해도 어디서 시간이 소요되는지 명확하지 않습니다.
데이터베이스 쿼리일까요, 외부 API 호출일까요, 아니면 내부 로직일까요? 이런 성능 문제는 마이크로서비스 아키텍처에서 특히 복잡합니다.
하나의 요청이 여러 서비스를 거치면서 각 단계별로 얼마나 시간이 걸리는지 추적하기 어렵습니다. 바로 이럴 때 필요한 것이 Cloud Trace입니다.
요청이 시스템을 통과하는 전체 경로를 시각화하고, 어느 단계에서 병목이 발생하는지 정확히 보여줍니다.
개요
간단히 말해서, Cloud Trace는 분산 트레이싱 시스템으로, 하나의 요청이 여러 서비스와 함수를 거치는 동안 각 단계의 실행 시간을 측정하고 시각화합니다. 실무에서 마이크로서비스를 운영하면 하나의 사용자 요청이 프론트엔드, API 게이트웨이, 인증 서비스, 비즈니스 로직, 데이터베이스 등 여러 컴포넌트를 거칩니다.
Cloud Trace를 사용하면 이 전체 흐름이 타임라인으로 표시되어 "인증 서비스에서 200ms, 데이터베이스 쿼리에서 3초"처럼 각 단계의 시간을 정확히 알 수 있습니다. 예를 들어, 전체 응답 시간이 5초인데 Cloud Trace를 보니 외부 결제 API 호출이 4.5초를 차지한다는 것을 발견할 수 있습니다.
기존에는 각 서비스에 타이머를 심고 로그로 시간을 기록해서 수동으로 분석했다면, 이제는 Cloud Trace가 자동으로 추적하고 워터폴 차트로 시각화해줍니다. Cloud Trace의 핵심 특징은 자동 트레이스 수집(App Engine, Cloud Run 등), 커스텀 스팬 생성, 그리고 레이턴시 분석입니다.
이러한 특징들은 성능 최적화가 필요한 정확한 지점을 찾아주어 무분별한 최적화를 방지합니다.
코드 예제
from google.cloud import trace_v1
from google.cloud.trace_v1 import TraceServiceClient
from opentelemetry import trace
from opentelemetry.exporter.cloud_trace import CloudTraceSpanExporter
from opentelemetry.sdk.trace import TracerProvider
from opentelemetry.sdk.trace.export import BatchSpanProcessor
# Cloud Trace 익스포터 설정
tracer_provider = TracerProvider()
cloud_trace_exporter = CloudTraceSpanExporter()
tracer_provider.add_span_processor(BatchSpanProcessor(cloud_trace_exporter))
trace.set_tracer_provider(tracer_provider)
# Tracer 생성
tracer = trace.get_tracer(__name__)
# 트레이스 추적 예제
def process_order(order_id):
with tracer.start_as_current_span("process_order") as span:
span.set_attribute("order.id", order_id)
# 데이터베이스 조회 추적
with tracer.start_as_current_span("database_query"):
order = fetch_order_from_db(order_id) # 1.2초 소요
# 결제 처리 추적
with tracer.start_as_current_span("payment_processing"):
result = charge_payment(order) # 3.5초 소요
# 재고 업데이트 추적
with tracer.start_as_current_span("inventory_update"):
update_inventory(order) # 0.3초 소요
return result
설명
이것이 하는 일: OpenTelemetry를 사용하여 코드의 각 단계에 트레이스 스팬(span)을 생성하고, Cloud Trace로 전송하여 요청 처리 과정을 시각화합니다. 첫 번째로, OpenTelemetry 설정을 초기화합니다.
OpenTelemetry는 벤더 중립적인 관찰성 프레임워크로, 코드를 한 번 작성하면 Cloud Trace뿐만 아니라 Jaeger, Zipkin 같은 다른 트레이싱 시스템으로도 데이터를 보낼 수 있습니다. CloudTraceSpanExporter는 수집된 트레이스 데이터를 GCP의 Cloud Trace 서비스로 전송하는 역할을 합니다.
그 다음으로, start_as_current_span()을 사용하여 추적하고 싶은 코드 블록을 감쌉니다. 각 스팬은 하나의 작업 단위를 나타내며, 시작 시간과 종료 시간이 자동으로 기록됩니다.
중첩된 스팬은 부모-자식 관계를 형성하여 "process_order 안에 database_query, payment_processing, inventory_update가 포함되어 있다"는 계층 구조를 만듭니다. 또한 set_attribute()로 order_id 같은 메타데이터를 추가하면 나중에 특정 주문의 트레이스만 필터링하여 조회할 수 있습니다.
마지막으로, Cloud Trace 콘솔에서 이 트레이스를 보면 전체 process_order 함수가 5초(1.2 + 3.5 + 0.3)가 걸렸고, 그 중 결제 처리가 70%를 차지한다는 것을 한눈에 알 수 있습니다. 워터폴 차트에서 각 스팬이 시작된 시점과 지속 시간이 막대 그래프로 표시되어, 어떤 작업이 병렬로 실행되고 어떤 작업이 순차적으로 실행되는지도 파악할 수 있습니다.
여러분이 이 코드를 사용하면 성능 문제의 원인을 추측하는 대신 데이터 기반으로 정확히 진단할 수 있습니다. 예를 들어, 결제 처리가 느린 것을 발견하면 해당 부분만 최적화하거나 비동기 처리로 변경할 수 있습니다.
또한 Cloud Trace는 자동으로 레이턴시 분포를 분석하여 "95%의 요청은 1초 이내에 완료되지만, 상위 5%는 5초 이상 걸린다"는 통계를 제공합니다. 이를 통해 간헐적으로 느린 요청(outlier)의 패턴을 찾아낼 수 있습니다.
실전 팁
💡 모든 함수에 스팬을 추가하지 마세요. 너무 많은 스팬은 오히려 차트를 복잡하게 만듭니다. 외부 API 호출, 데이터베이스 쿼리, 무거운 연산처럼 시간이 오래 걸릴 가능성이 있는 작업에만 스팬을 추가하세요.
💡 스팬 이름은 일관된 명명 규칙을 사용하세요. 예를 들어 "db.query.users", "http.request.payment_api" 같은 계층적 이름을 사용하면 나중에 필터링과 그룹화가 쉬워집니다.
💡 샘플링 비율을 조정하여 비용을 관리하세요. 모든 요청을 추적하면 비용이 많이 들고 오버헤드도 증가합니다. 프로덕션에서는 1-10% 정도만 샘플링하고, 문제가 있는 특정 요청은 강제로 추적하도록 설정할 수 있습니다.
💡 느린 트레이스에 알림을 설정하세요. Cloud Monitoring과 연동하여 "95 백분위수 레이턴시가 3초를 초과하면 알림"처럼 설정하면 성능 저하를 빠르게 감지할 수 있습니다.
💡 다른 서비스로 HTTP 요청을 보낼 때는 trace context를 헤더에 포함시키세요. traceparent 헤더를 전달하면 마이크로서비스 간에도 트레이스가 연결되어 전체 시스템의 end-to-end 추적이 가능합니다.
5. VPC 네트워크 문제 해결
시작하며
여러분의 Compute Engine 인스턴스에서 특정 API에 연결할 수 없습니다. 인터넷 연결은 되는데 내부 서비스 간 통신만 안 됩니다.
방화벽 규칙이 문제일까요, 라우팅이 잘못되었을까요, 아니면 서브넷 설정이 틀렸을까요? 이런 네트워크 문제는 진단하기가 매우 까다롭습니다.
GCP의 VPC 네트워킹은 방화벽 규칙, 라우트, 서브넷, IAP 등 여러 계층이 복잡하게 얽혀 있어서 어디가 문제인지 찾기 어렵습니다. 바로 이럴 때 필요한 것이 VPC 네트워크 트러블슈팅 기법입니다.
체계적으로 각 계층을 점검하면 문제를 빠르게 찾을 수 있습니다.
개요
간단히 말해서, VPC 네트워크 문제 해결은 방화벽 규칙, 라우팅 테이블, 연결 테스트를 순차적으로 확인하여 네트워크 연결 실패의 원인을 찾는 프로세스입니다. 실무에서 마이크로서비스를 운영하면 여러 VPC에 걸쳐 인스턴스가 배포되고, 각각 다른 방화벽 규칙이 적용됩니다.
예를 들어, 프론트엔드는 외부 접근을 허용해야 하지만 데이터베이스는 특정 서브넷에서만 접근 가능해야 합니다. 이런 복잡한 설정에서 연결이 안 될 때는 VPC Flow Logs와 Firewall Rules Logging을 활용하여 패킷이 어디서 차단되는지 추적할 수 있습니다.
기존에는 tcpdump로 패킷을 캡처하거나 각 방화벽 규칙을 일일이 확인했다면, 이제는 GCP의 Connectivity Test를 사용하여 자동으로 경로를 시뮬레이션하고 문제를 찾을 수 있습니다. VPC 트러블슈팅의 핵심은 방화벽 규칙 검증, 라우팅 경로 확인, 그리고 연결 테스트입니다.
이러한 단계들을 체계적으로 수행하면 네트워크 문제의 90% 이상을 해결할 수 있습니다.
코드 예제
from google.cloud import compute_v1
# Compute Engine 클라이언트 초기화
firewall_client = compute_v1.FirewallsClient()
project_id = "your-project-id"
# 특정 IP와 포트에 대한 방화벽 규칙 확인
def check_firewall_rules(target_ip, target_port):
request = compute_v1.ListFirewallsRequest(project=project_id)
firewalls = firewall_client.list(request=request)
print(f"🔍 {target_ip}:{target_port}에 대한 방화벽 규칙 검사\n")
for firewall in firewalls:
# Ingress 규칙 중 해당 포트를 허용하는 규칙 찾기
if firewall.direction == "INGRESS":
for allowed in firewall.allowed:
if str(target_port) in allowed.ports:
print(f"✅ 허용 규칙 발견: {firewall.name}")
print(f" 소스: {firewall.source_ranges}")
print(f" 프로토콜: {allowed.I_p_protocol}")
print(f" 포트: {allowed.ports}\n")
# Deny 규칙 확인
if firewall.denied:
for denied in firewall.denied:
if str(target_port) in denied.ports:
print(f"❌ 거부 규칙 발견: {firewall.name}")
print(f" 이 규칙이 연결을 차단하고 있을 수 있습니다!\n")
# 사용 예시
check_firewall_rules("10.128.0.5", 8080)
설명
이것이 하는 일: Compute Engine API를 사용하여 프로젝트의 모든 방화벽 규칙을 조회하고, 특정 대상 IP와 포트에 대한 접근이 허용되는지 또는 차단되는지 분석합니다. 첫 번째로, FirewallsClient를 생성하여 방화벽 규칙 관리 API에 접근합니다.
GCP의 방화벽은 상태 기반(stateful) 방화벽으로, ingress(들어오는) 규칙을 허용하면 해당 연결의 egress(나가는) 응답도 자동으로 허용됩니다. 따라서 보통은 ingress 규칙만 확인하면 됩니다.
그 다음으로, list() 메서드로 프로젝트의 모든 방화벽 규칙을 가져옵니다. 각 방화벽 규칙은 우선순위(priority)를 가지고 있으며, 숫자가 낮을수록 먼저 평가됩니다.
규칙을 순회하면서 allowed 필드를 확인하여 해당 포트가 허용되는지 검사합니다. source_ranges는 어떤 IP 범위에서 오는 트래픽에 적용되는지 나타내며, 0.0.0.0/0은 모든 IP를 의미합니다.
마지막으로, deny 규칙도 확인합니다. GCP 방화벽은 기본적으로 모든 ingress를 차단하고 필요한 것만 허용하는 화이트리스트 방식이지만, 명시적인 deny 규칙을 추가할 수도 있습니다.
Deny 규칙은 allow 규칙보다 우선순위가 높아서, 같은 조건에 대해 allow와 deny가 모두 있으면 deny가 적용됩니다. 여러분이 이 코드를 사용하면 웹 콘솔에서 수십 개의 방화벽 규칙을 일일이 클릭하는 대신, 코드로 자동화하여 특정 연결이 왜 안 되는지 빠르게 진단할 수 있습니다.
예를 들어, 이 스크립트를 실행했을 때 허용 규칙이 하나도 출력되지 않으면 해당 포트가 열려있지 않다는 뜻이고, deny 규칙이 발견되면 명시적으로 차단되고 있다는 것을 즉시 알 수 있습니다. 또한 이 로직을 확장하여 전체 네트워크 토폴로지를 시각화하거나, 규정 준수를 위해 22번 포트(SSH)가 0.0.0.0/0에 열려있는지 자동으로 검사할 수도 있습니다.
실전 팁
💡 VPC Flow Logs를 활성화하여 실제 트래픽 패턴을 분석하세요. 방화벽 규칙은 이론적으로 허용하는데도 연결이 안 되면, Flow Logs를 보면 패킷이 실제로 어떤 경로로 전송되는지 확인할 수 있습니다.
💡 Connectivity Tests 도구를 사용하여 가상 패킷을 보내보세요. 실제 트래픽을 발생시키지 않고도 source에서 destination까지 경로를 시뮬레이션하여 어느 단계에서 차단되는지 정확히 보여줍니다.
💡 방화벽 규칙을 만들 때는 최소 권한 원칙을 따르세요. 0.0.0.0/0 대신 특정 IP 범위나 서비스 계정으로 제한하면 보안이 크게 향상됩니다. 예를 들어 데이터베이스 포트는 애플리케이션 서버 서브넷에서만 접근 가능하도록 설정하세요.
💡 네트워크 태그와 서비스 계정을 활용하여 동적인 방화벽 규칙을 만드세요. IP 주소 대신 "web-server" 태그가 있는 모든 인스턴스에 규칙을 적용하면, 인스턴스를 추가할 때마다 방화벽 규칙을 수정할 필요가 없습니다.
💡 문제 해결 순서를 체계화하세요. 1) 방화벽 규칙 확인 → 2) 라우팅 테이블 확인 → 3) 대상 서비스가 실제로 해당 포트에서 리스닝하는지 확인 (netstat -tulpn) → 4) DNS 해석 문제 확인 순으로 점검하면 대부분의 문제를 찾을 수 있습니다.
6. IAM 권한 문제 디버깅
시작하며
여러분의 Cloud Function이 Cloud Storage 버킷에 파일을 업로드하려고 하는데 "403 Forbidden" 에러가 발생합니다. 서비스 계정에 권한을 부여했다고 생각했는데 왜 접근이 거부될까요?
조직 정책 때문일까요, 버킷 정책 때문일까요, 아니면 IAM 역할이 잘못되었을까요? 이런 권한 문제는 GCP에서 가장 흔한 트러블슈팅 케이스입니다.
GCP의 IAM은 프로젝트 레벨, 리소스 레벨, 조직 정책 등 여러 계층에서 권한이 작동하기 때문에 어디서 거부되는지 파악하기 어렵습니다. 바로 이럴 때 필요한 것이 IAM Policy Troubleshooter입니다.
특정 작업이 왜 허용되거나 거부되는지 단계별로 분석해줍니다.
개요
간단히 말해서, IAM 권한 문제 해결은 서비스 계정이나 사용자가 특정 리소스에 접근할 수 있는 권한을 가지고 있는지 확인하고, 권한이 없다면 어떤 역할을 부여해야 하는지 찾는 과정입니다. 실무에서는 최소 권한 원칙을 따르기 위해 세밀한 권한 관리가 필요합니다.
예를 들어, 로그를 읽기만 하는 서비스에는 roles/logging.viewer 역할을, 로그를 쓰는 서비스에는 roles/logging.logWriter 역할을 부여해야 합니다. 하지만 프로젝트 전체에 역할을 부여할 수도 있고, 특정 버킷이나 특정 Pub/Sub 토픽에만 부여할 수도 있어서 권한 구조가 복잡해집니다.
기존에는 권한 문제가 발생하면 일단 roles/owner 같은 강력한 권한을 주고 나중에 줄이려고 했다면, 이제는 IAM Policy Troubleshooter와 권한 분석 도구를 사용하여 정확히 필요한 권한만 부여할 수 있습니다. IAM 트러블슈팅의 핵심은 권한 계층 이해, Policy Troubleshooter 활용, 그리고 감사 로그 확인입니다.
이러한 도구들을 사용하면 "왜 접근이 거부되었는지"를 추측이 아닌 명확한 근거로 알 수 있습니다.
코드 예제
from google.cloud import asset_v1
from google.cloud import storage
# IAM 정책 조회 클라이언트
asset_client = asset_v1.AssetServiceClient()
project_id = "your-project-id"
# 특정 서비스 계정이 버킷에 대해 가진 권한 확인
def check_bucket_permissions(service_account, bucket_name):
# 리소스 전체 경로
scope = f"projects/{project_id}"
# 버킷에 대한 IAM 정책 조회
resource = f"//storage.googleapis.com/{bucket_name}"
print(f"🔍 서비스 계정: {service_account}")
print(f"📦 버킷: {bucket_name}\n")
# Storage 클라이언트로 버킷 IAM 정책 직접 조회
storage_client = storage.Client()
bucket = storage_client.bucket(bucket_name)
policy = bucket.get_iam_policy()
# 서비스 계정이 가진 역할 찾기
found_roles = []
for role, members in policy.items():
member_string = f"serviceAccount:{service_account}"
if member_string in members:
found_roles.append(role)
if found_roles:
print("✅ 다음 역할이 부여되어 있습니다:")
for role in found_roles:
print(f" - {role}")
else:
print("❌ 이 서비스 계정에 부여된 역할이 없습니다.")
print("💡 다음 역할 중 하나를 부여하세요:")
print(" - roles/storage.objectViewer (읽기만)")
print(" - roles/storage.objectCreator (쓰기만)")
print(" - roles/storage.objectAdmin (읽기/쓰기/삭제)")
# 사용 예시
check_bucket_permissions(
"my-service@project.iam.gserviceaccount.com",
"my-bucket"
)
설명
이것이 하는 일: Cloud Storage 버킷의 IAM 정책을 조회하여 특정 서비스 계정이 해당 버킷에 대해 어떤 역할(role)을 가지고 있는지 확인하고, 권한이 없으면 필요한 역할을 제안합니다. 첫 번째로, Storage 클라이언트를 생성하고 버킷 객체를 가져옵니다.
GCP의 모든 리소스는 자체 IAM 정책을 가질 수 있으며, get_iam_policy() 메서드로 해당 리소스의 정책을 조회할 수 있습니다. 버킷 정책은 프로젝트 레벨 정책과 별도로 작동하므로, 프로젝트에 권한이 있어도 버킷 레벨에서 거부될 수 있습니다.
그 다음으로, 정책 딕셔너리를 순회하면서 서비스 계정이 포함된 역할을 찾습니다. IAM 정책은 "역할 → 멤버 목록" 구조로 되어 있어서, 예를 들어 roles/storage.objectViewer 역할에 ["serviceAccount:my-service@...", "user:alice@..."] 같은 멤버 목록이 매핑됩니다.
서비스 계정은 반드시 serviceAccount: 프리픽스를 붙여야 하며, 사용자는 user:, 그룹은 group: 프리픽스를 사용합니다. 마지막으로, 발견된 역할을 출력하거나, 역할이 없으면 상황에 맞는 권장 역할을 제안합니다.
Storage 권한은 세분화되어 있어서 읽기만 필요하면 objectViewer, 쓰기만 필요하면 objectCreator, 삭제까지 필요하면 objectAdmin을 부여하는 것이 최소 권한 원칙에 부합합니다. 여러분이 이 코드를 사용하면 "왜 403 에러가 나는지" 즉시 파악할 수 있습니다.
역할이 아예 없으면 IAM 바인딩을 추가해야 하고, 역할은 있는데 여전히 접근이 거부되면 조직 정책이나 버킷 정책 조건(conditional policy)을 확인해야 합니다. 또한 이 로직을 확장하여 전체 프로젝트의 모든 서비스 계정이 어떤 권한을 가지고 있는지 감사(audit)하는 스크립트를 만들 수 있습니다.
예를 들어, 너무 강력한 roles/editor나 roles/owner 역할이 불필요하게 부여된 서비스 계정을 찾아내어 보안을 강화할 수 있습니다.
실전 팁
💡 IAM Policy Troubleshooter를 웹 콘솔에서 활용하세요. "이 사용자가 이 리소스에 이 작업을 할 수 있는가?"를 입력하면 자동으로 전체 권한 체인을 분석하여 허용/거부 이유를 단계별로 설명해줍니다.
💡 조직 정책(Organization Policy)도 확인하세요. IAM 역할이 있어도 조직 정책에서 제한하면 접근이 거부됩니다. 예를 들어 constraints/iam.allowedPolicyMemberDomains는 특정 도메인의 사용자만 허용하도록 제한할 수 있습니다.
💡 조건부 IAM 역할을 활용하여 세밀한 권한 제어를 하세요. 예를 들어 "오전 9시부터 오후 6시 사이에만 접근 가능" 또는 "특정 IP 범위에서만 접근 가능"처럼 시간이나 네트워크 조건을 추가할 수 있습니다.
💡 서비스 계정 키를 다운로드하는 대신 Workload Identity를 사용하세요. GKE나 Cloud Run에서 실행되는 애플리케이션은 서비스 계정 키 파일 없이도 IAM 권한을 사용할 수 있어 보안이 훨씬 강화됩니다.
💡 감사 로그(Admin Activity, Data Access)를 활성화하여 누가 언제 어떤 권한을 사용했는지 추적하세요. 권한 문제를 디버깅할 때 "이 서비스 계정이 실제로 버킷에 접근을 시도했는가?"를 감사 로그로 확인할 수 있습니다.
7. Compute Engine 인스턴스 문제 해결
시작하며
여러분이 Compute Engine 인스턴스를 시작했는데 SSH로 연결할 수 없습니다. 인스턴스는 실행 중으로 표시되는데 ping도 안 되고, Serial Console에는 에러 메시지만 반복됩니다.
부팅이 실패한 걸까요, 네트워크 설정이 잘못된 걸까요? 이런 인스턴스 문제는 다양한 원인이 있습니다.
디스크 용량 부족, 부팅 스크립트 오류, SSH 키 설정 문제, 방화벽 규칙 등 여러 가지가 복합적으로 작용할 수 있습니다. 바로 이럴 때 필요한 것이 Compute Engine 트러블슈팅 기법입니다.
Serial Console 로그, 인스턴스 스크린샷, 디스크 연결 등의 도구를 활용하면 접근 불가능한 인스턴스도 복구할 수 있습니다.
개요
간단히 말해서, Compute Engine 트러블슈팅은 인스턴스의 Serial Console 출력, 메타데이터, 디스크 상태를 확인하여 부팅 실패나 연결 문제의 원인을 찾는 과정입니다. 실무에서 인스턴스를 대규모로 운영하다 보면 간헐적으로 부팅이 실패하거나 네트워크가 끊기는 경우가 있습니다.
예를 들어, 디스크 용량이 100% 찬 상태에서는 SSH 데몬이 로그를 쓸 수 없어서 연결이 거부될 수 있습니다. 또는 부팅 스크립트에서 무한 루프가 발생하여 인스턴스가 정상적으로 시작되지 않을 수도 있습니다.
기존에는 인스턴스에 접근할 수 없으면 삭제하고 다시 만드는 경우가 많았다면, 이제는 Serial Console, 스크린샷, 디스크 연결 같은 도구를 사용하여 데이터 손실 없이 문제를 해결할 수 있습니다. Compute Engine 트러블슈팅의 핵심은 Serial Console 로그 분석, 메타데이터 검증, 그리고 복구 인스턴스를 통한 디스크 마운트입니다.
이러한 기법들을 알면 99%의 인스턴스 문제를 해결할 수 있습니다.
코드 예제
from google.cloud import compute_v1
# Compute Engine 클라이언트
instances_client = compute_v1.InstancesClient()
project_id = "your-project-id"
zone = "us-central1-a"
instance_name = "problematic-instance"
# Serial Console 로그 조회 (부팅 문제 진단)
def get_serial_port_output(instance_name, port=1):
request = compute_v1.GetSerialPortOutputInstanceRequest(
project=project_id,
zone=zone,
instance=instance_name,
port=port,
)
response = instances_client.get_serial_port_output(request=request)
print(f"📋 {instance_name}의 Serial Console 출력:\n")
print(response.contents[-2000:]) # 마지막 2000자만 출력
# 일반적인 에러 패턴 검색
if "kernel panic" in response.contents.lower():
print("\n❌ 커널 패닉 발견! 부팅 디스크나 커널 설정 문제입니다.")
if "out of disk space" in response.contents.lower():
print("\n❌ 디스크 공간 부족! 디스크를 연결하여 파일을 삭제해야 합니다.")
if "sshd" not in response.contents.lower():
print("\n⚠️ SSH 데몬이 시작되지 않았습니다. SSH 연결이 불가능합니다.")
return response.contents
# 인스턴스 메타데이터 확인 (SSH 키, 시작 스크립트 등)
def check_instance_metadata(instance_name):
request = compute_v1.GetInstanceRequest(
project=project_id,
zone=zone,
instance=instance_name,
)
instance = instances_client.get(request=request)
print(f"\n🔧 {instance_name}의 메타데이터:\n")
if instance.metadata and instance.metadata.items:
for item in instance.metadata.items:
print(f" {item.key}: {item.value[:100]}...") # 처음 100자만
# 사용 예시
get_serial_port_output(instance_name)
check_instance_metadata(instance_name)
설명
이것이 하는 일: Compute Engine API를 통해 인스턴스의 Serial Console 출력을 읽어서 부팅 과정에서 발생한 에러를 찾고, 메타데이터를 확인하여 설정 문제가 있는지 검사합니다. 첫 번째로, get_serial_port_output() 메서드로 Serial Console 로그를 조회합니다.
Serial Console은 인스턴스의 가상 시리얼 포트로, SSH 연결 없이도 부팅 메시지와 커널 로그를 볼 수 있습니다. 마치 물리 서버에 모니터를 연결한 것처럼 작동하므로, 네트워크가 완전히 끊겨도 로그를 확인할 수 있습니다.
로그가 매우 길 수 있어서 마지막 2000자만 출력하여 최근 메시지를 집중적으로 봅니다. 그 다음으로, 로그 내용에서 흔한 에러 패턴을 검색합니다.
"kernel panic"은 운영체제 자체가 크래시한 것으로 보통 하드웨어 문제나 손상된 커널을 의미합니다. "out of disk space"는 디스크가 가득 찬 것으로, 이 경우 복구 인스턴스를 만들어 문제가 있는 디스크를 연결하고 불필요한 파일을 삭제해야 합니다.
"sshd"가 로그에 없다면 SSH 서비스가 시작되지 않은 것으로, 부팅 스크립트나 SSH 설정 파일을 확인해야 합니다. 마지막으로, get() 메서드로 인스턴스의 전체 정보를 조회하여 메타데이터를 확인합니다.
메타데이터에는 SSH 공개 키(ssh-keys), 시작 스크립트(startup-script), 사용자 데이터 등이 포함됩니다. 예를 들어, startup-script에 문법 오류가 있으면 부팅 중에 실패하여 인스턴스가 정상 작동하지 않을 수 있습니다.
여러분이 이 코드를 사용하면 SSH로 접근할 수 없는 인스턴스도 진단할 수 있습니다. Serial Console 로그는 부팅 과정의 모든 단계를 보여주므로 "어느 시점에서 멈췄는지" 정확히 알 수 있습니다.
실무에서는 이 스크립트를 자동화하여 새로 생성된 인스턴스가 5분 이내에 SSH 가능 상태가 되지 않으면 자동으로 Serial Console을 확인하고 알림을 보내도록 설정할 수 있습니다. 또한 메타데이터 검증을 통해 인스턴스 템플릿에 잘못된 설정이 포함되지 않았는지 사전에 체크할 수 있습니다.
실전 팁
💡 복구 인스턴스(rescue instance)를 활용하세요. 문제가 있는 인스턴스의 부팅 디스크를 멈춘 후, 새 인스턴스를 만들어 해당 디스크를 추가 디스크로 연결하면 파일 시스템에 접근하여 로그를 확인하거나 설정을 수정할 수 있습니다.
💡 인스턴스 스크린샷 기능을 사용하세요. Serial Console이 텍스트 기반이라면, 스크린샷은 실제 화면을 캡처하여 그래픽 부팅 메시지나 GUI 에러 대화상자를 볼 수 있습니다.
💡 Interactive Serial Console을 활성화하면 읽기만 하는 것이 아니라 명령어를 입력할 수 있습니다. SSH가 안 되는 상황에서 root 비밀번호를 재설정하거나 네트워크 설정을 수정할 때 유용합니다.
💡 시작 스크립트는 반드시 로깅을 추가하세요. exec > >(tee /var/log/startup-script.log) 2>&1 같은 리디렉션을 추가하면 스크립트 실행 내용이 로그 파일에 기록되어 나중에 확인할 수 있습니다.
💡 Managed Instance Group을 사용한다면 Auto-healing을 설정하세요. Health Check가 실패하면 자동으로 인스턴스를 재생성하여 서비스 중단 시간을 최소화할 수 있습니다. 단, 재생성 전에 Serial Console 로그를 저장하도록 설정하여 문제 원인을 분석할 수 있게 하세요.
8. Cloud Storage 액세스 문제
시작하며
여러분의 웹 애플리케이션에서 Cloud Storage에 업로드한 이미지를 표시하려고 하는데 CORS 에러가 발생합니다. 브라우저 콘솔에는 "Access to fetch blocked by CORS policy"라는 메시지가 나타납니다.
버킷은 공개로 설정했는데 왜 접근이 안 될까요? 이런 Storage 액세스 문제는 IAM 권한, 버킷 정책, CORS 설정이 복합적으로 작용합니다.
파일이 퍼블릭인데도 브라우저에서는 CORS 제한 때문에 로드되지 않거나, 권한은 있는데 서명된 URL이 만료되어 접근이 거부되는 경우가 많습니다. 바로 이럴 때 필요한 것이 Cloud Storage 트러블슈팅 기법입니다.
IAM 권한, CORS 설정, 서명된 URL 생성을 체계적으로 확인하면 대부분의 액세스 문제를 해결할 수 있습니다.
개요
간단히 말해서, Cloud Storage 액세스 문제 해결은 버킷과 객체의 IAM 정책, CORS 설정, 그리고 서명된 URL의 유효성을 확인하여 파일 접근이 거부되는 원인을 찾는 과정입니다. 실무에서는 다양한 방식으로 Cloud Storage를 사용합니다.
정적 웹사이트 호스팅, 사용자 업로드 파일 저장, 백업 데이터 보관 등 용도에 따라 접근 제어 방식이 다릅니다. 예를 들어, 공개 블로그의 이미지는 누구나 읽을 수 있어야 하지만(allUsers에 읽기 권한), 사용자의 개인 문서는 해당 사용자만 접근할 수 있어야 합니다(서명된 URL 사용).
기존에는 "안 되면 버킷을 완전히 공개로 만들자"는 식으로 접근했다면, 이제는 최소 권한 원칙에 따라 정확히 필요한 만큼만 권한을 부여하고, CORS와 서명된 URL을 활용하여 안전하게 파일을 공유할 수 있습니다. Cloud Storage 트러블슈팅의 핵심은 권한 계층 이해(버킷 vs 객체), CORS 정책 설정, 그리고 서명된 URL 생성입니다.
이러한 기법들을 조합하면 보안을 유지하면서도 필요한 접근을 허용할 수 있습니다.
코드 예제
from google.cloud import storage
from datetime import timedelta
import json
# Storage 클라이언트 초기화
storage_client = storage.Client()
bucket_name = "my-public-bucket"
# CORS 설정 확인 및 수정
def configure_cors(bucket_name):
bucket = storage_client.bucket(bucket_name)
# 현재 CORS 설정 확인
print(f"📦 {bucket_name}의 현재 CORS 설정:")
print(json.dumps(bucket.cors, indent=2))
# 웹 애플리케이션을 위한 CORS 설정
cors_configuration = [{
"origin": ["https://your-app.com", "http://localhost:3000"],
"method": ["GET", "POST", "PUT"],
"responseHeader": ["Content-Type", "x-goog-resumable"],
"maxAgeSeconds": 3600
}]
bucket.cors = cors_configuration
bucket.patch()
print("\n✅ CORS 설정이 업데이트되었습니다.")
# 서명된 URL 생성 (비공개 파일 임시 공유)
def generate_signed_url(bucket_name, blob_name, expiration_minutes=15):
bucket = storage_client.bucket(bucket_name)
blob = bucket.blob(blob_name)
# 15분 동안 유효한 서명된 URL 생성
url = blob.generate_signed_url(
version="v4",
expiration=timedelta(minutes=expiration_minutes),
method="GET"
)
print(f"\n🔗 서명된 URL (15분간 유효):")
print(url)
return url
# 객체를 공개로 만들기
def make_blob_public(bucket_name, blob_name):
bucket = storage_client.bucket(bucket_name)
blob = bucket.blob(blob_name)
# allUsers에게 읽기 권한 부여
blob.make_public()
print(f"\n🌐 {blob_name}이(가) 공개되었습니다:")
print(f"공개 URL: {blob.public_url}")
# 사용 예시
configure_cors(bucket_name)
generate_signed_url(bucket_name, "private/user-document.pdf")
make_blob_public(bucket_name, "public/logo.png")
설명
이것이 하는 일: Cloud Storage 버킷의 CORS 정책을 설정하여 브라우저에서의 크로스 오리진 요청을 허용하고, 비공개 파일에 대해서는 임시 서명된 URL을 생성하거나 완전히 공개하여 접근할 수 있게 합니다. 첫 번째로, CORS 설정을 구성합니다.
CORS(Cross-Origin Resource Sharing)는 브라우저 보안 정책으로, 다른 도메인의 리소스를 로드하려면 서버가 명시적으로 허용해야 합니다. origin은 어떤 웹사이트에서의 요청을 허용할지 지정하며, 개발 환경의 localhost와 프로덕션 도메인을 모두 포함시킬 수 있습니다.
method는 GET(읽기), POST(업로드), PUT(업데이트) 등 허용할 HTTP 메서드를 지정합니다. maxAgeSeconds는 브라우저가 CORS preflight 응답을 캐시하는 시간으로, 1시간(3600초)으로 설정하면 매번 확인하지 않아 성능이 향상됩니다.
그 다음으로, 서명된 URL을 생성합니다. 서명된 URL은 비공개 파일을 IAM 권한 없이도 일정 시간 동안 접근할 수 있게 해주는 임시 링크입니다.
URL에 서명(signature)이 포함되어 있어서 누구든 URL을 가진 사람은 파일을 다운로드할 수 있지만, 만료 시간이 지나면 자동으로 무효화됩니다. 예를 들어, 사용자가 결제 영수증을 요청하면 서버가 15분 동안만 유효한 URL을 생성하여 제공하는 식으로 사용합니다.
마지막으로, make_public() 메서드는 특정 객체를 완전히 공개합니다. 이는 객체의 IAM 정책에 allUsers 역할을 추가하여 인증 없이 누구나 접근할 수 있게 만듭니다.
공개된 객체는 https://storage.googleapis.com/버킷명/객체명 형식의 공개 URL로 직접 접근할 수 있습니다. 블로그 이미지나 공개 다운로드 파일처럼 항상 누구나 접근할 수 있어야 하는 파일에 적합합니다.
여러분이 이 코드를 사용하면 다양한 액세스 시나리오를 안전하게 처리할 수 있습니다. CORS를 설정하면 프론트엔드 애플리케이션에서 직접 Storage에 파일을 업로드하거나 다운로드할 수 있어서 서버를 경유하지 않아도 됩니다.
서명된 URL은 민감한 파일을 안전하게 공유하는 데 이상적이며, 만료 시간을 조절하여 보안 수준을 제어할 수 있습니다. 실무에서는 서명된 URL 생성을 서버 API로 제공하고, 클라이언트는 이 API를 호출하여 업로드/다운로드 URL을 받는 패턴을 많이 사용합니다.
이렇게 하면 클라이언트 코드에 서비스 계정 키를 노출하지 않아도 됩니다.
실전 팁
💡 Uniform bucket-level access를 활성화하면 버킷 레벨 IAM만으로 권한을 관리할 수 있어 간단합니다. 객체별로 ACL을 관리하는 것보다 훨씬 관리하기 쉽고 감사(audit)하기도 편합니다.
💡 CDN(Cloud CDN)을 앞에 두어 Storage 버킷을 캐싱하면 성능이 크게 향상되고 egress 비용도 절약됩니다. 특히 전 세계 사용자에게 이미지나 동영상을 제공하는 경우 필수입니다.
💡 서명된 URL을 생성할 때 서비스 계정 키가 필요합니다. 보안을 위해 키 파일을 직접 관리하는 대신 iam.serviceAccountTokenCreator 역할을 사용하여 키 없이 서명된 URL을 생성할 수 있습니다.
💡 객체 버전 관리(Object Versioning)를 활성화하면 실수로 파일을 삭제하거나 덮어쓴 경우에도 이전 버전을 복구할 수 있습니다. 중요한 데이터가 저장된 버킷에는 반드시 설정하세요.
💡 Requester Pays 기능을 활용하면 사용자가 다운로드 비용을 부담하게 할 수 있습니다. 대용량 공개 데이터셋을 제공할 때 egress 비용 폭탄을 방지하는 데 유용합니다.
이 카드뉴스가 포함된 코스
댓글 (0)
함께 보면 좋은 카드 뉴스
Helm 마이크로서비스 패키징 완벽 가이드
Kubernetes 환경에서 마이크로서비스를 효율적으로 패키징하고 배포하는 Helm의 핵심 기능을 실무 중심으로 학습합니다. Chart 생성부터 릴리스 관리까지 체계적으로 다룹니다.
Grafana 대시보드 완벽 가이드
실시간 모니터링의 핵심, Grafana 대시보드를 처음부터 끝까지 배워봅니다. Prometheus 연동부터 알람 설정까지, 초급 개발자도 쉽게 따라할 수 있는 실전 가이드입니다.
Prometheus 메트릭 수집 완벽 가이드
Spring Boot 애플리케이션의 메트릭을 Prometheus로 수집하고 모니터링하는 방법을 배웁니다. Actuator 설정부터 PromQL 쿼리까지 실무에 필요한 모든 내용을 다룹니다.
보안 아키텍처 구성 완벽 가이드
프로젝트의 보안을 처음부터 설계하는 방법을 배웁니다. AWS 환경에서 VPC부터 WAF, 암호화, 접근 제어까지 실무에서 바로 적용할 수 있는 보안 아키텍처를 단계별로 구성해봅니다.
AWS Organizations 완벽 가이드
여러 AWS 계정을 체계적으로 관리하고 통합 결제와 보안 정책을 적용하는 방법을 실무 스토리로 쉽게 배워봅니다. 초보 개발자도 바로 이해할 수 있는 친절한 설명과 실전 예제를 제공합니다.