Go로 간단히 웹서버 만들기

개요

요즘 Go 언어의 주가가 지속적으로 오르면서 Go 언어에 대해서 들어보지 못한 사람은 없을 것이다. Go 언어는 배우기 쉽고 간단함에도 불구하고 매우 좋은 성능을 자랑한다. (그 특유의 단순함으로 인해 개발자마다 극명하게 호불호가 갈리기도 한다.) 또한, 내부적으로 가지고 있는 기본 라이브러리도 풍부해서 생산성도 좋다. 여기에서는 따로 의존성없이 Go의 기본 라이브러리만 사용해서 간단히 웹서버를 개발해볼 것이다. Go 언어를 할 줄 몰라도 다른 프로그래밍 언어를 접해본 적이 있다면 코드를 대략적으로 이해하는데 어려움은 없을 것이다.

Getting Started

Hello World

늘 그렇듯이 Hello World 예제부터 살펴보자. 아래의 코드는 /hello 경로로 요청시 “Hello World!!”라는 문자열을 반환하는 웹서버의 Go 코드이다.

package main

import (
	"fmt"
	"log"
	"net/http"
)

func main() {
    log.Println("starting Web Server")      // (1)

    http.HandleFunc("/hello", func(w http.ResponseWriter, r *http.Request) {    // (2)
        fmt.Fprintln(w, "Hello World!!")
    })

    err := http.ListenAndServe(":8080", nil)    // (3)
    if err != nil {             // (4)
        log.Fatalln(err)
    }
}

여기서 핵심은 net/http 패키지이다. net/http 패키지는 Go에서 HTTP 서버 및 클라이언트와 관련된 기능을 제공하는 기본 내장 모듈이다. Go 역시 다른 언어들처럼 보다 간편하고 좋은 성능을 위해서 Gin, Echo 등의 웹 프레임워크가 존재하지만 간단한 웹서버의 경우 net/http 만으로도 충분할 것이다.

위 코드를 하나씩 살펴보자. (1)은 간단하게 서버시작을 알리는 로그이다. 역시 내장된 log 모듈을 사용하여 로그를 남기고 있다. (2)부터 중요한 부분이다. net/http 모듈을 사용하여 /hello 경로를 등록하고 있다. 두 번째 파라미터로 넘기는 값은 함수이다. Go 언어는 high-order function을 지원한다. /hello 경로가 호출되면 함께 등록된 함수가 실행될 것이다. 이때 등록된 함수의 첫 번째 파라미터(http.ResponseWriter)는 응답과 관련된 기능을, 두 번째 파라미터(r *http.Request)는 들어온 요청과 관련된 기능을 제공한다. 함수 내에서는 간단하게 Hello World를 응답으로 내보내고 있다. 집고 넘어가야할 것이, fmt.Println이 아니다. fmt.Fprintln이다. 이 함수는 첫 번째 파라미터로 주어지는 Writer에 해당 문자열을 출력하는 기능을 수행한다. C 언어를 접해본 사람은 조금 익숙한 방식의 함수일 것이다. Java 언어에 익숙한 사람은 Go의 Writer를 Java의 OutputStream이라고 생각하면 쉽게 이해될 것이다. (3)은 8080 포트로 바인딩해서 서버를 시작하고 요청을 기다린다. 이때 http.ListenAndServe 함수의 반환값은 error이다. 대부분의 Go 함수는 내부적으로 예상되는 에러가 있다면 마지막 반환값으로 에러를 반환한다. 만약 에러 반환값이 nil(다른 언어의 null과 동일하다)이 아니라면 함수 수행 중 에러가 발생한 것이라는 의미이다. 그래서 (4)에서 err가 nil이 아닌 경우 Fatal 로그를 남기고 있다. 그리고 추가적으로 := 연산자는 변수를 생성하고 바로 값을 할당하는 것을 의미한다.

