redgoose(붉은거위)

redgoose 서비스 포팅

Nest
Tool & Service
Category
Personal
Hit
826
Star
1

redgoose 서비스는 API와 클라이언트로 구성되어 있으며 RestAPI 형식으로 통신을 하고 대표적으로 사용되는 서비스는 다음과 같다.

2022년 10월쯤에 서버로 사용하고 있던 나스가 죽어버려서 서비스를 전부 사용하지 못하는 사태가 벌어졌다.
데이터가 날라간것은 아니지만 복구가 어려워 한달동안 서버가 닫힌 상태였다.

docker-logo.webp

이 일을 계기로 서비스 인프라 구성을 다시 만들어볼 생각이 생겼다.
기존의 방식이 서버 한대에 전부 집중화 되어있고 각 자식 서비스들이 블럭화 되어있으면 좋겠다는 생각이 들어서 이번에는 도커를 좀더 적극적으로 활용해보고 싶은 욕심이 생겼다.

인프라에 대한 도커구성

이번에는 진짜 도커를 사용할 생각으로 서버를 구성하면서 나에게 필요한 내용들을 찾아보았다.
실질적으로 사용할 설정을 만들다보니 이론적으로 알아봤던 요소들이 직감적으로 받아들일 수 있었다. 처음 접할때보다는 나았지만 삽질을 거듭하여 결국에는 기초적인 구성은 완료해 두었다.

docker-compose를 통하여 컨테이너들을 대단히 구조화된 형태로 관리할 수 있었다.
컨테이너들은 각자 작은 서비스들이다보니 서로 영향을 주기도 하고 많은 액션에 대한 조작이 필요하다. 이러한 것들을 한꺼번에, 그리고 구조적으로 관리하는데 많은 도움을 받았다.

infra-structure.webp

docker-compose 그룹을 두개로 나누었으며 역할은 다음과 같다.

service

데이터베이스, 리버스 프록시, https 도메인 기능을 하는 컨테이너들을 다룬다.
서비스의 환경을 다루는 성격의 도구들을 모아둔 컨테이너 그룹이라고 볼 수 있다.

OS의 시스템 설정같은 느낌릴까..

www

주로 노출되는 서비스들을 사용하는 컨테이너라고 볼 수 있다. API나 서비스들, 기타 다른 툴들을 컨테이너로 열어두고 리버스 프록시 컨테이너에서 컨트롤한다.
컨테이너 이름을 호스트로 사용하니 상당히 편하게 구분해서 컨테이너 서버들을 관리할 수 있다.

새로운 방식의 서비스 프로젝트 재구성

현재까지 노출되는 서비스들은 검색봇을 위하여 서버에서 완성된 html이 출력되는 방식을 고수해왔다. 그래서 백엔드 프로젝트나 SSR(Server side rendering)로 프로젝트를 구성해야 했다.
두 구성 장단점이 뚜렷하게 존재하고 만족스러운 모습이 아니었다.


백엔드와 SSR의 특징은 다음과 같다.

백엔드 (PHP)

  • 속도가 대단히 빠르고 서버 점유율이 적다.
  • HTML 출력까지 구현이 간단한 편이지만 스타일시트나 자바스크립트 구현이 까다롭다.
  • 서버에서 HTML을 출력하기 때문에 화면에 표시되는 소스를 검색복에 그대로 노출한다.
  • 매 페이지를 이동할때마다 새로 로드한다.
  • 비동기 통신 구현을 시작하기부터 개발이 복잡해진다.
  • 최신 트랜드의 프론트엔드 기술구현이 까다롭다. (vue, react.. 등등)

SSR

  • 자바스크립트로 dom을 표현하기 때문에 다이나믹한 동작이 쉬워진다.
  • 서버에서의 렌더링과 클라이언트에서의 렌더링 전부 관리하기 때문에 동작의 조건이 복잡해지기 때문에 처리하는 부분들이 복잡해진다.
  • 많은 조건과 동작을 처리하기 위하여 커다란 프레임워크에서 개발을 해야한다. (next.js, nuxt)
  • 프레임워크에서 개발해야하기 때문에 구현하는 표현에 있어 많이 경직된다.
  • 프레임워크의 덩치가 크기 때문에 메모리를 많이 사용한다.

