Go/ehco

리버스 프록시(echo,reverse proxy)

guiwoo 2023. 8. 8. 01:05

엔진엑스를 사용하지 않고 에코에서 는 리버스 프록시 미들웨어를 제공해 준다. 해당 기능을 이용해서 작성해 보자.

그전에 프록시 에 대해 보다 명확하게 하고 넘어가자.

 

프록시 란?

프록시란? 대리라는 의미로 주로 네트워크 상에서 통신이 불가능한 두 점 사이를 통신 가능하게 만들어 주는 중계기 역할을 의미한다.

그 외에도 프록시는 보안, 로드밸런싱, 캐싱 다양한 기능을 제공한다.

먼저 그중 자주 언급되는 2가지에 대해 알아보자.

1. 로드벨런싱 :  여러 서버 사이의 트래픽을 분산시켜 서비스의 안정성과 성능을 향상한다.

예를 들어 8080 8081 이렇게 두 개의 포트로 동일한 서버가 존재하고 있다면 프로시 서버에서는 하나의 요청을 통해 어디 포트로 요청할지
특정 알고리즘을 활용해 각 서버의 트래픽 부하를 분산시켜 주는 것을 생각하면 편하다.

2. 캐싱 : 자주 요청되는 데이터 또는 특정 데이터들을 프록시 서버에 보관해 리소스를 효율적으로 사용하며 성능을 향상한다.

외부서버 와 데이터 통신을 줄여 네트워크의 병목현상을 방지하는 효과도 얻을 수 있게 된다.

 

(위 2가지의 기능을 본다면 어떤 특정 소프트웨어가 떠올라야 한다.)

 

이러한 프록시에는 2가지 종류가 있다. 여기서 보안의 목적이 나온다.

 

1. 포워드 프록시

포워드 프록시는 그림에서 보는것처럼 요청자,클라이언트 들은 인터넷에 직접 요청을 하는것이 아닌 프록시 서버가 요청을 받아 해당 인터넷 요청 결과를 전달해주는 것이다.

딱 봐도 저 박스 안에 갇혀있는 유저들을 관리하기 쉽다. 정해진 사이트의 요청만 가능하도록 제한하기 용이하며, 요청이 한 군데로 모여 결국 프록시 서버와 인터넷 이 통신을 하기때문에 비용절감에 탁월하다. 이에 기업환경에서 많이 사용된다.

 

2.  리버스 프록시

포워드 프록시와 다른점은 인터넷 과 프록시 서버의 위치가 변경되었다. 

이렇게 되면 클라이언트, 유저 들은 프록시에 연결되었다는 사실을 인지할 수 없으며 마치 요청을 최종 요청을 내부망에 보는 것과 같이 느끼게 된다. 이렇게 하는 이유는 보안이 주된 이유이다. 

내부망이 바로 인터넷과 통신하여도 문제는 없다 다면 내부망 혹은 내부 서비스를 제공하는 서버가 해킹당하거나, 털리는 경우 심각한 보안문제를 초래할 수 있다. 
그렇기에 리버스프록시를 설정하여 위와 같은 구조를 가져가게 된다.

 

두 개 중 한 개를 택해서 구현할 수도 있고 두 개를 복합적으로 선택해서 구현할 수도 있으니 이분법 적인 사고에 갇히지 말자.

 

개념을 알게 되었으니 이제 고에서 제공하는 리버스 프록시 미들웨어 구현을 바로 가보자.

func setupProxyGroup(e *echo.Echo, path string, url *url.URL, transport http.RoundTripper) {
	group := e.Group(path)
	group.Use(middleware.ProxyWithConfig(middleware.ProxyConfig{
		Balancer:  middleware.NewRoundRobinBalancer([]*middleware.ProxyTarget{{URL: url}}),
		Transport: transport,
	}))
}