그런데 함수의 마지막 반환값은 또 무슨 말인가 싶은 사람도 있을 것이다. 기본적으로 Go 언어의 함수는 2개 이상의 반환값을 가질 수 있다. 아래의 예제처럼 여러 개의 반환값을 여러 개의 변수로 한 번에 받아서 사용할 수 있다.

func DoSomething(arg string) (string, int, error) {
    ...
}

result1, result2, err := DoSomething("hello")
if err != nil {
    ...
}

여기서 Go를 접해보지 못한 사람의 경우 *http.Request처럼 타입 앞에 붙은 *가 무엇을 의미하는지 궁금할 것이다. 이는 C 언어처럼 해당 타입의 포인터 임을 의미한다. Go 언어는 포인터를 지원한다. 걱정하지는 말자. Go 언어의 포인터는 훨씬 간단하며 일반 객체를 다루는 것과 크게 다르지 않다. 대부분 단순한 용도로만 사용된다. 그럼 왜 첫 번째 파라미터는 일반 타입이고 두 번째는 포인터인가하는 의문이 생길 수도 있는데, 이는 Go 언어에서 더 깊숙히 들어가야하는 것들이니 일단 넘어가도록 하자.

또 다른 궁금증은 함수명이나 타입명이 소문자로 시작하는 것도 있고 대문자로 시작하는 것도 있다는 것에 의문을 표할 것이다. 이는 절대 오타가 아니다. Go 언어는 개발자 간에 코딩 컨벤션을 통일하는 것을 선호한다. 그래서 일부 기능은 함수 또는 타입의 이름이 무엇이냐에 따라서 갈린다. 대문자로 시작하는 이름은 Exported로, Java로 치면 public 메소드 또는 클래스라고 생각하면 된다. 반대로 소문자로 시작한다면 같은 패키지 내에서만 사용할 수 있다.

Routing

앞에서 봤던 것처럼 net/http에서도 HandleFunc라는 함수를 이용하여 경로와 실행될 함수를 매핑할 수 있다. 함수를 넘겨주는 방법 외에도 직접 Handler를 구현하는 방법도 존재한다.

Handler

Handler 인터페이스는 아래와 같은 형태를 하고 있다.

type Handler interface {
	ServeHTTP(ResponseWriter, *Request)
}

별거없다. 간단하게 ServeHTTP 함수를 구현하면 된다. 한 번 Handler를 구현해서 사용해보자.

type MyHandler struct {
    myField string
}

func (m *MyHandler) ServeHTTP(w http.ResponseWriter, r *http.Request) {
    ...
}

위 예제는 MyHandler라는 타입은 선언하고 MyHandler에 ServeHTTP를 정의하고 있다. Go 언어는 Duck Typing 언어이기 때문에 명시적으로 특정 인터페이스를 구현한다고 선언할 필요없이 해당 인터페이스가 정의하는 함수와 동일한 함수를 구현하고 있다면 해당 인터페이스 타입으로 취급한다. 이제 핸들러를 등록해보자.

func main() {
    myHandler := MyBandler[}
    http.Handle("/my-handler", myHandler)
    ...
}

HandleFunc 대신 Handle이라는 함수를 사용하고 있다. 이를 통해서 간단하게 핸들러를 등록하여 사용할 수 있다.

Predefined Handler

사실 Go 언어는 보편적으로 많이 사용되는 성격의 핸들러를 미리 구현하여 제공하고 있다.

FileServer

이름에서 알 수 있듯이 정적 파일 서버 역할을 하는 햄들러이다. 파라미터로 경로를 지정해주면 해당 경로 내의 파일을 호스팅해준다. 아래의 코드는 사용 예이다.

http.Handle("/", http.FileServer(http.Dir("/tmp")))
NotFoundHandler

이름 그대로 404 NOT_FOUND를 반환하는 핸들러이다.

http.Handle("/resources", http.NotFoundHandler())
RedirectHandler

역시 이름에서 바로 알 수 있듯이 redirect를 처리하는 핸들러이다.

http.Handle("/redirect-test", http.RedirectHandler("https://google.com", http.StatusTemporaryRedirect))
TimeoutHandler