여태까지 성능에 집중하기 위하여 PHP로 작업해왔다.
하지만 요즘은 react, vue 같은 자바스크립트 프로그램으로 프론트엔드 개발을 해오다보니 일반적인 html, js, css 들을 각자 작성해오던 시절보다 많이 발전해오고 노하우가 늘어났다.

그래서 최근에 SNS에서 커뮤니케이션을 하다가 문득 착안을 한 부분이 있는것이..

브라우저가 아닌 검색봇이 들어오면 HTML 소스코드만 출력한다.

라는 조건을 시작으로 브라우저로 서버에 접속하면 SPA로 렌더링을 하고, 그게 아니라면 node 서버에서 html 코드를 출력한다.
단순히 html 출력하는것과 spa로 출력되는일은 서로 성격이 다르기 때문에 같은 페이지를 두벌로 만드는것과 진배없다. 이 방식은 생산성이 대단히 떨어지기 때문에 선호하는 방법은 아닐 것이다.

하지민 잘 생각해보면 서비스의 모든 페이지들이 전부 검색봇에 노출되기를 원하지 않을 것이다.
봇에 노출되는 페이지는 일부분일 뿐이고 그 부분만 대처해주면 작업양이 크기 않을거라 생각되었다. 그리고 각 페이지에 대한 구조도 굉장히 심플해지기 때문에 결과물이 명확하고 최적화되어 있어 사람이든 봇이든 방문객에에 좋은 결과를 준다고 생각한다.

service-diagram.webp

그림으로 정리해보자면 이런 모습이 되는데 이러한 구상을 보일러플레이트를 만들어보면서 원하는 모습으로 구현이 되는지 확인해 보았다.
https://github.com/redgoose-dev/starter/tree/vite-node

브라우저와 봇을 구분하는 node.js 디펜던시도 존재하니 상상했던 것들을 현실로 만드는데 불가능하지 않다는것을 깨닫으며 대단히 기뻤다.

svelte 사용

자바스크립트 렌더링에 react, vue를 사용하다가 최근에는 svelte를 사용하기 시작했다.
svelte는 vue와 비슷한 방식으로 단순하고 빠른 성능으로 구현을 해주기 때문에 많은 기대를 가지고 있어 개인 작업에서 많은 것들을 svelte로 코드작성을 하고싶어졌다.

라우트만을 이용하여 완전한 SPA 프로그램을 만들었다. 나중에는 PWA 적용도 문제없이 구현할 수 있을거라고 예상된다.
아직 svelte에 익숙하지 않아서 작업에 생각 이상으로 시간이 걸린것 뿐이지만 프로그램 자체는 대단히 단순했다.

일반적인 페이지

모든 페이지들이 API를 통한 프로그램만 존재하는것이 아니라 일반적인 HTML 페이지도 존재하기 때문에 일반적인 페이지에서도 통합된 데이터가 존재해야 내용이 변할때 대처가 편해진다.

https://redgoose.me/page/about/ 페이지는 내용이 되는 요소는 json 파일로 보관해두고 다음과 같이 svelte와 pug로 작성했다.

about.svelte