func main() {
	flag.Parse()

	cfg, err := config.Init(*configFile)
	if err != nil {
		fmt.Printf("config init failure file(%s) : %s\n", *configFile, err)
		os.Exit(-1)
	}
	fmt.Println(cfg)
	utils.ProcessIgnoreSignal()

	e := echo.New()
	e.Use(middleware.CORSWithConfig(middleware.CORSConfig{
		AllowOrigins: []string{"*"},
		AllowHeaders: []string{"*"},
		AllowMethods: []string{"*"},
	}))

	e.Use(middleware.Recover())
	e.Use(middleware.LoggerWithConfig(middleware.LoggerConfig{
		Format: "[Proxy] ${status} ${method} ${host}${path} ${latency_human} ${time_rfc3339}" + "\n",
	}))

	transport := &http.Transport{
		TLSClientConfig: &tls.Config{
			InsecureSkipVerify: true,
		},
	}
	/**
	cms server
	*/
	cmsServerURL, err := url.Parse(cfg.Server.CmsServer)
	if err != nil {
		logrus.WithError(err).Errorf("cms server parse err %v", cfg.Server.CmsServer)
	}
    // 주소 생략

	/**
	api server
	*/
	apiServerURL, err := url.Parse(cfg.Server.ApiServer)
	if err != nil {
		logrus.WithError(err).Errorf("api server parse error %v", cfg.Server.ApiServer)
		os.Exit(-1)
	}
    // 주소 생략

	/**
	chat server
	*/
	chatServerURL, err := url.Parse(cfg.Server.ChatServer)
	if err != nil {
		logrus.WithError(err).Errorf("chat server parse err %v", cfg.Server.ChatServer)
	}
	setupProxyGroup(e, "/chat/live/:id", chatServerURL, transport)

	/**
	manager server
	*/
	managerServerURL, err := url.Parse(cfg.Server.ManagerServer)
	if err != nil {
		logrus.WithError(err).Errorf("chat server parse err %v", cfg.Server.ChatServer)
	}
	setupProxyGroup(e, "/chat", managerServerURL, transport)

	server := http.Server{
		Addr:    cfg.Server.Port,
		Handler: e,
		TLSConfig: &tls.Config{
			NextProtos: []string{acme.ALPNProto},
		},
	}
	log.Error(server.ListenAndServeTLS(cfg.Server.SSLCrt, cfg.Server.SSLKey))
}

리버스 프록시가 되어야 하기 때문에 CORS에 해당하는 모든 부분은 열어주었다. 왜? 내부서비스 가장 앞단에 위치해야 하기 때문이다.

중간에 보면 Recover와 Logger의 미들웨어를 추가적으로 사용했다.

우선 Recover는 http 프로토콜 통신간 어느 한 체인에서 패닉이 발생하더라도 리커버로 해당 패닉을 수습해 계속 서버를 유지하기 위해 작성했다.
실제 서비스에서 이러한 옵션은 버그를 양산할 수 있는 부분이 될 수 있으니 사용을 지양해야 한다. 
리버스프록시 서버의 목적에 맞게 옵션을 추가해서 사용하자.

 

프록시그룹 셋업 함수를 살펴보면 

func setupProxyGroup(e *echo.Echo, path string, url *url.URL, transport http.RoundTripper) {
	group := e.Group(path)
	group.Use(middleware.ProxyWithConfig(middleware.ProxyConfig{
		Balancer:  middleware.NewRoundRobinBalancer([]*middleware.ProxyTarget{{URL: url}}),
		Transport: transport,
	}))
}

이렇게 각 해당 서비스 별로 패스를 구분 지어 그룹 단위로 셋업을 진행했다. 
이중 ProxyCofig 구조체 생성간에 Balancer는 필수 값이다. 
위에서 언급한 것처럼 프록시에 는 로드밸런싱 기능이 있는데 에코에서는 이를 필수 값으로 지정해 타깃으로 정한다. 
작성된 코드는 오직 하나의 url을 이용하기 때문에 로드밸런싱의 기능은 전혀 활용하지 못하고 있다고 보면 된다.

 

Transport라는 생소한 부분이 보일 텐데 이는 Custom Tls 즉 사설 인증서를 사용하는 경우 필수 적이라고 명시되어 있다.

해당 포트 및 서버는 사설 인증을 받아 https를 테스트 용도의 목적으로 개설했기 때문에 옵션을 설정해주어야 한다.

 

모든 프록시는 동일한 http.TransPort를 받는데

	transport := &http.Transport{
		TLSClientConfig: &tls.Config{
			InsecureSkipVerify: true,
		},
	}

저기서 InsecureSkipVerify는 기본이 false 옵션이다. true 옵션을 추가하게 된다면  crypto/tls의 패키지에서는 아무 인증서나 패스해 주는 것을 의미한다. 따라서 이는 테스트 환경에서만 적용할 것을 권장한다고 주석 처리 되어 있으니 해당 옵션은 테스트 서버에서만 사용하자.

 

엔진엑스를 사용 못하고, 경량화된 프록시의 역할을 하는 무언가가 필요하다면  이렇게 에코에서 제공하는 리버스 프록시 서버를 작성해 보는 것도 좋은 방법 중에 하나가 될 수 있을 것 같다.

 

이외에도 에코 에는 구조체, 인터페이스 별로 주석이 정말 잘 달려있다. 무조건 함수 혹은 인터페이스 타고 들어가서 구현체 확인해 보자. 
구글도 알려주지 않는 것을 주석에서 알려주고 있다.