주어진 시간내 요청이 마무리되지 않으면 타임아웃을 발생시킨다.

http.Handle("/timeout-test", http.TimeoutHandler(MyHandler{}, 3*time.Second, "test timeout"))
ReverseProxy

들어온 요청을 주어진 URL로 프록시한다. net/http 패키지가 아닌 net/httputil 패키지임을 유의한다.

    parsedUrl, err := url.Parse("http://localhost:8888")
    if err != nil {
        log.Fatal(err)
    }
    http.Handle("/proxy-test", httputil.NewSingleHostReverseProxy(parsedUrl))

Parameters

이번에는 요청과 응답을 다루는 방법에 대해서 알아보고자 한다. 앞에서 핸들러 또는 핸들러 함수를 구현할 때 사용했던 http.Request가 중심이 될 것이다.

Query Parameters

http.Request의 URL 필드를 통해서 얻을 수 있다.

func ServeHTTP(w http.ResponseWriter, r *http.Request) {
    value := r.URL.Query().Get("key")
    ...
}

Request Body

Go 언어는 기본적으로 JSON을 다룰 수 있는 모듈을 제공한다. encode/json 패키지를 통해 해당 기능을 사용할 수 있다. 아래의 예제는 Content-Type이 application/json인 요청 body 값을 직접 정의한 SomeType이라는 구조체로 변환하는 코드이다.


type SomeType struct {
    Name string `json:"name"`
    Age  int    `json:"age"`
}

...

body := SomeType{}
err := json.NewDecoder(r.Body).Decode(&body)
if err != nil {
    ...
}

SomeType 구조체의 필드 뒤에 붙은 json... 값은 해당 필드의 태그이다. Java의 어노테이션(@)이라고 SomeType의 객체를 생성하고 그 주소값(&)을 Decode 함수에 전달하다. 이제 Decode 함수 내에서 주어진 포인터를 사용하여 알맞은 값을 할당해줄 것이다.

Response

Text

http.ResponseWriter는 Go 언어의 io.Writer 인터페이스를 구현하고 있다. 때문에 앞에서 봤던 것처럼 fmt 패키지의 함수들과 함께 사용할 수 있다.

n, err := fmt.Fprintf(w, "hello world: %d", arg)

JSON

Request Body를 JSON으로 바꾸던 것과 비슷한 방법을 통해서 JSON으로 응답을 할 수 있다. 아래의 코드를 통해서 요청자는 JSON 포맷의 데이터를 받게 될 것이다.

body := SomeType{
    Name: "kwseo",
    Age: 10,
}
err := json.NewEncoder(w).Encode(body)

net/httputil

끝마침

지금까지 웹서버에서 가장 필수적이라고 할 수 있는 API 라우팅 설정과 요청/응답을 다루는 방법을 net/http 패키지(약간의 net/httputil도…)를 통해서 간단히 알아보았다. 이외에도 더 많은 기능들을 제공하지만, 이정도만으로도 작은 웹서버를 만드는 것은 별로 어렵지 않을 것이다. 나중에 기회가 된다면 더 소개해보도록 하겠다. 이 만큼의 기능만으로도 유용하게 사용할 수 있겠지만, 앞에서도 언급했듯이 고급 기능과 더 간편한 사용을 원한다면 그땐 gin-gonic, echo, fastHTTP 등 잘 만들어진 프레임워크를 가져다 사용하는 것이 더 편할 것이다.

github-jekyll에 어떻게 글을 쓰나요? (feat. Markdown)

우리가 만드는 이 블로그는 Github와 jekyll으로 구성되어 있다.
Markdown 문서를 만들어 Github에 반영하면 글이 보인다. 이것을 jekyll을 통해 서비스를 하고 있다.
글을 쓰는 방법이 어렵고 쉽게 잊을 수 있어 정리하여 기록한다.

hyper-cube.io에 글을 쓰자