<article class="about">
  <div class="about__wrap">
    <header class="about__header">
      <p>Introduce</p>
      <h1>{content.title}</h1>
    </header>
    <figure class="about__profile">
      <img
        srcset={`${content.coverImage.src[1]} 2x, ${content.coverImage.src[0]} 1x`}
        src={content.coverImage.src[0]}
        alt={content.coverImage.alt}/>
    </figure>
    <div class="about__description">
      <p>
        {@html content.description}
      </p>
    </div>
    <div class="about__metas">
      <div>
        {#each content.information as o,k}
          <section>
            <h1>{o.title}</h1>
            <dl>
              {#each o.items as oo}
                <dt>{oo.label}</dt>
                <dd>{@html oo.body}</dd>
              {/each}
            </dl>
          </section>
        {/each}
      </div>
    </div>
  </div>
</article>

<script lang="ts">
import content from '../../../server/resource/about.json'
</script>

<style src="./about.scss" lang="scss"></style>

about.pug

article
  header
    p Introduce
    h1 #{content.title}
    figure
      img(src=content.coverImage.src[0] alt=content.coverImage.alt)
    p !{content.description}
  each o in content.information
    section
      h1 #{o.title}
      dl
        each oo in o.items
          dt #{oo.label}
          dd !{oo.body}

같은 페이지라도 코드를 따로 작성하다보니 그 환경에 맞춰 전략을 취할 수 있다는것이 마음에 들었다.

브라우저와 아닌경우의 비교

역시 코드를 두벌로 만들다보니 브라우저에서 열면 자바스크립트와 스타일시트에 더욱 집중할 수 있고, 브라우저가 아니라면 순수하게 html 문서만 표시되기 때문에 더욱 적절한 형태로 표현할 수 있고, 관리적인 측면은 좀 더 지켜봐야 할것이다. (작고 단순한 프로그램이라면 관리도 더 작아질테니..)

redgoose-browser.webp

브라우저에서 보이는 redgoose.me 첫페이지
아무것도 없는 상태에서 스크립트로 스타일시트와 dom을 전부 만들어서 이렇게 나온 것이다. 새로고침을 하지 않는다면 이 상태에서 그대로 유지될테니깐..

redgoose-bot.webp

스타일시트 속성 하나도 없는 순수한 html 페이지다.
분석에 용이하도록 엘리먼트 종류를 의미있게 사용하는것이 좋다.

API 통신

서비스 외부에 데이터들을 가져오는일은 중복되는 작업이기 때문에 반드시 통합시켜야 할 일중 하나다.
어떻게 해야할까 고민을 해보다가 프론트엔드에서 프록시 서버를 사용하는일에서 착안하여 프록시서버와 같은 역할을 node.js에서 직접 구현해보기로 했다.

서비스 프로젝트 내부 API를 만들어서 외부 API와 서비스 내부를 중계해주는 역할을 한다.
프록시서버와 똑같은 역할을 하기 때문에 외부 API서버 환경은 그대로 감추어져 있고 봇에서 출력되는 영역과 브라우저 영역에서는 내부 API요청을 하면 동일한 데이터를 유지할 수 있었다.

내부 API 에서 단순히 통신만 하는것이 아니라 요청을 검증하고 응답처리와 받은 값들을 보정하는 역할도 하니 출력하는 영역에서는 내부 API를 신뢰한다면 출력 로직이 대단히 단순해진다.

마무리

서비스 프로그램이 단순하더라도 새로운 것들을 도전하는 단계라서 새롭게 코드를 작성하는일이 많고, 시행착오나 고민들이 많았다.
그리하여 생각보다 작업시간이 늘어났다.

아무리 단순한 프로그램이라도 꼼꼼하게 프로그램을 만든다면 손이 많아 갈수밖에 없다.

한창 복구중..

주요한 부분들은 복구가 되었고, 아직 복구해야할 부분들이 더 남아있다.

containers.webp

이전에는 서버속에 php나 node 프로그램들이 전부 들어가 있었지만 변경한 이후로는 도커만 설치되어 있고 컨테이너들만 잔뜩 실행되어 있다.

이렇게 컨테이너들 돌리면 설정과 만들어진 데이터들만 존재하기 때문에 백업이 수월하고 복구도 상대적으로 쉬워진다. 설정과 데이터만 백업하고 다른곳에서 풀어놓고 컨텐이너들을 실행시키기만 하면 대부분 돌아갈 것이다.

구상을 해두었던 방식으로 만든 사이트들도 있고하니 좀더 지켜봐야 할것이다. 이것이 좋은 방법일런지..