리버스 프록시
스터디 준비 자료로 작성한 글입니다. 원본은 github에서 확인 가능합니다.
📡 프록시 서버란?
- 프록시 서버는 통상적으로 포워드 프록시 서버를 뜻함.
- 포워드 프록시 서버는 클라이언트와 서버 사이에 위치하여 클라이언트의 요청을 서버에 전달하고 서버의 응답을 클라이언트에 전달하는 역할을 함.
- 프라이빗 네트워크와 퍼블릭 인터넷 사이의 중개자 역할을 함.
- 클라이언트는 프록시 서버를 “서버”로 인식하고, 서버는 프록시 서버를 “클라이언트”라고 인식함
👍🏻 포워드 프록시 서버의 장점
- 캐싱
- 클라이언트와 서버 사이에서 중개자 역할을 하며 전달하는 내용들을 캐싱함.
- CDN 서버와 유사하게 캐싱을 통해 빠른 응답을 할 수 있음.
- 자주 요청하는 리소스를 캐싱해두면 불필요한 외부 요청을 줄일 수 있고 네트워크 병목 현상을 방지해줌.
- 보안
- 포워드 프록시 서버를 통해 서버에 요청을 보내면, 클라이언트의 요청 정보를 숨길 수 있음.
- 서버 입장에서는 프록시 서버를 거쳐 요청이 오기 때문에 클라이언트의 정보를 알 수 없음. (IP 주소, User-Agent 등)
🔎 포워드 프록시 서버 사용 예시
- 특정 사이트 접속 제한
- 프록시 서버를 통해 요청을 보내기 때문에, 프록시 서버에서 요청을 제한할 수 있음.
- 예를 들어, 특정 IP 주소나 User-Agent를 차단하거나, 특정 시간대에만 요청을 받을 수 있음.
🔥 리버스 프록시 서버란?
- 포워드 프록시와 다르게 하나 이상의 웹 서버 앞에 위치하여 클라이언트의 요청을 가로채는 서버임.
- 클라이언트가 서버에 요청을 보내면 리버스 프록시 서버가 요청을 가로채고, 웹 서버에 요청을 전달함.
- 클라이언트는 서버를 “서버”로 인식하고, 서버는 리버스 프록시 서버를 “클라이언트”라고 인식함
👍🏻 리버스 프록시 서버의 장점
- 로드 밸런싱
- 수백만 이상의 사용자를 가진 서비스의 경우 단일 서버로는 모든 트래픽을 처리하지 못할 수 있음.
- 리버스 프록시 서버를 사용하면 여러 대의 서버를 두고 로드 밸런싱을 통해 트래픽을 분산시킬 수 있음.
- 요청을 받은 서버가 과부하 상태라면 다른 서버로 요청을 전달함.
- 공격 보호
- 리버스 프록시를 사용하면 웹 사이트 또는 서비스에서 원본 서버의 IP 주소를 공개하지 않아도 됨.
- DDOS 공격 등의 위협으로부터 원본 서버를 보호할 수 있음.
- 캐싱
- 리버스 프록시 또한 포워드 프록시와 유사하게 캐싱 기능을 제공할 수 있음.
- 서버에 요청되는 주기가 짧은 리소스들을 리버스 프록시 서버에 캐싱해두면 서버의 부하를 줄일 수 있음.
- SSL/TLS 암호화
- 리버스 프록시 서버를 사용하면 웹 서버와 클라이언트 사이에 SSL/TLS 암호화를 적용할 수 있음.
- 웹 서버는 SSL/TLS 암호화를 지원하지 않아도 됨.
NginX로 리버스 프록시 서버 구축하기
- nginx로 로드밸런싱 기능을 하는 간단한 리버스 프록시 서버를 구축해보았음.
http 서버
- 간단한 http 서버.
- 서버 구분을 위해 uuid를 생성하고 보여주는 서버임.
package main
import (
"net/http"
"github.com/google/uuid"
"github.com/labstack/echo"
"github.com/labstack/echo/middleware"
)
func main() {
e := echo.New()
e.Use(middleware.Logger())
e.Use(middleware.Recover())
e.GET("/", hello)
e.Logger.Fatal(e.Start(":3000"))
}
func hello(c echo.Context) error {
u, err := uuid.NewRandom()
if err != nil {
panic(err)
}
return c.String(http.StatusOK, u.String())
}
NginX 설정
nginx.conf
파일 설정
worker_processes 4;
events { worker_connections 1024; }
http {
upstream echo {
least_conn;
server server1:3000 weight=10 max_fails=3 fail_timeout=30s;
server server2:3000 weight=10 max_fails=3 fail_timeout=30s;
server server3:3000 weight=10 max_fails=3 fail_timeout=30s;
server server4:3000 weight=10 max_fails=3 fail_timeout=30s;
server server5:3000 weight=10 max_fails=3 fail_timeout=30s;
server server6:3000 weight=10 max_fails=3 fail_timeout=30s;
}
server {
listen 80;
location / {
proxy_pass http://echo;
proxy_http_version 1.1;
proxy_set_header Upgrade $http_upgrade;
proxy_set_header Connection 'upgrade';
proxy_set_header Host $host;
proxy_cache_bypass $http_upgrade;
}
}
}
- nginx는 이벤트 기반으로 동작함.
upstream
설정 -> upstream 안의 서버들에게 request를 보냄.least_conn
: 연결된 커넥션이 가장 적은 서버에게 request를 보냄. 그 이의 방식으로는round-robin(default)
: 라운드로빈으로 분배hash
: 해시값을 기반으로 분배ip_hash
: 클라이언트의 ip를 기반으로 분배random
: 랜덤으로 분배least_time
: 가장 빠른 응답시간을 가진 서버에게 request를 보냄.
Docker image 빌드
echo/Dockerfile
FROM golang:1.18.1-alpine
ENV GO111MODULE=on
WORKDIR /src
COPY . /src
RUN go build -o main
EXPOSE 3000
CMD ["./main"]
` $ docker build -t echo .`
nginx/Dockerfile
FROM nginx:1.15-alpine
COPY nginx.conf /etc/nginx/nginx.conf
` $ docker build -t nginx_server .`
Docker compose
docker-compose.yml
version: '3.1'
networks:
lb_network:
driver: bridge
services:
nginx:
container_name: nginx_server
image: nginx_server
restart: always
ports:
- "8080:80"
networks:
- lb_network
depends_on:
- server1
- server2
- server3
- server4
- server5
- server6
server1:
container_name: server1
image: echo
restart: always
expose:
- "3000"
networks:
- lb_network
server2:
container_name: server2
image: echo
restart: always
expose:
- "3000"
networks:
- lb_network
server3:
container_name: server3
image: echo
restart: always
expose:
- "3000"
networks:
- lb_network
server4:
container_name: server4
image: echo
restart: always
expose:
- "3000"
networks:
- lb_network
server5:
container_name: server5
image: echo
restart: always
expose:
- "3000"
networks:
- lb_network
server6:
container_name: server6
image: echo
restart: always
expose:
- "3000"
networks:
- lb_network
- docker network 내부에서 자체적으로 DNS 서버를 가지고 있기 때문에 컨테이너 이름으로 접근 가능함.
결과
- docker 컨테이너들과 네트워크가 생성된 것을 볼 수 있음
- 앞에서 생성한 http 서버는 서버 생성 시 고유한 uuid 값을 받아
/
경로로 요청이 오면 해당 uuid를 반환하는 서버임. - nginx 서버에 접근하면 nginx 서버는 upstream 설정에 따라 http 서버로 로드밸런싱하여 request를 보냄.
PREVIOUS[JPA] JPA 맛보기
NEXT[네트워크] HTTP2