우리의 blog에 글을 쓰려면 서비스 하고 있는 hyper-cube-io계정의 ‘hyper-cube.io’ Repo에 Markdown 문서를 올리면 된다. 단, 우리는 jekyll로 서비스를 하고 있기때문에 jekyll에서 정의한 gh-pages라는 브랜치에 해당 작업이 진행되어야 한다. 우리들은 blog 관리를 위해 ‘hyper-cube.io’ Repo를 각자 Github 계정으로 Fork하여 글을 올린 후 Pull Request를 하도록 정하였다.
물론 'hyper-cube.io'를 로컬로 clone 후 gh-pages의 브랜치에 바로 push하여 글을 올리는 것도 가능하다.

hyper-cube-io에서 내 Github 계정으로 Repository Fork하기

글을 쓰기 위해서는 자신의 Github로 ‘hper-cube.io’ Repo를 Fork 할 필요가 있다. fork to repo

내 Repo 로컬로 clone하여 문서 작성

일반적인 Git을 사용하듯이 Fork한 Repo를 로컬로 clone한다.

git clone <repo_url>

clone 후 실제 문서는 gh-pages 브랜치의 _posts에 넣어줘야 보인다. 우리도 gh-pages 브랜치로 이동하여 작성하도록 한다.
clone 후 실제 작성한 문서는 master 브랜치에서 작업하며 마스터 브랜치의 _posts에 넣어 줘서 작업한다. 이것은 ‘hyper-cube.io’ Repo의 gh-pages에 올라와 있는 다른 사람의 문서를 건드릴 수 없게 하기 위함이다.
폴더 구조는 jekyll문서에 잘 나와 있으니 참고 하면 된다. Directory Structure

git checkout master

현재 작성 중인 ‘_posts/<문서명>.md' 문서 자체는 Markdown 형식으로 작성하며 Google에 검색하면 많은 예제가 있으니 참고 하시길..

document write

이제 작성한 문서를 내 Repo에 push한다.

git add _posts/2020-02-15-how-to-write-gh-page.md
git add public/posts_images/fork_to_repo.png
git add public/posts_images/document_write.png

git commit -m \"문서 작성법 업로드\"
git push 

hyper-cube-io에 Pull Request하기

앞에서 말했듯이 실제 페이지의 서비스는 hyper-cube-io/hyper-cube.io의 gh-pages 브랜치에서 하고 있다. 따라서 내 Repo에 올린 내용을 hyper-cube-io/hyper-cube.io에 반영 해 줘야 한다.

내 Repo에 내용들을 실제 hyper-cube-io에 반영하기 위해서는 Pull Request라는 과정을 거쳐야 한다. pull request create pull request 위 사진에서 보듯이 pull request를 생성할 때 base repository의 branch를 gh-pages로 head repository의 branch를 master로 설정 한다. 아래에는 내 repository에서 원본 repository로 반영할 commit 목록이 보인다. 이 과정은 내가 작업한 내용들이 실제 서비스에 반영해도 되는지 hyper-cube-io를 운영하는 권한 있는 사람들에게 승인을 받는다.

승인 과정이 끝나면 몇 초후에 페이지에서 제대로 반영되는지 확인한다.

매일 15분 전공인증 3기 서울 정모

오랫만에 포스팅인데, 정모 후기 포스팅이다.
‘매일 15분 전공공부를 위한 모임’[각주1]이 벌써 3기째다. 늘 쳐지지만 자극을 많이 받는 모임.
이하 15분 공부방

2019년 1월에 갑자기 진행된 서울 정모의 회의록을 공유한다.


2019 seoul 1월 정모

  • 20:00:00 모임 완료
    • 코리안 타임 없이 다들 정확하게 20:00 정각에 모였다.
      • 부지런, 근면성실한 사람들의 모임이라는것을 만나자마자 느꼈다.

모임시작

  • 윤빠님이 에어닷 이야기를 원철님에게 물어봄
  • 대세 이어폰 이야기 시작
    • qcy 가 가성비가 좋다.
    • qcy2 가 나왔다.
    • 가성비가 킹왕짱이다.

