서버리스 아키텍처는 현대 웹 애플리케이션의 표준이 되었지만, 개발자들은 Lambda 함수나 Edge Functions에서 모든 요청이 클라우드 제공업체의 데이터 센터 IP 주소에서 발생하는 문제에 자주 직면합니다. 이는 외부 API에 접근하거나 데이터 파싱 또는 작업 자동화 시 차단으로 이어집니다. 이 가이드에서는 제한, 속도 제한 및 지역 차단을 우회하기 위해 서버리스 함수에 프록시를 통합하는 방법을 살펴보겠습니다.
서버리스 함수에 프록시가 필요한 이유
서버리스 플랫폼(AWS Lambda, Google Cloud Functions, Vercel, Cloudflare Workers)은 클라우드 인프라에서 코드를 실행하며 데이터 센터의 IP 주소를 사용합니다. 이는 개발자에게 몇 가지 중요한 문제를 발생시킵니다:
문제 1: 데이터 센터 IP 차단. 많은 서비스가 AWS, Google Cloud 또는 Azure의 알려진 IP 주소에서 오는 요청을 자동으로 차단합니다. 예를 들어, 전자상거래 사이트(Amazon, eBay, Wildberries)나 소셜 미디어(Instagram API, TikTok API)를 파싱할 때, Lambda 함수는 첫 번째 요청에서 HTTP 403 또는 CAPTCHA를 받을 수 있습니다. 봇 방지 시스템(Cloudflare, Akamai, DataDome)은 클라우드 데이터 센터에서 오는 트래픽을 즉시 인식합니다.
문제 2: IP 수준의 속도 제한. 수천 개의 동시 호출로 서버리스 애플리케이션을 배포하는 경우 모든 요청이 하나 또는 여러 개의 AWS IP 주소에서 발생할 수 있습니다. 외부 API는 빠르게 제한에 도달합니다(예: GitHub API — 하나의 IP에서 시간당 60 요청, Google Maps API — 초당 100 요청). 확장된 API 요금제를 구독하더라도 IP 제한은 여전히 적용됩니다.
문제 3: 지역 차단. us-east-1 지역의 서버리스 함수는 러시아, 유럽 또는 아시아에서만 접근 가능한 콘텐츠에 접근할 수 없습니다. 이는 지역 마켓플레이스(Ozon, Yandex.Market)를 파싱하거나 다른 국가의 광고를 확인하거나 웹사이트의 로컬라이제이션을 테스트할 때 중요합니다.
문제 4: 다른 사용자와의 공유 IP. 서버리스 환경에서 귀하의 함수는 클라우드 제공업체의 다른 고객이 이미 사용한 IP 주소를 받을 수 있습니다. 누군가가 이전에 이 IP를 남용했다면(스팸, DDoS, 파싱) 블랙리스트에 올라 있을 수 있습니다. 귀하는 아무런 잘못 없이 차단될 수 있습니다.
이러한 모든 문제를 해결하는 방법은 프록시 서버를 통합하는 것입니다. 프록시는 서버리스 함수가 일반 사용자처럼 보이는 주거용 또는 모바일 IP 주소를 통해 요청을 보낼 수 있게 해줍니다. 이는 차단을 해제하고 속도 제한을 우회하며 지역 차단된 콘텐츠에 접근할 수 있게 합니다.
서버리스에 적합한 프록시 유형
프록시 유형의 선택은 서버리스 애플리케이션의 작업에 따라 다릅니다. 세 가지 주요 옵션과 사용 시나리오를 살펴보겠습니다:
| 프록시 유형 | 속도 | 익명성 | 사용 시나리오 |
|---|---|---|---|
| 데이터 센터 프록시 | 매우 높음 (10-50 ms) | 낮음 | 엄격한 제한 없이 API 호출, 서비스 가용성 확인, 가동 시간 모니터링 |
| 주거용 프록시 | 중간 (100-500 ms) | 높음 | 전자상거래 파싱, 소셜 미디어 작업, Cloudflare 우회, 지역 차단된 콘텐츠 접근 |
| 모바일 프록시 | 중간 (150-600 ms) | 매우 높음 | 모바일 API 작업(Instagram, TikTok), 모바일 애플리케이션 테스트, 가장 엄격한 보호 우회 |
대부분의 서버리스 애플리케이션에는 주거용 프록시를 사용하는 것이 권장됩니다. 이들은 속도와 익명성 간의 최적의 균형을 제공합니다. 주거용 IP는 일반 가정 사용자처럼 보이므로 봇 방지 및 속도 제한을 우회할 수 있습니다.
데이터 센터 프록시는 간단한 작업(HTTP 상태 확인, 제한 없는 공개 API 작업)에만 적합합니다. 모바일 프록시는 모바일 API와 작업하거나 최대 익명성이 중요한 경우에 필요합니다.
AWS Lambda에서 프록시 설정하기
AWS Lambda는 가장 인기 있는 서버리스 플랫폼이며, 여기서 프록시 통합은 HTTP 클라이언트의 올바른 설정이 필요합니다. Lambda 함수는 다양한 프로그래밍 언어(Node.js, Python, Go)를 사용할 수 있으며, 가장 일반적인 예제를 살펴보겠습니다.
Node.js (axios)
Axios는 Node.js에서 HTTP 요청을 위한 가장 인기 있는 라이브러리입니다. 프록시를 설정하려면 구성에서 proxy 매개변수를 사용하세요:
const axios = require('axios');
exports.handler = async (event) => {
const proxyConfig = {
host: 'proxy.example.com',
port: 8080,
auth: {
username: 'your_username',
password: 'your_password'
},
protocol: 'http'
};
try {
const response = await axios.get('https://api.example.com/data', {
proxy: proxyConfig,
timeout: 10000 // 10초
});
return {
statusCode: 200,
body: JSON.stringify(response.data)
};
} catch (error) {
console.error('프록시 오류:', error.message);
return {
statusCode: 500,
body: JSON.stringify({ error: error.message })
};
}
};
중요한 점: 프록시 자격 증명을 AWS Systems Manager Parameter Store 또는 AWS Secrets Manager에 저장하고 코드에 저장하지 마세요. 이는 보안을 보장하고 함수를 다시 빌드하지 않고도 프록시를 쉽게 변경할 수 있게 해줍니다.
Python (requests)
Python에서는 proxies 매개변수를 사용하여 requests 라이브러리로 프록시를 설정합니다:
import requests
import json
def lambda_handler(event, context):
proxies = {
'http': 'http://username:password@proxy.example.com:8080',
'https': 'http://username:password@proxy.example.com:8080'
}
try:
response = requests.get(
'https://api.example.com/data',
proxies=proxies,
timeout=10
)
return {
'statusCode': 200,
'body': json.dumps(response.json())
}
except requests.exceptions.RequestException as e:
print(f'프록시 오류: {str(e)}')
return {
'statusCode': 500,
'body': json.dumps({'error': str(e)})
}
SOCKS5 프록시(더 안전한 프로토콜)의 경우 Python에서 추가 라이브러리 requests[socks]를 설치하고 URL 형식을 변경해야 합니다:
proxies = {
'http': 'socks5://username:password@proxy.example.com:1080',
'https': 'socks5://username:password@proxy.example.com:1080'
}
콜드 스타트를 위한 최적화
Lambda 함수는 콜드 스타트 문제를 가지고 있으며, 대기 상태 후 첫 번째 요청은 1-3초가 소요됩니다. 프록시를 사용할 경우 이 시간이 증가합니다. 지연을 최소화하려면 핸들러 함수 외부에서 HTTP 클라이언트를 생성하세요:
const axios = require('axios');
// 컨테이너 초기화 시 클라이언트를 한 번 생성합니다.
const httpClient = axios.create({
proxy: {
host: 'proxy.example.com',
port: 8080,
auth: {
username: process.env.PROXY_USER,
password: process.env.PROXY_PASS
}
},
timeout: 10000
});
exports.handler = async (event) => {
// 각 호출 시 클라이언트를 재사용합니다.
const response = await httpClient.get('https://api.example.com/data');
return {
statusCode: 200,
body: JSON.stringify(response.data)
};
};
이러한 접근 방식은 Lambda 컨테이너 생성 시 프록시 구성이 한 번만 수행되므로 콜드 스타트 시간을 200-500ms 단축합니다.
Vercel Edge Functions에 프록시 통합하기
Vercel은 두 가지 유형의 서버리스 함수를 제공합니다: Node.js Functions(AWS Lambda와 유사)와 Edge Functions(CDN에서 실행). Edge Functions는 Cloudflare Workers와 유사한 런타임에서 작동하며 Node.js API 사용에 제한이 있습니다. 두 가지 옵션을 살펴보겠습니다.
Vercel Node.js Functions
일반 Vercel Functions의 경우 AWS Lambda와 동일한 접근 방식을 사용하세요. api/fetch-data.js 파일을 생성하세요:
import axios from 'axios';
export default async function handler(req, res) {
const proxyConfig = {
host: process.env.PROXY_HOST,
port: parseInt(process.env.PROXY_PORT),
auth: {
username: process.env.PROXY_USER,
password: process.env.PROXY_PASS
}
};
try {
const response = await axios.get(req.query.url, {
proxy: proxyConfig,
timeout: 8000
});
res.status(200).json(response.data);
} catch (error) {
res.status(500).json({ error: error.message });
}
}
Vercel Dashboard(Settings → Environment Variables)에서 환경 변수를 추가하세요: PROXY_HOST, PROXY_PORT, PROXY_USER, PROXY_PASS.
Vercel Edge Functions
Edge Functions는 Node.js 라이브러리 대신 Web Fetch API를 사용합니다. 프록시는 커스텀 헤더 또는 미들웨어를 통해 설정됩니다:
export const config = {
runtime: 'edge',
};
export default async function handler(req) {
const proxyUrl = `http://${process.env.PROXY_USER}:${process.env.PROXY_PASS}@${process.env.PROXY_HOST}:${process.env.PROXY_PORT}`;
// Edge Runtime에서는 에이전트를 통해 프록시와 함께 fetch가 필요합니다(폴리필 필요)
// 대안: 프록시 API를 직접 사용하세요
const targetUrl = new URL(req.url).searchParams.get('target');
const response = await fetch(targetUrl, {
headers: {
'User-Agent': 'Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36'
}
});
return new Response(await response.text(), {
status: response.status,
headers: response.headers
});
}
중요한 제한: Edge Runtime은 표준 Node.js 프록시 에이전트를 지원하지 않습니다. 프록시와 완전히 호환되도록 하려면 Node.js Functions를 사용하거나 Edge Functions에서 요청을 수신할 별도의 프록시 서버를 설정하는 것이 좋습니다.
Cloudflare Workers의 프록시
Cloudflare Workers는 V8 isolates에서 실행되며 Vercel Edge Functions보다 더 엄격한 제한이 있습니다. Node.js 라이브러리를 통한 프록시 연결의 표준 방법은 여기서 작동하지 않습니다. 두 가지 작업 방법이 있습니다:
방법 1: HTTP CONNECT 터널링
HTTP CONNECT 방법을 지원하는 프록시를 사용하세요. 프록시 서버를 통해 터널을 생성합니다:
addEventListener('fetch', event => {
event.respondWith(handleRequest(event.request))
})
async function handleRequest(request) {
const proxyUrl = 'http://proxy.example.com:8080';
const targetUrl = 'https://api.example.com/data';
const proxyAuth = btoa(`${PROXY_USER}:${PROXY_PASS}`);
const response = await fetch(proxyUrl, {
method: 'CONNECT',
headers: {
'Host': new URL(targetUrl).host,
'Proxy-Authorization': `Basic ${proxyAuth}`
}
});
if (response.status === 200) {
// 터널이 설정되었습니다. 기본 요청을 수행합니다.
const finalResponse = await fetch(targetUrl, {
headers: {
'User-Agent': 'Mozilla/5.0 (Windows NT 10.0; Win64; x64)'
}
});
return finalResponse;
}
return new Response('프록시 연결 실패', { status: 502 });
}
이 방법은 CONNECT를 지원하는 HTTP 프록시에서만 작동합니다. 대부분의 주거용 프록시 제공업체는 이러한 기능을 제공합니다.
방법 2: 프록시 게이트웨이(추천)
더 신뢰할 수 있는 방법은 별도의 서버(VPS 또는 AWS EC2 등)에 중간 프록시 게이트웨이를 배포하는 것입니다. Cloudflare Worker는 귀하의 게이트웨이에 요청을 보내고, 그 후 프록시를 통해 요청을 전달합니다:
// Cloudflare Worker
addEventListener('fetch', event => {
event.respondWith(handleRequest(event.request))
})
async function handleRequest(request) {
const targetUrl = new URL(request.url).searchParams.get('url');
const gatewayUrl = 'https://your-proxy-gateway.com/fetch';
const response = await fetch(gatewayUrl, {
method: 'POST',
headers: {
'Content-Type': 'application/json',
'X-API-Key': API_KEY // 귀하의 게이트웨이를 보호합니다.
},
body: JSON.stringify({
url: targetUrl,
method: 'GET'
})
});
return response;
}
프록시 게이트웨이 측(Node.js 서버):
const express = require('express');
const axios = require('axios');
const app = express();
app.use(express.json());
const proxyConfig = {
host: 'proxy.example.com',
port: 8080,
auth: {
username: process.env.PROXY_USER,
password: process.env.PROXY_PASS
}
};
app.post('/fetch', async (req, res) => {
if (req.headers['x-api-key'] !== process.env.API_KEY) {
return res.status(401).json({ error: 'Unauthorized' });
}
try {
const response = await axios({
url: req.body.url,
method: req.body.method || 'GET',
proxy: proxyConfig,
timeout: 10000
});
res.json(response.data);
} catch (error) {
res.status(500).json({ error: error.message });
}
});
app.listen(3000);
이 접근 방식은 추가적인 홉을 추가하여 지연을 50-100ms 증가시키지만, 프록시 연결에 대한 완전한 호환성과 제어를 보장합니다.
서버리스 환경에서 IP 주소 회전
프록시 사용의 주요 이유 중 하나는 속도 제한을 우회하기 위해 여러 IP 주소 간에 요청을 분산하는 것입니다. 서버리스 아키텍처에서는 회전하는 두 가지 접근 방식이 있습니다:
프록시 제공업체 측의 자동 회전
대부분의 주거용 프록시 제공업체는 회전 프록시를 제공합니다. 하나의 엔드포인트에 연결하면 IP가 요청마다 또는 설정된 간격(예: 5분마다) 자동으로 변경됩니다. 이는 서버리스에 가장 간단한 옵션입니다:
// 하나의 엔드포인트, IP가 자동으로 변경됩니다.
const proxyConfig = {
host: 'rotating.proxy.example.com',
port: 8080,
auth: {
username: 'user-session-' + Date.now(), // 고유한 세션
password: 'password'
}
};
일부 제공업체는 사용자 이름의 매개변수를 통해 회전을 관리할 수 있습니다: user-session-random (각 요청마다 새로운 IP), user-session-sticky-300 (300초 동안 하나의 IP).
프록시 풀을 통한 수동 회전
정적 프록시 목록이 있는 경우(예: 전용 프록시를 구입한 경우), 애플리케이션 수준에서 회전을 구현할 수 있습니다. 서버리스 환경에서는 DynamoDB(AWS) 또는 KV Storage(Cloudflare)를 사용하여 상태를 저장하세요:
const AWS = require('aws-sdk');
const dynamodb = new AWS.DynamoDB.DocumentClient();
const PROXY_POOL = [
{ host: 'proxy1.example.com', port: 8080 },
{ host: 'proxy2.example.com', port: 8080 },
{ host: 'proxy3.example.com', port: 8080 }
];
async function getNextProxy() {
// DynamoDB에서 현재 인덱스를 가져옵니다.
const result = await dynamodb.get({
TableName: 'ProxyRotation',
Key: { id: 'current_index' }
}).promise();
const currentIndex = result.Item?.index || 0;
const nextIndex = (currentIndex + 1) % PROXY_POOL.length;
// 인덱스를 업데이트합니다.
await dynamodb.put({
TableName: 'ProxyRotation',
Item: { id: 'current_index', index: nextIndex }
}).promise();
return PROXY_POOL[currentIndex];
}
exports.handler = async (event) => {
const proxy = await getNextProxy();
const response = await axios.get('https://api.example.com/data', {
proxy: {
...proxy,
auth: {
username: process.env.PROXY_USER,
password: process.env.PROXY_PASS
}
}
});
return { statusCode: 200, body: JSON.stringify(response.data) };
};
이 방법은 회전에 대한 완전한 제어를 제공하지만 DynamoDB에 대한 추가 요청이 필요하므로(10-30ms의 지연이 추가됨) 고부하 애플리케이션의 경우 Lambda 컨테이너의 메모리에 인덱스를 캐시하고 100-1000 요청마다 업데이트하는 것이 좋습니다.
오류 및 타임아웃 처리
프록시는 서버리스 애플리케이션에 추가적인 실패 지점을 추가합니다. 사용자 요청을 잃지 않도록 오류를 올바르게 처리하는 것이 중요합니다.
프록시 작업 시 일반적인 오류
| 오류 | 원인 | 해결 방법 |
|---|---|---|
| ETIMEDOUT | 프록시가 응답하지 않거나 느리게 작동함 | 타임아웃을 5-8초로 줄이고 다른 프록시로 재시도하세요 |
| ECONNREFUSED | 프록시 서버에 접근할 수 없음 | 프록시의 접근 가능성을 확인하고 다른 프록시로 대체하세요 |
| 407 Proxy Authentication Required | 잘못된 자격 증명 | 사용자 이름/비밀번호를 확인하고 Lambda IP가 프록시의 화이트리스트에 있는지 확인하세요 |
| 502 Bad Gateway | 프록시가 대상 사이트에 연결할 수 없음 | 사이트가 프록시를 차단할 수 있으므로 다른 IP 또는 프록시 유형을 시도하세요 |
재시도 로직 구현 및 대체
오류 발생 시 대체 프록시로 자동 재시도를 추가하세요:
const axios = require('axios');
const PRIMARY_PROXY = {
host: 'primary.proxy.com',
port: 8080,
auth: { username: 'user', password: 'pass' }
};
const FALLBACK_PROXY = {
host: 'fallback.proxy.com',
port: 8080,
auth: { username: 'user', password: 'pass' }
};
async function fetchWithRetry(url, maxRetries = 3) {
for (let attempt = 0; attempt < maxRetries; attempt++) {
const proxy = attempt === 0 ? PRIMARY_PROXY : FALLBACK_PROXY;
try {
const response = await axios.get(url, {
proxy,
timeout: 8000
});
return response.data;
} catch (error) {
console.log(`시도 ${attempt + 1} 실패:`, error.message);
// 클라이언트 오류(4xx)에서는 재시도하지 않음
if (error.response && error.response.status < 500) {
throw error;
}
// 마지막 시도 — 오류를 발생시킴
if (attempt === maxRetries - 1) {
throw error;
}
// 재시도 전 지연을 추가합니다.
await new Promise(resolve => setTimeout(resolve, 1000 * Math.pow(2, attempt)));
}
}
}
exports.handler = async (event) => {
try {
const data = await fetchWithRetry('https://api.example.com/data');
return { statusCode: 200, body: JSON.stringify(data) };
} catch (error) {
return { statusCode: 500, body: JSON.stringify({ error: error.message }) };
}
};
이 구현은 세 번의 요청 시도를 제공합니다: 첫 번째는 기본 프록시를 통해, 나머지는 대체 프록시를 통해 수행됩니다. 시도 간에는 지연이 추가되어 과도한 부하를 피합니다.
모니터링 및 알림
CloudWatch(AWS), Vercel Analytics 또는 Sentry를 통해 프록시 오류 모니터링을 설정하세요. 다음 메트릭을 추적하세요:
- 프록시를 통한 성공적인 요청 비율(95% 이상이어야 함)
- 요청의 평균 지연 시간(증가하면 프록시 문제를 나타낼 수 있음)
- 타임아웃 오류 수(5% 초과 시 — 프록시 과부하 또는 느림)
- 오류 코드별 분포(407, 502, ETIMEDOUT 등)
임계값 초과 시 알림을 설정하세요 — 이를 통해 대체 프록시 제공업체로 신속하게 전환하거나 구성을 변경할 수 있습니다.
결론
서버리스 애플리케이션에 프록시를 통합하면 데이터 센터 IP 차단, 속도 제한 및 지역 차단과 같은 중요한 문제를 해결할 수 있습니다. AWS Lambda(Node.js 및 Python), Vercel Functions 및 Cloudflare Workers에서의 프록시 설정, IP 주소 회전 및 오류 처리 구현을 살펴보았습니다.
주요 권장 사항: 높은 익명성이 필요한 작업(파싱, 소셜 미디어 API 작업)에는 주거용 프록시를 사용하고, 자격 증명을 안전한 저장소(AWS Secrets Manager, Vercel Environment Variables)에 보관하며, 대체 프록시로의 재시도 로직을 구현하고 오류 모니터링을 설정하세요.
안정성이 높은 서버리스 애플리케이션의 경우 주거용 프록시와 자동 회전을 사용하는 것이 좋습니다 — 이는 속도, 익명성 및 신뢰성 간의 최적의 균형을 제공하여 외부 API 및 서비스와 작업할 때 차단 위험을 최소화합니다.