자기소개

  • 아윤빠, 안드로이드 개발
  • 허원철, 게임 회사 서버개발
  • 어리양, blockchain fe, flash, action script 개발자였음
  • 권양, 79년생, AI 공부한지 몇개월, 최근 인공지능 쳇봇이나 콜봇에 관심이 생김
    • 새롭고 신기하고 재밋게 하고 있음
    • EXO 수호봇을 만들겠다.
      • 셀카, 음성 등등을 보내주는 봇이될 예정
  • 박찬호(??? 아직 가입 안되있음, 친구따라 모임에 나옴.), 센터 운영팀(콜센터 운영하는 삼성카드나, 요기요나) 서버 사이트 운영
  • Lone(갓수), 군대를 가려다가 나온 근무지가 맘에 안들어서 병특 알아보는 중. 외주 1년쯤 하고있다 요즘 놀고 있음.
    • 외주는 잡부마냥..
    • 프론트는 일반 타이피컬한 웹을 만들어봤다., node.js
    • 관심사가 매우 다양함
  • 후오오, 서버개발하는 잡부
    • 잡부

모인 이유

  • ‘어리양’님이 모이자고 한걸 후오오가 덥썩 물어서 일정을 추진했다.
    • 그냥 갑자기 보고 싶어서 ㅋㅋ
      • (서기 사족) 사실 전공공부 오픈채팅방에 근면성실한 사람들이 참 많다.
        • 다들 뭐하시는 분들인지 궁금함이 마구 생긴다.
  • 토이프로젝트 하고 있는것들 공유
  • ‘마기’님은 소중함. 밥 잘 사주시는 형이다.[각주2]

15분 공부방에서 활동 한게 된 후 느낀거

  • 후오오
    • 15분 공부방 짱짱맨, 간증을 시작합니다.
    • 나는 몹시 게으른데, 열심히 하시는 분들을 보고 감명 받아서 따라 하게됨.
    • 몇일 빠지더라도, 100일을 채워야 겠다 라는 마음가짐(만)이 생김
    • 공부 인증 외에도 개발 꿀팁들이 마구 올라와서 좋음
    • 사실 15분 공부방에 어떻게 들어오게되었는지 기억은 나지 않음
  • 아윤빠
    • 안드로이드방에서 수지아부[각주3]님 약에 팔려서 들어오게 되었다.
      • 갓원철님이 영혼의 라이벌이었다.
        • 매일매일 원철님이랑 같이 영혼의 공부 맞다이
        • 누가누가 열심히 하나 배틀이 시작되어 서로 성장해버렸다.
      • 4.5년차에 맞는 나의 실력을 키우기 위해서 열심히 하다가…
      • 슬럼프가 좀 있다가…
      • 끌려다닐만한 사람을 찾아라
        • 배울 사람들을 찾아다니면서 실력이 늘어난 것 같다
      • 곧 성공각
      • 어리양님이 아윤빠님을 칭찬해주었다.
  • 원철님
    • 1기때 열심히 했다.
      • 아윤빠님과 인증배틀
    • 주변에 그렇게 하는 사람들이 있으니까, 불타오르게 화하아아아 하게되었다.
    • (사족) 영문 블로그 링크를 자주 전달해준다.
    • 너무 열심히 하면 디스크 터지니 쉬엄쉬엄 하자
  • 어리양
    • 액션스크립트에서 프론트로 이직을 했음.
    • 어떻게하는지는 알겠는데 환경이 달라서
    • 머리로는 아는데 손이 안따라줌
    • reactive 방(오픈채팅)에 계신 수지아부님의 링크를 보고 공부를 해야겠다 라고 다짐을 하고 채팅방에 들어옴
    • 함수형방도 1년쯤 됨
      • 함수형프로그래밍 방 아껴주세요 (하트)
    • 어떻게든 15분씩 했다.
      • (사족 진짜 대단)
    • 머리아파도 틈틈히 공부했다.
      • (사족 진짜 대단.2)
    • 요콩님과 인증 배틀
      • (사족) 역시 라이벌이 있으면 크게 성장하는 것 같다.
    • 코틀린 공부가 당장 필요하지는 않지만,
      • 같이 공부하는게 참 좋다.
      • 공부도 공부지만, 어떤 사람들과 공부를 하느냐도 중요하다
    • 엉뚱한 프로젝트를 하고 있는데, 도전하고, 러닝메이트들이 있어서 할 수 있을 것 같은 느낌이 든다.
    • 안드로이드 앱 만들고 있다
    • Q. 인강 듣는거?
      • 인강 따로 듣는건 없고 Vue.js 토이프로젝트
      • 하스켈 스터디중
        • 가장쉬운 하스켈 <- 책 추천받음
  • 권양
    • 비트코인 방 가면 블록체인 기술을 가르쳐 줄 것으로 알고 방에 감
    • 인공지능 코인들을 보다가
    • 메인 잡과 인공지능이 시너지가 가능할 것 같다는 생각이 스침
    • 개발자들 공부하는거에 자극 받음 -> 나도 해야겠다
    • 비트코인방에서 수지아부님의 링크를 보고 유입
  • Lone
    • 어떻게 들어왔는지 기억이 안남
    • 뭐 하나만 파는게 아니라, 여러개의 뽐뿌를 막 생기는 모임이다
    • 관심사의 변경 히스토리
      • unity
      • node.jsk
      • c++, 옥찬우님 책을 사서… 방-치
      • 최근
        • python, flask, django
  • 박찬호
    • 개발자들 모여있다. 라고 해서 옴
    • Lone 의 소개로 옴
      • 친구랑 처음만나서 했던거 : unity
    • 백엔드 쪽 공부중

Q. 요즘 보고 있는 책이나 최근 주문한 책?

(이어서…)책안보는 개발자

  • 인터넷의 도큐멘트가 잘 되있음
    • 사실 공식 도큐멘트가 짱임
  • vs 이동시간에 뭔가 할게 없어서 책을 본다

Q. 업무툴로 추천할만한 것들 뭐 없나?

  • 와카타임
    • 코드 타임 트래킹…
    • 내가 업무시간에 어떤걸 얼만큼 하는지 트래킹해줌
    • 각종 IDE에 플러그인으로 장착가능
  • 노션
    • 역시 요즘 핫한 노션. 역시 쓰는사람이 있다.
  • 에버노트
    • 유료로..
  • bear + trello
  • 원노트
  • github issue
  • bitbucket

마무리

  • 다들 학구열이 높당.
  • 수지아부님은 짱인것 같다.
    • 아닌게 아니라 다들 수지아부님의 영향을 많이 받고 있음.
  • 개발자 생태계는 수도권이 아무래도 잘 되있다.
  • k는 무엇인가
    • 2017 드로이드 나이츠에서 나눠준 스티커의 정체불명 ‘K’ 심볼. 무슨뜻인지 끝내 알지 못했다.

각주

  1. ‘매일 15분 전공공부를 위한 모임’
    • 카카오톡 오픈채팅방. 매일매일 15분씩 전공공부를 하고 인증하자. 취지로 개설되어 현재 3기.
    • 지금(2019-02-12)은 3기지만, 2기 모집글
  2. ‘마기님’
  3. ‘수지아빠’
    • 못하는 것도, 모르는 것도 없는 수지아빠님
    • 자세한 설명은 블로그로 대신한다.

진짜 마무리

혼자 공부하는 것도 좋지만, 뛰어난 러닝메이트들과 함께 할 수 있다면 시너지가 배가된다.

Hello, Service Discovery

다른 수많은 소프트웨어 기술과 같이 Service Discovery도 이전부터 존재하던 개념이다. 그러나 최근 클라우드 및 마이크로서비스 아키텍처의 부흥과 함께 종종 언급되며 우리들의 눈에 띄는 것처럼 보인다. 한번 이번 기회에 Service Discovery에 대해 간단히 알아보자.

Service Discovery

위키피디아에서는 Service Discovery에 대한 문서가 빈약한 편이지만 간단하게 Service Discovery는 네트워크 상에서 제공되는 서비스 또는 장치의 자동 탐지라고 정의하고 있다. MSA의 입장에서 살펴보면 네트워크로 연결되어있는 여러 서비스들을 자동으로 탐지하여 그 정보들을 관리하는 것이다. 네트워크 상에 A 서비스와 B 서비스가 존재하며 각 서비스는 HTTP RESTful API를 제공한다. A가 B의 API를 사용하기 위해서는 B의 IP와 포트 같은 주소가 필요하다. 같은 물리 서버에 동작한다고해도 최소한 자신과 같은 서버에서 동작한다는 사실과 포트는 알고 있어야할 것이다. 이렇게 A가 B의 주소 정보를 구체적으로 알고 있어야 한다는 사실은 A와 B의 구조에 몇가지 제한을 만들게 된다.

첫 번째로 B를 scale-out 하는 경우를 예로 들 수 있다. B가 보단 많은 트레픽을 처리하기 위해서 scale-out을 한다고 해보자. A는 추가된 B 서비스의 서버 주소 정보를 추가적으로 알아야한다. 개발자는 직접 A에 B와 관련된 설정이 해주어야할 것이다. 만약, A 뿐만이 아니라 C, D 서비스도 B를 이용하는 있었다면 모두 일일이 새롭게 설정을 해주어야할 것이다. 설정이 별로 어렵지 않고 그정도는 직접 해줘도 괜찮다고 할 수도 있다. 그렇다면 auto scale-out은 어떻게 해야할까? 들어오는 트레픽에 비례해서 자동으로 B의 인스턴스가 추가된다고하면 A, C, D는 어떻게 할 것인가? B 서비스가 별도의 로드밸런서에 연동되어 자동으로 부하분산을 해주고 있다고해도 역시 개발자가 직접 로드밸런서에 새롭게 추가된 추가적인 B 인스턴스에 대해서 설정을 해주어야하는 번거로운 작업이 존재한다.

두 번째로 클라우드 환경에서는 IP 같은 주소 정보가 매우 동적이라는 것이다. 어찌보면 위에서 잠깐 언급한 auto scale-out와 이어지는 이야기일 수도 있다. 클라우드 환경에서는 서버 인스턴스가 얼마든지 실시간으로 간단하게 추가 및 제거될 수 있다. 이 과정에서 부여받는 주소 정보는 예측이 불가능하다.

Service Discovery는 클라이언트가 주소를 명시적으로 알고 있어야한다는 제한점으로부터 우리를 해방시켜준다. Service Discovery는 서비스들의 주소를 자동으로 탐지하고 관리를 해주기 때문이다. Service Discovery에 의해 주소 정보가 관리되기에 클라이언트는 더 이상 서버의 주소 및 포트에 대해서 알 필요가 없으며 어떤 서비스가 존재한다는 것만 알면 된다. 다른 서비스와의 통신에 좀 더 추상화된 레이어를 제공한다고 볼 수 있다. 또한, 서버 측도 서비스 주소를 자동으로 탐지하여 관리하기에 보다 물리적인 인프라에 제약을 받지않고 다이나믹하게 서비스를 운영할 수 있다.

Service Registry

Service Discovery에서 네트워크 내의 각종 서비스에 대한 주소 정보를 저장하고 관리하는 중앙 서버를 Service Registry라고 부른다. 다른 서비스들의 주소 정보를 제공하므로 고가용성이 요구되지만 최신 Service Discovery 플랫폼들은 만에 하나 Service Registry가 죽어버리더라도 문제없이 서비스들 간에 통신이 가능하도록 방법을 제공하기 때문에 부담은 덜하다.

Pattern

Service Discovery는 크게 두가지 패턴을 가지고 있다. 하나는 Client-Side Discovery Pattern이며 나머지 하나는 Server-Side Discovery이다.

Client-side Discovery

Client-side Discovery

클라이언트가 직접 Service Registry로부터 특정 서비스의 주소 정보를 받아와서 통신을 하는 패턴. 클라이언트가 직접 주소를 가져와서 서비스와 직접 통신을 하기 때문에 불필요한 네트워크 부하를 줄일 수 있다. 또한, 클라이언트가 특정 서비스의 모든 인스턴스 주소를 알 수 있기 때문에 자신인 원하는대로 로드밸런싱을 할 수도 있다. 단 이점은 단점이 되기도 하는데, 자신이 서비스 주소를 가져오고 원하는대로 로드밸런싱을 할 수 있다는 말은 클라이언트가 Service Discovery를 위한 추가적인 기능을 개발해야된다는 말이기 때문이다.

Server-side Discovery

Server-side Discovery

이 패턴은 클라이언트가 Service Registry에 대해서는 전혀 몰라도 되는 패턴이다. 서버와 클라이언트 사이에는 요청 유형에 따라(예를 들어, URL 경로 등) 적절한 서비스 인스턴스로 전달을 해주는 라우팅 서버가 존재한다. 이 라우팅 서버는 Service Registry를 참고하여 서비스에 대한 주소 정보를 얻고 이를 바탕으로하여 클라이언트로부터의 요청을 서비스 인스턴스에 전달한다. 클라이언트는 로드밸런서와만 통신을 하기 때문에 따로 Service Discovery와 관련된 특별한 작업을 신경 쓸 필요가 없다. 하지만 로드밸런서는 SPOF(Single Point Of Failure)가 될 수 있기 때문에 매우 중요한 요소로서 높은 고가용성이 요구될 것이다.

Next - Service Discovery 구현체

다음 포스트에서는 Service Discovery 구현체를 몇가지 알아볼 것이다. 첫 번째로 Java로 개발된 Netflix의 Eureka에 대해서 알아보고, 두 번째로 Go로 개발된 HashiCorp의 Consul 이렇게 두 가지를 알아볼 것이다.

pm2와 config를 활용한 node.js app 실행 환경 관리

process managing

node.js 앱을 배포하다 보면, 모니터링, 프로세스 관리, std 로그 관리 등이 필요할 때가 있다. 요런 니즈로 사용하는 것들은 크게 forever, nodemon, pm2등이 있다.

forever,nodemon,pm2 npmtrends 생각보다 nodemon을 많이쓴다!?


그중에서도 pm2는 모니터링 및 프로세스의 현재 상태를 시각적으로도 잘 보여주고, 여러 프로세스를 관리할 때 용이하게 되어있어서 애용하는 도구다. 무엇보다 세련되게 생겼다.

http://pm2.keymetrics.io/

기존 pm2 1.x.x 버전을 쓰고 있는 서버가 있어서, 이번에 배포 환경 정리하면서 pm2 로 업그레이드하였고, pm2 2.x에서 나온 ecosystem을 활용했다.

ecosystem 외에도 clustering, deployment 등도 제공한다. 나중에 시간 내서 읽어보자. PM2 Documents

config

https://github.com/lorenwest/node-config

config 패키지를 사용하면 application 내에서 손쉽게 configuration에 접근할 수 있다. 물론 환경별로 configuration을 관리할 수 있고, default 값에 변경되는 값만 override 할 수도 있다.

pm2 + config

pm2의 ecosystem을 활용하면 실행 시에 NODE_ENV를 명시해서 넘길 수 있다.

pm2 ecosystem.config.js --env production

// ex) ecosystem.config.js
{
  "apps" : [{
    "name"        : "worker",
    "script"      : "./worker.js",
    "watch"       : true,
    "env": {
      "NODE_ENV": "development"
    },
    "env_production" : {
       "NODE_ENV": "production"
    }
  }]
}

전달받은 NODE_ENV 이름에 맞는 config 파일은 config/ 디렉토리 안에 추가해주면, application 전체에서 config로 쉽게 접근이 가능하다.

[Project Root]
  ├ (...)
  ├ package.json
  ├ config/
  │ ├ default.json // config 의 기본 파일이다.
  │ └ production.json
  │
  └ ecosystem.config.js