<?xml version="1.0" encoding="UTF-8"?>
<rss version="2.0">
  <channel>
    <title>생존과 성장 사이</title>
    <link>https://dokdo2013.tistory.com/</link>
    <description>Web Full Stack Engineer</description>
    <language>ko</language>
    <pubDate>Fri, 19 Jun 2026 10:37:44 +0900</pubDate>
    <generator>TISTORY</generator>
    <ttl>100</ttl>
    <managingEditor>HAENU</managingEditor>
    <image>
      <title>생존과 성장 사이</title>
      <url>https://tistory1.daumcdn.net/tistory/4277059/attach/a3294b66efe844bdafe7f143bd668417</url>
      <link>https://dokdo2013.tistory.com</link>
    </image>
    <item>
      <title>반년 가까이 월간 회고를 진행하며 느낀 점들</title>
      <link>https://dokdo2013.tistory.com/15</link>
      <description>&lt;p data-ke-size=&quot;size16&quot;&gt;지난 1월부터 개발자 지인들과 함께 있는 디스코드 서버에서 월간 회고를 진행하고 있습니다. 반년 가까이 월간 회고를 진행해보며 느낀 점들을 정리해보겠습니다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;먼저 가장 최근에 진행했던 4월 회고 링크를 남기면서, 반년간의 이야기를 찬찬히 풀어보겠습니다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;a href=&quot;https://docs.haenu.com/retrospect/2024/04&quot;&gt;https://docs.haenu.com/retrospect/2024/04&lt;/a&gt;&lt;/p&gt;
&lt;figure id=&quot;og_1715498924967&quot; contenteditable=&quot;false&quot; data-ke-type=&quot;opengraph&quot; data-ke-align=&quot;alignCenter&quot; data-og-type=&quot;website&quot; data-og-title=&quot;2024년 4월 회고 - Haenu Docs&quot; data-og-description=&quot;Nextra: the next docs builder&quot; data-og-host=&quot;docs.haenu.com&quot; data-og-source-url=&quot;https://docs.haenu.com/retrospect/2024/04&quot; data-og-url=&quot;https://docs.haenu.com/retrospect/2024/04&quot; data-og-image=&quot;&quot;&gt;&lt;a href=&quot;https://docs.haenu.com/retrospect/2024/04&quot; target=&quot;_blank&quot; rel=&quot;noopener&quot; data-source-url=&quot;https://docs.haenu.com/retrospect/2024/04&quot;&gt;
&lt;div class=&quot;og-image&quot; style=&quot;background-image: url();&quot;&gt;&amp;nbsp;&lt;/div&gt;
&lt;div class=&quot;og-text&quot;&gt;
&lt;p class=&quot;og-title&quot; data-ke-size=&quot;size16&quot;&gt;2024년 4월 회고 - Haenu Docs&lt;/p&gt;
&lt;p class=&quot;og-desc&quot; data-ke-size=&quot;size16&quot;&gt;Nextra: the next docs builder&lt;/p&gt;
&lt;p class=&quot;og-host&quot; data-ke-size=&quot;size16&quot;&gt;docs.haenu.com&lt;/p&gt;
&lt;/div&gt;
&lt;/a&gt;&lt;/figure&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;h3 data-ke-size=&quot;size23&quot;&gt;회고는 왜 시작했나요?&lt;/h3&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;가장 큰 건 '흘러가는대로 살고 싶지 않았다'는 생각이 들어서였습니다. 내 삶의 주도권을 내가 쥐고, 삶이 나아갈 방향성을 내가 설계할 수 있었으면 좋겠다는 생각이었습니다. 그리고 마침! 이직도 성공했고, 2024년 한 해가 새로 시작되는 만큼, 무언가를 돌아보며 새로운 그림을 그려보기 좋은 타이밍이기도 했습니다. 그렇게 지인들과 함께 월간 회고를 시작해보게 되었습니다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;h3 data-ke-size=&quot;size23&quot;&gt;회고, 어떻게 진행했나요?&lt;/h3&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;월간 회고 초기에는 정말 아무 형식도 없이 자유롭게 회고를 진행했습니다. 주로 지난 달에 잘한 점과 못한 점, 그리고 다음 달에는 어떻게 할지 등을 자유롭게 적어서 나눴습니다. 그러다가 하나씩 새로운 전략들을 도입해보고, 그 전략들을  개선해나가며 어느 정도 정형화된 포맷을 만들어나갔습니다. 하지만 회고에 정답은 없는 법, 각자에게 맞는 방식을 적용해 개인별로 회고 스타일이 달라져가는 모습을 보는 것도 재밌는 포인트 중의 하나였습니다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;지난 3월 회고부터는 모두에게 공통적으로 'Action Item을 설정해보자'는 제약 조건을 하나 걸어봤습니다. 회고를 통해 우리는 삶에 있어 계획적인 변화를 이끌어낼 수 있습니다. 측정 가능한 실천적 목표를 설정하고 그 목표를 다음 달 회고 때 측정하여, 목표의 달성률을 점차 개선해나가며 실제 삶의 변화를 만들어낼 수 있기를 기대하고 있습니다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;h3 data-ke-size=&quot;size23&quot;&gt;회고 모임, 느낀 점들&lt;/h3&gt;
&lt;h4 data-ke-size=&quot;size20&quot;&gt;꾸준함에 전략 한 스푼&lt;/h4&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;꾸준함을 밀고 나가면 결국 원하는 바를 이룰 수 있습니다. 월간 회고를 주기적으로 하며 내가 내 삶을 더 주도적으로 이끌어갈 수 있도록 한다? 그러면 분명 목표를 이뤄내는 데 도움을 주겠죠. 그런데 여기에 전략 한 스푼이 더해지면 그 목표를 더 잘 이룰 수 있는 것 같습니다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;여기서 말하는 '전략'이란 아래에서 설명할 '주기적인 목표 재방문'과 '단기/중기/장기 목표와 월간 목표의 Align', 그리고 '목표 변화에 유연성을 가져라' 가 포함됩니다. 이런 전략들을 이용해서 단순히 '지난 달에 어떻게 살았지? 그러면 이번 달에는 어떻게 하지?'에서 그치지 않고 회고를 보다 풍성하게 만들어볼 수 있을 것입니다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;h4 data-ke-size=&quot;size20&quot;&gt;목표를 돌아보지 않으면 까먹는다&lt;/h4&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;회고를 진행한 첫번째 달, 두번째 달까지는 크게 인지하지 못했는데요. 세번째 달쯤 되니까 생각보다 계획했던 목표가 잘 지켜지지 않음을 깨닫게 됩니다. 왜 목표를 세웠는데 그게 잘 달성되어 있지 않지? 라는 의문에, '까먹어서'라는 단순한 답이 발견되었습니다. 복잡한 현대 사회에, 쏟아지는 수많은 컨텍스트를 해치우다보면 월초에 계획했던 목표들은 기억 저 멀리 어딘가로 사라져있게 되는거죠.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;그래서 목표를 주기적으로 되돌아보며, 그 목표를 머리 속 주기억장치로 끌어올려야 합니다. 10일, 20일경에 한 번쯤 목표를 다시 훑어보면서, 내가 이 목표를 잘 달성하고 있는지, 까먹고 아무 것도 진행 안 한 건 아닌지 체크하다보면 목표 달성률을 크게 높일 수 있을 것입니다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;h4 data-ke-size=&quot;size20&quot;&gt;단기/중기/장기 목표와 월간 목표의 Align&lt;/h4&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;확실히 회고는 '귀찮은 일'에 포함되는 것 같습니다. 해야 할 것들도 많은데 지난 달을 돌아보는게 꼭 필요하냐? 라는 우선순위의 이슈도 마주하기 쉽구요. 그래서 그런지 '회고를 위한 회고'가 되기 쉬운 것 같고, 그렇게 회고 매너리즘에 빠지다보면 단순히 회고를 작성하는 시점에서의 기분과, 그 시점에서의 관심사에 목표 설정과 내용이 치중될 수 있습니다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;그런데 회고를 하는 목적을 다시 생각해보면, 내가 내 삶의 주도권을 쥐고 삶의 방향성을 내가 원하는 방향으로 이끌기 위해서입니다. 여기서 말하는 삶의 방향성은 '단기/중기/장기 목표'를 달성해가는 방향과 같고, 그래서 월간 회고는 '단기/중기/장기 목표'를 주기적으로 돌아보는 역할을 해야하는 것이죠.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;월간 목표를 세울 때는 그래서 기존에 세워뒀던 '단기/중기/장기 목표'를 함께 띄워두고, 그 목표 달성을 위해 이번 달 수행할 것들을 정하는 게 필요하다고 생각합니다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;h4 data-ke-size=&quot;size20&quot;&gt;목표의 방향성은 계속 변한다&lt;/h4&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;그런데 사람은 기본적으로 다면적 성향을 가지고 있고, 꿈꾸는 모습, 지향하는 방향이 계속해서 변화해나갑니다. 그건 당연한 것이죠. 맡고 있는 프로젝트가 바뀌거나, 관심사가 바뀔 수도 있고, 이직을 하며 도메인이나 기술 스택이 바뀔 수도 있습니다. 그래서 기존에 세워뒀던 '단기/중기/장기 목표'는 일정 기간이 지나면 바뀌어야 할 부분이 분명 생길 것입니다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;그래서 '목표는 절대적이지 않고, 유연하게 언제든 바꿀 수 있어야 한다'는 마음을 가져야한다고 생각합니다. 목표를 바꾸는 것은 내가 목표를 이루지 못할 것같아서 자기방어적 관점에서 낮추는 것이 아니라, 사람이 바뀌어감에 있어서 목표의 방향도 자연스럽게 바뀌어가는 것이라구요.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;그러면 이 변화를 어떻게 캐치하고 언제 목표를 정비하면 좋을까요? 그 최적의 시점이 바로 월간 회고입니다. 월간 회고를 하다보면 기존 '단기/중기/장기 목표'를 다시 돌아볼 수 있고, 그 과정에서 자연스럽게 미세 조정을 해주면 됩니다. 의식적으로 목표를 인지하고, 내가 바라는 방향과 실제 목표를 일치시켜나가는 과정을 반복하다보면 분명 1년 뒤, 3년 뒤, 5년 뒤에 꿈꾸던 모습이 되어 있을 것입니다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;h3 data-ke-size=&quot;size23&quot;&gt;중요한 것은 실천!&lt;/h3&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;이제 중요한 것은 매월 회고를 수행하고, 목표로 잡았던 내용을 실천하는 것입니다! 아직 저조차도 세웠던 목표를 100% 달성해본 적이 아직 없는데, 이번 달에는 꼭 100% 완료해볼 수 있도록 다시금 더 노력해보려 합니다. 다음 포스팅에는 2024년 전반기 회고로 돌아오겠습니다 :)&lt;/p&gt;</description>
      <category>회고</category>
      <author>HAENU</author>
      <guid isPermaLink="true">https://dokdo2013.tistory.com/15</guid>
      <comments>https://dokdo2013.tistory.com/15#entry15comment</comments>
      <pubDate>Sun, 12 May 2024 16:28:04 +0900</pubDate>
    </item>
    <item>
      <title>AWSKRUG 플랫폼 엔지니어링 모임 후기 (2023/11/21)</title>
      <link>https://dokdo2013.tistory.com/14</link>
      <description>&lt;p&gt;&lt;figure class=&quot;imageblock alignCenter&quot; data-ke-mobileStyle=&quot;widthOrigin&quot; data-filename=&quot;KakaoTalk_20231121_231630708.jpg&quot; data-origin-width=&quot;1440&quot; data-origin-height=&quot;1080&quot;&gt;&lt;span data-url=&quot;https://blog.kakaocdn.net/dn/Y9p7N/btsAF3H83a1/vKsigEbFr1IAXLqsh4ZC71/img.jpg&quot; data-phocus=&quot;https://blog.kakaocdn.net/dn/Y9p7N/btsAF3H83a1/vKsigEbFr1IAXLqsh4ZC71/img.jpg&quot; data-alt=&quot;AWSKRUG Platform Engineering 모임 (센터필드 AWS Korea)&quot;&gt;&lt;img src=&quot;https://blog.kakaocdn.net/dn/Y9p7N/btsAF3H83a1/vKsigEbFr1IAXLqsh4ZC71/img.jpg&quot; srcset=&quot;https://img1.daumcdn.net/thumb/R1280x0/?scode=mtistory2&amp;fname=https%3A%2F%2Fblog.kakaocdn.net%2Fdn%2FY9p7N%2FbtsAF3H83a1%2FvKsigEbFr1IAXLqsh4ZC71%2Fimg.jpg&quot; onerror=&quot;this.onerror=null; this.src='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png'; this.srcset='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png';&quot; loading=&quot;lazy&quot; width=&quot;1440&quot; height=&quot;1080&quot; data-filename=&quot;KakaoTalk_20231121_231630708.jpg&quot; data-origin-width=&quot;1440&quot; data-origin-height=&quot;1080&quot;/&gt;&lt;/span&gt;&lt;figcaption&gt;AWSKRUG Platform Engineering 모임 (센터필드 AWS Korea)&lt;/figcaption&gt;
&lt;/figure&gt;
&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;span style=&quot;font-family: 'Noto Sans Demilight', 'Noto Sans KR';&quot;&gt;2023년 11월 21일, AWSKRUG에서 처음으로 플랫폼 엔지니어링 모임이 열렸습니다. &lt;b&gt;플랫폼 엔지니어링&lt;/b&gt;이라는 단어를 보자마자 이거다! 환호하며 바로 신청했고, 기다리던 모임에 참석했습니다. 그리고 모임에서 듣고 나눴던 이야기를 잊지 않도록 집에 들어오자마자 바로 정리해보았습니다.&lt;/span&gt;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;h2 data-ke-size=&quot;size26&quot;&gt;&lt;span style=&quot;font-family: 'Noto Sans Demilight', 'Noto Sans KR';&quot;&gt;&lt;b&gt;플랫폼 엔지니어링이란 무엇인가?&lt;/b&gt;&lt;/span&gt;&lt;/h2&gt;
&lt;blockquote data-ke-style=&quot;style2&quot;&gt;&lt;span style=&quot;font-family: 'Noto Sans Demilight', 'Noto Sans KR';&quot;&gt;&lt;b&gt;플랫폼 엔지니어링이란?&lt;/b&gt;&lt;br /&gt;플랫폼 엔지니어링은 클라우드 네이티브 시대에 소프트웨어 엔지니어링 조직의 작업을 촉진하고 가속화하기 위한 플랫폼을 설계하고 제공하는 분야입니다. 이는 모든 부분과 그에 대한 역량, 즉 사람, 프로세스, 정책, 기술을 포괄합니다.&lt;/span&gt;&lt;/blockquote&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;span style=&quot;font-family: 'Noto Sans Demilight', 'Noto Sans KR';&quot;&gt;다시 말해, 플랫폼 엔지니어링의 &lt;b&gt;목적&lt;/b&gt;은 &quot;&lt;b&gt;소프트웨어 엔지니어링 조직이 일을 더 빠르게 잘 할 수 있도록 하는 것&lt;/b&gt;&quot;이고, 그 문제를 &quot;&lt;b&gt;플랫폼&lt;/b&gt;&quot;을 통해 해결해나갑니다.&lt;/span&gt;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;span style=&quot;font-family: 'Noto Sans Demilight', 'Noto Sans KR';&quot;&gt;여기까지 읽으면 사실 DevOps와 큰 차이가 없다고 느껴질 수 있습니다. &lt;span style=&quot;text-align: start;&quot;&gt;DevOps 역시 애플리케이션을 빠르게 제공할 수 있도록 여러 방법을 이용해서 지원하고자 하거든요.&lt;/span&gt;&lt;/span&gt;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;blockquote data-ke-style=&quot;style2&quot;&gt;&lt;span style=&quot;font-family: 'Noto Sans Demilight', 'Noto Sans KR';&quot;&gt;&lt;b&gt;DevOps란?&lt;/b&gt;&lt;/span&gt;&lt;br /&gt;&lt;span style=&quot;font-family: 'Noto Sans Demilight', 'Noto Sans KR';&quot;&gt;애플리케이션과 서비스를 빠른 속도로 제공할 수 있도록 조직의 역량을 향상시키는 문화 철학, 방식 및 도구의 조합&lt;/span&gt;&lt;/blockquote&gt;
&lt;p data-ke-size=&quot;size14&quot;&gt;&lt;span style=&quot;font-family: 'Noto Sans Demilight', 'Noto Sans KR';&quot;&gt;출처 : &lt;a href=&quot;https://aws.amazon.com/ko/devops/what-is-devops/&quot; target=&quot;_blank&quot; rel=&quot;noopener&amp;nbsp;noreferrer&quot;&gt;https://aws.amazon.com/ko/devops/what-is-devops/&lt;/a&gt;&lt;/span&gt;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;span style=&quot;font-family: 'Noto Sans Demilight', 'Noto Sans KR';&quot;&gt;사실 플랫폼 엔지니어링은 이 DevOps에서 한 걸음 더 나아간 형태입니다. 개발과 운영 조직의 비효율성을 문화와 도구들을 이용해 해결하려는게 DevOps이고, 플랫폼 엔지니어링은 DevOps의 도구로 '플랫폼'을 채택한 것이라 볼 수 있습니다.&lt;/span&gt;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;span style=&quot;font-family: 'Noto Sans Demilight', 'Noto Sans KR';&quot;&gt;플랫폼 엔지니어링 팀은 DevOps 방법론 위에서 소프트웨어 엔지니어링 팀, 프로덕트 팀을 지원할 수 있는 플랫폼을 만듭니다. &lt;span style=&quot;font-family: 'Noto Sans Demilight', 'Noto Sans KR'; text-align: start;&quot;&gt;일반적으로 플랫폼이라고 말하면 인프라부터 공통 모듈, API까지 모두 묶어서 이야기하는데요, 플랫폼 엔지니어링 팀이 만드는 플랫폼 역시 큰 틀에서 같은 구조를 가집니다.&lt;/span&gt;&lt;/span&gt;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;h2 data-ke-size=&quot;size26&quot;&gt;&lt;span style=&quot;font-family: 'Noto Sans Demilight', 'Noto Sans KR';&quot;&gt;&lt;b&gt;왜 지금 플랫폼 엔지니어링을 해야할까요?&lt;/b&gt;&lt;/span&gt;&lt;/h2&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;span style=&quot;font-family: 'Noto Sans Demilight', 'Noto Sans KR';&quot;&gt;플랫폼의 성숙도가 높아질 수록, 프로덕트 팀이 &lt;b&gt;더 빠르게&lt;/b&gt; 서비스를 만들어내고 &lt;b&gt;비즈니스에 임팩트&lt;/b&gt;를 만들어낼 수 있습니다.&lt;/span&gt;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;h3 data-ke-size=&quot;size23&quot;&gt;&lt;span style=&quot;font-family: 'Noto Sans Demilight', 'Noto Sans KR';&quot;&gt;플랫폼 엔지니어링을 통해 얻을 수 있는 이점&lt;/span&gt;&lt;/h3&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;span style=&quot;font-family: 'Noto Sans Demilight', 'Noto Sans KR';&quot;&gt;플랫폼 엔지니어링은 아래와 같은 여러 이점을 가지고 있습니다.&lt;/span&gt;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;span style=&quot;font-family: 'Noto Sans Demilight', 'Noto Sans KR';&quot;&gt;1. 제품 팀의 &lt;b&gt;인지 부하&lt;/b&gt;를 줄여 제품 개발 및 제공을 가속화할 수 있습니다.&lt;/span&gt;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;span style=&quot;font-family: 'Noto Sans Demilight', 'Noto Sans KR';&quot;&gt;2. 플랫폼 기능을 구성하고 관리할 전문가를 전담하여 플랫폼 기능에 의존하는 제품의 안정성과 복원력을 향상시킬 수 있습니다.&lt;/span&gt;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;span style=&quot;font-family: 'Noto Sans Demilight', 'Noto Sans KR';&quot;&gt;3. 기업 내 여러 팀에서 플랫폼 도구와 지식을 재사용하고 공유하여 제품 개발 및 제공을 가속화할 수 있습니다.&lt;/span&gt;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;span style=&quot;font-family: 'Noto Sans Demilight', 'Noto Sans KR';&quot;&gt;4. 제품 및 서비스의 보안, 규제 및 기능 문제의 위험을 줄일 수 있습니다.&lt;/span&gt;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;span style=&quot;font-family: 'Noto Sans Demilight', 'Noto Sans KR';&quot;&gt;이 중 특히 인지 부하와 관련된 부분을 좀 더 자세히 설명하고 싶은데요,&lt;/span&gt;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;span style=&quot;font-family: 'Noto Sans Demilight', 'Noto Sans KR';&quot;&gt;뛰어난 팀은 개발부터 인프라를 비롯해 QA까지 모두 프로페셔널하게 수행할 수 있습니다. 그런데... 대부분 그건 환상에 불과합니다. 개발자가 서비스 하나를 만드는데 보안도 알아야하고, WAF도 알아야하고, VPC도 알아야하고... 알아야 할 내용이 너무 많습니다.&lt;/span&gt;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;span style=&quot;font-family: 'Noto Sans Demilight', 'Noto Sans KR';&quot;&gt;대부분의 사람들에게 이처럼 인지 부하가 커지고, 이로 인해 제품 개발 속도도 느려지고 개별 개발자의 의욕 상실이나 번아웃 등도 걱정해야할 수 있습니다.&lt;/span&gt;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;span style=&quot;font-family: 'Noto Sans Demilight', 'Noto Sans KR';&quot;&gt;그래서 &lt;b&gt;잘 추상화되고, 투명성이 보장된 플랫폼&lt;/b&gt;을 만들고 이용할 수 있다면, 제품 팀이 빠르게 제품 자체에 집중하며 가속화할 수 있습니다.&lt;/span&gt;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;h3 data-ke-size=&quot;size23&quot;&gt;&lt;span style=&quot;font-family: 'Noto Sans Demilight', 'Noto Sans KR';&quot;&gt;Gartner가 선정한 10대 전략 기술 트렌드 - 2023, 2024 연속으로 선정 (각 5위, 4위)&lt;/span&gt;&lt;/h3&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;span style=&quot;font-family: 'Noto Sans Demilight', 'Noto Sans KR';&quot;&gt;Gartner가 매년 선정하고 있는 10대 전략 기술 트렌드에 Platform Engineering이 2023년, 2024년까지 2년 연속으로 선정되었습니다. 생성형 AI 관련된 내용이 주를 이루는 가운데, Platform Engineering이 당당하게 순위권에 위치하고 있습니다.&lt;/span&gt;&lt;/p&gt;
&lt;p&gt;&lt;figure class=&quot;imageblock alignCenter&quot; data-ke-mobileStyle=&quot;widthOrigin&quot; data-filename=&quot;gartner-top-10-strategic-technology-trends-2023.png&quot; data-origin-width=&quot;1500&quot; data-origin-height=&quot;1344&quot;&gt;&lt;span data-url=&quot;https://blog.kakaocdn.net/dn/wgYI5/btsAJ0b7eQ9/5jFe6nTEKxJiWWajqwpHDk/img.png&quot; data-phocus=&quot;https://blog.kakaocdn.net/dn/wgYI5/btsAJ0b7eQ9/5jFe6nTEKxJiWWajqwpHDk/img.png&quot; data-alt=&quot;Gartner 2023 Top Strategic Technology Trends - Rank 5&quot;&gt;&lt;img src=&quot;https://blog.kakaocdn.net/dn/wgYI5/btsAJ0b7eQ9/5jFe6nTEKxJiWWajqwpHDk/img.png&quot; srcset=&quot;https://img1.daumcdn.net/thumb/R1280x0/?scode=mtistory2&amp;fname=https%3A%2F%2Fblog.kakaocdn.net%2Fdn%2FwgYI5%2FbtsAJ0b7eQ9%2F5jFe6nTEKxJiWWajqwpHDk%2Fimg.png&quot; onerror=&quot;this.onerror=null; this.src='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png'; this.srcset='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png';&quot; loading=&quot;lazy&quot; width=&quot;600&quot; height=&quot;538&quot; data-filename=&quot;gartner-top-10-strategic-technology-trends-2023.png&quot; data-origin-width=&quot;1500&quot; data-origin-height=&quot;1344&quot;/&gt;&lt;/span&gt;&lt;figcaption&gt;Gartner 2023 Top Strategic Technology Trends - Rank 5&lt;/figcaption&gt;
&lt;/figure&gt;
&lt;figure class=&quot;imageblock alignCenter&quot; data-ke-mobileStyle=&quot;widthOrigin&quot; data-filename=&quot;top-strategic-technology-trends-2024.png&quot; data-origin-width=&quot;1080&quot; data-origin-height=&quot;1080&quot;&gt;&lt;span data-url=&quot;https://blog.kakaocdn.net/dn/c3iutF/btsAJ37KBg1/l31aoIPX3oFSrc2PnFkCo1/img.png&quot; data-phocus=&quot;https://blog.kakaocdn.net/dn/c3iutF/btsAJ37KBg1/l31aoIPX3oFSrc2PnFkCo1/img.png&quot; data-alt=&quot;Gartner 2024 Top Strategic Technology Trends - Rank 4&quot;&gt;&lt;img src=&quot;https://blog.kakaocdn.net/dn/c3iutF/btsAJ37KBg1/l31aoIPX3oFSrc2PnFkCo1/img.png&quot; srcset=&quot;https://img1.daumcdn.net/thumb/R1280x0/?scode=mtistory2&amp;fname=https%3A%2F%2Fblog.kakaocdn.net%2Fdn%2Fc3iutF%2FbtsAJ37KBg1%2Fl31aoIPX3oFSrc2PnFkCo1%2Fimg.png&quot; onerror=&quot;this.onerror=null; this.src='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png'; this.srcset='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png';&quot; loading=&quot;lazy&quot; width=&quot;600&quot; height=&quot;600&quot; data-filename=&quot;top-strategic-technology-trends-2024.png&quot; data-origin-width=&quot;1080&quot; data-origin-height=&quot;1080&quot;/&gt;&lt;/span&gt;&lt;figcaption&gt;Gartner 2024 Top Strategic Technology Trends - Rank 4&lt;/figcaption&gt;
&lt;/figure&gt;
&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;h2 data-ke-size=&quot;size26&quot;&gt;&lt;span style=&quot;font-family: 'Noto Sans Demilight', 'Noto Sans KR';&quot;&gt;&lt;b&gt;플랫폼 엔지니어링에서 꼭 챙겨야 할 3가지 개념&lt;/b&gt;&lt;/span&gt;&lt;/h2&gt;
&lt;h3 data-ke-size=&quot;size23&quot;&gt;&lt;span style=&quot;font-family: 'Noto Sans Demilight', 'Noto Sans KR';&quot;&gt;(1) Self-Service &amp;amp; Internal Developer Platform (IDP)&lt;/span&gt;&lt;/h3&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;span style=&quot;font-family: 'Noto Sans Demilight', 'Noto Sans KR';&quot;&gt;플랫폼 엔지니어링이라고 하면 가장 많이 언급되는 내용이 바로 Self-Service와 Internal Developer Platform입니다. 가장 쉽게 설명된 자료가 Outsider님의 인프콘 발표 자료여서 해당 내용을 자료로 가져와봤습니다. (여담으로 1월 플랫폼 엔지니어링 모임 때 Outsider님이 발표를 하신다고 하네요! 기대됩니다 ㅎㅎ)&lt;/span&gt;&lt;/p&gt;
&lt;p&gt;&lt;figure class=&quot;imageblock alignCenter&quot; data-ke-mobileStyle=&quot;widthOrigin&quot; data-origin-width=&quot;2738&quot; data-origin-height=&quot;1304&quot;&gt;&lt;span data-url=&quot;https://blog.kakaocdn.net/dn/xkexE/btsAFGzGtye/xsn3DBrFkv9x5fBVa3uNz1/img.png&quot; data-phocus=&quot;https://blog.kakaocdn.net/dn/xkexE/btsAFGzGtye/xsn3DBrFkv9x5fBVa3uNz1/img.png&quot; data-alt=&quot;전통적인 방식의 인프라 작업 요청 구조 (Outsider님의 인프콘 발표 자료 발췌)&quot;&gt;&lt;img src=&quot;https://blog.kakaocdn.net/dn/xkexE/btsAFGzGtye/xsn3DBrFkv9x5fBVa3uNz1/img.png&quot; srcset=&quot;https://img1.daumcdn.net/thumb/R1280x0/?scode=mtistory2&amp;fname=https%3A%2F%2Fblog.kakaocdn.net%2Fdn%2FxkexE%2FbtsAFGzGtye%2Fxsn3DBrFkv9x5fBVa3uNz1%2Fimg.png&quot; onerror=&quot;this.onerror=null; this.src='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png'; this.srcset='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png';&quot; loading=&quot;lazy&quot; width=&quot;2738&quot; height=&quot;1304&quot; data-origin-width=&quot;2738&quot; data-origin-height=&quot;1304&quot;/&gt;&lt;/span&gt;&lt;figcaption&gt;전통적인 방식의 인프라 작업 요청 구조 (Outsider님의 인프콘 발표 자료 발췌)&lt;/figcaption&gt;
&lt;/figure&gt;
&lt;figure class=&quot;imageblock alignCenter&quot; data-ke-mobileStyle=&quot;widthOrigin&quot; data-origin-width=&quot;2739&quot; data-origin-height=&quot;1453&quot;&gt;&lt;span data-url=&quot;https://blog.kakaocdn.net/dn/qtsJG/btsAJGdPoyp/RZrMiOqyTqVvumPHhgt1pK/img.png&quot; data-phocus=&quot;https://blog.kakaocdn.net/dn/qtsJG/btsAJGdPoyp/RZrMiOqyTqVvumPHhgt1pK/img.png&quot; data-alt=&quot;내부 개발자 플랫폼을 통한 인프라 요청 처리 구조 (Outsider님의 인프콘 발표 자료 발췌)&quot;&gt;&lt;img src=&quot;https://blog.kakaocdn.net/dn/qtsJG/btsAJGdPoyp/RZrMiOqyTqVvumPHhgt1pK/img.png&quot; srcset=&quot;https://img1.daumcdn.net/thumb/R1280x0/?scode=mtistory2&amp;fname=https%3A%2F%2Fblog.kakaocdn.net%2Fdn%2FqtsJG%2FbtsAJGdPoyp%2FRZrMiOqyTqVvumPHhgt1pK%2Fimg.png&quot; onerror=&quot;this.onerror=null; this.src='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png'; this.srcset='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png';&quot; loading=&quot;lazy&quot; width=&quot;2739&quot; height=&quot;1453&quot; data-origin-width=&quot;2739&quot; data-origin-height=&quot;1453&quot;/&gt;&lt;/span&gt;&lt;figcaption&gt;내부 개발자 플랫폼을 통한 인프라 요청 처리 구조 (Outsider님의 인프콘 발표 자료 발췌)&lt;/figcaption&gt;
&lt;/figure&gt;
&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;span style=&quot;font-family: 'Noto Sans Demilight', 'Noto Sans KR';&quot;&gt;전통적인 방식에서는 Software Engineer가 티켓이나 기타 방법들을 이용해 인프라 팀에 작업을 요청하였습니다. 그러면 인프라 팀은 직접 인프라 작업을 수행합니다. 이 과정에서 자동화 등도 많이 이뤄졌습니다.&lt;/span&gt;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;span style=&quot;font-family: 'Noto Sans Demilight', 'Noto Sans KR';&quot;&gt;반면 내부 개발자 플랫폼은 인프라팀이 수동으로 처리하던 작업을 대체하였습니다. Software Engineer가 인프라 작업에 대해 '요청'하는 것이 아닌 직접 '작업'을 하는 것이죠. 그런데 직접 인프라를 만지는게 아닙니다. 플랫폼을 통해 추상화 레이어를 거쳐서 실제 인프라 관련 작업은 플랫폼이 수행합니다.&lt;/span&gt;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;span style=&quot;font-family: 'Noto Sans Demilight', 'Noto Sans KR';&quot;&gt;플랫폼 엔지니어링 팀은 여기서 내부 개발자 플랫폼을 만드는 역할을 합니다.&lt;/span&gt;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;h3 data-ke-size=&quot;size23&quot;&gt;&lt;span style=&quot;font-family: 'Noto Sans Demilight', 'Noto Sans KR';&quot;&gt;(2) Platform as a Product&lt;/span&gt;&lt;/h3&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;span style=&quot;font-family: 'Noto Sans Demilight', 'Noto Sans KR';&quot;&gt;두 번째는 플랫폼을 단순히 부품 또는 요소로 생각하는 것이 아닌, 하나의 프로덕트로 바라보자는 내용입니다. 사실 아무리 Internal Platform이라고 해도 그것 자체가 일종의 프로덕트라고 볼 수 있습니다. 플랫폼 엔지니어링 팀은 Productivity를 가지고, 내부 고객들의 니즈를 파악하여, 내부 고객들이 필요로 하는 걸 제품으로 만들어야 합니다.&lt;/span&gt;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;span style=&quot;font-family: 'Noto Sans Demilight', 'Noto Sans KR';&quot;&gt;이런 과정 자체가 스타트업 등에서 프로덕트를 만들어나가는 과정이랑 유사한데요, 플랫폼 엔지니어링 팀의 규모가 커지게 되면, 애자일 조직에서의 프로덕트 오너와 같은 Platform Product Manager라는 역할도 고려된다고 하네요.&lt;/span&gt;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;h3 data-ke-size=&quot;size23&quot;&gt;&lt;span style=&quot;font-family: 'Noto Sans Demilight', 'Noto Sans KR';&quot;&gt;(3) Thinnest Viable Platform (TVP)&lt;/span&gt;&lt;/h3&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;span style=&quot;font-family: 'Noto Sans Demilight', 'Noto Sans KR';&quot;&gt;마지막은 바로 TVP입니다. 플랫폼 엔지니어링에 사용되는 플랫폼은, 플랫폼이 제공할 수 있는 가치 중 가장 얇은 가치를 제공해야 합니다. 내부 플랫폼이 엄청나게 뛰어난 UI나 UX를 제공하는게 목표로 설정되지 않듯, 플랫폼이 제공해야하는 기본적인 가치만 제공한다면 (= 충분한 Quality만 제공할 수 있다면), 가장 쉬운 기술을 선택해야한다는 뜻입니다.&lt;/span&gt;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;span style=&quot;font-family: 'Noto Sans Demilight', 'Noto Sans KR';&quot;&gt;이른바 &quot;Don't reinvent the wheel&quot; 과 일맥상통하는 말이며, Lightweight Platform Engineering이라는 표현으로도 설명될 수 있습니다. 흔히 DevOps 방법론을 설명하는 용어 중 CALMS 프레임워크가 있는데, 여기서 Lean이랑도 가깝게 이해할 수 있습니다.&lt;/span&gt;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;span style=&quot;font-family: 'Noto Sans Demilight', 'Noto Sans KR';&quot;&gt;저는 개인적으로 &lt;b&gt;적정기술&lt;/b&gt;이라는 용어가 떠올랐습니다. 내부에서 활용되는 플랫폼의 특성상 너무 최신 기술, 복잡한 아키텍쳐를 채택하는 대신, 익숙하면서도 검증된, 그러면서도 문제를 빠르고 적절하게 해결할 수 있는 기술을 선택해야 한다. 그것이 바로 Platform Engineering에서 기술을 선택할 때 주의해야 할 점이라고 이해했습니다.&lt;/span&gt;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;h2 data-ke-size=&quot;size26&quot;&gt;&lt;span style=&quot;font-family: 'Noto Sans Demilight', 'Noto Sans KR';&quot;&gt;&lt;b&gt;그 외 여러 이야기들..&lt;/b&gt;&lt;/span&gt;&lt;/h2&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;span style=&quot;font-family: 'Noto Sans Demilight', 'Noto Sans KR';&quot;&gt;발표가 마치고 자유롭게 플랫폼 엔지니어링과 관련된 여러 이야기들을 나눴습니다. 그 중 기억에 남는 몇 가지를 남겨봅니다.&lt;/span&gt;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;h3 data-ke-size=&quot;size23&quot;&gt;&lt;span style=&quot;font-family: 'Noto Sans Demilight', 'Noto Sans KR';&quot;&gt;플랫폼이 오히려 기술적인 장벽을 세우는게 아닐까?&lt;/span&gt;&lt;/h3&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;span style=&quot;font-family: 'Noto Sans Demilight', 'Noto Sans KR';&quot;&gt;플랫폼의 추상화 정도가 높아질 수록, 개별 개발자는 인프라에 대해 전혀 신경쓰지 않고 코드만 바라보면 됩니다. 이런 상황 자체는 조직 전체로 보았을 때 효율성을 높여줌으로 이득을 가져오나, 개별 개발자 입장에서는 성장과 경험 면에서 부정적일 수 있다는 의견이었습니다. 특히 주니어 레벨의 개발자들에게는 인프라와 관련된 이해를 아예 하지 않아도 되니, 그런 부분에서의 성장이 더뎌지는 등 모습을 봤을 때, 오히려 플랫폼이 기술적인 장벽을 세울 수도 있겠다는 의견이었습니다.&lt;/span&gt;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;span style=&quot;font-family: 'Noto Sans Demilight', 'Noto Sans KR';&quot;&gt;개인적으로 굉장히 인상 깊었던 질문이었습니다. 저는 개인적으로 저 부분이 플랫폼 엔지니어링이 가지는 양면성이라고 보고, 시스템을 통해 해결해나가야하는 부분이라 생각했습니다. 예를 들어 매니징 측면에서, 교육이나 연수 등을 통해 그런 결핍 등을 풀어나가는 방식으로요.&lt;/span&gt;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;h3 data-ke-size=&quot;size23&quot;&gt;&lt;span style=&quot;font-family: 'Noto Sans Demilight', 'Noto Sans KR';&quot;&gt;플랫폼 엔지니어링을 조직에 도입해야 할 시기는 언제일까?&lt;/span&gt;&lt;/h3&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;span style=&quot;font-family: 'Noto Sans Demilight', 'Noto Sans KR';&quot;&gt;보통 새로운 개념이나 기술들을 도입한다면 어떤 문제나 Pain Point를 느껴서 도입하는 경우가 많은데, Platform Engineering을 조직에 도입해야겠다고 느낄 시기나 그 기준에 대한 질문이었습니다.&lt;/span&gt;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;span style=&quot;font-family: 'Noto Sans Demilight', 'Noto Sans KR';&quot;&gt;발표자 분은 프로덕트의 개수를 기준으로 삼았습니다. &lt;b&gt;프로덕트가 5개 이상&lt;/b&gt;이라면 플랫폼 엔지니어링 도입을 검토해볼 수 있다는 의견을 제시하였습니다.&lt;/span&gt;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;h3 data-ke-size=&quot;size23&quot;&gt;&lt;span style=&quot;font-family: 'Noto Sans Demilight', 'Noto Sans KR';&quot;&gt;플랫폼 엔지니어링의 성과, 어떻게 측정할 것인가?&lt;/span&gt;&lt;/h3&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;span style=&quot;font-family: 'Noto Sans Demilight', 'Noto Sans KR';&quot;&gt;이게 참 어려운 질문이었습니다. 제품의 수익이 올라가는 걸 플랫폼 엔지니어링의 성과로 볼 수 있는가? 어떤 지표를 KPI로 잡아야 플랫폼 엔지니어링을 통한 성과를 정확히 측정할 수 있을까? 하는게 주요 고민 포인트였습니다.&lt;/span&gt;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;span style=&quot;font-family: 'Noto Sans Demilight', 'Noto Sans KR';&quot;&gt;대부분 공감했던 부분은 &lt;b&gt;Time To Market (TTM)&lt;/b&gt; 을 주요 지표로 활용하자는 부분이었습니다. DevOps 뿐 아니라 플랫폼 엔지니어링 자체가 지향하는 바가, 결국 프로덕트 조직 또는 소프트웨어 엔지니어링 조직이 더 빠르게 제품을 만들고 Deliver 할 수 있도록 지원하는 것이기 때문에, 실제 제품 출시까지의 시간이 얼마나 단축되었는지가 KPI로 적절하다는 내용이었습니다.&lt;/span&gt;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;span style=&quot;font-family: 'Noto Sans Demilight', 'Noto Sans KR';&quot;&gt;이와 함께 Platform Engineering의 성과 설정은, Platform Engineering을 도입하기 전에 해야한다는 의견도 있었습니다. 도입 전에 먼저, &quot;Platform Engineering을 왜 도입해야하는가?&quot;를 먼저 생각해보고, 여기서 나온 포인트를 가지고 기준을 정하라는 뜻입니다. 예를 들어, Business의 Ideation 단계부터 Launching까지의 Journey 중 몇 퍼센트의 시간을 줄일 수 있을까? 를 각 과정별로 체크하여 비효율성을 개선할 수도 있겠고, 지난 번 장애에서 Platform Engineering을 도입하여 해결할 수 있었던게 있었을까? 와 같이 현재의 어떤 문제를 해결할 수 있을까? 를 먼저 고민해보자는 내용이었습니다. 이렇게 고민이 먼저 이뤄진다면, 이걸 바탕으로 어떤 지표를 측정해야할지는 자연스럽게 정해지니까요.&lt;/span&gt;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;h2 data-ke-size=&quot;size26&quot;&gt;&lt;span style=&quot;font-family: 'Noto Sans Demilight', 'Noto Sans KR';&quot;&gt;&lt;b&gt;후기&lt;/b&gt;&lt;/span&gt;&lt;/h2&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;span style=&quot;font-family: 'Noto Sans Demilight', 'Noto Sans KR';&quot;&gt;첫 모임이어서 그런지 거의 100분 가까이 많은 분들이 찾아오셨습니다. 플랫폼 엔지니어링에 관한 뜨거운 관심을 확인할 수 있었고, &lt;/span&gt;&lt;span style=&quot;font-family: 'Noto Sans Demilight', 'Noto Sans KR';&quot;&gt;현재 재직 중인 회사나, 또 앞으로 커리어패스 상에서의 도전 과정에서 꼭 한 번 Platform Engineering 분야에서 임팩트를 만들어보고싶다는 생각이 들었습니다!&lt;/span&gt;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;span style=&quot;font-family: 'Noto Sans Demilight', 'Noto Sans KR';&quot;&gt;다음 1월 모임이 정말 기대되는 정말 만족스러운 모임이었습니다 :)&lt;/span&gt;&lt;/p&gt;</description>
      <category>컨퍼런스 &amp;amp; 밋업 참여 후기</category>
      <author>HAENU</author>
      <guid isPermaLink="true">https://dokdo2013.tistory.com/14</guid>
      <comments>https://dokdo2013.tistory.com/14#entry14comment</comments>
      <pubDate>Wed, 22 Nov 2023 01:55:11 +0900</pubDate>
    </item>
    <item>
      <title>해커톤에서 10분에 한 번 배포하기 (해커톤을 위한 DevOps)</title>
      <link>https://dokdo2013.tistory.com/13</link>
      <description>&lt;p&gt;&lt;figure class=&quot;imageblock alignCenter&quot; data-ke-mobileStyle=&quot;widthOrigin&quot; data-filename=&quot;U10_Form_Cover-1.png&quot; data-origin-width=&quot;2460&quot; data-origin-height=&quot;630&quot;&gt;&lt;span data-url=&quot;https://blog.kakaocdn.net/dn/V98Dy/btswcIG9k9a/zESATgeQsaEbnKZ5xKIOu0/img.png&quot; data-phocus=&quot;https://blog.kakaocdn.net/dn/V98Dy/btswcIG9k9a/zESATgeQsaEbnKZ5xKIOu0/img.png&quot; data-alt=&quot;Unithon 10th&quot;&gt;&lt;img src=&quot;https://blog.kakaocdn.net/dn/V98Dy/btswcIG9k9a/zESATgeQsaEbnKZ5xKIOu0/img.png&quot; srcset=&quot;https://img1.daumcdn.net/thumb/R1280x0/?scode=mtistory2&amp;fname=https%3A%2F%2Fblog.kakaocdn.net%2Fdn%2FV98Dy%2FbtswcIG9k9a%2FzESATgeQsaEbnKZ5xKIOu0%2Fimg.png&quot; onerror=&quot;this.onerror=null; this.src='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png'; this.srcset='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png';&quot; loading=&quot;lazy&quot; width=&quot;2460&quot; height=&quot;630&quot; data-filename=&quot;U10_Form_Cover-1.png&quot; data-origin-width=&quot;2460&quot; data-origin-height=&quot;630&quot;/&gt;&lt;/span&gt;&lt;figcaption&gt;Unithon 10th&lt;/figcaption&gt;
&lt;/figure&gt;
&lt;/p&gt;
&lt;p style=&quot;text-align: justify;&quot; data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p style=&quot;text-align: justify;&quot; data-ke-size=&quot;size16&quot;&gt;생애 첫 해커톤으로 Unit에서 운영하는 10번째 Unithon에 참여했습니다. 비록 상은 받지 못했지만, 그 뒤에서 치열하게 수많은 피쳐를 만들어내고 안정적인 개발 프로세스를 만들어내기 위해 노력했던 이야기를 풀어보려 합니다.&lt;/p&gt;
&lt;p style=&quot;text-align: justify;&quot; data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p style=&quot;text-align: justify;&quot; data-ke-size=&quot;size16&quot;&gt;&lt;b&gt;DevOps 공개 레포지토리&lt;/b&gt; 보러 가기 : &lt;a href=&quot;https://github.com/Unithon-10th-team8/devops&quot; target=&quot;_blank&quot; rel=&quot;noopener&quot;&gt;https://github.com/Unithon-10th-team8/devops&lt;/a&gt;&lt;/p&gt;
&lt;figure id=&quot;og_1695974957771&quot; contenteditable=&quot;false&quot; data-ke-type=&quot;opengraph&quot; data-ke-align=&quot;alignCenter&quot; data-og-type=&quot;object&quot; data-og-title=&quot;GitHub - Unithon-10th-team8/devops: gitops &amp;amp; infrastructure repository&quot; data-og-description=&quot;gitops &amp;amp; infrastructure repository. Contribute to Unithon-10th-team8/devops development by creating an account on GitHub.&quot; data-og-host=&quot;github.com&quot; data-og-source-url=&quot;https://github.com/Unithon-10th-team8/devops&quot; data-og-url=&quot;https://github.com/Unithon-10th-team8/devops&quot; data-og-image=&quot;https://scrap.kakaocdn.net/dn/Yo1QR/hyT2t8a7Ub/IxikveTI58x2FS1JepX31K/img.png?width=1200&amp;amp;height=600&amp;amp;face=0_0_1200_600&quot;&gt;&lt;a href=&quot;https://github.com/Unithon-10th-team8/devops&quot; target=&quot;_blank&quot; rel=&quot;noopener&quot; data-source-url=&quot;https://github.com/Unithon-10th-team8/devops&quot;&gt;
&lt;div class=&quot;og-image&quot; style=&quot;background-image: url('https://scrap.kakaocdn.net/dn/Yo1QR/hyT2t8a7Ub/IxikveTI58x2FS1JepX31K/img.png?width=1200&amp;amp;height=600&amp;amp;face=0_0_1200_600');&quot;&gt;&amp;nbsp;&lt;/div&gt;
&lt;div class=&quot;og-text&quot;&gt;
&lt;p class=&quot;og-title&quot; data-ke-size=&quot;size16&quot;&gt;GitHub - Unithon-10th-team8/devops: gitops &amp;amp; infrastructure repository&lt;/p&gt;
&lt;p class=&quot;og-desc&quot; data-ke-size=&quot;size16&quot;&gt;gitops &amp;amp; infrastructure repository. Contribute to Unithon-10th-team8/devops development by creating an account on GitHub.&lt;/p&gt;
&lt;p class=&quot;og-host&quot; data-ke-size=&quot;size16&quot;&gt;github.com&lt;/p&gt;
&lt;/div&gt;
&lt;/a&gt;&lt;/figure&gt;
&lt;p style=&quot;text-align: justify;&quot; data-ke-size=&quot;size16&quot;&gt;&lt;b&gt;10th 유니톤&lt;/b&gt;에 대해 더 알아보고 싶다면 : &lt;span&gt;&lt;/span&gt;&lt;a href=&quot;https://bit.ly/U10_Dashboard&quot;&gt;https://bit.ly/U10_Dashboard&lt;/a&gt;&lt;/p&gt;
&lt;figure id=&quot;og_1695998347247&quot; contenteditable=&quot;false&quot; data-ke-type=&quot;opengraph&quot; data-ke-align=&quot;alignCenter&quot; data-og-type=&quot;article&quot; data-og-title=&quot;UNITHON 10TH Dashboard&quot; data-og-description=&quot;IT community United Hackathon, UNITHON 유니톤은 IT 커뮤니티 구성원들의 즐거운 성장과 교류를 목표로 하는 해커톤입니다.&quot; data-og-host=&quot;unit-center.notion.site&quot; data-og-source-url=&quot;https://bit.ly/U10_Dashboard&quot; data-og-url=&quot;https://unit-center.notion.site/56853d1d444f4112819bdb76786a5b26&quot; data-og-image=&quot;&quot;&gt;&lt;a href=&quot;https://bit.ly/U10_Dashboard&quot; target=&quot;_blank&quot; rel=&quot;noopener&quot; data-source-url=&quot;https://bit.ly/U10_Dashboard&quot;&gt;
&lt;div class=&quot;og-image&quot; style=&quot;background-image: url();&quot;&gt;&amp;nbsp;&lt;/div&gt;
&lt;div class=&quot;og-text&quot;&gt;
&lt;p class=&quot;og-title&quot; data-ke-size=&quot;size16&quot;&gt;UNITHON 10TH Dashboard&lt;/p&gt;
&lt;p class=&quot;og-desc&quot; data-ke-size=&quot;size16&quot;&gt;IT community United Hackathon, UNITHON 유니톤은 IT 커뮤니티 구성원들의 즐거운 성장과 교류를 목표로 하는 해커톤입니다.&lt;/p&gt;
&lt;p class=&quot;og-host&quot; data-ke-size=&quot;size16&quot;&gt;unit-center.notion.site&lt;/p&gt;
&lt;/div&gt;
&lt;/a&gt;&lt;/figure&gt;
&lt;p style=&quot;text-align: justify;&quot; data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;h3 style=&quot;text-align: justify;&quot; data-ke-size=&quot;size23&quot;&gt;&lt;b&gt;해커톤을 위한 DevOps&lt;/b&gt;&lt;/h3&gt;
&lt;h4 style=&quot;text-align: justify;&quot; data-ke-size=&quot;size20&quot;&gt;&lt;b&gt;왜 DevOps인가?&lt;/b&gt;&lt;/h4&gt;
&lt;blockquote data-ke-style=&quot;style1&quot;&gt;&lt;span style=&quot;font-family: 'Noto Serif KR';&quot;&gt;해커톤은 그냥 로컬에서만 돌아가도 문제 없는거 아니야? 배포는 그냥 시간 나면 EC2에 간단하게 올리면 되는거고.&lt;/span&gt;&lt;/blockquote&gt;
&lt;p style=&quot;text-align: justify;&quot; data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p style=&quot;text-align: justify;&quot; data-ke-size=&quot;size16&quot;&gt;물론 아예 틀린 말은 아니라고 생각합니다. 그냥 로컬에서만 돌아가도 문제 없죠. 시연할 때도 그냥 보여지기만 하면 되니까요. 그런데 DevOps를 채택해서 실제로 적용해보면, 그냥 로컬에서만 돌아가는 것과 엄청난 간극이 존재한다는 걸 알 수 있습니다. 굳이 이런 것까지 필요할까? 생각이 들지만, 생각보다 DevOps가 Seamless하게 처리해주는 부분을 개발자 직접 수행할 때 발생하는 Context Switching과 이로 인한 비용이 상당하다는 걸 알 수 있습니다.&lt;/p&gt;
&lt;p style=&quot;text-align: justify;&quot; data-ke-size=&quot;size16&quot;&gt;이번 해커톤 후기에서는 해커톤에 DevOps를 도입함으로 얻을 수 있었던 효과와 내부의 상세한 아키텍쳐까지 살펴보겠습니다.&lt;/p&gt;
&lt;h4 style=&quot;text-align: justify;&quot; data-ke-size=&quot;size20&quot;&gt;&lt;b&gt;DevOps 도입을 통한 성과 1 - CI/CD 파이프라인 (&lt;u&gt;24시간동안 133번의 배포)&lt;/u&gt;&lt;/b&gt;&lt;/h4&gt;
&lt;p style=&quot;text-align: justify;&quot; data-ke-size=&quot;size16&quot;&gt;2박 3일 해커톤이었지만, 기획과 디자인이 나온 뒤에 기능개발 착수에 들어간 건 2번째 날 부터였습니다. 그래서 실제 개발을 진행한 24시간 가량동안 총 133번의 배포를 진행했습니다. (GitHub Actions CI 동작 횟수 기준)&lt;/p&gt;
&lt;p&gt;&lt;figure class=&quot;imageblock alignCenter&quot; data-ke-mobileStyle=&quot;widthOrigin&quot; data-filename=&quot;스크린샷 2023-09-29 16.52.52.png&quot; data-origin-width=&quot;3336&quot; data-origin-height=&quot;1892&quot;&gt;&lt;span data-url=&quot;https://blog.kakaocdn.net/dn/ExZfz/btsv5mTn9GF/l6kJzK6JlYNqFXz8PlmpgK/img.png&quot; data-phocus=&quot;https://blog.kakaocdn.net/dn/ExZfz/btsv5mTn9GF/l6kJzK6JlYNqFXz8PlmpgK/img.png&quot; data-alt=&quot;ArgoCD 배포 파이프라인&quot;&gt;&lt;img src=&quot;https://blog.kakaocdn.net/dn/ExZfz/btsv5mTn9GF/l6kJzK6JlYNqFXz8PlmpgK/img.png&quot; srcset=&quot;https://img1.daumcdn.net/thumb/R1280x0/?scode=mtistory2&amp;fname=https%3A%2F%2Fblog.kakaocdn.net%2Fdn%2FExZfz%2Fbtsv5mTn9GF%2Fl6kJzK6JlYNqFXz8PlmpgK%2Fimg.png&quot; onerror=&quot;this.onerror=null; this.src='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png'; this.srcset='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png';&quot; loading=&quot;lazy&quot; width=&quot;3336&quot; height=&quot;1892&quot; data-filename=&quot;스크린샷 2023-09-29 16.52.52.png&quot; data-origin-width=&quot;3336&quot; data-origin-height=&quot;1892&quot;/&gt;&lt;/span&gt;&lt;figcaption&gt;ArgoCD 배포 파이프라인&lt;/figcaption&gt;
&lt;/figure&gt;
&lt;/p&gt;
&lt;p style=&quot;text-align: justify;&quot; data-ke-size=&quot;size16&quot;&gt;해커톤에서 과하다고 느껴질 수 있지만 사실 CI/CD 파이프라인만 잘 구축되어 있으면 배포라는 가장 큰 고민 하나가 줄어듭니다. 그리고 &lt;b&gt;배포가 두렵지 않은 팀&lt;/b&gt;이 되면, 그 때부터 개발 속도가 월등히 빨라집니다. 이건 비단 해커톤에만 적용되는 내용이 아니고, 실무에서도 100% 동일하게 적용되는 내용입니다. 웹 기반 서비스인데, 실서버 배포를 2주~1달에 한 번씩 날짜를 미리 정해서 공지를 올리고 해야한다? 배포 과정이 수동으로 이뤄지고 혹시나 문제가 발생하여 롤백했을 때 그 프로세스가 확립되어 있지 않고, 이런 환경들 때문에 배포 자체가 두려운 팀이다? 이런 환경에서 어떻게 개발자가 개발에만 전념하고 좋은 제품을 만들기 위한 고민을 같이 할 수 있을까요?&lt;/p&gt;
&lt;p style=&quot;text-align: justify;&quot; data-ke-size=&quot;size16&quot;&gt;&lt;b&gt;개발 환경에 적용된 사항이 빠르고 매끄럽게 서버에 배포되는 것&lt;/b&gt;, 여기서부터 &lt;b&gt;각 펑션별로 유기적인 업무 flow&lt;/b&gt;가 자연스럽게 만들어집니다. 백엔드에서 API 엔드포인트를 하나 만들 때마다 바로 서버에 배포되고, 프론트는 완성된 API를 바로바로 가져다 쓸 수 있습니다. 문제가 생기면 바로 노티줄 수 있고 실시간 피드백이 가능해집니다. 프론트에서 페이지가 완성되면 역시 곧바로 서버에 배포되고, 기획/디자인 리뷰와 함께 QA도 곧바로 이뤄집니다. 이런 빠른 피드백이 왔다갔다 할수록 진정한 '애자일'이 되는거고, 밀도 있고 완성도 있는 서비스 개발이 가능해집니다.&lt;/p&gt;
&lt;h4 style=&quot;text-align: justify;&quot; data-ke-size=&quot;size20&quot;&gt;&lt;b&gt;DevOps 도입을 통한 성과 2 - 로그 모니터링&lt;/b&gt;&lt;/h4&gt;
&lt;p style=&quot;text-align: justify;&quot; data-ke-size=&quot;size16&quot;&gt;프론트엔드/백엔드 개발자 전체에게 Grafana 접근 권한을 부여했습니다. Grafana를 통해 실제 서버에 기록된 로그를 개발자가 직접 확인할 수 있어 버그와 문제 상황을 빠르게 확인할 수 있었습니다. 만일 로컬 환경에서만 개발하다 마지막에 서버에 올렸다면, 그 때부터 버그를 발견하고 트러블슈팅하는 과정을 거쳐야하니 비교적 시간이 오래 걸릴 수밖에 없습니다.&lt;/p&gt;
&lt;p style=&quot;text-align: justify;&quot; data-ke-size=&quot;size16&quot;&gt;이걸 각 피쳐 개발이 끝날 때마다 서버에 올리고 이상 로그가 있는지 확인하면 동작의 불확실성을 줄여줄 뿐 아니라, 각 개발자가 서버 환경에서 동작하는 앱을 컨트롤할 수 있다는 점에서 심리적 편안함을 주고, 이를 통해 전반적인 개발시간 자체를 줄일 수 있게 됩니다.&lt;/p&gt;
&lt;p&gt;&lt;figure class=&quot;imageblock alignCenter&quot; data-ke-mobileStyle=&quot;widthOrigin&quot; data-filename=&quot;스크린샷 2023-09-29 23.23.01.png&quot; data-origin-width=&quot;2414&quot; data-origin-height=&quot;912&quot;&gt;&lt;span data-url=&quot;https://blog.kakaocdn.net/dn/REkQG/btsv9JNwAVi/vTkAzr0mmNikZKSWlWWSq0/img.png&quot; data-phocus=&quot;https://blog.kakaocdn.net/dn/REkQG/btsv9JNwAVi/vTkAzr0mmNikZKSWlWWSq0/img.png&quot; data-alt=&quot;예시 에러 로그 - Backend ORM 로그에서 Foreign Key Constraints 에러 내용을 알려주고 있다&quot;&gt;&lt;img src=&quot;https://blog.kakaocdn.net/dn/REkQG/btsv9JNwAVi/vTkAzr0mmNikZKSWlWWSq0/img.png&quot; srcset=&quot;https://img1.daumcdn.net/thumb/R1280x0/?scode=mtistory2&amp;fname=https%3A%2F%2Fblog.kakaocdn.net%2Fdn%2FREkQG%2Fbtsv9JNwAVi%2FvTkAzr0mmNikZKSWlWWSq0%2Fimg.png&quot; onerror=&quot;this.onerror=null; this.src='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png'; this.srcset='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png';&quot; loading=&quot;lazy&quot; width=&quot;2414&quot; height=&quot;912&quot; data-filename=&quot;스크린샷 2023-09-29 23.23.01.png&quot; data-origin-width=&quot;2414&quot; data-origin-height=&quot;912&quot;/&gt;&lt;/span&gt;&lt;figcaption&gt;예시 에러 로그 - Backend ORM 로그에서 Foreign Key Constraints 에러 내용을 알려주고 있다&lt;/figcaption&gt;
&lt;/figure&gt;
&lt;/p&gt;
&lt;h4 style=&quot;text-align: justify;&quot; data-ke-size=&quot;size20&quot;&gt;&lt;b&gt;DevOps 도입을 통한 성과 3 - Post 해커톤&lt;/b&gt;&lt;/h4&gt;
&lt;p style=&quot;text-align: justify;&quot; data-ke-size=&quot;size16&quot;&gt;해커톤이 끝나면 해커톤 당시 만든 서비스는 어떻게 될까요? 만일 배포까지 완료하지 못한 팀이라면 서비스는 코드 상태로만 남아있을 것이고, 설사 배포를 하더라도 EC2 인스턴스의 관리를 지속적으로 해주지 않는다면 서비스가 지속적으로 운영되지 못할 것입니다. 또 나중에 다시 배포를 하려고 해도, 배포 프로세스가 획일화되어 있지 않고 일일이 사람의 손을 타야한다면, 그 당시 환경 그대로 배포할 수도 없을 것입니다. &lt;b&gt;해커톤이 끝나면 서비스가 내려갈 수 있다는 것&lt;/b&gt;. 그게 해커톤 팀들이 가지는 어려움이라고 생각했습니다. 나중에 개인 포트폴리오로 활용하고, 다른 사람들에게 소개도 하고 하려면 계속 남아있어야하는데 말이죠.&lt;/p&gt;
&lt;p style=&quot;text-align: justify;&quot; data-ke-size=&quot;size16&quot;&gt;이걸 IaC를 통해서 해결했습니다. 팀에서 배포한 모든 앱과 인프라 구성은 코드로 작성되어 깃허브를 통해 모두에게 공유되어 있습니다. 인프라 프로비저닝을 위한 Terraform 파일, 각 코드를 빌드/배포하기 위한 Dockerfile, Docker 이미지, GitHub Actions 배포 yaml 파일, 쿠버네티스에 배포되는 매니페스트 파일이 모두 공개되어 있습니다. 어떤 환경에서든 동일한 형태로 명령어 한 줄이면 배포할 수 있는 것입니다. 따라서 해커톤이 끝나더라도 의지만 있다면 서비스를 계속 유지할 수 있고, 서버에 올리는 작업도 정말 간단하게 수행할 수 있습니다.&lt;/p&gt;
&lt;h4 style=&quot;text-align: justify;&quot; data-ke-size=&quot;size20&quot;&gt;&lt;b&gt;DevOps의 존재 이유 - 개발자가 오로지 개발에만 집중할 수 있도록&lt;/b&gt;&lt;/h4&gt;
&lt;p style=&quot;text-align: justify;&quot; data-ke-size=&quot;size16&quot;&gt;DevOps는 전체 제품 개발 파이프라인에서 각 조직이 유기적으로 동작할 수 있도록 윤활유 역할을 해줍니다. 개발자가 만든 변경사항을 빠르게 배포해서 확인해볼 수 있도록 하고, 운영 조직이 그 내용을 바탕으로 필요한 작업을 수행할 수 있도록 지원합니다. 개발자 관점으로 좁혀보면, 개발자가 오로지 개발에만 집중할 수 있도록 개발 이외의 모든 것을 지원합니다.&lt;/p&gt;
&lt;p style=&quot;text-align: justify;&quot; data-ke-size=&quot;size16&quot;&gt;인프라 프로비저닝, CI/CD 파이프라인 구축, 도메인 연결, DB서버 세팅, 로그 확인 / 배포 상태 노티, 환경변수 관리, 팀 내 보안 요소 점검 등... 만일 개발자가 해야했다면 정말 귀찮고 번거로운 작업들이겠지만, 이걸 다 해줍니다. 그게 DevOps가 존재하는 이유니까요.&lt;/p&gt;
&lt;h4 style=&quot;text-align: justify;&quot; data-ke-size=&quot;size20&quot;&gt;&lt;b&gt;다양한 툴 지원&lt;/b&gt;&lt;/h4&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;이렇게 DevOps가 개발자들의 짐을 덜어준 덕분에, &lt;b&gt;개발자들은 부담없이 코드에만 집중&lt;/b&gt;할 수 있게 되었습니다. 지금껏 사용해본 적 없는 &lt;b&gt;새로운 기술이더라도 부담없이 도입&lt;/b&gt;해보고, 함께 협업하며 &lt;b&gt;진정한 성장&lt;/b&gt;을 이뤄낼 수 있었습니다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;아래 기술 스택 이미지와 같이 프론트엔드와 백엔드 팀원들이 정말 다양한 최신 기술스택을 사용했고, 해당 기술을 안정적인 시스템 위에서 동작할 수 있게 지원했습니다. 많은 분들이 어려움을 겪는 Next.js 앱 배포도 standalone 모드와 Cloudflare CDN의 조합으로 안정적인 서빙을 가능하게 했고, 백엔드도 다양한 요구사항을 인프라단 제약사항 없이 모두 지원했습니다.&lt;/p&gt;
&lt;p&gt;&lt;figure class=&quot;imageblock alignCenter&quot; data-ke-mobileStyle=&quot;widthOrigin&quot; data-filename=&quot;tech-stacks.001.jpeg&quot; data-origin-width=&quot;1920&quot; data-origin-height=&quot;1080&quot;&gt;&lt;span data-url=&quot;https://blog.kakaocdn.net/dn/dmHF44/btswgVzx9GC/O5s8HCkAzrxhvR73oH2yB1/img.jpg&quot; data-phocus=&quot;https://blog.kakaocdn.net/dn/dmHF44/btswgVzx9GC/O5s8HCkAzrxhvR73oH2yB1/img.jpg&quot;&gt;&lt;img src=&quot;https://blog.kakaocdn.net/dn/dmHF44/btswgVzx9GC/O5s8HCkAzrxhvR73oH2yB1/img.jpg&quot; srcset=&quot;https://img1.daumcdn.net/thumb/R1280x0/?scode=mtistory2&amp;fname=https%3A%2F%2Fblog.kakaocdn.net%2Fdn%2FdmHF44%2FbtswgVzx9GC%2FO5s8HCkAzrxhvR73oH2yB1%2Fimg.jpg&quot; onerror=&quot;this.onerror=null; this.src='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png'; this.srcset='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png';&quot; loading=&quot;lazy&quot; width=&quot;1920&quot; height=&quot;1080&quot; data-filename=&quot;tech-stacks.001.jpeg&quot; data-origin-width=&quot;1920&quot; data-origin-height=&quot;1080&quot;/&gt;&lt;/span&gt;&lt;/figure&gt;
&lt;/p&gt;
&lt;p style=&quot;text-align: justify;&quot; data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;h3 style=&quot;text-align: justify;&quot; data-ke-size=&quot;size23&quot;&gt;&lt;b&gt;세부 DevOps 시스템 구축하기&lt;/b&gt;&lt;/h3&gt;
&lt;h4 style=&quot;text-align: justify;&quot; data-ke-size=&quot;size20&quot;&gt;&lt;b&gt;Kubernetes 환경&lt;/b&gt;&lt;/h4&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;모든 앱 배포가 쿠버네티스 위에서 구동됩니다. Kubernetes는 Container Orchestration 툴로 컨테이너 환경에서 동작하는 앱들을 안정적으로 구동할 수 있도록 지원합니다. 프론트와 백엔드 앱들은 모두 컨테이너 환경에서 동작할 수 있도록 Dockerize하고, 이미지로 빌드해 배포하는 파이프라인이 세팅되었습니다. 이렇게 동작할 수 있도록 메인 클라우드 프로바이더를 선정했습니다.&lt;/p&gt;
&lt;h4 style=&quot;text-align: justify;&quot; data-ke-size=&quot;size20&quot;&gt;&lt;b&gt;클라우드 프로바이더 선정하기 - Vultr&lt;/b&gt;&lt;/h4&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;AWS같은 다른 선택지도 있었지만 Vultr를 선택한 이유는 '익숙함'과 '비용' 때문이었습니다. 짧은 기간이니 AWS를 써도 비용 차이가 엄청 크진 않았겠지만, 이정도 규모에서 Vultr를 써도 사실 충분하긴 해서 채택했습니다. Vultr와 Vultr Kubernetes Engine에 대해 자세히 알아보고 싶다면 아래 포스트를 읽어보세요!&lt;/p&gt;
&lt;figure id=&quot;og_1695982632846&quot; contenteditable=&quot;false&quot; data-ke-type=&quot;opengraph&quot; data-ke-align=&quot;alignCenter&quot; data-og-type=&quot;article&quot; data-og-title=&quot;Vultr Kubernetes Engine 사용기&quot; data-og-description=&quot;정말 저렴한 가격으로 관리형 쿠버네티스 엔진을 사용할 수 있는 Vultr Kubernetes Engine을 소개한다. Vultr란? https://www.vultr.com/ SSD VPS Servers, Cloud Servers and Cloud Hosting Vultr Global Cloud Hosting - Brilliantly Fast &quot; data-og-host=&quot;blog.haenu.com&quot; data-og-source-url=&quot;https://blog.haenu.com/12&quot; data-og-url=&quot;https://blog.haenu.com/12&quot; data-og-image=&quot;https://scrap.kakaocdn.net/dn/cCPqcp/hyT2sVKnyP/40fuQA9ICZHtNq9xUA2Mq0/img.png?width=800&amp;amp;height=800&amp;amp;face=0_0_800_800,https://scrap.kakaocdn.net/dn/cPr2e7/hyT2z8pd5U/1Yr2Z4KcSj2uCM9CoaU18k/img.png?width=800&amp;amp;height=800&amp;amp;face=0_0_800_800,https://scrap.kakaocdn.net/dn/bmYPKh/hyT2wDTUiT/ck7jfwGBzhDhGEnA0ymg0K/img.png?width=2564&amp;amp;height=1814&amp;amp;face=0_0_2564_1814&quot;&gt;&lt;a href=&quot;https://blog.haenu.com/12&quot; target=&quot;_blank&quot; rel=&quot;noopener&quot; data-source-url=&quot;https://blog.haenu.com/12&quot;&gt;
&lt;div class=&quot;og-image&quot; style=&quot;background-image: url('https://scrap.kakaocdn.net/dn/cCPqcp/hyT2sVKnyP/40fuQA9ICZHtNq9xUA2Mq0/img.png?width=800&amp;amp;height=800&amp;amp;face=0_0_800_800,https://scrap.kakaocdn.net/dn/cPr2e7/hyT2z8pd5U/1Yr2Z4KcSj2uCM9CoaU18k/img.png?width=800&amp;amp;height=800&amp;amp;face=0_0_800_800,https://scrap.kakaocdn.net/dn/bmYPKh/hyT2wDTUiT/ck7jfwGBzhDhGEnA0ymg0K/img.png?width=2564&amp;amp;height=1814&amp;amp;face=0_0_2564_1814');&quot;&gt;&amp;nbsp;&lt;/div&gt;
&lt;div class=&quot;og-text&quot;&gt;
&lt;p class=&quot;og-title&quot; data-ke-size=&quot;size16&quot;&gt;Vultr Kubernetes Engine 사용기&lt;/p&gt;
&lt;p class=&quot;og-desc&quot; data-ke-size=&quot;size16&quot;&gt;정말 저렴한 가격으로 관리형 쿠버네티스 엔진을 사용할 수 있는 Vultr Kubernetes Engine을 소개한다. Vultr란? https://www.vultr.com/ SSD VPS Servers, Cloud Servers and Cloud Hosting Vultr Global Cloud Hosting - Brilliantly Fast&lt;/p&gt;
&lt;p class=&quot;og-host&quot; data-ke-size=&quot;size16&quot;&gt;blog.haenu.com&lt;/p&gt;
&lt;/div&gt;
&lt;/a&gt;&lt;/figure&gt;
&lt;h4 style=&quot;text-align: justify;&quot; data-ke-size=&quot;size20&quot;&gt;&lt;b&gt;인프라 프로비저닝 - Terraform&lt;/b&gt;&lt;/h4&gt;
&lt;p style=&quot;text-align: justify;&quot; data-ke-size=&quot;size16&quot;&gt;이번에 Vultr Provider로 Terraform은 처음 사용해봤습니다. 아무래도 VPS 서비스 특성상 테라폼 지원은 제한적일 수밖에 없는데 Vultr는 그래도 지원 자체는 되어서 이번에 해봤습니다. 간단하게 만들 수 있어서 아래와 같이 설정했습니다. AWS는 Cluster Autoscaler가 ASG와 연동되는데 여기는 VKE 자체에서 컨트롤할 수 있어서 구조 자체는 좀 더 심플해보였습니다.&amp;nbsp;&lt;/p&gt;
&lt;pre id=&quot;code_1695983191491&quot; class=&quot;typescript&quot; data-ke-language=&quot;typescript&quot; data-ke-type=&quot;codeblock&quot;&gt;&lt;code&gt;resource &quot;vultr_kubernetes&quot; &quot;k8&quot; {
  region  = &quot;icn&quot;
  label   = &quot;unithon-cluster&quot;
  version = &quot;v1.27.2+1&quot;

  node_pools {
    label         = &quot;unithon-main-node&quot;
    node_quantity = 3
    plan          = &quot;vc2-2c-4gb&quot;
    auto_scaler   = true
    min_nodes     = 3
    max_nodes     = 5
  }
}&lt;/code&gt;&lt;/pre&gt;
&lt;h4 style=&quot;text-align: justify;&quot; data-ke-size=&quot;size20&quot;&gt;&lt;b&gt;각종 내부 애플리케이션 세팅하기 - Helm&lt;/b&gt;&lt;/h4&gt;
&lt;p&gt;&lt;figure class=&quot;imageblock alignCenter&quot; data-ke-mobileStyle=&quot;widthOrigin&quot; data-filename=&quot;스크린샷 2023-09-29 19.28.22.png&quot; data-origin-width=&quot;541&quot; data-origin-height=&quot;629&quot;&gt;&lt;span data-url=&quot;https://blog.kakaocdn.net/dn/nge0R/btsv5jPKIGN/IZVPBFMLkVYSSfkcyc2P00/img.png&quot; data-phocus=&quot;https://blog.kakaocdn.net/dn/nge0R/btsv5jPKIGN/IZVPBFMLkVYSSfkcyc2P00/img.png&quot; data-alt=&quot;세팅한 내부 애플리케이션&quot;&gt;&lt;img src=&quot;https://blog.kakaocdn.net/dn/nge0R/btsv5jPKIGN/IZVPBFMLkVYSSfkcyc2P00/img.png&quot; srcset=&quot;https://img1.daumcdn.net/thumb/R1280x0/?scode=mtistory2&amp;fname=https%3A%2F%2Fblog.kakaocdn.net%2Fdn%2Fnge0R%2Fbtsv5jPKIGN%2FIZVPBFMLkVYSSfkcyc2P00%2Fimg.png&quot; onerror=&quot;this.onerror=null; this.src='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png'; this.srcset='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png';&quot; loading=&quot;lazy&quot; width=&quot;462&quot; height=&quot;537&quot; data-filename=&quot;스크린샷 2023-09-29 19.28.22.png&quot; data-origin-width=&quot;541&quot; data-origin-height=&quot;629&quot;/&gt;&lt;/span&gt;&lt;figcaption&gt;세팅한 내부 애플리케이션&lt;/figcaption&gt;
&lt;/figure&gt;
&lt;/p&gt;
&lt;p style=&quot;text-align: justify;&quot; data-ke-size=&quot;size16&quot;&gt;쿠버네티스 클러스터 내부에서 동작해야하는 기본 애플리케이션들은 Helm으로 설치했습니다. 배포를 위한 ArgoCD, 인증서를 위한 Cert Manager, 파드배치와 스케줄링을 위한 descheduler, 메트릭 수집과 시각화를 위한 kube-prometheus-stack (prometheus, grafana), 로그 수집을 위한 loki-stack (Loki, promtail), 내부 메트릭 수집을 위한 metrics-server, Ingress를 위한 Nginx Ingress Controller, 백엔드 DB서버 postgresql, 환경변수 관리 및 주입을 위한 Sealed Secrets가 Helm을 통해 설치되었습니다.&lt;/p&gt;
&lt;h4 style=&quot;text-align: justify;&quot; data-ke-size=&quot;size20&quot;&gt;&lt;b&gt;CI/CD 파이프라인 구축하기 - GitHub Actions &amp;amp; ArgoCD &amp;amp; DockerHub&lt;/b&gt;&lt;/h4&gt;
&lt;p style=&quot;text-align: justify;&quot; data-ke-size=&quot;size16&quot;&gt;CI/CD 파이프라인은 GitHub Actions와 ArgoCD를 통해 구축했습니다. 쿠버네티스 환경에서 동작하기 때문에 GitHub Actions는 Dockerfile을 바탕으로 컨테이너 이미지로 빌드합니다. 빌드가 완료되면 이미지를 DockerHub에 올리고, devops 레포지토리에 이미지 태그를 변경합니다. 그러면 ArgoCD가 변경된 이미지 태그로 새롭게 파드를 배포합니다. 일반적으로 GitOps라고 불리는 패턴을 그대로 사용했습니다.&lt;/p&gt;
&lt;p style=&quot;text-align: justify;&quot; data-ke-size=&quot;size16&quot;&gt;GitHub Actions에 들어갈 Secret은 GitHub Organization 전체 공통 시크릿으로 만들어 조직 내 어느 레포에서든 가져다 쓸 수 있도록 했고, Argo는 후술할 Cloudflare Access와의 연계를 위해 자체 Auth 기능을 해제했습니다. Docker Hub는 원래 Pro를 사용하고 있어 private으로 레포를 만들 수도 있었으나, public으로 만드는게 클러스터 내에 private registry 인증을 위한 secret을 넣지 않아도 돼 그냥 public으로 만들었습니다.&lt;/p&gt;
&lt;h4 style=&quot;text-align: justify;&quot; data-ke-size=&quot;size20&quot;&gt;&lt;b&gt;모니터링 시스템 - Prometheus &amp;amp; Grafana &amp;amp; Loki&lt;/b&gt;&lt;/h4&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;kube-prometheus-stack Helm Chart를 이용해 Prometheus와 Grafana를 설정했고, loki-stack Helm Chart를 이용해 Loki와 Promtail을 설정했습니다. 로그와 메트릭을 Grafana 한 곳에서 모아서 볼 수 있기 때문에 전체 시스템의 가시성을 확보하기 용이하고, 이걸 활용하면 개발자들이 로그도 편하게 볼 수 있습니다. Grafana에서 로그를 보는 방법은 매우 간단하기 때문에 한 번만 알려줘도 다들 쉽게 사용하는 모습을 볼 수 있었습니다.&lt;/p&gt;
&lt;h4 style=&quot;text-align: justify;&quot; data-ke-size=&quot;size20&quot;&gt;&lt;b&gt;환경변수 관리 - Sealed Secrets&lt;/b&gt;&lt;/h4&gt;
&lt;p style=&quot;text-align: justify;&quot; data-ke-size=&quot;size16&quot;&gt;환경변수 관리는 Sealed Secrets를 이용했습니다. Sealed Secrets는 Bitnami에서 시크릿 관리를 위해 내놓은 툴로, GitOps에서 민감정보가 쉽게 노출된다는 문제점을 해결하기 위해, 공개 키로 Secret을 암호화하고 이를 Git에 포함시킨 뒤, 실제 클러스터 내에서는 이를 다시 복호화하여 쿠버네티스 Secret으로 만들어주는 방식을 사용합니다. 설명이 다소 복잡하지만, 암호화된 Secret은 외부에 공개되어도 상관 없고, 이를 복호화하기 위해서는 클러스터 내에 있는 인증서가 반드시 필요하다고 보면 됩니다.&lt;/p&gt;
&lt;p style=&quot;text-align: justify;&quot; data-ke-size=&quot;size16&quot;&gt;클러스터 내부 접근 권한은 DevOps만 보유하고 있으므로 모든 환경변수 관리는 DevOps가 수행해야 합니다. 직접 환경변수를 받아서 Sealed Secrets 매니페스트로 만들고, 최종적으로 GitOps 레포에 포함시키는 것까지 직접 수행해야 하죠. Vault와 같은 툴을 사용하는 방법도 있겠으나, 안정적으로 Vault를 운영하기 위해서는 다양한 부가 기술들이 필요한 만큼 이번에는 시도해보지 못했습니다.&lt;/p&gt;
&lt;p&gt;&lt;figure class=&quot;imageblock alignCenter&quot; data-ke-mobileStyle=&quot;widthOrigin&quot; data-filename=&quot;스크린샷 2023-09-29 23.19.13.png&quot; data-origin-width=&quot;833&quot; data-origin-height=&quot;102&quot;&gt;&lt;span data-url=&quot;https://blog.kakaocdn.net/dn/x8TXd/btswpX4Slor/rkEzMIZJKvdNOtkT5FKlhK/img.png&quot; data-phocus=&quot;https://blog.kakaocdn.net/dn/x8TXd/btswpX4Slor/rkEzMIZJKvdNOtkT5FKlhK/img.png&quot; data-alt=&quot;Argo에서 볼 수 있는 Secret 주입 과정&quot;&gt;&lt;img src=&quot;https://blog.kakaocdn.net/dn/x8TXd/btswpX4Slor/rkEzMIZJKvdNOtkT5FKlhK/img.png&quot; srcset=&quot;https://img1.daumcdn.net/thumb/R1280x0/?scode=mtistory2&amp;fname=https%3A%2F%2Fblog.kakaocdn.net%2Fdn%2Fx8TXd%2FbtswpX4Slor%2FrkEzMIZJKvdNOtkT5FKlhK%2Fimg.png&quot; onerror=&quot;this.onerror=null; this.src='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png'; this.srcset='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png';&quot; loading=&quot;lazy&quot; width=&quot;645&quot; height=&quot;79&quot; data-filename=&quot;스크린샷 2023-09-29 23.19.13.png&quot; data-origin-width=&quot;833&quot; data-origin-height=&quot;102&quot;/&gt;&lt;/span&gt;&lt;figcaption&gt;Argo에서 볼 수 있는 Secret 주입 과정&lt;/figcaption&gt;
&lt;/figure&gt;
&lt;/p&gt;
&lt;h4 style=&quot;text-align: justify;&quot; data-ke-size=&quot;size20&quot;&gt;&lt;b&gt;내부 접근제어 - Cloudflare Access&lt;/b&gt;&lt;/h4&gt;
&lt;p style=&quot;text-align: justify;&quot; data-ke-size=&quot;size16&quot;&gt;보통 Argo나 Grafana 등 내부 툴에 접근을 허용하기 위해서는 OAuth나 ID/PW 기반 인증 방식을 많이 사용합니다. 실제로 각 제품별로 인증을 처리하기 위한 모듈이 내부에 포함되어 있고, Helm values를 통해 인증에 관한 설정을 디테일하게 만질 수 있습니다. 하지만 이 경우 OAuth 인증에 필요한 Client ID와 Secret 등이 Git 레포에 포함될 수밖에 없는 문제가 있습니다. 이를 해결하려면 외부에서 Secret 영역은 주입을 해주거나 하는 프로세스가 필요한데, 구현하기 위한 방법이 꽤 까다롭습니다.&lt;/p&gt;
&lt;p&gt;&lt;figure class=&quot;imageblock alignCenter&quot; data-ke-mobileStyle=&quot;widthOrigin&quot; data-filename=&quot;edited_스크린샷 2023-09-29 16.52.15.png&quot; data-origin-width=&quot;2268&quot; data-origin-height=&quot;1886&quot;&gt;&lt;span data-url=&quot;https://blog.kakaocdn.net/dn/dSo2i1/btsv8zRUlYH/sc3KRtYrqJrwsybQqHKufK/img.png&quot; data-phocus=&quot;https://blog.kakaocdn.net/dn/dSo2i1/btsv8zRUlYH/sc3KRtYrqJrwsybQqHKufK/img.png&quot; data-alt=&quot;Cloudflare Access를 이용한 접근 제어&quot;&gt;&lt;img src=&quot;https://blog.kakaocdn.net/dn/dSo2i1/btsv8zRUlYH/sc3KRtYrqJrwsybQqHKufK/img.png&quot; srcset=&quot;https://img1.daumcdn.net/thumb/R1280x0/?scode=mtistory2&amp;fname=https%3A%2F%2Fblog.kakaocdn.net%2Fdn%2FdSo2i1%2Fbtsv8zRUlYH%2Fsc3KRtYrqJrwsybQqHKufK%2Fimg.png&quot; onerror=&quot;this.onerror=null; this.src='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png'; this.srcset='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png';&quot; loading=&quot;lazy&quot; width=&quot;817&quot; height=&quot;679&quot; data-filename=&quot;edited_스크린샷 2023-09-29 16.52.15.png&quot; data-origin-width=&quot;2268&quot; data-origin-height=&quot;1886&quot;/&gt;&lt;/span&gt;&lt;figcaption&gt;Cloudflare Access를 이용한 접근 제어&lt;/figcaption&gt;
&lt;/figure&gt;
&lt;/p&gt;
&lt;p style=&quot;text-align: justify;&quot; data-ke-size=&quot;size16&quot;&gt;따라서 정말 간단하게 Cloudflare Access를 앞에 씌워서 인증 처리를 거치고, 인증에 통과되면 뒤에 있는 Argo나 Grafana는 내부 인증 없이 이용할 수 있도록 했습니다. 이 때 내부 인증 없이 모든 기능을 이용할 수 있기 때문에 유저별 권한 제어나 감사 로그 등이 제대로 동작하지 않는데요, 이 부분은 '해커톤이니까'라는 단어로 넘겼습니다.&lt;/p&gt;
&lt;p style=&quot;text-align: justify;&quot; data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;h3 style=&quot;text-align: justify;&quot; data-ke-size=&quot;size23&quot;&gt;&lt;b&gt;한 걸음 더 나아가서...&lt;/b&gt;&lt;/h3&gt;
&lt;h4 style=&quot;text-align: justify;&quot; data-ke-size=&quot;size20&quot;&gt;&lt;b&gt;Cloudflare Access에서 특정 Path 제외하고 인증 걸기&lt;/b&gt;&lt;/h4&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;이거는 나중에 새로운 글로 포스팅을 쓰는게 나을 것 같아서 여기서는 간단히만 적고 넘어가겠습니다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;Cloudflare Access는 기본적으로 하나의 Application에서 특정 도메인과 path에 대한 Allow/Deny Rule을 함께 적용할 수 없습니다. 이 때문에 만일 도메인 전체에 차단을 걸지만, 특정 path에 대해서만 allow를 열고 싶다면 별도의 Bypass Application을 만드는 방식으로 대응할 수 있습니다. (아래 이미지처럼 설정해줘야 합니다)&lt;/p&gt;
&lt;p&gt;&lt;figure class=&quot;imageblock alignCenter&quot; data-ke-mobileStyle=&quot;widthOrigin&quot; data-filename=&quot;스크린샷 2023-09-29 23.07.19.png&quot; data-origin-width=&quot;1270&quot; data-origin-height=&quot;653&quot;&gt;&lt;span data-url=&quot;https://blog.kakaocdn.net/dn/J2MmQ/btsv8bjlt8M/VKWyVjfL2F4G0OEeSjHPt0/img.png&quot; data-phocus=&quot;https://blog.kakaocdn.net/dn/J2MmQ/btsv8bjlt8M/VKWyVjfL2F4G0OEeSjHPt0/img.png&quot; data-alt=&quot;기본 Application Rule&quot;&gt;&lt;img src=&quot;https://blog.kakaocdn.net/dn/J2MmQ/btsv8bjlt8M/VKWyVjfL2F4G0OEeSjHPt0/img.png&quot; srcset=&quot;https://img1.daumcdn.net/thumb/R1280x0/?scode=mtistory2&amp;fname=https%3A%2F%2Fblog.kakaocdn.net%2Fdn%2FJ2MmQ%2Fbtsv8bjlt8M%2FVKWyVjfL2F4G0OEeSjHPt0%2Fimg.png&quot; onerror=&quot;this.onerror=null; this.src='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png'; this.srcset='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png';&quot; loading=&quot;lazy&quot; width=&quot;1270&quot; height=&quot;653&quot; data-filename=&quot;스크린샷 2023-09-29 23.07.19.png&quot; data-origin-width=&quot;1270&quot; data-origin-height=&quot;653&quot;/&gt;&lt;/span&gt;&lt;figcaption&gt;기본 Application Rule&lt;/figcaption&gt;
&lt;/figure&gt;
&lt;figure class=&quot;imageblock alignCenter&quot; data-ke-mobileStyle=&quot;widthOrigin&quot; data-filename=&quot;스크린샷 2023-09-29 23.07.56.png&quot; data-origin-width=&quot;1119&quot; data-origin-height=&quot;491&quot;&gt;&lt;span data-url=&quot;https://blog.kakaocdn.net/dn/oLocF/btsv8h4Wtn6/riVoq2gtkfw93pLUFcv6i0/img.png&quot; data-phocus=&quot;https://blog.kakaocdn.net/dn/oLocF/btsv8h4Wtn6/riVoq2gtkfw93pLUFcv6i0/img.png&quot; data-alt=&quot;Bypass Rule&quot;&gt;&lt;img src=&quot;https://blog.kakaocdn.net/dn/oLocF/btsv8h4Wtn6/riVoq2gtkfw93pLUFcv6i0/img.png&quot; srcset=&quot;https://img1.daumcdn.net/thumb/R1280x0/?scode=mtistory2&amp;fname=https%3A%2F%2Fblog.kakaocdn.net%2Fdn%2FoLocF%2Fbtsv8h4Wtn6%2FriVoq2gtkfw93pLUFcv6i0%2Fimg.png&quot; onerror=&quot;this.onerror=null; this.src='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png'; this.srcset='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png';&quot; loading=&quot;lazy&quot; width=&quot;1119&quot; height=&quot;491&quot; data-filename=&quot;스크린샷 2023-09-29 23.07.56.png&quot; data-origin-width=&quot;1119&quot; data-origin-height=&quot;491&quot;/&gt;&lt;/span&gt;&lt;figcaption&gt;Bypass Rule&lt;/figcaption&gt;
&lt;/figure&gt;
&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;여기에 실제 Bypass를 거는 부분은 Policy에서 설정해줘야 하니 해당 내용도 참고 바랍니다.&lt;/p&gt;
&lt;h4 style=&quot;text-align: justify;&quot; data-ke-size=&quot;size20&quot;&gt;&lt;b&gt;Database Sync를 위한 init container 만들기&lt;/b&gt;&lt;/h4&gt;
&lt;p style=&quot;text-align: justify;&quot; data-ke-size=&quot;size16&quot;&gt;백엔드 개발 중 파이썬 배포시 코드 상의 Schema와 실제 DB의 상태를 동기화하는 부분을 수행하도록 요청을 받았습니다. 이에 파드 &lt;a title=&quot;initContainer&quot; href=&quot;https://kubernetes.io/docs/concepts/workloads/pods/init-containers/&quot; target=&quot;_blank&quot; rel=&quot;noopener&quot;&gt;initContainer&lt;/a&gt; 기능을 이용해 먼저 파드를 띄워 migrate 기능을 쉘로 실행시켰습니다.&lt;/p&gt;
&lt;pre id=&quot;code_1695995653892&quot; class=&quot;typescript&quot; data-ke-language=&quot;typescript&quot; data-ke-type=&quot;codeblock&quot;&gt;&lt;code&gt;    spec:
      initContainers:
        - name: unithon-backend-main-init
          image: hyeonwoo5342/unithon-backend-main:7
          imagePullPolicy: Always
          command: [ 'sh', '-c', '/app/.venv/bin/python manage.py migrate' ]
          envFrom:
            - secretRef:
                name: unithon-backend-main-secret
          resources:
            requests:
              memory: '200Mi'
              cpu: '150m'
            limits:
              memory: '200Mi'
              cpu: '150m'&lt;/code&gt;&lt;/pre&gt;
&lt;p style=&quot;text-align: justify;&quot; data-ke-size=&quot;size16&quot;&gt;그러면 Alembic이 코드 상의 스키마와 실제 DB의 상태를 동일하게 맞춰줍니다. (아래는 로그)&lt;/p&gt;
&lt;pre id=&quot;code_1695995771236&quot; class=&quot;typescript&quot; data-ke-language=&quot;typescript&quot; data-ke-type=&quot;codeblock&quot;&gt;&lt;code&gt;INFO  [alembic.runtime.migration] Context impl PostgresqlImpl.
INFO  [alembic.runtime.migration] Will assume transactional DDL.&lt;/code&gt;&lt;/pre&gt;
&lt;p style=&quot;text-align: justify;&quot; data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;h3 style=&quot;text-align: justify;&quot; data-ke-size=&quot;size23&quot;&gt;&lt;b&gt;그래서, 해커톤 후기&lt;/b&gt;&lt;/h3&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;그래도 해커톤을 치뤘으니 후기는 간단하게 남기는게 좋을 것 같아서 남겨봅니다.&lt;/p&gt;
&lt;h4 style=&quot;text-align: justify;&quot; data-ke-size=&quot;size20&quot;&gt;&lt;b&gt;간단한 프로덕트 소개&lt;/b&gt;&lt;/h4&gt;
&lt;p style=&quot;text-align: justify;&quot; data-ke-size=&quot;size16&quot;&gt;해커톤에서 만든 프로덕트는 '영업사원을 위한 인맥 관리 서비스' KAMY(까미) 입니다.&lt;/p&gt;
&lt;p&gt;&lt;figure class=&quot;imageblock alignCenter&quot; data-ke-mobileStyle=&quot;widthOrigin&quot; data-filename=&quot;설명 (1).png&quot; data-origin-width=&quot;2000&quot; data-origin-height=&quot;1125&quot;&gt;&lt;span data-url=&quot;https://blog.kakaocdn.net/dn/ecIJi7/btswbzwP7MG/oPaNc9k6rfz0NtyBezfRiK/img.png&quot; data-phocus=&quot;https://blog.kakaocdn.net/dn/ecIJi7/btswbzwP7MG/oPaNc9k6rfz0NtyBezfRiK/img.png&quot;&gt;&lt;img src=&quot;https://blog.kakaocdn.net/dn/ecIJi7/btswbzwP7MG/oPaNc9k6rfz0NtyBezfRiK/img.png&quot; srcset=&quot;https://img1.daumcdn.net/thumb/R1280x0/?scode=mtistory2&amp;fname=https%3A%2F%2Fblog.kakaocdn.net%2Fdn%2FecIJi7%2FbtswbzwP7MG%2FoPaNc9k6rfz0NtyBezfRiK%2Fimg.png&quot; onerror=&quot;this.onerror=null; this.src='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png'; this.srcset='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png';&quot; loading=&quot;lazy&quot; width=&quot;2000&quot; height=&quot;1125&quot; data-filename=&quot;설명 (1).png&quot; data-origin-width=&quot;2000&quot; data-origin-height=&quot;1125&quot;/&gt;&lt;/span&gt;&lt;/figure&gt;
&lt;/p&gt;
&lt;p style=&quot;text-align: justify;&quot; data-ke-size=&quot;size16&quot;&gt;훌륭한 기획자, 디자이너, 그리고 프론트/백엔드 개발자분들과 함께 정말 열심히 만들었으니 관심 있는 분들은 &lt;a title=&quot;링크&quot; href=&quot;https://unit-center.notion.site/KAMY-Team8-8f66f99448794d8db1a764d9ececa145&quot; target=&quot;_blank&quot; rel=&quot;noopener&quot;&gt;링크&lt;/a&gt;를 참고해주세요!&lt;/p&gt;
&lt;p style=&quot;text-align: justify;&quot; data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p style=&quot;text-align: justify;&quot; data-ke-size=&quot;size16&quot;&gt;프로덕트 사용해보기 : &lt;a href=&quot;https://front.haenu.dev&quot;&gt;https://front.haenu.dev&lt;/a&gt;&amp;nbsp;&lt;/p&gt;
&lt;figure id=&quot;og_1695998557846&quot; contenteditable=&quot;false&quot; data-ke-type=&quot;opengraph&quot; data-ke-align=&quot;alignCenter&quot; data-og-type=&quot;website&quot; data-og-title=&quot;https://front.haenu.dev/signIn&quot; data-og-description=&quot;&quot; data-og-host=&quot;front.haenu.dev&quot; data-og-source-url=&quot;https://front.haenu.dev&quot; data-og-url=&quot;https://front.haenu.dev/signIn&quot; data-og-image=&quot;&quot;&gt;&lt;a href=&quot;https://front.haenu.dev&quot; target=&quot;_blank&quot; rel=&quot;noopener&quot; data-source-url=&quot;https://front.haenu.dev&quot;&gt;
&lt;div class=&quot;og-image&quot; style=&quot;background-image: url();&quot;&gt;&amp;nbsp;&lt;/div&gt;
&lt;div class=&quot;og-text&quot;&gt;
&lt;p class=&quot;og-title&quot; data-ke-size=&quot;size16&quot;&gt;https://front.haenu.dev/signIn&lt;/p&gt;
&lt;p class=&quot;og-desc&quot; data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p class=&quot;og-host&quot; data-ke-size=&quot;size16&quot;&gt;front.haenu.dev&lt;/p&gt;
&lt;/div&gt;
&lt;/a&gt;&lt;/figure&gt;
&lt;p style=&quot;text-align: justify;&quot; data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;h4 style=&quot;text-align: justify;&quot; data-ke-size=&quot;size20&quot;&gt;&lt;b&gt;결과는 아쉽지만 후회는 없어&lt;/b&gt;&lt;/h4&gt;
&lt;p style=&quot;text-align: justify;&quot; data-ke-size=&quot;size16&quot;&gt;해커톤 결과 자체는 아쉽지만 후회는 없습니다. 실망은 했지만, 상 받는게 목적은 아니었으니까요. 상 대신, 그보다 더 소중한 '&lt;b&gt;성장&lt;/b&gt;', 그리고 &lt;b&gt;비슷한 열정 수준을 가지고 있는 동료들&lt;/b&gt;을 만날 수 있었습니다.&lt;/p&gt;
&lt;p style=&quot;text-align: justify;&quot; data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p style=&quot;text-align: justify;&quot; data-ke-size=&quot;size16&quot;&gt;특히 BackEnd로 참여하긴 했지만, DevOps를 프로젝트에 직접적으로 적용해보며 제로 베이스부터 체계적으로 인프라를 구성해보는 값진 경험을 얻었고, 그 과정 속에서 실제 제품 개발 전체 과정에서 효율성을 극도로 끌어올릴 수 있었던 것 같아 확실한 성장과 즐거움을 느꼈습니다. 또 다른 팀원들과 함께 참여한 분들에게 DevOps의 세계를 보여줄 수 있어서도 기쁘게 생각합니다.&lt;/p&gt;
&lt;p style=&quot;text-align: justify;&quot; data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p style=&quot;text-align: justify;&quot; data-ke-size=&quot;size16&quot;&gt;생애 첫 해커톤, 열정 넘치는 그 무대 속에서 더 나은 개발 프로세스를 만들기 위해 노력한 그 순간을 기억하며, 이 글을 바탕으로 앞으로의 해커톤 세계에 DevOps를 접목하는 또 다른 케이스가 나오길 기대합니다!&lt;/p&gt;</description>
      <category>DevOps</category>
      <category>DevOps</category>
      <category>유니톤</category>
      <category>쿠버네티스</category>
      <category>해커톤</category>
      <author>HAENU</author>
      <guid isPermaLink="true">https://dokdo2013.tistory.com/13</guid>
      <comments>https://dokdo2013.tistory.com/13#entry13comment</comments>
      <pubDate>Sat, 30 Sep 2023 00:00:46 +0900</pubDate>
    </item>
    <item>
      <title>Vultr Kubernetes Engine 사용기</title>
      <link>https://dokdo2013.tistory.com/12</link>
      <description>&lt;p data-ke-size=&quot;size16&quot;&gt;정말 저렴한 가격으로 관리형 쿠버네티스 엔진을 사용할 수 있는 Vultr Kubernetes Engine을 소개한다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;h3 data-ke-size=&quot;size23&quot;&gt;Vultr란?&lt;/h3&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;a href=&quot;https://www.vultr.com/&quot; target=&quot;_blank&quot; rel=&quot;noopener&quot;&gt;https://www.vultr.com/&lt;/a&gt;&lt;/p&gt;
&lt;figure id=&quot;og_1690290787724&quot; contenteditable=&quot;false&quot; data-ke-type=&quot;opengraph&quot; data-ke-align=&quot;alignCenter&quot; data-og-type=&quot;website&quot; data-og-title=&quot;SSD VPS Servers, Cloud Servers and Cloud Hosting&quot; data-og-description=&quot;Vultr Global Cloud Hosting - Brilliantly Fast SSD VPS Cloud Servers. 100% KVM Virtualization&quot; data-og-host=&quot;www.vultr.com&quot; data-og-source-url=&quot;https://www.vultr.com/&quot; data-og-url=&quot;https://www.vultr.com/&quot; data-og-image=&quot;https://scrap.kakaocdn.net/dn/tEAzd/hyTqwLbmAy/Wt017k4KmsUUEFPbIpT2bK/img.png?width=800&amp;amp;height=420&amp;amp;face=0_0_800_420,https://scrap.kakaocdn.net/dn/WlSrN/hyTqxpMbRP/GAAjkZp66ijb6l6jsKLAjK/img.png?width=800&amp;amp;height=420&amp;amp;face=0_0_800_420&quot;&gt;&lt;a href=&quot;https://www.vultr.com/&quot; target=&quot;_blank&quot; rel=&quot;noopener&quot; data-source-url=&quot;https://www.vultr.com/&quot;&gt;
&lt;div class=&quot;og-image&quot; style=&quot;background-image: url('https://scrap.kakaocdn.net/dn/tEAzd/hyTqwLbmAy/Wt017k4KmsUUEFPbIpT2bK/img.png?width=800&amp;amp;height=420&amp;amp;face=0_0_800_420,https://scrap.kakaocdn.net/dn/WlSrN/hyTqxpMbRP/GAAjkZp66ijb6l6jsKLAjK/img.png?width=800&amp;amp;height=420&amp;amp;face=0_0_800_420');&quot;&gt;&amp;nbsp;&lt;/div&gt;
&lt;div class=&quot;og-text&quot;&gt;
&lt;p class=&quot;og-title&quot; data-ke-size=&quot;size16&quot;&gt;SSD VPS Servers, Cloud Servers and Cloud Hosting&lt;/p&gt;
&lt;p class=&quot;og-desc&quot; data-ke-size=&quot;size16&quot;&gt;Vultr Global Cloud Hosting - Brilliantly Fast SSD VPS Cloud Servers. 100% KVM Virtualization&lt;/p&gt;
&lt;p class=&quot;og-host&quot; data-ke-size=&quot;size16&quot;&gt;www.vultr.com&lt;/p&gt;
&lt;/div&gt;
&lt;/a&gt;&lt;/figure&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;Vultr는 흔히 VPS(Virtual Private Server) 서비스를 제공하는 업체로 많이 알려져있다. 기존에 Cafe24같은 호스팅 서비스에 비하여 단독 서버를 제공해서 다르게 불리는 걸로 알고 있다. EC2같은 서비스는 비슷하지만 지향점이 조금 다르고, AWS Lightsail이나 Linode, DigitalOcean 등 업체/서비스랑 비슷하다고 보면 된다. &lt;b&gt;저렴한 비용에 적절한 성능의 서버를 제공하고, 거기에 기본 Outbound Traffic을 넉넉히 잡아서 제공하는게 기본 컨셉&lt;/b&gt;이다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;기존에는 그냥 '넉넉하고 적당한 성능의 서비스가 필요하다면 고를 수 있는 선택지' 정도로 생각했는데, 여기에 관리형 쿠버네티스 서비스인 Vultr Kubernetes Engine(이하 VKE)이 있다고 하여 체험삼아 사용해봤다. 그런데 생각보다 비용도 합리적이고 서비스 자체도 만족스러워서 관련 사용기를 작성해보려 한다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;h3 data-ke-size=&quot;size23&quot;&gt;왜 관리형 쿠버네티스 서비스를 사용하는가?&lt;/h3&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;클라우드 3사에서 제공해주는 쿠버네티스 서비스들이 있다.&lt;/p&gt;
&lt;ul style=&quot;list-style-type: disc;&quot; data-ke-list-type=&quot;disc&quot;&gt;
&lt;li&gt;AWS의 EKS (Elastic Kubernetes Service)&lt;/li&gt;
&lt;li&gt;GCP의 GKE (Google Kubernetes Engine)&lt;/li&gt;
&lt;li&gt;Azure의 AKS (Azure Kubernetes Service)&lt;/li&gt;
&lt;/ul&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;업계에서도 이 3사에서 제공해주는 관리형 서비스를 많이 사용하고 있다. 자료도 많고 사용자도 많아서 문제가 생겼을 때 대응하기도 좋고, 여러모로 프로덕션 환경에 적용하기에 적합하다. 특히 마스터 노드를 클라우드에서 직접 관리해주기 때문에, 컨트롤 플레인을 엔지니어가 따로 세팅하고 관리해야하는 수고에서 벗어날 수 있다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;이런 이유로 쿠버네티스를 전문적으로 운영할 인력이 충분하지 않은 기업에서는 대부분 관리형 쿠버네티스 서비스를 사용하고, 큰 규모의 기업에서도 관리형 서비스를 채택하는 경우가 적지 않다. 다만 이미 전문인력이 있고, 내부적으로 정교하게 관리하고자 하는 경우에는 자체 구축하기도 한다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;이런 관리형 서비스도 중소규모 서비스나 사업체에서는 비용 문제에 직면한다. 일반적으로 마스터 노드에도 운영비용을 받기 때문에 통상 시간당 $0.1, 한 달에 약 10만원 가량의 비용이 들어간다. (GKE에서는 여기에 무료 구간을 둬서 클러스터 한 개 정도까지는 마스터 노드에 비용을 부과하지 않기 때문에 조금 낫긴 하다) 여기에 워커 노드 비용도 따로 들고, 로드 밸런서, 블록 스토리지, 네트워크 비용 등 여러 비용들이 추가로 들어간다. 결국 서비스 하나를 띄워도 최소 15~20만원은 생각하고 들어가야 한다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;h3 data-ke-size=&quot;size23&quot;&gt;VKE의 과금 구조&lt;/h3&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;이걸 생각하고 VKE의 과금 구조를 보면 정말 저렴하다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;일단 &lt;b&gt;마스터 노드 비용이 무료&lt;/b&gt;이다. 클러스터 n개까지 무료가 아니라, 몇 개를 만들어도 &lt;b&gt;무제한 무료&lt;/b&gt;이다. 여기서부터 게임이 끝났는데, 조금 더 들어가보면 가격 경쟁력이 괜찮게 느껴진다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;일반적인 용도로 쓰이는 Cloud Compute 티어의 서버는 아래와 같은 가격 구성을 보인다.&lt;/p&gt;
&lt;p&gt;&lt;figure class=&quot;imageblock alignCenter&quot; data-ke-mobileStyle=&quot;widthOrigin&quot; data-filename=&quot;스크린샷 2023-07-25 22.51.08.png&quot; data-origin-width=&quot;2564&quot; data-origin-height=&quot;1814&quot;&gt;&lt;span data-url=&quot;https://blog.kakaocdn.net/dn/bga8ov/btso09kIxu9/cgmKrg18nH3APsd2NyFOXK/img.png&quot; data-phocus=&quot;https://blog.kakaocdn.net/dn/bga8ov/btso09kIxu9/cgmKrg18nH3APsd2NyFOXK/img.png&quot; data-alt=&quot;'Cloud Compute' 티어 가격 구성&quot;&gt;&lt;img src=&quot;https://blog.kakaocdn.net/dn/bga8ov/btso09kIxu9/cgmKrg18nH3APsd2NyFOXK/img.png&quot; srcset=&quot;https://img1.daumcdn.net/thumb/R1280x0/?scode=mtistory2&amp;fname=https%3A%2F%2Fblog.kakaocdn.net%2Fdn%2Fbga8ov%2Fbtso09kIxu9%2FcgmKrg18nH3APsd2NyFOXK%2Fimg.png&quot; onerror=&quot;this.onerror=null; this.src='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png'; this.srcset='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png';&quot; loading=&quot;lazy&quot; width=&quot;2564&quot; height=&quot;1814&quot; data-filename=&quot;스크린샷 2023-07-25 22.51.08.png&quot; data-origin-width=&quot;2564&quot; data-origin-height=&quot;1814&quot;/&gt;&lt;/span&gt;&lt;figcaption&gt;'Cloud Compute' 티어 가격 구성&lt;/figcaption&gt;
&lt;/figure&gt;
&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;일반적으로 많이 사용하는 2 vCPU에 4GB 메모리 구성이 $20이다. 거기에 서버당 3TB의 트래픽 비용이 포함되어 있다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;아래는 같은 2코어 4GB 메모리를 사용하는 AWS EC2 t3.medium 인스턴스와 Vultr의 Cloud Compute 인스턴스를 비교한 표이다.&lt;/p&gt;
&lt;table style=&quot;border-collapse: collapse; width: 100%;&quot; border=&quot;1&quot; data-ke-align=&quot;alignLeft&quot; data-ke-style=&quot;style12&quot;&gt;
&lt;tbody&gt;
&lt;tr&gt;
&lt;td style=&quot;width: 33.3333%;&quot;&gt;[클라우드] 인스턴스 유형&lt;/td&gt;
&lt;td style=&quot;width: 33.3333%;&quot;&gt;&lt;b&gt;[AWS]&lt;/b&gt; t3.medium&lt;/td&gt;
&lt;td style=&quot;width: 33.3333%;&quot;&gt;&lt;b&gt;[Vultr]&lt;/b&gt; Cloud Compute 2C 4G Mem&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td style=&quot;width: 33.3333%;&quot;&gt;월 비용 (시간당 비용 * 720)&lt;/td&gt;
&lt;td style=&quot;width: 33.3333%;&quot;&gt;0.052 * 720 = &lt;b&gt;37.44&lt;/b&gt;&lt;/td&gt;
&lt;td style=&quot;width: 33.3333%;&quot;&gt;0.03 * 720 = &lt;b&gt;21.6&lt;/b&gt;&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td style=&quot;width: 33.3333%;&quot;&gt;Data Transfer 비용 (3TB 사용 가정)&lt;/td&gt;
&lt;td style=&quot;width: 33.3333%;&quot;&gt;0.126 * 3000 = &lt;b&gt;378&lt;/b&gt;&lt;/td&gt;
&lt;td style=&quot;width: 33.3333%;&quot;&gt;&lt;b&gt;0&lt;/b&gt; (포함)&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td style=&quot;width: 33.3333%;&quot;&gt;스토리지 비용 (80G)&lt;/td&gt;
&lt;td style=&quot;width: 33.3333%;&quot;&gt;gp3 0.0912 * 80 = &lt;b&gt;7.296&lt;/b&gt;&lt;/td&gt;
&lt;td style=&quot;width: 33.3333%;&quot;&gt;&lt;b&gt;0&lt;/b&gt; (포함)&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td style=&quot;width: 33.3333%;&quot;&gt;합계&lt;/td&gt;
&lt;td style=&quot;width: 33.3333%;&quot;&gt;&lt;b&gt;$ 422.736&lt;/b&gt;&lt;/td&gt;
&lt;td style=&quot;width: 33.3333%;&quot;&gt;&lt;b&gt;$ 21.6&lt;/b&gt;&lt;/td&gt;
&lt;/tr&gt;
&lt;/tbody&gt;
&lt;/table&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;가격 차이가 거의 20배 가까이 난다. 물론 Vultr에 최대한 유리한 구조로 항목이 구성되긴 했으나, 비용 자체가 차이가 많이 나긴 한다. 표를 통해 보면 Data Transfer 비용이 나오지 않는다는게 굉장한 장점이다. 여기에 인스턴스 비용 자체도 저렴한 편이다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;Vultr에서는 쿠버네티스의 로드밸런서 타입으로 쓸 수 있게 자체적인 로드밸런서를 제공한다. 이거는 개당 고정비용 &lt;b&gt;월 $10&lt;/b&gt;이다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;Block Storage도 제공하는데 HDD 타입은 월 기준 &lt;b&gt;40GB / $1&lt;/b&gt; 이고, SSD 타입은 &lt;b&gt;10GB / $1&lt;/b&gt; 이다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;h4 data-ke-size=&quot;size20&quot;&gt;VKE로 샘플 클러스터 비용 산출해보기&lt;/h4&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;해당 내용을 바탕으로 샘플 쿠버네티스 클러스터를 만든다고 가정해보자.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;먼저 워커 노드는 2코어 4GB 메모리 인스턴스로 4대를 띄워보자. 로드밸런서는 Ingress Controller용으로 하나를 띄우고, 내부에 PV로 사용할 Block Storage도 5개정도를 띄워보자.&lt;/p&gt;
&lt;ul style=&quot;list-style-type: disc;&quot; data-ke-list-type=&quot;disc&quot;&gt;
&lt;li&gt;마스터 노드 비용 - &lt;b&gt;$0&lt;/b&gt;&lt;/li&gt;
&lt;li&gt;워커 노드 비용 - $21.6 * 4 = &lt;b&gt;$86.4&lt;/b&gt;&lt;/li&gt;
&lt;li&gt;로드밸런서 비용 - $10 * 1 = &lt;b&gt;$10&lt;/b&gt;&lt;/li&gt;
&lt;li&gt;Block Storage 비용 - $1 * 5 = &lt;b&gt;$5&lt;/b&gt;&lt;/li&gt;
&lt;/ul&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;모두 합치면 &lt;b&gt;$101.4&lt;/b&gt; 이다! 소규모 서비스나 테스트 환경 구성에서 이정도 사양(합계 8코어 16GB 메모리)이면 대부분 차고 넘치는 수준이다. 정말 저렴하게 사용할 수 있다!&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;h3 data-ke-size=&quot;size23&quot;&gt;실제 사용하면서 경험한 특징&lt;/h3&gt;
&lt;h4 data-ke-size=&quot;size20&quot;&gt;Easy Configuration&lt;/h4&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;이전에 GKE를 띄워보면서 좀 곤란했던 점은 gcloud cli를 설치하고 인증하고 여러 과정을 거쳐야지만 클러스터 api를 이용할 수 있다는 점이었다. 당시 GCP를 처음 써봐서 많이 헤맨걸 수도 있으나, 초기 세팅하기에 많이 불편한 건 사실이었다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;그런데 VKE는 정말 간단하게 설정 방법을 제공한다. 바로 &lt;b&gt;Configuration File을 직접 웹에서 다운받을 수 있게&lt;/b&gt; 해준다.&lt;/p&gt;
&lt;p&gt;&lt;figure class=&quot;imageblock alignCenter&quot; data-ke-mobileStyle=&quot;widthOrigin&quot; data-filename=&quot;스크린샷 2023-07-25 23.20.59.png&quot; data-origin-width=&quot;2540&quot; data-origin-height=&quot;750&quot;&gt;&lt;span data-url=&quot;https://blog.kakaocdn.net/dn/pmYU4/btsoZopiUZU/YZHZRU2xZcq7vSngk74Ik1/img.png&quot; data-phocus=&quot;https://blog.kakaocdn.net/dn/pmYU4/btsoZopiUZU/YZHZRU2xZcq7vSngk74Ik1/img.png&quot; data-alt=&quot;VKE Dashboard&quot;&gt;&lt;img src=&quot;https://blog.kakaocdn.net/dn/pmYU4/btsoZopiUZU/YZHZRU2xZcq7vSngk74Ik1/img.png&quot; srcset=&quot;https://img1.daumcdn.net/thumb/R1280x0/?scode=mtistory2&amp;fname=https%3A%2F%2Fblog.kakaocdn.net%2Fdn%2FpmYU4%2FbtsoZopiUZU%2FYZHZRU2xZcq7vSngk74Ik1%2Fimg.png&quot; onerror=&quot;this.onerror=null; this.src='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png'; this.srcset='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png';&quot; loading=&quot;lazy&quot; width=&quot;2540&quot; height=&quot;750&quot; data-filename=&quot;스크린샷 2023-07-25 23.20.59.png&quot; data-origin-width=&quot;2540&quot; data-origin-height=&quot;750&quot;/&gt;&lt;/span&gt;&lt;figcaption&gt;VKE Dashboard&lt;/figcaption&gt;
&lt;/figure&gt;
&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;대시보드 화면에서 'Download Configuration' 버튼을 누르면 kubeconfig 파일이 다운받아진다. 그걸 그대로 kube api 호출에 사용할 수 있다! 보안상 취약점이 있을 수 있으나, 초기 세팅 면에서는 정말 쉽고 편하게 느껴진다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;h4 data-ke-size=&quot;size20&quot;&gt;로드밸런서의 제약 조건이 많다&lt;/h4&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;Vultr의 로드밸런서가 실제 사용해보면 자잘한 제약사항들이 많다. 일단 기본적으로 &lt;b&gt;L4 로드밸런서&lt;/b&gt;이다. 그래서 L7 기능을 제공해주지 않아 처음에 ALB같은 역할을 기대했다 실망하기도 했다. 다만 이거는 ingress controller를 쓰면 되는 것이니 문제될 부분은 아니다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;그런데 문제는 로드밸런서가 L4이지만 &lt;b&gt;UDP를 지원하지 않는다&lt;/b&gt;. AWS에서는 아무 걱정 없던 부분에서 갑자기 막히니 좀 당황스러웠다. 이건 Wireguard를 세팅하다가 알게 된건데, 왜 자꾸 연결이 안 되나 했는데, 프로토콜을 지원하지 않아서였다... 결국 VPN 세팅은 다른 방법으로 했다. (Pritunl VPN을 처음 써봤는데 만족스러웠다. 이건 추후 포스트 예정)&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;h4 data-ke-size=&quot;size20&quot;&gt;Block Storage에 제약 조건이 걸려있다 (최소 용량)&lt;/h4&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;Helm으로 이것저것 필요한 것들을 설치하는데 이상하게 PVC에서 에러를 뿜어냈다. 뭐지하고 살펴보니까 PV를 못 만들고 있었다. 그러다 문서를 보게 되었는데... &lt;b&gt;Vultr의 Block Storage에는 최소 용량이 정해져있다&lt;/b&gt;. SSD는 10GB, HDD는 40GB로 정해져있어서 8GB로 설정된 PVC가 에러를 내뿜은 것이다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;그래서 고개를 갸웃하며 10GB로 value를 바꿔서 실행시켰다. 그런데 여전히 안 된다. 뭐지... 하고 또 살펴보니 &lt;b&gt;한국(서울 리전)은 SSD Block Storage를 지원하지 않는다&lt;/b&gt;. (???)&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;결국 울며 겨자먹기로 40GB HDD Block Storage로 설정했다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p&gt;&lt;figure class=&quot;imageblock alignCenter&quot; data-ke-mobileStyle=&quot;widthOrigin&quot; data-filename=&quot;스크린샷 2023-07-25 23.29.12.png&quot; data-origin-width=&quot;470&quot; data-origin-height=&quot;612&quot;&gt;&lt;span data-url=&quot;https://blog.kakaocdn.net/dn/XBGAd/btsoZnqqnDn/1ObEAl8nk4WaylkqvcsjZ1/img.png&quot; data-phocus=&quot;https://blog.kakaocdn.net/dn/XBGAd/btsoZnqqnDn/1ObEAl8nk4WaylkqvcsjZ1/img.png&quot; data-alt=&quot;40Gi로 설정된 PV들 ...&quot;&gt;&lt;img src=&quot;https://blog.kakaocdn.net/dn/XBGAd/btsoZnqqnDn/1ObEAl8nk4WaylkqvcsjZ1/img.png&quot; srcset=&quot;https://img1.daumcdn.net/thumb/R1280x0/?scode=mtistory2&amp;fname=https%3A%2F%2Fblog.kakaocdn.net%2Fdn%2FXBGAd%2FbtsoZnqqnDn%2F1ObEAl8nk4WaylkqvcsjZ1%2Fimg.png&quot; onerror=&quot;this.onerror=null; this.src='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png'; this.srcset='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png';&quot; loading=&quot;lazy&quot; width=&quot;300&quot; height=&quot;391&quot; data-filename=&quot;스크린샷 2023-07-25 23.29.12.png&quot; data-origin-width=&quot;470&quot; data-origin-height=&quot;612&quot;/&gt;&lt;/span&gt;&lt;figcaption&gt;40Gi로 설정된 PV들 ...&lt;/figcaption&gt;
&lt;/figure&gt;
&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;h4 data-ke-size=&quot;size20&quot;&gt;처음 사용하면 Resource Limit에 쉽게 걸린다.&lt;/h4&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;제일 처음 Vultr 계정을 생성하면 인스턴스는 5대, 블록 스토리지 + 로드 밸런서를 합쳐서 최대 10개까지만 만들 수 있도록 되어 있다. 그래서 Resource Limit이 다소 적은 편인데, 이건 Billing 탭의 Limits 항목에 들어가면 추가를 요청할 수 있다.&lt;/p&gt;
&lt;p&gt;&lt;figure class=&quot;imageblock alignCenter&quot; data-ke-mobileStyle=&quot;widthOrigin&quot; data-filename=&quot;스크린샷 2023-07-25 23.31.21.png&quot; data-origin-width=&quot;1178&quot; data-origin-height=&quot;896&quot;&gt;&lt;span data-url=&quot;https://blog.kakaocdn.net/dn/bIUF6T/btsoZLYXsOz/MUGO5kJUr36eNW01wjbwt1/img.png&quot; data-phocus=&quot;https://blog.kakaocdn.net/dn/bIUF6T/btsoZLYXsOz/MUGO5kJUr36eNW01wjbwt1/img.png&quot; data-alt=&quot;내 계정의 Limits&quot;&gt;&lt;img src=&quot;https://blog.kakaocdn.net/dn/bIUF6T/btsoZLYXsOz/MUGO5kJUr36eNW01wjbwt1/img.png&quot; srcset=&quot;https://img1.daumcdn.net/thumb/R1280x0/?scode=mtistory2&amp;fname=https%3A%2F%2Fblog.kakaocdn.net%2Fdn%2FbIUF6T%2FbtsoZLYXsOz%2FMUGO5kJUr36eNW01wjbwt1%2Fimg.png&quot; onerror=&quot;this.onerror=null; this.src='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png'; this.srcset='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png';&quot; loading=&quot;lazy&quot; width=&quot;500&quot; height=&quot;380&quot; data-filename=&quot;스크린샷 2023-07-25 23.31.21.png&quot; data-origin-width=&quot;1178&quot; data-origin-height=&quot;896&quot;/&gt;&lt;/span&gt;&lt;figcaption&gt;내 계정의 Limits&lt;/figcaption&gt;
&lt;/figure&gt;
&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;Request Limit Increase 버튼을 누르면 계정 정보를 입력하고, 어떤 서비스를 돌리는지, 왜 Limit 상향이 필요한지, 얼마까지 상향을 요청하는지 적을 수 있는 칸이 나온다. 여기에 &lt;b&gt;영어로 작성해서&lt;/b&gt; 요청하면 된다. Vultr는 한국어로 고객 지원을 하지 않기 때문에... 무조건 영어로 작성해야만 처리된다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;Limit 관련 상향 요청은 미동부 기준 영업시간에만 이뤄지고, 따라서 주말 등에 상향이 필요하면 진행이 안 될 수 있다. 나의 경우는 처음에는 다소 까다롭게 어떤 용도인지 물어봤는데, 세부 서비스 링크까지 제공하면서 설명을 해야 한도를 풀어주었다. 그런데 첫 번째 이후부터는 그렇게 빡빡하게 한도 상향 심사는 하지 않는 것 같다. Service Quota로 목을 조르는 GCP 케이스(...)를 생각하다보면 Vultr의 Limit은 다소 널널하게 느껴지는 것 같다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;h3 data-ke-size=&quot;size23&quot;&gt;총평&lt;/h3&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;실제 사용한지는 아직 두 달이 채 되지 않았다. 그러나 현재까지의 경험은 '매우 만족'에 가깝다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;이전에 개인 쿠버네티스 클러스터로 EKS를 사용하다 비용 문제로 접었고, GKE 역시 비용 문제와 EKS와의 미묘한 차이로 인해 러닝커브를 극복하지 못하고 접었다. 그러나 VKE는 그 사이에 쿠버네티스 숙련도가 높아져서 그럴 수도 있겠으나, 정말 쉽게 다가갔고 쉽게 사용할 수 있었다. 그리고 저렴한 비용으로 다양한 시도를 할 수 있게 되었다! 덕분에 기존에 EC2 기반으로 운영하던 개인 프로젝트들을 모두 VKE 클러스터로 이전했다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;VKE는 크게 세 가지 케이스에 사용을 추천해보고 싶다.&lt;/p&gt;
&lt;ul style=&quot;list-style-type: disc;&quot; data-ke-list-type=&quot;disc&quot;&gt;
&lt;li&gt;&lt;b&gt;소규모 스타트업&lt;/b&gt; : 개발조직이 5인 이하의 소규모로 구성되어 있는데 쿠버네티스를 도입해야 한다면 VKE를 강력 추천한다. 비용 자체도 매우 저렴하고 관리 공수도 적게 들어서 초기에 빠르게 MVP를 만들고 스케일업 해나가는데 아주 좋다고 생각된다. 그리고 이쪽은 어차피 규모가 커지고 사용자가 많아지면 EKS같은 서비스로 옮겨갈 것이기 때문에 쿠버네티스 환경의 Best Practice대로만 설계하고 운영하면 이전도 그리 어렵지 않을 것이다.&lt;/li&gt;
&lt;li&gt;&lt;b&gt;개인 사이드 프로젝트 운영&lt;/b&gt; : 개인용으로 사이드 프로젝트를 만들어서 올리거나, 소규모지만 그래도 꾸준히 수요가 있는 서비스의 경우 VKE 사용을 추천한다. 이건 다른 대체재들이 많아서 강력 추천은 못 하겠지만, 개인 프로젝트를 올리면서 &quot;쿠버네티스도 경험해보고 싶다면&quot; VKE를 써보는게 어떨까? 입문 용도로는 제격일 것 같다.&lt;/li&gt;
&lt;li&gt;&lt;b&gt;관리형 쿠버네티스를 경험해보고 싶은 입문자&lt;/b&gt; : k3s나 미니큐브같은 서비스만 이용해보던 쿠버네티스 입문자들의 경우 EKS나 GKE같은 메이저 3사의 서비스를 사용하기엔 비용 부담이 많이 들 것이다. 그렇다면 중간중간에 조금 다른 부분들은 있지만 그래도 Managed 서비스인 VKE를 사용해보면 어떨까?&lt;/li&gt;
&lt;/ul&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;아주 만족하며 쓰고 있는 VKE 서비스. 다음 번에는 VKE 클러스터의 Managed Upgrade (v1.26 -&amp;gt; v1.27) 과정에서 겪은 재미난(?) 경험을 다뤄보겠다.&lt;/p&gt;</description>
      <category>DevOps</category>
      <category>aks</category>
      <category>EKS</category>
      <category>GKE</category>
      <category>VKE</category>
      <category>vultr</category>
      <category>쿠버네티스</category>
      <author>HAENU</author>
      <guid isPermaLink="true">https://dokdo2013.tistory.com/12</guid>
      <comments>https://dokdo2013.tistory.com/12#entry12comment</comments>
      <pubDate>Tue, 25 Jul 2023 23:50:39 +0900</pubDate>
    </item>
    <item>
      <title>Next.js에서 recoil-persist 사용하기</title>
      <link>https://dokdo2013.tistory.com/10</link>
      <description>&lt;h3 data-ke-size=&quot;size23&quot;&gt;&lt;b&gt;Recoil이란?&lt;/b&gt;&lt;/h3&gt;
&lt;figure id=&quot;og_1688723866393&quot; contenteditable=&quot;false&quot; data-ke-type=&quot;opengraph&quot; data-ke-align=&quot;alignCenter&quot; data-og-type=&quot;website&quot; data-og-title=&quot;Recoil&quot; data-og-description=&quot;A state management library for React.&quot; data-og-host=&quot;recoiljs.org&quot; data-og-source-url=&quot;https://recoiljs.org/ko/&quot; data-og-url=&quot;https://recoiljs.org/ko/&quot; data-og-image=&quot;https://scrap.kakaocdn.net/dn/hhd34/hyTfGTR3em/Dk1rhVKVAsI9xbc118h6X1/img.png?width=1420&amp;amp;height=646&amp;amp;face=0_0_1420_646,https://scrap.kakaocdn.net/dn/djVrc7/hyTfANcwMH/i7kBk2g6NAZ4K36rHqNarK/img.png?width=1420&amp;amp;height=646&amp;amp;face=0_0_1420_646&quot;&gt;&lt;a href=&quot;https://recoiljs.org/ko/&quot; target=&quot;_blank&quot; rel=&quot;noopener&quot; data-source-url=&quot;https://recoiljs.org/ko/&quot;&gt;
&lt;div class=&quot;og-image&quot; style=&quot;background-image: url('https://scrap.kakaocdn.net/dn/hhd34/hyTfGTR3em/Dk1rhVKVAsI9xbc118h6X1/img.png?width=1420&amp;amp;height=646&amp;amp;face=0_0_1420_646,https://scrap.kakaocdn.net/dn/djVrc7/hyTfANcwMH/i7kBk2g6NAZ4K36rHqNarK/img.png?width=1420&amp;amp;height=646&amp;amp;face=0_0_1420_646');&quot;&gt;&amp;nbsp;&lt;/div&gt;
&lt;div class=&quot;og-text&quot;&gt;
&lt;p class=&quot;og-title&quot; data-ke-size=&quot;size16&quot;&gt;Recoil&lt;/p&gt;
&lt;p class=&quot;og-desc&quot; data-ke-size=&quot;size16&quot;&gt;A state management library for React.&lt;/p&gt;
&lt;p class=&quot;og-host&quot; data-ke-size=&quot;size16&quot;&gt;recoiljs.org&lt;/p&gt;
&lt;/div&gt;
&lt;/a&gt;&lt;/figure&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;Recoil은 리액트를 위한 상태관리 라이브러리이다. NPM Trends를 살펴봤을 때 (2023.07 기준) 다른 라이브러리들에 비해 다운로드 수가 다소 낮은 수준이지만, 그럼에도 불구하고 편리한 사용성으로 적지 않은 관심을 받고 있는 라이브러리이다.&lt;/p&gt;
&lt;p&gt;&lt;figure class=&quot;imageblock alignCenter&quot; data-ke-mobileStyle=&quot;widthOrigin&quot; data-filename=&quot;스크린샷 2023-07-07 18.58.28.png&quot; data-origin-width=&quot;2642&quot; data-origin-height=&quot;1788&quot;&gt;&lt;span data-url=&quot;https://blog.kakaocdn.net/dn/dIaBw0/btsmOTyS3Wy/LCiORK4DK6aMnpAYn8wBAK/img.png&quot; data-phocus=&quot;https://blog.kakaocdn.net/dn/dIaBw0/btsmOTyS3Wy/LCiORK4DK6aMnpAYn8wBAK/img.png&quot; data-alt=&quot;https://npmtrends.com/mobx-vs-react-redux-vs-recoil-vs-zustand&quot;&gt;&lt;img src=&quot;https://blog.kakaocdn.net/dn/dIaBw0/btsmOTyS3Wy/LCiORK4DK6aMnpAYn8wBAK/img.png&quot; srcset=&quot;https://img1.daumcdn.net/thumb/R1280x0/?scode=mtistory2&amp;fname=https%3A%2F%2Fblog.kakaocdn.net%2Fdn%2FdIaBw0%2FbtsmOTyS3Wy%2FLCiORK4DK6aMnpAYn8wBAK%2Fimg.png&quot; onerror=&quot;this.onerror=null; this.src='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png'; this.srcset='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png';&quot; loading=&quot;lazy&quot; width=&quot;2642&quot; height=&quot;1788&quot; data-filename=&quot;스크린샷 2023-07-07 18.58.28.png&quot; data-origin-width=&quot;2642&quot; data-origin-height=&quot;1788&quot;/&gt;&lt;/span&gt;&lt;figcaption&gt;https://npmtrends.com/mobx-vs-react-redux-vs-recoil-vs-zustand&lt;/figcaption&gt;
&lt;/figure&gt;
&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;이번 글은 Recoil을 소개하는 글이 아니니 자세한 설명은 생략하겠다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;h3 data-ke-size=&quot;size23&quot;&gt;&lt;b&gt;Next.js와 Recoil&lt;/b&gt;&lt;/h3&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;Next.js와 Recoil 라이브러리 자체는 잘 어울리는 편이다. Magic의 영역이 다소 있지만 사용법 자체가 워낙 직관적이고, Next.js에서의 일반적인 개발 흐름에 잘 맞는다. SSR 환경에서도 잘 동작하고...&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;그런데 만약에 이 state를 클라이언트에 저장하고싶다면? 다시 말해 웹 스토리지에 저장하고 싶다면? 그 때는 &lt;a title=&quot;recoil-persist&quot; href=&quot;https://github.com/polemius/recoil-persist&quot; target=&quot;_blank&quot; rel=&quot;noopener&quot;&gt;recoil-persist&lt;/a&gt; 라이브러리를 사용하면 된다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;recoil-persist 라이브러리는 local storage 혹은 session storage에 데이터를 저장한다. 일단 최초에 로딩이 되면 지정한 스토리지에서 데이터를 불러온다. 그리고 불러온 데이터를 recoil 내의 state와 동기화시키고, 실제 상태를 읽고 쓰는 행위 자체는 recoil이 담당하게 된다. recoil-persist는 atom에 업데이트가 발생할 때마다 이를 storage에 넣어 동기화시킨다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;h3 data-ke-size=&quot;size23&quot;&gt;&lt;b&gt;SSR에서의 이슈&lt;/b&gt;&lt;/h3&gt;
&lt;h4 data-ke-size=&quot;size20&quot;&gt;&lt;b&gt;1) 서버에는 window.localStorage 객체가 없다&lt;/b&gt;&lt;/h4&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;바로 여기서 문제가 발생한다. SSR 구조에서 서버는 미리 렌더링할 데이터를 만들어서 정적으로 내려줘야하는데, 서버에는 웹 스토리지가 없으므로 오류가 발생한다. 이거는 localStorage를 일단 undefined로 선언해서, 서버에서 window.localStorage 객체를 로드하지 않도록 처리하면 된다.&lt;/p&gt;
&lt;pre id=&quot;code_1688726964068&quot; class=&quot;typescript&quot; data-ke-language=&quot;typescript&quot; data-ke-type=&quot;codeblock&quot;&gt;&lt;code&gt;import { recoilPersist } from &quot;recoil-persist&quot;;

const localStorage =
  typeof window !== &quot;undefined&quot; ? window.localStorage : undefined;

const { persistAtom } = recoilPersist({
  key: &quot;recoil-states&quot;,
  storage: localStorage,
});&lt;/code&gt;&lt;/pre&gt;
&lt;h4 data-ke-size=&quot;size20&quot;&gt;&lt;b&gt;2) State를 읽어올 때 발생하는 Hydration 이슈&lt;/b&gt;&lt;/h4&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;이제 각 컴포넌트에서 Recoil State를 불러오는 상황을 가정해보자. 일단 Recoil에서 제공해주는 훅으로 State를 불러올 것이다.&lt;/p&gt;
&lt;pre id=&quot;code_1688727180134&quot; class=&quot;typescript&quot; data-ke-language=&quot;typescript&quot; data-ke-type=&quot;codeblock&quot;&gt;&lt;code&gt;import { useRecoilState } from &quot;recoil&quot;;
import { ExampleState } from &quot;@/states&quot;;

function ExampleComponent() {
  const [exampleState, setExampleState] = useRecoilState(ExampleState);
  
  ...
  
}&lt;/code&gt;&lt;/pre&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;그렇게 실행해보면! Hydration 오류가 발생한다. 왜일까?&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;일단 서버에서는 localStorage에서 데이터를 불러오도록 설정했는데, localStorage가 일단 undefined로 들어갔으니까, atom에서 선언한 기본 값이 state로 들어갈꺼다. 그리고 클라이언트에서 Hydration을 실행할 때는 localStorage에서 실제 들어갈 값을 불러오게 된다. 이 때 기본 값과 실제 값이 달라지게 된다. 바로 여기서 Hydration 오류가 발생하는 것이다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;그렇다면 이를 해결하는 방법은? Hydration 자체를 하지 않도록, 다시 말해 서버에서 값을 가져오지 않도록 useEffect를 쓰면 된다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;이제 다시 코드를 조금 수정해보자.&lt;/p&gt;
&lt;pre id=&quot;code_1688727638019&quot; class=&quot;typescript&quot; data-ke-language=&quot;typescript&quot; data-ke-type=&quot;codeblock&quot;&gt;&lt;code&gt;import { useRecoilState } from &quot;recoil&quot;;
import { ExampleState } from &quot;@/states&quot;;
import { useState, useEffect } from &quot;react&quot;;

function ExampleComponent() {
  const [exampleState, setClientExampleState] = useState(&quot;&quot;);
  const [recoilExampleState, setExampleState] = useRecoilState(ExampleState);
  
  useEffect(() =&amp;gt; {
    setClientExampleState(recoilExampleState);
  }, [recoilExampleState]);
  
  // state를 변경할 때
  setExampleState(&quot;변경하고 싶은 값&quot;);
  
  ...
  
}&lt;/code&gt;&lt;/pre&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;일단 useState로 클라이언트에서 동작하는 state를 하나 만들었다. 그리고 마찬가지로 Recoil에서도 state를 불러온다. 근데 이름이 뭔가 다르다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;원래라면 [&lt;span style=&quot;color: #a6bc00;&quot;&gt;&lt;b&gt;exampleState&lt;/b&gt;&lt;/span&gt;, &lt;span style=&quot;color: #f89009;&quot;&gt;&lt;b&gt;setExampleState&lt;/b&gt;&lt;/span&gt;] 였는데, 아래와 같이 이름이 배치되었다.&lt;/p&gt;
&lt;hr contenteditable=&quot;false&quot; data-ke-type=&quot;horizontalRule&quot; data-ke-style=&quot;style6&quot; /&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;const [&lt;span style=&quot;color: #a6bc00;&quot;&gt;&lt;b&gt;exampleState&lt;/b&gt;&lt;/span&gt;, setClientExampleState] = useState(&quot;&quot;);&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;const [recoilExampleState, &lt;span style=&quot;color: #f89009;&quot;&gt;&lt;b&gt;setExampleState&lt;/b&gt;&lt;/span&gt;] = useRecoilState(exampleState);&lt;/p&gt;
&lt;hr contenteditable=&quot;false&quot; data-ke-type=&quot;horizontalRule&quot; data-ke-style=&quot;style6&quot; /&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;그리고 useEffect 구문을 보면, 색깔로 칠한 변수는 보이지 않는다.&lt;/p&gt;
&lt;hr contenteditable=&quot;false&quot; data-ke-type=&quot;horizontalRule&quot; data-ke-style=&quot;style6&quot; /&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;useEffect(() =&amp;gt; {&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp; setClientExampleState(recoilExampleState);&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;}, [recoilExampleState]);&lt;/p&gt;
&lt;hr contenteditable=&quot;false&quot; data-ke-type=&quot;horizontalRule&quot; data-ke-style=&quot;style6&quot; /&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;그렇다면 색깔로 칠한 변수는 언제 쓰냐? 초록색은 값을 불러올 때, 주황색은 값을 설정할 때 사용한다. 다시 말해 일반적인 state 사용할 때 쓰는 컨벤션과 동일하게 맞춘 것이다. 바로 아래 코드처럼 사용하면 된다.&lt;/p&gt;
&lt;hr contenteditable=&quot;false&quot; data-ke-type=&quot;horizontalRule&quot; data-ke-style=&quot;style6&quot; /&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;lt;div&amp;gt;{&lt;b&gt;&lt;span style=&quot;color: #a6bc00;&quot;&gt;exampleState&lt;/span&gt;&lt;/b&gt;}&amp;lt;/div&amp;gt;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;lt;button onClick={() =&amp;gt; { &lt;b&gt;&lt;span style=&quot;color: #f89009;&quot;&gt;setExampleState&lt;/span&gt;&lt;/b&gt;(&quot;눌렀다!&quot;); }}&amp;gt;눌러보세요&amp;lt;/button&amp;gt;&lt;/p&gt;
&lt;hr contenteditable=&quot;false&quot; data-ke-type=&quot;horizontalRule&quot; data-ke-style=&quot;style6&quot; /&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;구조를 다시 한 번 살펴보자.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;실제 화면에 표시되는 &lt;span style=&quot;color: #a6bc00;&quot;&gt;&lt;b&gt;exampleState&lt;/b&gt;&lt;/span&gt;는 useState로 선언되어 일단 기본으로 빈 값이 들어간다. 서버에서도 빈 값으로 렌더링하고, Hydration 과정에서도 빈 값이 유지된다. 그 이후에 useEffect가 실행되면서 Recoil State로부터 값을 불러오고, 불러온 값을 &lt;span style=&quot;color: #a6bc00;&quot;&gt;&lt;b&gt;exampleState&lt;/b&gt;&lt;/span&gt;에 넣어주게 된다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;만일 state 변경이 발생하면 &lt;b&gt;&lt;span style=&quot;color: #f89009;&quot;&gt;setExampleState&lt;/span&gt;&lt;/b&gt;를 호출하는데, &lt;b&gt;&lt;span style=&quot;color: #f89009;&quot;&gt;setExampleState&lt;/span&gt;&lt;/b&gt;는 Recoil의 상태를 변경하고, Recoil의 상태가 변경되면 useEffect가 실행되면서 화면에 표시되는 &lt;b&gt;&lt;span style=&quot;color: #a6bc00;&quot;&gt;exampleState&lt;/span&gt;&lt;/b&gt;도 변경되게 된다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;확실히 방식이 다소 복잡하다. 다만 기본적으로 상태 관리 툴들은 클라이언트 사이드에서의 상태관리를 염두에 뒀기 때문에 서버가 함께 돌아가는 환경에서는 복잡성이 높아질 수밖에 없는 것 같다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;h3 data-ke-size=&quot;size23&quot;&gt;&lt;b&gt;라이브러리 공식 문서에서 설명하는 SSR 대응법&lt;/b&gt;&lt;/h3&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;2023년 5월에 README에 SSR Section이 추가되었다. 위에서 사용한 방법은 문서 업데이트 이전에 작업했던거라 컴포넌트 내에서 직접 state들을 관리해줬는데, 내용을 보니 문서대로 Custom Hook을 만드는 방법도 좋아 보인다. 본인의 상황에 맞는 적절한 방법을 선택하면 될 것 같다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;a href=&quot;https://github.com/polemius/recoil-persist#server-side-rendering&quot; target=&quot;_blank&quot; rel=&quot;noopener&quot;&gt;https://github.com/polemius/recoil-persist#server-side-rendering&lt;/a&gt;&lt;/p&gt;
&lt;figure id=&quot;og_1688724502194&quot; contenteditable=&quot;false&quot; data-ke-type=&quot;opengraph&quot; data-ke-align=&quot;alignCenter&quot; data-og-type=&quot;object&quot; data-og-title=&quot;GitHub - polemius/recoil-persist: Package for recoil state manager to persist and rehydrate store&quot; data-og-description=&quot;Package for recoil state manager to persist and rehydrate store - GitHub - polemius/recoil-persist: Package for recoil state manager to persist and rehydrate store&quot; data-og-host=&quot;github.com&quot; data-og-source-url=&quot;https://github.com/polemius/recoil-persist#server-side-rendering&quot; data-og-url=&quot;https://github.com/polemius/recoil-persist&quot; data-og-image=&quot;https://scrap.kakaocdn.net/dn/cHzsSO/hyTfv6ctdF/rn2vXx4EJhzTlPqCjcSgBK/img.png?width=1200&amp;amp;height=600&amp;amp;face=0_0_1200_600&quot;&gt;&lt;a href=&quot;https://github.com/polemius/recoil-persist#server-side-rendering&quot; target=&quot;_blank&quot; rel=&quot;noopener&quot; data-source-url=&quot;https://github.com/polemius/recoil-persist#server-side-rendering&quot;&gt;
&lt;div class=&quot;og-image&quot; style=&quot;background-image: url('https://scrap.kakaocdn.net/dn/cHzsSO/hyTfv6ctdF/rn2vXx4EJhzTlPqCjcSgBK/img.png?width=1200&amp;amp;height=600&amp;amp;face=0_0_1200_600');&quot;&gt;&amp;nbsp;&lt;/div&gt;
&lt;div class=&quot;og-text&quot;&gt;
&lt;p class=&quot;og-title&quot; data-ke-size=&quot;size16&quot;&gt;GitHub - polemius/recoil-persist: Package for recoil state manager to persist and rehydrate store&lt;/p&gt;
&lt;p class=&quot;og-desc&quot; data-ke-size=&quot;size16&quot;&gt;Package for recoil state manager to persist and rehydrate store - GitHub - polemius/recoil-persist: Package for recoil state manager to persist and rehydrate store&lt;/p&gt;
&lt;p class=&quot;og-host&quot; data-ke-size=&quot;size16&quot;&gt;github.com&lt;/p&gt;
&lt;/div&gt;
&lt;/a&gt;&lt;/figure&gt;</description>
      <category>Frontend</category>
      <category>hydration</category>
      <category>Next.js</category>
      <category>RECOIL</category>
      <category>recoil-persist</category>
      <category>SSR</category>
      <author>HAENU</author>
      <guid isPermaLink="true">https://dokdo2013.tistory.com/10</guid>
      <comments>https://dokdo2013.tistory.com/10#entry10comment</comments>
      <pubDate>Tue, 11 Jul 2023 18:00:15 +0900</pubDate>
    </item>
    <item>
      <title>NestJS Devtools 사용법 및 간단 후기</title>
      <link>https://dokdo2013.tistory.com/9</link>
      <description>&lt;figure id=&quot;og_1688569858173&quot; contenteditable=&quot;false&quot; data-ke-type=&quot;opengraph&quot; data-ke-align=&quot;alignCenter&quot; data-og-type=&quot;website&quot; data-og-title=&quot;Documentation | NestJS - A progressive Node.js framework&quot; data-og-description=&quot;Nest is a framework for building efficient, scalable Node.js server-side applications. It uses progressive JavaScript, is built with TypeScript and combines elements of OOP (Object Oriented Programming), FP (Functional Programming), and FRP (Functional Rea&quot; data-og-host=&quot;docs.nestjs.com&quot; data-og-source-url=&quot;https://docs.nestjs.com/devtools/overview&quot; data-og-url=&quot;https://docs.nestjs.com&quot; data-og-image=&quot;https://scrap.kakaocdn.net/dn/bznd1k/hyTedSrYew/x5pA310JzePACRsyRcnNik/img.png?width=820&amp;amp;height=429&amp;amp;face=0_0_820_429&quot;&gt;&lt;a href=&quot;https://docs.nestjs.com/devtools/overview&quot; target=&quot;_blank&quot; rel=&quot;noopener&quot; data-source-url=&quot;https://docs.nestjs.com/devtools/overview&quot;&gt;
&lt;div class=&quot;og-image&quot; style=&quot;background-image: url('https://scrap.kakaocdn.net/dn/bznd1k/hyTedSrYew/x5pA310JzePACRsyRcnNik/img.png?width=820&amp;amp;height=429&amp;amp;face=0_0_820_429');&quot;&gt;&amp;nbsp;&lt;/div&gt;
&lt;div class=&quot;og-text&quot;&gt;
&lt;p class=&quot;og-title&quot; data-ke-size=&quot;size16&quot;&gt;Documentation | NestJS - A progressive Node.js framework&lt;/p&gt;
&lt;p class=&quot;og-desc&quot; data-ke-size=&quot;size16&quot;&gt;Nest is a framework for building efficient, scalable Node.js server-side applications. It uses progressive JavaScript, is built with TypeScript and combines elements of OOP (Object Oriented Programming), FP (Functional Programming), and FRP (Functional Rea&lt;/p&gt;
&lt;p class=&quot;og-host&quot; data-ke-size=&quot;size16&quot;&gt;docs.nestjs.com&lt;/p&gt;
&lt;/div&gt;
&lt;/a&gt;&lt;/figure&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;h3 data-ke-size=&quot;size23&quot;&gt;&lt;span style=&quot;font-family: 'Noto Sans Demilight', 'Noto Sans KR';&quot;&gt;&lt;b&gt;기능 소개&lt;/b&gt;&lt;/span&gt;&lt;/h3&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;span style=&quot;font-family: 'Noto Sans Demilight', 'Noto Sans KR';&quot;&gt;기능은 크게 5가지이다.&lt;/span&gt;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;span style=&quot;font-family: 'Noto Sans Demilight', 'Noto Sans KR';&quot;&gt;1. Dependency Graph (Modules, Classes 의존성 조회)&lt;/span&gt;&lt;span style=&quot;font-family: 'Noto Sans Demilight', 'Noto Sans KR';&quot;&gt;&lt;/span&gt;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;span style=&quot;font-family: 'Noto Sans Demilight', 'Noto Sans KR';&quot;&gt;2. Routes Explorer (API Endpoint 조회, Endpoint별 처리 프로세스 조회)&lt;/span&gt;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;span style=&quot;font-family: 'Noto Sans Demilight', 'Noto Sans KR';&quot;&gt;3. Playground (NestJS 코드 불러다가 쓸 수 있음)&lt;/span&gt;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;span style=&quot;font-family: 'Noto Sans Demilight', 'Noto Sans KR';&quot;&gt;4. Bootstrap Performance (Bootstrap 속도 분석)&lt;/span&gt;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;span style=&quot;font-family: 'Noto Sans Demilight', 'Noto Sans KR';&quot;&gt;5. Audit (코드 개선사항 제안)&lt;/span&gt;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;h3 data-ke-size=&quot;size23&quot;&gt;&lt;span style=&quot;font-family: 'Noto Sans Demilight', 'Noto Sans KR';&quot;&gt;&lt;b&gt;Prerequisite&lt;/b&gt;&lt;/span&gt;&lt;/h3&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;span style=&quot;font-family: 'Noto Sans Demilight', 'Noto Sans KR';&quot;&gt;1. NestJS 버전을 9.3.10 이상으로 업그레이드 해야한다. 2023년 7월 현재 Nest는 10버전까지 출시했으므로 현재 시점 이후로 세팅하는 대부분의 앱들은 Devtools 세팅이 가능할 것이다.&lt;/span&gt;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;span style=&quot;font-family: 'Noto Sans Demilight', 'Noto Sans KR';&quot;&gt;2. 최초 실행시 snapshot 속성을 true로 설정해야 한다.&lt;/span&gt;&lt;/p&gt;
&lt;pre id=&quot;code_1688570962214&quot; class=&quot;typescript&quot; data-ke-language=&quot;typescript&quot; data-ke-type=&quot;codeblock&quot;&gt;&lt;code&gt;// main.ts

async function bootstrap() {
  const app = await NestFactory.create(AppModule, {
    snapshot: true,
  });
  await app.listen(3000);
}&lt;/code&gt;&lt;/pre&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;span style=&quot;font-family: 'Noto Sans Demilight', 'Noto Sans KR';&quot;&gt;3. @nestjs/devtools-integration 라이브러리를 설치한다.&lt;/span&gt;&lt;/p&gt;
&lt;pre id=&quot;code_1688571014659&quot; class=&quot;bash&quot; data-ke-language=&quot;bash&quot; data-ke-type=&quot;codeblock&quot;&gt;&lt;code&gt;yarn add @nestjs/devtools-integration&lt;/code&gt;&lt;/pre&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;span style=&quot;font-family: 'Noto Sans Demilight', 'Noto Sans KR';&quot;&gt;4. AppModule에 DevtoolsModule을 import한다. (port 번호는 main.ts에서 설정한 포트와 다른 번호로 지정) 여기서 설정한 port 번호는 Devtools에 연결할 때 사용된다.&lt;/span&gt;&lt;/p&gt;
&lt;pre id=&quot;code_1688571071754&quot; class=&quot;typescript&quot; data-ke-language=&quot;typescript&quot; data-ke-type=&quot;codeblock&quot;&gt;&lt;code&gt;@Module({
  imports: [
    DevtoolsModule.register({
      http: process.env.NODE_ENV !== 'production',
      port: 3001,
    }),
  ],
  controllers: [AppController],
  providers: [AppService],
})
export class AppModule {}&lt;/code&gt;&lt;/pre&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;h3 data-ke-size=&quot;size23&quot;&gt;&lt;span style=&quot;font-family: 'Noto Sans Demilight', 'Noto Sans KR';&quot;&gt;&lt;b&gt;로컬에서 Devtools 연결하기&lt;/b&gt;&lt;/span&gt;&lt;/h3&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;span style=&quot;font-family: 'Noto Sans Demilight', 'Noto Sans KR';&quot;&gt;1. 먼저 Devtools에 접속한다. &lt;a href=&quot;https://devtools.nestjs.com&quot;&gt;https://devtools.nestjs.com&lt;/a&gt;&amp;nbsp;&lt;/span&gt;&lt;/p&gt;
&lt;figure id=&quot;og_1688571131977&quot; contenteditable=&quot;false&quot; data-ke-type=&quot;opengraph&quot; data-ke-align=&quot;alignCenter&quot; data-og-type=&quot;website&quot; data-og-title=&quot;Devtools | NestJS - A progressive Node.js framework&quot; data-og-description=&quot;Easily identify dependencies and connections between modules, and dive deep into the inner workings of your classes. Say goodbye to tedious manual analysis and hello to streamlined, efficient troubleshooting.&quot; data-og-host=&quot;devtools.nestjs.com&quot; data-og-source-url=&quot;https://devtools.nestjs.com&quot; data-og-url=&quot;https://devtools.nestjs.com&quot; data-og-image=&quot;https://scrap.kakaocdn.net/dn/eCaSkH/hyTebtAa61/rVrIf9ZL5xkIbplyE3uky1/img.png?width=820&amp;amp;height=429&amp;amp;face=0_0_820_429&quot;&gt;&lt;a href=&quot;https://devtools.nestjs.com&quot; target=&quot;_blank&quot; rel=&quot;noopener&quot; data-source-url=&quot;https://devtools.nestjs.com&quot;&gt;
&lt;div class=&quot;og-image&quot; style=&quot;background-image: url('https://scrap.kakaocdn.net/dn/eCaSkH/hyTebtAa61/rVrIf9ZL5xkIbplyE3uky1/img.png?width=820&amp;amp;height=429&amp;amp;face=0_0_820_429');&quot;&gt;&amp;nbsp;&lt;/div&gt;
&lt;div class=&quot;og-text&quot;&gt;
&lt;p class=&quot;og-title&quot; data-ke-size=&quot;size16&quot;&gt;Devtools | NestJS - A progressive Node.js framework&lt;/p&gt;
&lt;p class=&quot;og-desc&quot; data-ke-size=&quot;size16&quot;&gt;Easily identify dependencies and connections between modules, and dive deep into the inner workings of your classes. Say goodbye to tedious manual analysis and hello to streamlined, efficient troubleshooting.&lt;/p&gt;
&lt;p class=&quot;og-host&quot; data-ke-size=&quot;size16&quot;&gt;devtools.nestjs.com&lt;/p&gt;
&lt;/div&gt;
&lt;/a&gt;&lt;/figure&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;span style=&quot;font-family: 'Noto Sans Demilight', 'Noto Sans KR';&quot;&gt;2. 상단 입력창에 http://localhost:port 입력해서 연결한다. (서버 포트가 아니고 위에서 설정한 devtools 연결 포트)&lt;/span&gt;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;h3 data-ke-size=&quot;size23&quot;&gt;&lt;b&gt;세부 기능 설명&lt;/b&gt;&lt;/h3&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;span style=&quot;font-family: 'Noto Sans Demilight', 'Noto Sans KR';&quot;&gt;공개할 만한 자료가 없어서 이미지는 공홈 자료로 대체한다.&lt;/span&gt;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;h4 data-ke-size=&quot;size20&quot;&gt;&lt;span style=&quot;font-family: 'Noto Sans Demilight', 'Noto Sans KR';&quot;&gt;&lt;b&gt;Dependency Graph&lt;/b&gt;&lt;/span&gt;&lt;/h4&gt;
&lt;p&gt;&lt;figure class=&quot;imageblock alignCenter&quot; data-ke-mobileStyle=&quot;widthOrigin&quot; data-filename=&quot;classes-graph.png&quot; data-origin-width=&quot;1918&quot; data-origin-height=&quot;878&quot;&gt;&lt;span data-url=&quot;https://blog.kakaocdn.net/dn/OTj3J/btsmCOpu7w5/bjZVPPMlRjcCDDK3WukmLk/img.png&quot; data-phocus=&quot;https://blog.kakaocdn.net/dn/OTj3J/btsmCOpu7w5/bjZVPPMlRjcCDDK3WukmLk/img.png&quot; data-alt=&quot;의존관계를 파악하고 순환 참조를 검출할 수 있다.&quot;&gt;&lt;img src=&quot;https://blog.kakaocdn.net/dn/OTj3J/btsmCOpu7w5/bjZVPPMlRjcCDDK3WukmLk/img.png&quot; srcset=&quot;https://img1.daumcdn.net/thumb/R1280x0/?scode=mtistory2&amp;fname=https%3A%2F%2Fblog.kakaocdn.net%2Fdn%2FOTj3J%2FbtsmCOpu7w5%2FbjZVPPMlRjcCDDK3WukmLk%2Fimg.png&quot; onerror=&quot;this.onerror=null; this.src='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png'; this.srcset='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png';&quot; loading=&quot;lazy&quot; width=&quot;1918&quot; height=&quot;878&quot; data-filename=&quot;classes-graph.png&quot; data-origin-width=&quot;1918&quot; data-origin-height=&quot;878&quot;/&gt;&lt;/span&gt;&lt;figcaption&gt;의존관계를 파악하고 순환 참조를 검출할 수 있다.&lt;/figcaption&gt;
&lt;/figure&gt;
&lt;figure class=&quot;imageblock alignCenter&quot; data-ke-mobileStyle=&quot;widthOrigin&quot; data-filename=&quot;node-popup.png&quot; data-origin-width=&quot;582&quot; data-origin-height=&quot;424&quot;&gt;&lt;span data-url=&quot;https://blog.kakaocdn.net/dn/lLprh/btsmAM0euV7/NOSETEsNunnWhEXNOtpK00/img.png&quot; data-phocus=&quot;https://blog.kakaocdn.net/dn/lLprh/btsmAM0euV7/NOSETEsNunnWhEXNOtpK00/img.png&quot; data-alt=&quot;특정 모듈/컨트롤러를 기준으로 Incoming, Outgoing 노드를 파악할 수 있다.&quot;&gt;&lt;img src=&quot;https://blog.kakaocdn.net/dn/lLprh/btsmAM0euV7/NOSETEsNunnWhEXNOtpK00/img.png&quot; srcset=&quot;https://img1.daumcdn.net/thumb/R1280x0/?scode=mtistory2&amp;fname=https%3A%2F%2Fblog.kakaocdn.net%2Fdn%2FlLprh%2FbtsmAM0euV7%2FNOSETEsNunnWhEXNOtpK00%2Fimg.png&quot; onerror=&quot;this.onerror=null; this.src='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png'; this.srcset='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png';&quot; loading=&quot;lazy&quot; width=&quot;582&quot; height=&quot;424&quot; data-filename=&quot;node-popup.png&quot; data-origin-width=&quot;582&quot; data-origin-height=&quot;424&quot;/&gt;&lt;/span&gt;&lt;figcaption&gt;특정 모듈/컨트롤러를 기준으로 Incoming, Outgoing 노드를 파악할 수 있다.&lt;/figcaption&gt;
&lt;/figure&gt;
&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;h4 data-ke-size=&quot;size20&quot;&gt;&lt;span style=&quot;font-family: 'Noto Sans Demilight', 'Noto Sans KR';&quot;&gt;&lt;b&gt;Routes Explorer&lt;/b&gt;&lt;/span&gt;&lt;/h4&gt;
&lt;p&gt;&lt;figure class=&quot;imageblock alignCenter&quot; data-ke-mobileStyle=&quot;widthOrigin&quot; data-filename=&quot;routes.png&quot; data-origin-width=&quot;1916&quot; data-origin-height=&quot;875&quot;&gt;&lt;span data-url=&quot;https://blog.kakaocdn.net/dn/brZNMG/btsmAoyrl5E/TE8TLKjpAL4v4pPchUtgtK/img.png&quot; data-phocus=&quot;https://blog.kakaocdn.net/dn/brZNMG/btsmAoyrl5E/TE8TLKjpAL4v4pPchUtgtK/img.png&quot;&gt;&lt;img src=&quot;https://blog.kakaocdn.net/dn/brZNMG/btsmAoyrl5E/TE8TLKjpAL4v4pPchUtgtK/img.png&quot; srcset=&quot;https://img1.daumcdn.net/thumb/R1280x0/?scode=mtistory2&amp;fname=https%3A%2F%2Fblog.kakaocdn.net%2Fdn%2FbrZNMG%2FbtsmAoyrl5E%2FTE8TLKjpAL4v4pPchUtgtK%2Fimg.png&quot; onerror=&quot;this.onerror=null; this.src='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png'; this.srcset='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png';&quot; loading=&quot;lazy&quot; width=&quot;1916&quot; height=&quot;875&quot; data-filename=&quot;routes.png&quot; data-origin-width=&quot;1916&quot; data-origin-height=&quot;875&quot;/&gt;&lt;/span&gt;&lt;/figure&gt;
&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;span style=&quot;font-family: 'Noto Sans Demilight', 'Noto Sans KR';&quot;&gt;- 컨트롤러별 API 엔드포인트를 조회할 수 있다.&lt;/span&gt;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;span style=&quot;font-family: 'Noto Sans Demilight', 'Noto Sans KR';&quot;&gt;- 각 엔드포인트별로 해당 컨트롤러가 실행되기 위해 어떤 과정을 거치는지 그래프로 표시된다.&lt;/span&gt;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;h4 data-ke-size=&quot;size20&quot;&gt;&lt;span style=&quot;font-family: 'Noto Sans Demilight', 'Noto Sans KR';&quot;&gt;&lt;b&gt;그 외&lt;/b&gt;&lt;b&gt;&lt;/b&gt;&lt;/span&gt;&lt;/h4&gt;
&lt;p&gt;&lt;figure class=&quot;imageblock alignCenter&quot; data-ke-mobileStyle=&quot;widthOrigin&quot; data-filename=&quot;sandbox-table.png&quot; data-origin-width=&quot;1916&quot; data-origin-height=&quot;1078&quot;&gt;&lt;span data-url=&quot;https://blog.kakaocdn.net/dn/c3Lv4A/btsmBYlAqc6/jQnr1ljkjxaKMZKPVT5suK/img.png&quot; data-phocus=&quot;https://blog.kakaocdn.net/dn/c3Lv4A/btsmBYlAqc6/jQnr1ljkjxaKMZKPVT5suK/img.png&quot; data-alt=&quot;Sandbox (Playground)&quot;&gt;&lt;img src=&quot;https://blog.kakaocdn.net/dn/c3Lv4A/btsmBYlAqc6/jQnr1ljkjxaKMZKPVT5suK/img.png&quot; srcset=&quot;https://img1.daumcdn.net/thumb/R1280x0/?scode=mtistory2&amp;fname=https%3A%2F%2Fblog.kakaocdn.net%2Fdn%2Fc3Lv4A%2FbtsmBYlAqc6%2FjQnr1ljkjxaKMZKPVT5suK%2Fimg.png&quot; onerror=&quot;this.onerror=null; this.src='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png'; this.srcset='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png';&quot; loading=&quot;lazy&quot; width=&quot;1916&quot; height=&quot;1078&quot; data-filename=&quot;sandbox-table.png&quot; data-origin-width=&quot;1916&quot; data-origin-height=&quot;1078&quot;/&gt;&lt;/span&gt;&lt;figcaption&gt;Sandbox (Playground)&lt;/figcaption&gt;
&lt;/figure&gt;
&lt;figure class=&quot;imageblock alignCenter&quot; data-ke-mobileStyle=&quot;widthOrigin&quot; data-filename=&quot;bootstrap-performance.png&quot; data-origin-width=&quot;1915&quot; data-origin-height=&quot;857&quot;&gt;&lt;span data-url=&quot;https://blog.kakaocdn.net/dn/9LLMj/btsmAoLYvDx/duQ27UDKSHXNJ0fD9MDrtk/img.png&quot; data-phocus=&quot;https://blog.kakaocdn.net/dn/9LLMj/btsmAoLYvDx/duQ27UDKSHXNJ0fD9MDrtk/img.png&quot; data-alt=&quot;Bootstrap Performance&quot;&gt;&lt;img src=&quot;https://blog.kakaocdn.net/dn/9LLMj/btsmAoLYvDx/duQ27UDKSHXNJ0fD9MDrtk/img.png&quot; srcset=&quot;https://img1.daumcdn.net/thumb/R1280x0/?scode=mtistory2&amp;fname=https%3A%2F%2Fblog.kakaocdn.net%2Fdn%2F9LLMj%2FbtsmAoLYvDx%2FduQ27UDKSHXNJ0fD9MDrtk%2Fimg.png&quot; onerror=&quot;this.onerror=null; this.src='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png'; this.srcset='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png';&quot; loading=&quot;lazy&quot; width=&quot;1915&quot; height=&quot;857&quot; data-filename=&quot;bootstrap-performance.png&quot; data-origin-width=&quot;1915&quot; data-origin-height=&quot;857&quot;/&gt;&lt;/span&gt;&lt;figcaption&gt;Bootstrap Performance&lt;/figcaption&gt;
&lt;/figure&gt;
&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;span style=&quot;font-family: 'Noto Sans Demilight', 'Noto Sans KR';&quot;&gt;초기 로딩 속도를 말하는 것 같은데, 백엔드 앱이 뜨는데까지 걸리는 시간을 보여주는거라면 그걸 어떻게 활용할지 머리 속에 잘 안 그려진다.&lt;/span&gt;&lt;/p&gt;
&lt;p&gt;&lt;figure class=&quot;imageblock alignCenter&quot; data-ke-mobileStyle=&quot;widthOrigin&quot; data-filename=&quot;audit.png&quot; data-origin-width=&quot;1917&quot; data-origin-height=&quot;850&quot;&gt;&lt;span data-url=&quot;https://blog.kakaocdn.net/dn/ckBPNC/btsmCMZwSus/u650t2XHf6e9IyyQ9ZH9Ok/img.png&quot; data-phocus=&quot;https://blog.kakaocdn.net/dn/ckBPNC/btsmCMZwSus/u650t2XHf6e9IyyQ9ZH9Ok/img.png&quot; data-alt=&quot;Audit&quot;&gt;&lt;img src=&quot;https://blog.kakaocdn.net/dn/ckBPNC/btsmCMZwSus/u650t2XHf6e9IyyQ9ZH9Ok/img.png&quot; srcset=&quot;https://img1.daumcdn.net/thumb/R1280x0/?scode=mtistory2&amp;fname=https%3A%2F%2Fblog.kakaocdn.net%2Fdn%2FckBPNC%2FbtsmCMZwSus%2Fu650t2XHf6e9IyyQ9ZH9Ok%2Fimg.png&quot; onerror=&quot;this.onerror=null; this.src='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png'; this.srcset='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png';&quot; loading=&quot;lazy&quot; width=&quot;1917&quot; height=&quot;850&quot; data-filename=&quot;audit.png&quot; data-origin-width=&quot;1917&quot; data-origin-height=&quot;850&quot;/&gt;&lt;/span&gt;&lt;figcaption&gt;Audit&lt;/figcaption&gt;
&lt;/figure&gt;
&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;span style=&quot;font-family: 'Noto Sans Demilight', 'Noto Sans KR';&quot;&gt;개선사항을 제안해준다. 왼쪽에는 전체 모듈 개수, 컨트롤러, 프로바이더 개수 등 통계가 나와있고, 우측에는 Devtools의 제안사항이 나와있다. 용도에 맞지 않는 선언, 의존관계의 복잡도, 한 컨트롤러 내에 너무 많은 엔드포인트가 있는지 등 다양하게 검사해줘서 코드 개선시 참고할 수 있을 것 같다.&lt;/span&gt;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;span style=&quot;font-family: 'Noto Sans Demilight', 'Noto Sans KR';&quot;&gt;이것보다 더 코드 레벨에서의 검토에 집중하는 sonarqube 라는 서비스도 있는데, Devtools는 보다 구조적인 면에 집중한 것 같다.&lt;/span&gt;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;h3 data-ke-size=&quot;size23&quot;&gt;&lt;span style=&quot;font-family: 'Noto Sans Demilight', 'Noto Sans KR';&quot;&gt;&lt;b&gt;비용&lt;/b&gt;&lt;/span&gt;&lt;/h3&gt;
&lt;p&gt;&lt;figure class=&quot;imageblock alignCenter&quot; data-ke-mobileStyle=&quot;widthOrigin&quot; data-filename=&quot;스크린샷 2023-04-13 11.14.36.png&quot; data-origin-width=&quot;2980&quot; data-origin-height=&quot;1226&quot;&gt;&lt;span data-url=&quot;https://blog.kakaocdn.net/dn/FiBmQ/btsmBxPckWc/17JpaSoNKkiCfTDJXt7bDk/img.png&quot; data-phocus=&quot;https://blog.kakaocdn.net/dn/FiBmQ/btsmBxPckWc/17JpaSoNKkiCfTDJXt7bDk/img.png&quot; data-alt=&quot;2023.04 기준&quot;&gt;&lt;img src=&quot;https://blog.kakaocdn.net/dn/FiBmQ/btsmBxPckWc/17JpaSoNKkiCfTDJXt7bDk/img.png&quot; srcset=&quot;https://img1.daumcdn.net/thumb/R1280x0/?scode=mtistory2&amp;fname=https%3A%2F%2Fblog.kakaocdn.net%2Fdn%2FFiBmQ%2FbtsmBxPckWc%2F17JpaSoNKkiCfTDJXt7bDk%2Fimg.png&quot; onerror=&quot;this.onerror=null; this.src='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png'; this.srcset='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png';&quot; loading=&quot;lazy&quot; width=&quot;2980&quot; height=&quot;1226&quot; data-filename=&quot;스크린샷 2023-04-13 11.14.36.png&quot; data-origin-width=&quot;2980&quot; data-origin-height=&quot;1226&quot;/&gt;&lt;/span&gt;&lt;figcaption&gt;2023.04 기준&lt;/figcaption&gt;
&lt;/figure&gt;
&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;span style=&quot;font-family: 'Noto Sans Demilight', 'Noto Sans KR';&quot;&gt;기본적으로 &lt;b&gt;'유료'&lt;/b&gt; 서비스이다.&lt;/span&gt;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;span style=&quot;font-family: 'Noto Sans Demilight', 'Noto Sans KR';&quot;&gt;개인이 이용하면 월 $5의 요금을 내야하고, 팀으로 사용하면 $50 ~ $90 의 비용을 지불해야 한다.&lt;/span&gt;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;h3 data-ke-size=&quot;size23&quot;&gt;&lt;span style=&quot;font-family: 'Noto Sans Demilight', 'Noto Sans KR';&quot;&gt;&lt;b&gt;간단 후기 및 총평&lt;/b&gt;&lt;/span&gt;&lt;/h3&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;span style=&quot;font-family: 'Noto Sans Demilight', 'Noto Sans KR';&quot;&gt;Nest에서 가장 복잡하다고 생각했던 부분이 의존성 관리였다. 어떤 기준으로 모듈을 분리하고, 프로바이더별로 의존 관계는 어떻게 설정해야할 것인지. 이건 정답이 없고, 어떻게 구조를 짜야할지 계속 고민해서 나은 쪽으로 지향해 나가야 하는 것 같다. 사실 이건 설계의 문제라서 기술적으로 해결해주기는 어렵긴 하다.&lt;/span&gt;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;span style=&quot;font-family: 'Noto Sans Demilight', 'Noto Sans KR';&quot;&gt;다만 Devtools는 그 고민을 가능한 범위 내에서 최대한 해결할 수 있도록 support 해주려는 모습이 보였다. 복잡한 의존성 구조를 시각화해서 볼 수 있게 하고, 그걸 통해서 더 복잡한 비즈니스 요구사항을 풀어나갈 수 있도록 지원해주려 한 것 같다. 그리고 실제로 사용해보니 한 눈에 구조를 보고, 앞으로 어떻게 구조를 개선해나가야 할 지 머리 속에서 그림이 그려지긴 했다.&lt;/span&gt;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;span style=&quot;font-family: 'Noto Sans Demilight', 'Noto Sans KR';&quot;&gt;그래서 개인적으로는 첫 체험으로 지불한 $5에 만족했는데, 이걸 계속 쓸 것인가에 대한 질문에는 솔직히 잘 모르겠다. 분명 좋은 툴인데 유료로 활용하기에는 다소 부담스러운 감이 있다. 지금까지 Devtools 없이도 잘 개발해왔는데 비용을 태웠을 때 얼마나 생산성을 높여줄 것인가는 의문이다. 근본적으로 생산성을 높여주기 위한 툴이 아니라, 구조적으로 개선점을 조언하기 위한 정도에 그치는 것도 결정을 망설이는 요인 중 하나인 것 같다.&lt;/span&gt;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;span style=&quot;font-family: 'Noto Sans Demilight', 'Noto Sans KR';&quot;&gt;개인적으로는 1달정도는 결제해서 기존 Nest 프로젝트들에 한 번 쭉 적용시켜보고 어떤 구조인지 파악하고 개선사항들을 적용시켜보는 정도로 해보고 따로 연장하진 않을 것 같다. 앞으로 더 디벨롭해서 다양하고 유용한 기능들이 추가된다면 그 때는 생각이 바뀔까?&lt;/span&gt;&lt;/p&gt;</description>
      <category>Backend</category>
      <category>devtools</category>
      <category>nest</category>
      <category>NestJS</category>
      <author>HAENU</author>
      <guid isPermaLink="true">https://dokdo2013.tistory.com/9</guid>
      <comments>https://dokdo2013.tistory.com/9#entry9comment</comments>
      <pubDate>Sat, 8 Jul 2023 18:00:02 +0900</pubDate>
    </item>
    <item>
      <title>코드앤버터로 프로덕션 환경에서 우아하게 팝업 관리하기</title>
      <link>https://dokdo2013.tistory.com/11</link>
      <description>&lt;figure id=&quot;og_1688731171187&quot; contenteditable=&quot;false&quot; data-ke-type=&quot;opengraph&quot; data-ke-align=&quot;alignCenter&quot; data-og-type=&quot;website&quot; data-og-title=&quot;코드앤버터 - 팝업을 쉽고 빠르게! 웹사이트를 꾸미는 가장 쉬운 방법&quot; data-og-description=&quot;코드를 작성하지 않고 웹사이트에 필요한 기능을 쉽고 빠르게 추가하세요!&quot; data-og-host=&quot;www.codenbutter.com&quot; data-og-source-url=&quot;https://www.codenbutter.com/&quot; data-og-url=&quot;https://www.codenbutter.com/&quot; data-og-image=&quot;https://scrap.kakaocdn.net/dn/dA1iJP/hyTfvLXmBU/W9Gp1xq80uLGTLxnFpE9n1/img.jpg?width=3600&amp;amp;height=1890&amp;amp;face=2278_360_2710_567,https://scrap.kakaocdn.net/dn/cAUszW/hyTfm2z70B/z4c62J3DbXusbADYkcMKp0/img.jpg?width=3600&amp;amp;height=1890&amp;amp;face=2278_360_2710_567,https://scrap.kakaocdn.net/dn/bpR4RU/hyTfoMP077/16M5kawNJcsSM2s4NUHNK0/img.png?width=1913&amp;amp;height=1169&amp;amp;face=0_0_1913_1169&quot;&gt;&lt;a href=&quot;https://www.codenbutter.com/&quot; target=&quot;_blank&quot; rel=&quot;noopener&quot; data-source-url=&quot;https://www.codenbutter.com/&quot;&gt;
&lt;div class=&quot;og-image&quot; style=&quot;background-image: url('https://scrap.kakaocdn.net/dn/dA1iJP/hyTfvLXmBU/W9Gp1xq80uLGTLxnFpE9n1/img.jpg?width=3600&amp;amp;height=1890&amp;amp;face=2278_360_2710_567,https://scrap.kakaocdn.net/dn/cAUszW/hyTfm2z70B/z4c62J3DbXusbADYkcMKp0/img.jpg?width=3600&amp;amp;height=1890&amp;amp;face=2278_360_2710_567,https://scrap.kakaocdn.net/dn/bpR4RU/hyTfoMP077/16M5kawNJcsSM2s4NUHNK0/img.png?width=1913&amp;amp;height=1169&amp;amp;face=0_0_1913_1169');&quot;&gt;&amp;nbsp;&lt;/div&gt;
&lt;div class=&quot;og-text&quot;&gt;
&lt;p class=&quot;og-title&quot; data-ke-size=&quot;size16&quot;&gt;코드앤버터 - 팝업을 쉽고 빠르게! 웹사이트를 꾸미는 가장 쉬운 방법&lt;/p&gt;
&lt;p class=&quot;og-desc&quot; data-ke-size=&quot;size16&quot;&gt;코드를 작성하지 않고 웹사이트에 필요한 기능을 쉽고 빠르게 추가하세요!&lt;/p&gt;
&lt;p class=&quot;og-host&quot; data-ke-size=&quot;size16&quot;&gt;www.codenbutter.com&lt;/p&gt;
&lt;/div&gt;
&lt;/a&gt;&lt;/figure&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;최근에 가장 만족하며 사용한 SaaS 프로덕트 중 하나여서 사용기를 공유해보려 한다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;h3 data-ke-size=&quot;size23&quot;&gt;&lt;b&gt;코드앤버터란?&lt;/b&gt;&lt;/h3&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;코드앤버터는 노코드 팝업 솔루션이다. 퍼플아이오에서 만들었는데, 이쪽은 이커머스 관련 스타트업이다. 아마 이커머스쪽 서비스를 만들면서 사용된 컴포넌트와 기술을 활용해 완전관리형 팝업 서비스를 런칭한 걸로 보인다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;원래도 퍼플아이오를 들어보긴 했었다. Purple Admin UI라고 Next와 Tailwind 기반으로 만든 어드민 템플릿이다. 이게 한 때 깃허브에서 유행했었는데, 나도 한 때 PHP로 백오피스 작업들을 많이 해봤기도 하고, 노드 기반으로 기술 스택을 옮긴 후에는 백오피스 작업이 없었는데, 나중에 백오피스 작업을 할 때가 오면 써봐야겠다 생각했었다. 아직 깔아서 시도는 못 해봤는데 나중에 해보면 되게 재밌을 것 같다.&lt;/p&gt;
&lt;figure id=&quot;og_1688731853311&quot; contenteditable=&quot;false&quot; data-ke-type=&quot;opengraph&quot; data-ke-align=&quot;alignCenter&quot; data-og-type=&quot;object&quot; data-og-title=&quot;GitHub - purpleio/purple-admin-ui: Next.js와 Tailwind를 이용한 모-던 어드민 템플릿&quot; data-og-description=&quot;Next.js와 Tailwind를 이용한 모-던 어드민 템플릿. Contribute to purpleio/purple-admin-ui development by creating an account on GitHub.&quot; data-og-host=&quot;github.com&quot; data-og-source-url=&quot;https://github.com/purpleio/purple-admin-ui&quot; data-og-url=&quot;https://github.com/purpleio/purple-admin-ui&quot; data-og-image=&quot;https://scrap.kakaocdn.net/dn/GtCEG/hyTfDwaniT/WFQsJ0YZUkHzrKrOLeFpK1/img.png?width=1600&amp;amp;height=800&amp;amp;face=0_0_1600_800&quot;&gt;&lt;a href=&quot;https://github.com/purpleio/purple-admin-ui&quot; target=&quot;_blank&quot; rel=&quot;noopener&quot; data-source-url=&quot;https://github.com/purpleio/purple-admin-ui&quot;&gt;
&lt;div class=&quot;og-image&quot; style=&quot;background-image: url('https://scrap.kakaocdn.net/dn/GtCEG/hyTfDwaniT/WFQsJ0YZUkHzrKrOLeFpK1/img.png?width=1600&amp;amp;height=800&amp;amp;face=0_0_1600_800');&quot;&gt;&amp;nbsp;&lt;/div&gt;
&lt;div class=&quot;og-text&quot;&gt;
&lt;p class=&quot;og-title&quot; data-ke-size=&quot;size16&quot;&gt;GitHub - purpleio/purple-admin-ui: Next.js와 Tailwind를 이용한 모-던 어드민 템플릿&lt;/p&gt;
&lt;p class=&quot;og-desc&quot; data-ke-size=&quot;size16&quot;&gt;Next.js와 Tailwind를 이용한 모-던 어드민 템플릿. Contribute to purpleio/purple-admin-ui development by creating an account on GitHub.&lt;/p&gt;
&lt;p class=&quot;og-host&quot; data-ke-size=&quot;size16&quot;&gt;github.com&lt;/p&gt;
&lt;/div&gt;
&lt;/a&gt;&lt;/figure&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;h3 data-ke-size=&quot;size23&quot;&gt;&lt;b&gt;왜 코드앤버터를 도입했는가?&lt;/b&gt;&lt;/h3&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;원래 사이드 프로젝트로 진행하던 서비스가 있었다. 비록 사이드지만 나름 MAU 3~4만은 나오던 작지 않은 서비스였는데, 이걸 회사 안으로 들고 들어올 수 있는 기회가 생겼다. 그러다 회사의 지원을 받아 Product Owner를 겸하면서 제품의 방향성을 정하고, 실제 개발 과정을 거쳐서 제품 출시까지 경험해볼 수 있었다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;PO를 경험해보니까 한정된 시간 내에 어떤 작업을 해야 ROI를 높일 수 있을지 자연스럽게 고민하게 되었다. 사실 아무리 작은 서비스라도 서비스가 운영되기 위해 필요한 기능들이 적지 않다. 공지사항부터 업데이트 로그, 백오피스도 있으면 좋고, 팝업이나 알림 기능 등 자잘한 게 되게 많은데... 그걸 다 포함해서 개발하기는 리소스와 일정 모두가 부족했다. 특히 MVP 출시를 위해 스프린트를 진행하고 있었는데 그런 자잘한 기능 말고도 쳐내야 할 핵심 피쳐들이 많이 쌓여 있었다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;h4 data-ke-size=&quot;size20&quot;&gt;&lt;b&gt;기존 팝업 시스템의 문제점&lt;/b&gt;&lt;/h4&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;가장 고민했던 부분 중 하나가 바로 팝업 시스템이다. MVP로 출시하면 초반에 자잘한 기능 업데이트들이 이어서 런칭될꺼고, 장애나 크리티컬 버그가 발생하면 이를 알릴 수도 있어야 한다. 프로모션 이벤트를 태운다면 그걸 홍보할 수도 있어야 한다. 그래서 사실 팝업 시스템은 '필수 기능'은 아니지만 그래도 일정을 쪼개서 넣어야만 하는 기능 중 하나였다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;그런데 전통적인 방식의 팝업 시스템으로 만들려면 작업공수가 예상보다 너무 많이 들어간다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;일단 팝업의 종류가 늘어나면, 종류가 늘어날 때마다 개발공수가 들어간다. 예를 들어 인시던트 상황 전파를 위한 페이지 상단 한줄공지 같은거나, 메인 페이지 중앙 팝업 등 각 종류별로 스타일도 잡아줘야 하고, 코드도 다 작성해야 하고...&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;그럼 만약에 종류를 줄여서 2가지 정도 타입의 팝업 시스템을 만든다고 가정하자. 일단 최소한의 리소스를 써야하니 디자인은 UI 라이브러리들에서 제공하는 템플릿을 그대로 써보자. 그래도 내부 코드는 다 짜야하고 스타일도 다 잡아줘야 한다. &quot;n일동안 보지 않기&quot; 같은 기능이 들어가면 로컬스토리지같은 걸 써서 일일이 관리해줘야 한다. 이렇게 요구사항을 줄였는데도 최소 1~2일은 걸릴 것이다. 10일~15일짜리 스프린트에 1~2일이면 결코 짧은 기간이 아니다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;어떤 팝업을 띄워줄지 관리하는 것도 골칫거리다. 회사로 들어오기 전 사이드 프로젝트 단계에서는 이런 종류의 데이터를 S3+CF로 관리했다. 깃헙에 정적 파일 전용 레포를 만들고, Github Actions로 커밋이 있을 때 해당 레포와 S3 버킷을 동기화하도록 처리해줬다. 그러면 자연스럽게 히스토리 관리도 되고, 정적 파일로 서빙하기 때문에 안정성도 높아진다. 그런데 이렇게 하면 개발자만 팝업 정보를 관리할 수 있게 된다. 프로덕트가 어느 정도 궤도로 올라가면 아마 회사에 계신 다른 전문 PM/PO 분이 가져가실건데, 개발자가 아니기 때문에 깃헙에서 그렇게 팝업을 관리하는게 상당히 어려울 것이다. 뭐 한 분은 문서를 잘 써서 방법을 가르쳐드린다고 해도, 긴급한 공지같은 건 CS 팀에서도 칠 수 있을건데 그러면 전혀 대응이 안 될 것이다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;그러면 이제 자연스럽게 지옥으로 빠져든다. 백오피스를 만들어야하거나, DB에서 팝업 정보를 관리해야하거나, 백엔드에서 파일을 구워서 올려주도록 api도 만들어줘야하고, 적절히 권한 관리도 해줘야하고... 개발서버랑 실서버랑 분리해서 먼저 테스트해볼 수도 있어야 하고... 이렇게 되면 팝업에만 1~2주를 쓰게 된다. 정말 충격과 공포의 상황이 아닐 수 없다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;h4 data-ke-size=&quot;size20&quot;&gt;&lt;b&gt;코드앤버터 도입하기&lt;/b&gt;&lt;/h4&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;일단 도입 당시(23년 2월)에는 베타 기간으로 무료였다. 당시에는 23년 7월까지 베타를 진행했다고 봤던 것 같은데, 오랜만에 랜딩 페이지를 들어가봤더니 23년까지 베타를 진행한다고 변경되었다. 아무튼 무료 서비스여서 초기에 도입하는데 크게 어려움은 없었다.&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;Next.js 환경이었는데 &lt;a title=&quot;개발자 문서&quot; href=&quot;https://docs.codenbutter.com/developer/script.html&quot; target=&quot;_blank&quot; rel=&quot;noopener&quot;&gt;개발자 문서&lt;/a&gt;를 보고 간단하게 스크립트만 추가하니 잘 동작했다.&lt;/p&gt;
&lt;pre id=&quot;code_1688734088846&quot; class=&quot;typescript&quot; data-ke-language=&quot;typescript&quot; data-ke-type=&quot;codeblock&quot;&gt;&lt;code&gt;// pages/_document.tsx

&amp;lt;script
    src=&quot;https://buttr.dev/butter.js&quot;
    data-site-id={NEXT_PUBLIC_BUTTER_SITE_ID}
    async
&amp;gt;&amp;lt;/script&amp;gt;&lt;/code&gt;&lt;/pre&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;이렇게 짧은 코드를 넣어주기만 하면, 그 다음부터는 모든 작업이 &lt;b&gt;NO CODE&lt;/b&gt;로 이뤄진다. 일반적으로 노코드 서비스에 대해 기능이 부족하지 않을까 많이 우려하는데, 코드앤버터는 필요한 기능들은 어느 정도 다 들어가 있게 느껴졌다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;h4 data-ke-size=&quot;size20&quot;&gt;&lt;b&gt;정말 다루기 쉬운 WYSIWYG 에디터&lt;/b&gt;&lt;/h4&gt;
&lt;p&gt;&lt;figure class=&quot;imageblock alignCenter&quot; data-ke-mobileStyle=&quot;widthOrigin&quot; data-filename=&quot;스크린샷 2023-07-07 22.08.19.png&quot; data-origin-width=&quot;5082&quot; data-origin-height=&quot;2578&quot;&gt;&lt;span data-url=&quot;https://blog.kakaocdn.net/dn/bFnrhz/btsmRokd5QR/bVOui2Z67Bs63n1hSCiCUK/img.png&quot; data-phocus=&quot;https://blog.kakaocdn.net/dn/bFnrhz/btsmRokd5QR/bVOui2Z67Bs63n1hSCiCUK/img.png&quot; data-alt=&quot;팝업을 편집하는 WYSIWYG 에디터&quot;&gt;&lt;img src=&quot;https://blog.kakaocdn.net/dn/bFnrhz/btsmRokd5QR/bVOui2Z67Bs63n1hSCiCUK/img.png&quot; srcset=&quot;https://img1.daumcdn.net/thumb/R1280x0/?scode=mtistory2&amp;fname=https%3A%2F%2Fblog.kakaocdn.net%2Fdn%2FbFnrhz%2FbtsmRokd5QR%2FbVOui2Z67Bs63n1hSCiCUK%2Fimg.png&quot; onerror=&quot;this.onerror=null; this.src='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png'; this.srcset='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png';&quot; loading=&quot;lazy&quot; width=&quot;5082&quot; height=&quot;2578&quot; data-filename=&quot;스크린샷 2023-07-07 22.08.19.png&quot; data-origin-width=&quot;5082&quot; data-origin-height=&quot;2578&quot;/&gt;&lt;/span&gt;&lt;figcaption&gt;팝업을 편집하는 WYSIWYG 에디터&lt;/figcaption&gt;
&lt;/figure&gt;
&lt;figure class=&quot;imageblock alignCenter&quot; data-ke-mobileStyle=&quot;widthOrigin&quot; data-filename=&quot;스크린샷 2023-07-07 22.08.38.png&quot; data-origin-width=&quot;1146&quot; data-origin-height=&quot;2046&quot;&gt;&lt;span data-url=&quot;https://blog.kakaocdn.net/dn/dNftJY/btsmP6dxraa/LVPAVGmmrp2x8DMbFaAqn1/img.png&quot; data-phocus=&quot;https://blog.kakaocdn.net/dn/dNftJY/btsmP6dxraa/LVPAVGmmrp2x8DMbFaAqn1/img.png&quot; data-alt=&quot;팝업을 보여주는 미리보기 화면&quot;&gt;&lt;img src=&quot;https://blog.kakaocdn.net/dn/dNftJY/btsmP6dxraa/LVPAVGmmrp2x8DMbFaAqn1/img.png&quot; srcset=&quot;https://img1.daumcdn.net/thumb/R1280x0/?scode=mtistory2&amp;fname=https%3A%2F%2Fblog.kakaocdn.net%2Fdn%2FdNftJY%2FbtsmP6dxraa%2FLVPAVGmmrp2x8DMbFaAqn1%2Fimg.png&quot; onerror=&quot;this.onerror=null; this.src='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png'; this.srcset='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png';&quot; loading=&quot;lazy&quot; width=&quot;400&quot; height=&quot;714&quot; data-filename=&quot;스크린샷 2023-07-07 22.08.38.png&quot; data-origin-width=&quot;1146&quot; data-origin-height=&quot;2046&quot;/&gt;&lt;/span&gt;&lt;figcaption&gt;팝업을 보여주는 미리보기 화면&lt;/figcaption&gt;
&lt;/figure&gt;
&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;팝업을 만들기 위해서 WYSIWYG 에디터를 사용한다. 이게 가끔 예상대로 잘 배치가 안 되기도 하는데, 전반적으로 원하는 모양을 잘 잡아서 배치할 수 있다. 아무리 숙련된 프론트엔드 개발자라도 대충 퍼블리싱 치고 기능 씌우고 하면 시간이 좀 걸리는데, 이건 비교도 안 되게 편하다. 차후에도 팝업 쪽은 개발 리소스를 전혀 투입하지 않으려면 이거 도입하는거밖에 방법이 없겠구나 싶었다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;특히 &quot;n일동안 보지 않기&quot; 등 기능도 설정으로 넣을 수 있어서 이 부분은 되게 만족스러웠다. 이런 세심한 기능들이 마음에 들었다. 참고로 개발자도구를 까보니 구현은 localStorage를 사용했다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;h4 data-ke-size=&quot;size20&quot;&gt;&lt;b&gt;다양한 설정 옵션들&lt;/b&gt;&lt;/h4&gt;
&lt;p&gt;&lt;figure class=&quot;imageblock alignCenter&quot; data-ke-mobileStyle=&quot;widthOrigin&quot; data-filename=&quot;스크린샷 2023-07-07 22.16.14.png&quot; data-origin-width=&quot;3166&quot; data-origin-height=&quot;674&quot;&gt;&lt;span data-url=&quot;https://blog.kakaocdn.net/dn/CE1Hq/btsmQQOMesE/4SCvJcGbaz22pScJwWeHtK/img.png&quot; data-phocus=&quot;https://blog.kakaocdn.net/dn/CE1Hq/btsmQQOMesE/4SCvJcGbaz22pScJwWeHtK/img.png&quot; data-alt=&quot;스케줄 설정 - 예약 기능이 가능하다&quot;&gt;&lt;img src=&quot;https://blog.kakaocdn.net/dn/CE1Hq/btsmQQOMesE/4SCvJcGbaz22pScJwWeHtK/img.png&quot; srcset=&quot;https://img1.daumcdn.net/thumb/R1280x0/?scode=mtistory2&amp;fname=https%3A%2F%2Fblog.kakaocdn.net%2Fdn%2FCE1Hq%2FbtsmQQOMesE%2F4SCvJcGbaz22pScJwWeHtK%2Fimg.png&quot; onerror=&quot;this.onerror=null; this.src='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png'; this.srcset='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png';&quot; loading=&quot;lazy&quot; width=&quot;3166&quot; height=&quot;674&quot; data-filename=&quot;스크린샷 2023-07-07 22.16.14.png&quot; data-origin-width=&quot;3166&quot; data-origin-height=&quot;674&quot;/&gt;&lt;/span&gt;&lt;figcaption&gt;스케줄 설정 - 예약 기능이 가능하다&lt;/figcaption&gt;
&lt;/figure&gt;
&lt;figure class=&quot;imageblock alignCenter&quot; data-ke-mobileStyle=&quot;widthOrigin&quot; data-filename=&quot;스크린샷 2023-07-07 22.17.11.png&quot; data-origin-width=&quot;3166&quot; data-origin-height=&quot;2314&quot;&gt;&lt;span data-url=&quot;https://blog.kakaocdn.net/dn/cMXM5y/btsmSujVSfi/uWIVTBhJhbiYUFGOS2LQz1/img.png&quot; data-phocus=&quot;https://blog.kakaocdn.net/dn/cMXM5y/btsmSujVSfi/uWIVTBhJhbiYUFGOS2LQz1/img.png&quot; data-alt=&quot;노출 대상 설정 - 어느 페이지에 보여줄지, 보여주지 말지 정할 수 있다&quot;&gt;&lt;img src=&quot;https://blog.kakaocdn.net/dn/cMXM5y/btsmSujVSfi/uWIVTBhJhbiYUFGOS2LQz1/img.png&quot; srcset=&quot;https://img1.daumcdn.net/thumb/R1280x0/?scode=mtistory2&amp;fname=https%3A%2F%2Fblog.kakaocdn.net%2Fdn%2FcMXM5y%2FbtsmSujVSfi%2FuWIVTBhJhbiYUFGOS2LQz1%2Fimg.png&quot; onerror=&quot;this.onerror=null; this.src='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png'; this.srcset='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png';&quot; loading=&quot;lazy&quot; width=&quot;3166&quot; height=&quot;2314&quot; data-filename=&quot;스크린샷 2023-07-07 22.17.11.png&quot; data-origin-width=&quot;3166&quot; data-origin-height=&quot;2314&quot;/&gt;&lt;/span&gt;&lt;figcaption&gt;노출 대상 설정 - 어느 페이지에 보여줄지, 보여주지 말지 정할 수 있다&lt;/figcaption&gt;
&lt;/figure&gt;
&lt;figure class=&quot;imageblock alignCenter&quot; data-ke-mobileStyle=&quot;widthOrigin&quot; data-filename=&quot;스크린샷 2023-07-07 22.17.42.png&quot; data-origin-width=&quot;3158&quot; data-origin-height=&quot;1608&quot;&gt;&lt;span data-url=&quot;https://blog.kakaocdn.net/dn/kF57n/btsmPAF0MXv/JCkWgVTpLtfqxQ5h83qR5k/img.png&quot; data-phocus=&quot;https://blog.kakaocdn.net/dn/kF57n/btsmPAF0MXv/JCkWgVTpLtfqxQ5h83qR5k/img.png&quot; data-alt=&quot;기본 통계 - 테스트로 만든다고 자료는 없는데 csv 다운도 된다&quot;&gt;&lt;img src=&quot;https://blog.kakaocdn.net/dn/kF57n/btsmPAF0MXv/JCkWgVTpLtfqxQ5h83qR5k/img.png&quot; srcset=&quot;https://img1.daumcdn.net/thumb/R1280x0/?scode=mtistory2&amp;fname=https%3A%2F%2Fblog.kakaocdn.net%2Fdn%2FkF57n%2FbtsmPAF0MXv%2FJCkWgVTpLtfqxQ5h83qR5k%2Fimg.png&quot; onerror=&quot;this.onerror=null; this.src='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png'; this.srcset='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png';&quot; loading=&quot;lazy&quot; width=&quot;3158&quot; height=&quot;1608&quot; data-filename=&quot;스크린샷 2023-07-07 22.17.42.png&quot; data-origin-width=&quot;3158&quot; data-origin-height=&quot;1608&quot;/&gt;&lt;/span&gt;&lt;figcaption&gt;기본 통계 - 테스트로 만든다고 자료는 없는데 csv 다운도 된다&lt;/figcaption&gt;
&lt;/figure&gt;
&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;코드앤버터를 채택하게 된 큰 이유 중 하나가 바로 디테일한 설정이 가능하다는 점이다. 팝업을 보여줄 대상에 따라 페이지를 다르게 설정할 수도 있고, 기간 설정도 유연하게 되는 점이 참 좋은 것 같다. 이게 사실 일반적으로 팝업 시스템을 자체 구축한다고 하면 다 만들어야하는 부분인데... 정말 프로젝트 초기에 많은 시간을 아낄 수 있었다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;뒤에서 언급할건데 &quot;제외 조건&quot; 부분은 문의로 피드백을 보냈는데 2주만에 기능으로 반영되어 돌아왔다. 지금도 정말 유용하게 잘 사용하고 있다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;h3 data-ke-size=&quot;size23&quot;&gt;&lt;b&gt;프로덕션 환경에서 팝업 관리하기&lt;/b&gt;&lt;/h3&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;오늘 글에서 가장 핵심적으로 얘기하려고 했던 부분이다. 프로덕션 환경에서의 팝업 관리.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;일반적으로 개발환경 구성은 회사나 제품별로 매우 상이한데, 현재 프로젝트는 '개발 - 스테이징 - 프로덕션' 의 3개 환경으로 구성되어 있다. 따라서 환경별로 다른 팝업을 테스트해보고, 검증이 완료되면 프로덕션으로 옮겨야 한다. 만약 이게 자체 구축이었다면 구조적으로 되게 고민이 많아질 수 있다. 왜냐하면 일반적으로 DB가 분리되어 있고, 각 환경은 서로 독립적으로 동작해야하기 때문에, 특정 환경에서 다른 환경으로 데이터를 옮기는게 불가능하다. 물론 가능하게 만들려면 만들 수 있겠지만 대부분 선호되지 않는 방식이다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;b&gt;이걸 코드앤버터는 단순히 '복제' 기능을 만듦으로써 해결했다.&lt;/b&gt;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;구조적으로 봤을 때 가장 먼저 떠오른 것은 쿠버네티스 환경에서 2개 이상의 환경을 관리하기 위해 따로 Management Cluster를 둔다는 컨셉이다. 실제 어플리케이션 레벨은 각 환경별 클러스터에서 구동되지만, CI/CD와 모니터링 등 공통으로 처리되어도 되는 부분들은 공통 관리 클러스터로 뺀다는 내용이다. 이걸 그대로 가져와서 살펴보면 3개의 Dev, Staging, Production 환경을 코드앤버터가 중앙에서 각각 컨트롤할 수 있기 때문에 특정 환경의 설정 정보를 다른 환경에 쉽게 이식이 가능하고, 이는 프로덕션 레벨에서 활용하기 좋다는 의미가 된다. 코드앤버터가 Management Cluster로의 역할을 한다는 얘기다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;참고자료 :&amp;nbsp;&lt;a href=&quot;https://blog.gruntwork.io/how-to-build-an-end-to-end-production-grade-architecture-on-aws-part-1-eae8eeb41fec&quot; target=&quot;_blank&quot; rel=&quot;noopener&quot;&gt;https://blog.gruntwork.io/how-to-build-an-end-to-end-production-grade-architecture-on-aws-part-1-eae8eeb41fec&lt;/a&gt;&lt;/p&gt;
&lt;figure id=&quot;og_1688736675812&quot; contenteditable=&quot;false&quot; data-ke-type=&quot;opengraph&quot; data-ke-align=&quot;alignCenter&quot; data-og-type=&quot;article&quot; data-og-title=&quot;How to Build an End to End Production-Grade Architecture on AWS Part 1&quot; data-og-description=&quot;Part 1: Network Configuration, Kubernetes, Microservices, and Load Balancing&quot; data-og-host=&quot;blog.gruntwork.io&quot; data-og-source-url=&quot;https://blog.gruntwork.io/how-to-build-an-end-to-end-production-grade-architecture-on-aws-part-1-eae8eeb41fec&quot; data-og-url=&quot;https://blog.gruntwork.io/how-to-build-an-end-to-end-production-grade-architecture-on-aws-part-1-eae8eeb41fec&quot; data-og-image=&quot;https://scrap.kakaocdn.net/dn/ceHgOr/hyTfsu0bMD/SJokYLDe1nG9UYV6QuPXBk/img.png?width=1200&amp;amp;height=709&amp;amp;face=0_0_1200_709&quot;&gt;&lt;a href=&quot;https://blog.gruntwork.io/how-to-build-an-end-to-end-production-grade-architecture-on-aws-part-1-eae8eeb41fec&quot; target=&quot;_blank&quot; rel=&quot;noopener&quot; data-source-url=&quot;https://blog.gruntwork.io/how-to-build-an-end-to-end-production-grade-architecture-on-aws-part-1-eae8eeb41fec&quot;&gt;
&lt;div class=&quot;og-image&quot; style=&quot;background-image: url('https://scrap.kakaocdn.net/dn/ceHgOr/hyTfsu0bMD/SJokYLDe1nG9UYV6QuPXBk/img.png?width=1200&amp;amp;height=709&amp;amp;face=0_0_1200_709');&quot;&gt;&amp;nbsp;&lt;/div&gt;
&lt;div class=&quot;og-text&quot;&gt;
&lt;p class=&quot;og-title&quot; data-ke-size=&quot;size16&quot;&gt;How to Build an End to End Production-Grade Architecture on AWS Part 1&lt;/p&gt;
&lt;p class=&quot;og-desc&quot; data-ke-size=&quot;size16&quot;&gt;Part 1: Network Configuration, Kubernetes, Microservices, and Load Balancing&lt;/p&gt;
&lt;p class=&quot;og-host&quot; data-ke-size=&quot;size16&quot;&gt;blog.gruntwork.io&lt;/p&gt;
&lt;/div&gt;
&lt;/a&gt;&lt;/figure&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;다른 환경에서 만든 팝업을 아래와 같이 모달창을 통해 다른 환경으로 복제할 수 있다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p&gt;&lt;figure class=&quot;imageblock alignCenter&quot; data-ke-mobileStyle=&quot;widthOrigin&quot; data-filename=&quot;edited_스크린샷 2023-07-07 22.34.45.png&quot; data-origin-width=&quot;1130&quot; data-origin-height=&quot;996&quot;&gt;&lt;span data-url=&quot;https://blog.kakaocdn.net/dn/bxYlUX/btsmRoYFzac/XJKak51Nnl1vpRJdfDxjh1/img.png&quot; data-phocus=&quot;https://blog.kakaocdn.net/dn/bxYlUX/btsmRoYFzac/XJKak51Nnl1vpRJdfDxjh1/img.png&quot; data-alt=&quot;팝업 복제 모달&quot;&gt;&lt;img src=&quot;https://blog.kakaocdn.net/dn/bxYlUX/btsmRoYFzac/XJKak51Nnl1vpRJdfDxjh1/img.png&quot; srcset=&quot;https://img1.daumcdn.net/thumb/R1280x0/?scode=mtistory2&amp;fname=https%3A%2F%2Fblog.kakaocdn.net%2Fdn%2FbxYlUX%2FbtsmRoYFzac%2FXJKak51Nnl1vpRJdfDxjh1%2Fimg.png&quot; onerror=&quot;this.onerror=null; this.src='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png'; this.srcset='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png';&quot; loading=&quot;lazy&quot; width=&quot;400&quot; height=&quot;353&quot; data-filename=&quot;edited_스크린샷 2023-07-07 22.34.45.png&quot; data-origin-width=&quot;1130&quot; data-origin-height=&quot;996&quot;/&gt;&lt;/span&gt;&lt;figcaption&gt;팝업 복제 모달&lt;/figcaption&gt;
&lt;/figure&gt;
&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;복제 버튼을 누르면 일단 비공개 상태로 복제가 되고, 복제된 환경에서 공개 설정을 만져서 공개로 바꿀 수 있다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;h3 data-ke-size=&quot;size23&quot;&gt;&lt;b&gt;피드백을 보냈는데 2주만에 반영되었다&lt;/b&gt;&lt;/h3&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;그렇게 프로덕션 레벨에서 코드앤버터를 사용하다 이슈를 하나 발견했다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;현재 프로젝트가 영상 관련 서비스라서 영상을 외부로 공유 (embed) 하는 경우가 있는데, 이 때 외부에서 iframe으로 임베드할 때도 공지 팝업이 떠버려서 불편을 겪는 유저가 있었다. 그래서 특정 주소만 노출을 안 할 수 있도록 가능한지 문의했다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;그렇게 문의한 지 얼마 지나지 않아 3월 31일에 첫 답변을 받았다!&lt;/p&gt;
&lt;p&gt;&lt;figure class=&quot;imageblock alignCenter&quot; data-ke-mobileStyle=&quot;widthOrigin&quot; data-filename=&quot;edited_스크린샷 2023-07-07 22.38.17.png&quot; data-origin-width=&quot;1118&quot; data-origin-height=&quot;712&quot;&gt;&lt;span data-url=&quot;https://blog.kakaocdn.net/dn/cJtW3u/btsmPFz81im/ditoVWgM45K56LkPggofF1/img.png&quot; data-phocus=&quot;https://blog.kakaocdn.net/dn/cJtW3u/btsmPFz81im/ditoVWgM45K56LkPggofF1/img.png&quot; data-alt=&quot;채널톡으로 문의했을 때 받은 답변 (1-1)&quot;&gt;&lt;img src=&quot;https://blog.kakaocdn.net/dn/cJtW3u/btsmPFz81im/ditoVWgM45K56LkPggofF1/img.png&quot; srcset=&quot;https://img1.daumcdn.net/thumb/R1280x0/?scode=mtistory2&amp;fname=https%3A%2F%2Fblog.kakaocdn.net%2Fdn%2FcJtW3u%2FbtsmPFz81im%2FditoVWgM45K56LkPggofF1%2Fimg.png&quot; onerror=&quot;this.onerror=null; this.src='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png'; this.srcset='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png';&quot; loading=&quot;lazy&quot; width=&quot;400&quot; height=&quot;255&quot; data-filename=&quot;edited_스크린샷 2023-07-07 22.38.17.png&quot; data-origin-width=&quot;1118&quot; data-origin-height=&quot;712&quot;/&gt;&lt;/span&gt;&lt;figcaption&gt;채널톡으로 문의했을 때 받은 답변 (1-1)&lt;/figcaption&gt;
&lt;/figure&gt;
&lt;figure class=&quot;imageblock alignCenter&quot; data-ke-mobileStyle=&quot;widthOrigin&quot; data-filename=&quot;edited_스크린샷 2023-07-07 22.40.35.png&quot; data-origin-width=&quot;1120&quot; data-origin-height=&quot;428&quot;&gt;&lt;span data-url=&quot;https://blog.kakaocdn.net/dn/mENQ9/btsmPaHx2vd/g3akpf2SheWDfApxcyLoQK/img.png&quot; data-phocus=&quot;https://blog.kakaocdn.net/dn/mENQ9/btsmPaHx2vd/g3akpf2SheWDfApxcyLoQK/img.png&quot; data-alt=&quot;채널톡으로 문의했을 때 받은 답변 (1-2)&quot;&gt;&lt;img src=&quot;https://blog.kakaocdn.net/dn/mENQ9/btsmPaHx2vd/g3akpf2SheWDfApxcyLoQK/img.png&quot; srcset=&quot;https://img1.daumcdn.net/thumb/R1280x0/?scode=mtistory2&amp;fname=https%3A%2F%2Fblog.kakaocdn.net%2Fdn%2FmENQ9%2FbtsmPaHx2vd%2Fg3akpf2SheWDfApxcyLoQK%2Fimg.png&quot; onerror=&quot;this.onerror=null; this.src='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png'; this.srcset='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png';&quot; loading=&quot;lazy&quot; width=&quot;400&quot; height=&quot;153&quot; data-filename=&quot;edited_스크린샷 2023-07-07 22.40.35.png&quot; data-origin-width=&quot;1120&quot; data-origin-height=&quot;428&quot;/&gt;&lt;/span&gt;&lt;figcaption&gt;채널톡으로 문의했을 때 받은 답변 (1-2)&lt;/figcaption&gt;
&lt;/figure&gt;
&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;친절하게 js sdk로 특정 주소 노출 제어가 가능하다는 설명과 함께 기능 반영도 해보겠다는 답변을 받았다! 아쉽게도 이 때 js sdk를 바로 도입하지는 못 했는데, 개발 우선순위상 밀려서 다른 작업들을 먼저 치고 있었고, Next.js 환경에서 어떻게 적용할지가 좀 애매했다. 아무튼 그렇게 기능 추가를 기다리고 있었는데...&lt;/p&gt;
&lt;p&gt;&lt;figure class=&quot;imageblock alignCenter&quot; data-ke-mobileStyle=&quot;widthOrigin&quot; data-filename=&quot;edited_스크린샷 2023-07-07 22.43.09.png&quot; data-origin-width=&quot;1110&quot; data-origin-height=&quot;602&quot;&gt;&lt;span data-url=&quot;https://blog.kakaocdn.net/dn/quGFe/btsmPATzHTm/3TXY2s7kbhjE5g08bEn0z0/img.png&quot; data-phocus=&quot;https://blog.kakaocdn.net/dn/quGFe/btsmPATzHTm/3TXY2s7kbhjE5g08bEn0z0/img.png&quot; data-alt=&quot;2주 후에 받은 추가 메시지!&quot;&gt;&lt;img src=&quot;https://blog.kakaocdn.net/dn/quGFe/btsmPATzHTm/3TXY2s7kbhjE5g08bEn0z0/img.png&quot; srcset=&quot;https://img1.daumcdn.net/thumb/R1280x0/?scode=mtistory2&amp;fname=https%3A%2F%2Fblog.kakaocdn.net%2Fdn%2FquGFe%2FbtsmPATzHTm%2F3TXY2s7kbhjE5g08bEn0z0%2Fimg.png&quot; onerror=&quot;this.onerror=null; this.src='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png'; this.srcset='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png';&quot; loading=&quot;lazy&quot; width=&quot;400&quot; height=&quot;217&quot; data-filename=&quot;edited_스크린샷 2023-07-07 22.43.09.png&quot; data-origin-width=&quot;1110&quot; data-origin-height=&quot;602&quot;/&gt;&lt;/span&gt;&lt;figcaption&gt;2주 후에 받은 추가 메시지!&lt;/figcaption&gt;
&lt;/figure&gt;
&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;4월 13일, 그러니까 정확히 2주만에 기능이 추가되었다고 연락을 받았다. 사실 깜짝 놀랐다. 이렇게 바로 기능이 추가될꺼라 크게 기대 안 하고, 그냥 내부 코드로 개선하려고 백로그에 넣어두고 있었는데 빠르게 반영되다니!&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;그렇게 지금은 임베드 페이지에 한해서 공지를 노출하지 않게 해서 아주 잘 사용하고 있다.&lt;/p&gt;
&lt;p&gt;&lt;figure class=&quot;imageblock alignCenter&quot; data-ke-mobileStyle=&quot;widthOrigin&quot; data-filename=&quot;edited_스크린샷 2023-07-07 21.59.41.png&quot; data-origin-width=&quot;3000&quot; data-origin-height=&quot;1107&quot;&gt;&lt;span data-url=&quot;https://blog.kakaocdn.net/dn/x5gqJ/btsmQqJzyEV/CPKYX4NrAHEAX8oKSvk1yk/img.png&quot; data-phocus=&quot;https://blog.kakaocdn.net/dn/x5gqJ/btsmQqJzyEV/CPKYX4NrAHEAX8oKSvk1yk/img.png&quot;&gt;&lt;img src=&quot;https://blog.kakaocdn.net/dn/x5gqJ/btsmQqJzyEV/CPKYX4NrAHEAX8oKSvk1yk/img.png&quot; srcset=&quot;https://img1.daumcdn.net/thumb/R1280x0/?scode=mtistory2&amp;fname=https%3A%2F%2Fblog.kakaocdn.net%2Fdn%2Fx5gqJ%2FbtsmQqJzyEV%2FCPKYX4NrAHEAX8oKSvk1yk%2Fimg.png&quot; onerror=&quot;this.onerror=null; this.src='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png'; this.srcset='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png';&quot; loading=&quot;lazy&quot; width=&quot;3000&quot; height=&quot;1107&quot; data-filename=&quot;edited_스크린샷 2023-07-07 21.59.41.png&quot; data-origin-width=&quot;3000&quot; data-origin-height=&quot;1107&quot;/&gt;&lt;/span&gt;&lt;/figure&gt;
&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;h3 data-ke-size=&quot;size23&quot;&gt;&lt;b&gt;안정적인 운영이 가능한 이유&lt;/b&gt;&lt;/h3&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;처음에는 과연 이 서비스가 많은 트래픽이 발생했을 때 장애가 발생하지는 않을까? 또는 장애가 발생해서 우리 프로덕트에 영향을 끼치지는 않을까? 하는 고민을 했다. 그 때 트윗을 하나 보게 되었다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;blockquote class=&quot;twitter-tweet&quot;&gt;&lt;p lang=&quot;ko&quot; dir=&quot;ltr&quot;&gt;이번 코드앤버터 구성의 핵심은 팝업 설정을 json 파일로 만들어 CDN으로 제공하는 거&lt;br&gt;&lt;br&gt;동접 100만도 문제없다! 파일시스템 최고! ㅎㅎ&lt;/p&gt;&amp;mdash; subicura (@subicura) &lt;a href=&quot;https://twitter.com/subicura/status/1551720615597395968?ref_src=twsrc%5Etfw&quot;&gt;July 26, 2022&lt;/a&gt;&lt;/blockquote&gt; &lt;script async src=&quot;https://platform.twitter.com/widgets.js&quot; charset=&quot;utf-8&quot;&gt;&lt;/script&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;이제 많은 것들이 이해되기 시작했다. 어떻게 저 서비스를 저렇게 저렴한 가격으로 운영하려 하지? 아 S3+CF 구성으로 정적 서빙하면 충분히 가능하겠구나! 이러면 CF를 쓰기 때문에 (CDN을 타기 때문에) 안정적으로 전세계에 서빙이 가능하고, 트래픽 비용도 적게 들어 운영도 괜찮겠구나 싶었다. 그래서 '아 장애 걱정은 안해도 되겠다'는 생각에 마음 편하게 도입할 수 있었던 것 같다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;오늘 글을 작성하려고 개발자 문서를 열어봤는데 '동작방식 소개'라는 글이 추가되어 있었다.&lt;/p&gt;
&lt;figure id=&quot;og_1688737819558&quot; contenteditable=&quot;false&quot; data-ke-type=&quot;opengraph&quot; data-ke-align=&quot;alignCenter&quot; data-og-type=&quot;website&quot; data-og-title=&quot;코드앤버터 안내서&quot; data-og-description=&quot;동작방식 소개&quot; data-og-host=&quot;docs.codenbutter.com&quot; data-og-source-url=&quot;https://docs.codenbutter.com/developer/how-to-work.html&quot; data-og-url=&quot;https://docs.codenbutter.com/developer/how-to-work.html&quot; data-og-image=&quot;https://scrap.kakaocdn.net/dn/I0ADJ/hyTfBS2fWx/z71ij6hfcYYQ4wK8TQbUv0/img.png?width=2048&amp;amp;height=1170&amp;amp;face=0_0_2048_1170,https://scrap.kakaocdn.net/dn/bJz7Dg/hyTfGfrckA/KYOvpbdCazkHc7otk52QZ1/img.png?width=2048&amp;amp;height=1170&amp;amp;face=0_0_2048_1170&quot;&gt;&lt;a href=&quot;https://docs.codenbutter.com/developer/how-to-work.html&quot; target=&quot;_blank&quot; rel=&quot;noopener&quot; data-source-url=&quot;https://docs.codenbutter.com/developer/how-to-work.html&quot;&gt;
&lt;div class=&quot;og-image&quot; style=&quot;background-image: url('https://scrap.kakaocdn.net/dn/I0ADJ/hyTfBS2fWx/z71ij6hfcYYQ4wK8TQbUv0/img.png?width=2048&amp;amp;height=1170&amp;amp;face=0_0_2048_1170,https://scrap.kakaocdn.net/dn/bJz7Dg/hyTfGfrckA/KYOvpbdCazkHc7otk52QZ1/img.png?width=2048&amp;amp;height=1170&amp;amp;face=0_0_2048_1170');&quot;&gt;&amp;nbsp;&lt;/div&gt;
&lt;div class=&quot;og-text&quot;&gt;
&lt;p class=&quot;og-title&quot; data-ke-size=&quot;size16&quot;&gt;코드앤버터 안내서&lt;/p&gt;
&lt;p class=&quot;og-desc&quot; data-ke-size=&quot;size16&quot;&gt;동작방식 소개&lt;/p&gt;
&lt;p class=&quot;og-host&quot; data-ke-size=&quot;size16&quot;&gt;docs.codenbutter.com&lt;/p&gt;
&lt;/div&gt;
&lt;/a&gt;&lt;/figure&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;개인적으로 참 좋아하는 구조인데, 내용이 많아서 핵심적인 부분만 간단하게 정리하자면 다음과 같다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;ul style=&quot;list-style-type: disc;&quot; data-ke-list-type=&quot;disc&quot;&gt;
&lt;li&gt;MSA 구조로 되어 있고 웹 관련 코드 조각들은 Monorepo 안에 구성되어 있다.&lt;/li&gt;
&lt;li&gt;랜딩페이지와 Docs는 각각 Vercel, GitHub Pages 등 관리형 서비스로 배포되고, 나머지 앱들은 EKS로 배포된다.&lt;/li&gt;
&lt;li&gt;CI/CD는 Jenkins - ArgoCD로 일반적인 GitOps Flow를 따라간다. (values.yaml 을 변경한다는 걸로 보아 Helm을 사용하는 듯 하다) 정적 파일은 Jenkins에서 바로 구워서 S3로 올리는 듯 하다.&lt;/li&gt;
&lt;li&gt;테스트코드도 잘 작성되어 있다. 테스트도 Jenkins에서 실행되는 듯 하다.&lt;/li&gt;
&lt;/ul&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;구조적으로도 2023년답게 잘 설계되어 있고, 성능과 비용 측면을 고려하여 정적 파일을 최대한 활용한 부분도 인상 깊었다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;h3 data-ke-size=&quot;size23&quot;&gt;&lt;b&gt;정리 및 총평&lt;/b&gt;&lt;/h3&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;실제 프로덕션 환경에서 아주 유용하게 사용했고, 지금도 계속 사용 중인 만큼, 프로덕션 환경에서 충분히 활용할 수 있다고 검증된 것 같다. 특히 서비스가 아키텍쳐 면에서도 잘 짜여져있고, 저렴한 비용으로 안정적인 운영도 가능할 것으로 보인다. 그나마 가장 아쉽다고 할만 한 부분이 에디터였는데, 이것도 점점 개선되는 것 같다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;그간 5~6개월가량 사용해봤는데 초기 스타트업이거나, 제품 초기에 MVP를 만들어가는 단계의 프로덕트에 매우 추천하고 싶다. 기본적으로 노코드이기 때문에 대부분의 케이스에 적합하긴 하지만, 개발 리소스가 충분한 일정 규모 이상의 프로덕트에서는 오히려 자체 개발하는게 장기적으로는 요구사항 맞추기에 더 적합할 수 있을 것 같다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;앞으로가 더 기대되는 프로덕트인 것 같다. 코드앤버터의 성장과 성공을 기원한다!&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;</description>
      <category>개발 기타</category>
      <author>HAENU</author>
      <guid isPermaLink="true">https://dokdo2013.tistory.com/11</guid>
      <comments>https://dokdo2013.tistory.com/11#entry11comment</comments>
      <pubDate>Fri, 7 Jul 2023 23:21:19 +0900</pubDate>
    </item>
    <item>
      <title>Next.js SSR 환경에서 Mantine 사용하기</title>
      <link>https://dokdo2013.tistory.com/8</link>
      <description>&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;figure id=&quot;og_1688566193358&quot; contenteditable=&quot;false&quot; data-ke-type=&quot;opengraph&quot; data-ke-align=&quot;alignCenter&quot; data-og-type=&quot;website&quot; data-og-title=&quot;Mantine&quot; data-og-description=&quot;You've submitted a pull request Fix incorrect notification message (#187) 34 minutes ago&quot; data-og-host=&quot;mantine.dev&quot; data-og-source-url=&quot;https://mantine.dev/&quot; data-og-url=&quot;https://mantine.dev/&quot; data-og-image=&quot;https://scrap.kakaocdn.net/dn/ccuwga/hyTd6FL2xZ/kg2FGiTlSPWE6qjvjKdqM0/img.png?width=1280&amp;amp;height=902&amp;amp;face=0_0_1280_902&quot;&gt;&lt;a href=&quot;https://mantine.dev/&quot; target=&quot;_blank&quot; rel=&quot;noopener&quot; data-source-url=&quot;https://mantine.dev/&quot;&gt;
&lt;div class=&quot;og-image&quot; style=&quot;background-image: url('https://scrap.kakaocdn.net/dn/ccuwga/hyTd6FL2xZ/kg2FGiTlSPWE6qjvjKdqM0/img.png?width=1280&amp;amp;height=902&amp;amp;face=0_0_1280_902');&quot;&gt;&amp;nbsp;&lt;/div&gt;
&lt;div class=&quot;og-text&quot;&gt;
&lt;p class=&quot;og-title&quot; data-ke-size=&quot;size16&quot;&gt;Mantine&lt;/p&gt;
&lt;p class=&quot;og-desc&quot; data-ke-size=&quot;size16&quot;&gt;You've submitted a pull request Fix incorrect notification message (#187) 34 minutes ago&lt;/p&gt;
&lt;p class=&quot;og-host&quot; data-ke-size=&quot;size16&quot;&gt;mantine.dev&lt;/p&gt;
&lt;/div&gt;
&lt;/a&gt;&lt;/figure&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;h3 data-ke-size=&quot;size23&quot;&gt;&lt;b&gt;Mantine이란?&lt;/b&gt;&lt;/h3&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;Mantine은 React에서 사용할 수 있는 UI Component 라이브러리이다. 2023년 7월 기준 v6까지 출시되었으며, 지속적으로 업데이트되고 있다. Chakra UI와 컨셉이 굉장히 유사한데, 깔끔하고 직관적인 디자인에 풍부한 컴포넌트, 다양하고 유용한 훅 제공 등, 단순 UI 컴포넌트뿐 아니라 종합 라이브러리 역할도 겸하는 것 같다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p&gt;&lt;figure class=&quot;imageblock alignCenter&quot; data-ke-mobileStyle=&quot;widthOrigin&quot; data-origin-width=&quot;1733&quot; data-origin-height=&quot;346&quot;&gt;&lt;span data-url=&quot;https://blog.kakaocdn.net/dn/cRpEFO/btsmzir9ezk/TUJTcijXNeetdgFvQZW4K1/img.png&quot; data-phocus=&quot;https://blog.kakaocdn.net/dn/cRpEFO/btsmzir9ezk/TUJTcijXNeetdgFvQZW4K1/img.png&quot;&gt;&lt;img src=&quot;https://blog.kakaocdn.net/dn/cRpEFO/btsmzir9ezk/TUJTcijXNeetdgFvQZW4K1/img.png&quot; srcset=&quot;https://img1.daumcdn.net/thumb/R1280x0/?scode=mtistory2&amp;fname=https%3A%2F%2Fblog.kakaocdn.net%2Fdn%2FcRpEFO%2Fbtsmzir9ezk%2FTUJTcijXNeetdgFvQZW4K1%2Fimg.png&quot; onerror=&quot;this.onerror=null; this.src='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png'; this.srcset='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png';&quot; loading=&quot;lazy&quot; width=&quot;1733&quot; height=&quot;346&quot; data-origin-width=&quot;1733&quot; data-origin-height=&quot;346&quot;/&gt;&lt;/span&gt;&lt;/figure&gt;
&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;여러 프레임워크/라이브러리들을 지원하고 있고, 최근 많이 사용되는 Next.js 환경에서 Mantine을 사용하면서 겪은 Layout Shift 이슈를 해결한 경험을 공유하고자 한다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;h3 data-ke-size=&quot;size23&quot;&gt;&lt;b&gt;Next.js SSR 환경에서 발생하는 Layout Shift&lt;/b&gt;&lt;/h3&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;Mantine은 내부적으로 Emotion을 이용해 스타일링이 되어 있다. 따라서 MantineProvider를 가져오기만 하면 Emotion cache를 자동으로 생성한다. 그런데 이게 SSR 환경에서는 기본설정으로 잘 동작하지 않는다. 이건 Emotion 자체의 이슈이기 때문에 Emotion을 사용하여 스타일링하는 Mantine이나 MUI 등 여러 라이브러리들이 공통으로 겪는 문제이다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;왜 그런 현상이 발생하는지는 검색해보니 참고할만 한 글이 있어서 &lt;a title=&quot;링크&quot; href=&quot;https://junghan92.medium.com/%EB%B2%88%EC%97%AD-%EC%9A%B0%EB%A6%AC%EA%B0%80-css-in-js%EC%99%80-%ED%97%A4%EC%96%B4%EC%A7%80%EB%8A%94-%EC%9D%B4%EC%9C%A0-a2e726d6ace6&quot; target=&quot;_blank&quot; rel=&quot;noopener&quot;&gt;링크&lt;/a&gt;해본다. 해당 내용도 발췌해봤다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;blockquote data-ke-style=&quot;style2&quot;&gt;- Emotion의 여러 인스턴스가 한 번에 로드됩니다. 이는 여러 인스턴스가 모두 동일한 버전의 Emotion인 경우에도 문제를 일으킬 수 있습니다.&lt;br /&gt;&lt;br /&gt;- 컴포넌트 라이브러리는 스타일이 삽입되는 순서를 완전히 제어할 수 없는 경우가 많습니다.&lt;br /&gt;&lt;br /&gt;- Emotion의 SSR지원은 리액트 17과 리액트 18에서 다르게 작동합니다. 이는 리액트 18의 스트리밍 SSR과의 호환성을 위해 필요했습니다.&lt;/blockquote&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;어찌되었건 이 문제로 인해 초기 로딩에서 Layout Shift가 발생한다. CSS가 제대로 로드되지 않은 상태에서 먼저 깨진 레이아웃이 보이고, 그 다음에 스타일이 로드되면서 자리를 찾아간다. 이것 자체가 사이트 성능에도 부정적인 영향을 끼치고, UX에도 마찬가지로 좋지 않다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;다행히도 Mantine에서 가이드를 제공해주고 있어서 그걸 참고하면 된다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;h3 data-ke-size=&quot;size23&quot;&gt;&lt;b&gt;적용해보기&lt;/b&gt;&lt;/h3&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;링크는 아래 참고 자료 란에 있다. 참고해서 바로 적용해보자.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;pre id=&quot;code_1688568000566&quot; class=&quot;typescript&quot; data-ke-language=&quot;typescript&quot; data-ke-type=&quot;codeblock&quot;&gt;&lt;code&gt;// pages/_document.tsx

const stylesServer = createStylesServer(emotionCache());

class _Document extends Document {
  static async getInitialProps(ctx: DocumentContext) {
    const initialProps = await Document.getInitialProps(ctx);

    return {
      ...initialProps,
      styles: (
        &amp;lt;&amp;gt;
          {initialProps.styles}
          &amp;lt;ServerStyles
            html={initialProps.html}
            server={stylesServer}
            key=&quot;styles&quot;
          /&amp;gt;
        &amp;lt;/&amp;gt;
      ),
    };
  }
  
  render() {
    return (
      &amp;lt;Html lang=&quot;ko&quot;&amp;gt;
        &amp;lt;Head&amp;gt;
        
        ...
        
        &amp;lt;/Head&amp;gt;
        &amp;lt;body&amp;gt;
          &amp;lt;Main /&amp;gt;
          &amp;lt;NextScript /&amp;gt;
        &amp;lt;/body&amp;gt;
      &amp;lt;/Html&amp;gt;
    );
  }
}&lt;/code&gt;&lt;/pre&gt;
&lt;pre id=&quot;code_1688568051279&quot; class=&quot;typescript&quot; data-ke-language=&quot;typescript&quot; data-ke-type=&quot;codeblock&quot;&gt;&lt;code&gt;// pages/_app.tsx

import { emotionCache } from &quot;../src/emotionCache&quot;;

...

&amp;lt;MantineProvider
  ...
  emotionCache={emotionCache()}
&amp;gt;&lt;/code&gt;&lt;/pre&gt;
&lt;pre id=&quot;code_1688568345251&quot; class=&quot;typescript&quot; data-ke-language=&quot;typescript&quot; data-ke-type=&quot;codeblock&quot;&gt;&lt;code&gt;// src/emotionCache.ts

import { createEmotionCache, EmotionCache } from &quot;@mantine/core&quot;;

let cache: EmotionCache | undefined;

export const emotionCache = () =&amp;gt; {
  if (!cache) {
    cache = createEmotionCache({
      key: &quot;example-cache-key&quot;,
      prepend: false,
    });
  }
  return cache;
};&lt;/code&gt;&lt;/pre&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;이렇게 적용하면 Emotion cache를 SSR 환경에서 정상적으로 이용할 수 있다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;h3 data-ke-size=&quot;size23&quot;&gt;&lt;b&gt;참고 자료&lt;/b&gt;&lt;/h3&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;a href=&quot;https://mantine.dev/theming/emotion-cache/&quot; target=&quot;_blank&quot; rel=&quot;noopener&quot;&gt;https://mantine.dev/theming/emotion-cache/&lt;/a&gt;&lt;/p&gt;
&lt;figure id=&quot;og_1688566657154&quot; contenteditable=&quot;false&quot; data-ke-type=&quot;opengraph&quot; data-ke-align=&quot;alignCenter&quot; data-og-type=&quot;website&quot; data-og-title=&quot;Emotion cache | Mantine&quot; data-og-description=&quot;Up next rem, em and px units &amp;ndash; Styles&quot; data-og-host=&quot;mantine.dev&quot; data-og-source-url=&quot;https://mantine.dev/theming/emotion-cache/&quot; data-og-url=&quot;https://mantine.dev/theming/emotion-cache/&quot; data-og-image=&quot;&quot;&gt;&lt;a href=&quot;https://mantine.dev/theming/emotion-cache/&quot; target=&quot;_blank&quot; rel=&quot;noopener&quot; data-source-url=&quot;https://mantine.dev/theming/emotion-cache/&quot;&gt;
&lt;div class=&quot;og-image&quot; style=&quot;background-image: url();&quot;&gt;&amp;nbsp;&lt;/div&gt;
&lt;div class=&quot;og-text&quot;&gt;
&lt;p class=&quot;og-title&quot; data-ke-size=&quot;size16&quot;&gt;Emotion cache | Mantine&lt;/p&gt;
&lt;p class=&quot;og-desc&quot; data-ke-size=&quot;size16&quot;&gt;Up next rem, em and px units &amp;ndash; Styles&lt;/p&gt;
&lt;p class=&quot;og-host&quot; data-ke-size=&quot;size16&quot;&gt;mantine.dev&lt;/p&gt;
&lt;/div&gt;
&lt;/a&gt;&lt;/figure&gt;</description>
      <category>Frontend</category>
      <category>EMOTION</category>
      <category>mantine</category>
      <category>nextjs</category>
      <author>HAENU</author>
      <guid isPermaLink="true">https://dokdo2013.tistory.com/8</guid>
      <comments>https://dokdo2013.tistory.com/8#entry8comment</comments>
      <pubDate>Wed, 5 Jul 2023 23:49:00 +0900</pubDate>
    </item>
    <item>
      <title>Cloudflare Pages는 게임 체인져가 될 수 있을까?</title>
      <link>https://dokdo2013.tistory.com/7</link>
      <description>&lt;p&gt;&lt;figure class=&quot;imageblock alignCenter&quot; data-ke-mobileStyle=&quot;widthOrigin&quot; data-filename=&quot;005 Cloudflare Pages는 게임체인져가 될 수 있을까_-001.png&quot; data-origin-width=&quot;1080&quot; data-origin-height=&quot;1080&quot;&gt;&lt;span data-url=&quot;https://blog.kakaocdn.net/dn/k68yS/btrE4TVAclH/w0Sk5oanpwSK5E648mZhSk/img.png&quot; data-phocus=&quot;https://blog.kakaocdn.net/dn/k68yS/btrE4TVAclH/w0Sk5oanpwSK5E648mZhSk/img.png&quot;&gt;&lt;img src=&quot;https://blog.kakaocdn.net/dn/k68yS/btrE4TVAclH/w0Sk5oanpwSK5E648mZhSk/img.png&quot; srcset=&quot;https://img1.daumcdn.net/thumb/R1280x0/?scode=mtistory2&amp;fname=https%3A%2F%2Fblog.kakaocdn.net%2Fdn%2Fk68yS%2FbtrE4TVAclH%2Fw0Sk5oanpwSK5E648mZhSk%2Fimg.png&quot; onerror=&quot;this.onerror=null; this.src='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png'; this.srcset='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png';&quot; loading=&quot;lazy&quot; width=&quot;300&quot; height=&quot;300&quot; data-filename=&quot;005 Cloudflare Pages는 게임체인져가 될 수 있을까_-001.png&quot; data-origin-width=&quot;1080&quot; data-origin-height=&quot;1080&quot;/&gt;&lt;/span&gt;&lt;/figure&gt;
&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;JAM 스택의 웹앱을 호스팅해주는 PaaS(Platform as a Service)가 여럿 존재한다. Netlify, Vercel, Github Pages 등... 여기에 Cloudflare Pages가 도전장을 내밀고 있는 추세이다. 클라우드플레어가 보유하고 있는 인프라와 네트워크가 주무기인데, 과연 Cloudflare는 기존의 벽을 넘어설 수 있을까?&lt;/p&gt;
&lt;p&gt;&lt;figure class=&quot;imageblock alignCenter&quot; data-ke-mobileStyle=&quot;widthOrigin&quot; data-filename=&quot;스크린샷 2022-06-18 17.39.26.png&quot; data-origin-width=&quot;2234&quot; data-origin-height=&quot;1324&quot;&gt;&lt;span data-url=&quot;https://blog.kakaocdn.net/dn/FW8bp/btrE4VzCjpp/2vmezNjj8dkB1sZnj6JzL1/img.png&quot; data-phocus=&quot;https://blog.kakaocdn.net/dn/FW8bp/btrE4VzCjpp/2vmezNjj8dkB1sZnj6JzL1/img.png&quot; data-alt=&quot;Cloudflare Pages 공식 홈페이지&quot;&gt;&lt;img src=&quot;https://blog.kakaocdn.net/dn/FW8bp/btrE4VzCjpp/2vmezNjj8dkB1sZnj6JzL1/img.png&quot; srcset=&quot;https://img1.daumcdn.net/thumb/R1280x0/?scode=mtistory2&amp;fname=https%3A%2F%2Fblog.kakaocdn.net%2Fdn%2FFW8bp%2FbtrE4VzCjpp%2F2vmezNjj8dkB1sZnj6JzL1%2Fimg.png&quot; onerror=&quot;this.onerror=null; this.src='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png'; this.srcset='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png';&quot; loading=&quot;lazy&quot; width=&quot;2234&quot; height=&quot;1324&quot; data-filename=&quot;스크린샷 2022-06-18 17.39.26.png&quot; data-origin-width=&quot;2234&quot; data-origin-height=&quot;1324&quot;/&gt;&lt;/span&gt;&lt;figcaption&gt;Cloudflare Pages 공식 홈페이지&lt;/figcaption&gt;
&lt;/figure&gt;
&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;Cloudflare 공식 홈페이지에 올라온 바에 따르면 빌드와 배포가 빠르다는 것을 전면에 내세우고 있다. 또 기존에 클라우드플레어가 보유 중인 edge network의 퍼포먼스를 온전히 활용할 수 있다는 점과, 서버리스 플랫폼인 Cloudflare Workers를 이용해 풀스택 서비스를 만들어낼 수 있다는 점도 눈에 띈다. 직접 Cloudflare Pages에 서비스를 올려보며 느낀 경험을 나눠보고 싶다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;h3 data-ke-size=&quot;size23&quot;&gt;&lt;b&gt;좋았던 점&lt;/b&gt;&lt;/h3&gt;
&lt;h4 data-ke-size=&quot;size20&quot;&gt;&lt;b&gt;1. 프로젝트 연결이 간편하다&lt;/b&gt;&lt;/h4&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;기본적으로 깃허브와 연동해서 돌아가서 굉장히 편리했다. 특히 빌드/배포 성공 여부와 배포된 위치를 Check로 알 수 있어 그 점도 좋았다.&lt;/p&gt;
&lt;p&gt;&lt;figure class=&quot;imageblock alignCenter&quot; data-ke-mobileStyle=&quot;widthOrigin&quot; data-filename=&quot;스크린샷 2022-06-18 18.31.20.png&quot; data-origin-width=&quot;900&quot; data-origin-height=&quot;704&quot;&gt;&lt;span data-url=&quot;https://blog.kakaocdn.net/dn/2ifv1/btrE4Vl44TH/Y0BHsRn4P4nwKgYmQmmttK/img.png&quot; data-phocus=&quot;https://blog.kakaocdn.net/dn/2ifv1/btrE4Vl44TH/Y0BHsRn4P4nwKgYmQmmttK/img.png&quot;&gt;&lt;img src=&quot;https://blog.kakaocdn.net/dn/2ifv1/btrE4Vl44TH/Y0BHsRn4P4nwKgYmQmmttK/img.png&quot; srcset=&quot;https://img1.daumcdn.net/thumb/R1280x0/?scode=mtistory2&amp;fname=https%3A%2F%2Fblog.kakaocdn.net%2Fdn%2F2ifv1%2FbtrE4Vl44TH%2FY0BHsRn4P4nwKgYmQmmttK%2Fimg.png&quot; onerror=&quot;this.onerror=null; this.src='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png'; this.srcset='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png';&quot; loading=&quot;lazy&quot; width=&quot;500&quot; height=&quot;391&quot; data-filename=&quot;스크린샷 2022-06-18 18.31.20.png&quot; data-origin-width=&quot;900&quot; data-origin-height=&quot;704&quot;/&gt;&lt;/span&gt;&lt;/figure&gt;
&lt;/p&gt;
&lt;h4 data-ke-size=&quot;size20&quot;&gt;&lt;b&gt;2. Cloudflare을 DNS Service Provider로 사용한다면 도메인 연결이 너무도 간편!&lt;/b&gt;&lt;/h4&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;기존에 DNS 서비스를 Cloudflare에서 이용하고 있었다면 배포한 서비스에 도메인을 연결하는 과정이 너무 간편했다. 연결할 도메인을 입력하면 CNAME도 자동으로 등록해주고 Pages와 연결까지 해주니 정말 편했다. 특히 Vercel이나 Netlify에서 Cloudflare에 등록된 도메인을 사용하려고 하면 꽤 복잡한 과정을 거쳐야하기에 이 장점은 더 두드러지는 것 같다.&lt;/p&gt;
&lt;h4 data-ke-size=&quot;size20&quot;&gt;&lt;b&gt;3. Zero Trust를 이용한 접근제어&lt;/b&gt;&lt;/h4&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;Staging 환경에서 특정 유저나 특정 IP 대역으로 접근을 제어하고 싶을 때가 있다. 보통은 앞에 서버나 LB같은 인프라 단이 있어 접근을 제어해줄 수 있지만 PaaS 서비스들은 직접적인 접근 제어를 구현하기 어렵다. 하지만 Cloudflare는 자체적으로 Zero Trust라는 권한관리, 접근제어 솔루션을 가지고 있어 승인받은 유저에 대해서만 접근하도록 네트워크단에서 제어해줄 수 있어 차별점을 가지고 있다.&lt;/p&gt;
&lt;h4 data-ke-size=&quot;size20&quot;&gt;&lt;b&gt;4. 무제한 트래픽? 와...&lt;/b&gt;&lt;/h4&gt;
&lt;p&gt;&lt;figure class=&quot;imageblock alignCenter&quot; data-ke-mobileStyle=&quot;widthOrigin&quot; data-filename=&quot;스크린샷 2022-06-18 18.56.02.png&quot; data-origin-width=&quot;2290&quot; data-origin-height=&quot;1216&quot;&gt;&lt;span data-url=&quot;https://blog.kakaocdn.net/dn/crBZ71/btrE4T87LGT/gGUgDBA27KQdmOYkKhK6I0/img.png&quot; data-phocus=&quot;https://blog.kakaocdn.net/dn/crBZ71/btrE4T87LGT/gGUgDBA27KQdmOYkKhK6I0/img.png&quot; data-alt=&quot;Cloudflare Pages 가격표&quot;&gt;&lt;img src=&quot;https://blog.kakaocdn.net/dn/crBZ71/btrE4T87LGT/gGUgDBA27KQdmOYkKhK6I0/img.png&quot; srcset=&quot;https://img1.daumcdn.net/thumb/R1280x0/?scode=mtistory2&amp;fname=https%3A%2F%2Fblog.kakaocdn.net%2Fdn%2FcrBZ71%2FbtrE4T87LGT%2FgGUgDBA27KQdmOYkKhK6I0%2Fimg.png&quot; onerror=&quot;this.onerror=null; this.src='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png'; this.srcset='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png';&quot; loading=&quot;lazy&quot; width=&quot;2290&quot; height=&quot;1216&quot; data-filename=&quot;스크린샷 2022-06-18 18.56.02.png&quot; data-origin-width=&quot;2290&quot; data-origin-height=&quot;1216&quot;/&gt;&lt;/span&gt;&lt;figcaption&gt;Cloudflare Pages 가격표&lt;/figcaption&gt;
&lt;/figure&gt;
&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;이게 말이 돼?? 소리가 나올만 한 가격이다. 특히 무료 플랜의 구성이 기가 막히게 좋다. 빌드 수 제한이 있긴 하지만, 개인 프로젝트에 500 빌드는 그렇게 적은 수치이진 않을 것 같다. 여기에 무제한 사이트, 무제한 요청, 무제한 대역폭을 제공한다는 것은 믿기지 않을 정도이다. 여기에 월 20$를 지불하는 Pro 요금제의 경우 동시 빌드를 5개까지 제공해, 동시 빌드 1개 추가마다 50$의 비용을 지불해야 하는 Vercel과 비교하면 정말 혜자스러운 구성이다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;h3 data-ke-size=&quot;size23&quot;&gt;&lt;b&gt;안 좋았던 점&lt;/b&gt;&lt;/h3&gt;
&lt;h4 data-ke-size=&quot;size20&quot;&gt;&lt;b&gt;5. 정적 웹사이트 호스팅에 최적화되어 있어 SSR을 활용하기에 불리하다&lt;/b&gt;&lt;/h4&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;React 진영에서 가장 대중적으로 사용되는 Next.js에 대해 Static HTML Export를 지원해 온전한 방식으로 SSR을 활용할 수 없다. 일부 사용자별로 니즈를 충족하지 못하는 경우가 생길 수 있을 것 같다.&lt;/p&gt;
&lt;h4 data-ke-size=&quot;size20&quot;&gt;&lt;b&gt;6. 외부 서비스들에서의 Integration이 좋지 않다 (Slack, Sentry 등)&lt;/b&gt;&lt;/h4&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;깃허브 외에 다른 서비스들에서 Cloudflare Pages와의 연동성이 별로 좋지 않다. 특히 배포 상황에 대해 Slack 알림을 현재는 지원하지 않아서 직접 대시보드에서 확인해야하는게 조금 아쉬운 부분이다. 다만 이는 Github의 Build Check 기능을 이용하면 어느정도 커버할 수 있는 부분이기도 하다. 이외에 Vercel과 조금 비교되는 부분은 Sentry 연동에서의 편의성이다. Vercel에서는 클릭 몇 번만으로 Sentry와의 연동 진행이 가능했다. 각종 Secret Key들을 환경변수로 잡아주고, 배포가 이뤄지면 Sentry에도 똑같이 반영되어 에러 트래킹에 편리했다. 하지만 Pages에서는 지원하지 않아 따로 설정해줄 수 없었다.&lt;/p&gt;
&lt;p&gt;&lt;figure class=&quot;imageblock alignCenter&quot; data-ke-mobileStyle=&quot;widthOrigin&quot; data-filename=&quot;스크린샷 2022-06-18 22.49.01.png&quot; data-origin-width=&quot;1480&quot; data-origin-height=&quot;594&quot;&gt;&lt;span data-url=&quot;https://blog.kakaocdn.net/dn/cH7cpa/btrFaNsVjl8/13py7NZ7J3GhraUF17Q9CK/img.png&quot; data-phocus=&quot;https://blog.kakaocdn.net/dn/cH7cpa/btrFaNsVjl8/13py7NZ7J3GhraUF17Q9CK/img.png&quot; data-alt=&quot;Vercel Sentry Integration (Environment Variables)&quot;&gt;&lt;img src=&quot;https://blog.kakaocdn.net/dn/cH7cpa/btrFaNsVjl8/13py7NZ7J3GhraUF17Q9CK/img.png&quot; srcset=&quot;https://img1.daumcdn.net/thumb/R1280x0/?scode=mtistory2&amp;fname=https%3A%2F%2Fblog.kakaocdn.net%2Fdn%2FcH7cpa%2FbtrFaNsVjl8%2F13py7NZ7J3GhraUF17Q9CK%2Fimg.png&quot; onerror=&quot;this.onerror=null; this.src='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png'; this.srcset='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png';&quot; loading=&quot;lazy&quot; width=&quot;1480&quot; height=&quot;594&quot; data-filename=&quot;스크린샷 2022-06-18 22.49.01.png&quot; data-origin-width=&quot;1480&quot; data-origin-height=&quot;594&quot;/&gt;&lt;/span&gt;&lt;figcaption&gt;Vercel Sentry Integration (Environment Variables)&lt;/figcaption&gt;
&lt;/figure&gt;
&lt;/p&gt;
&lt;h4 data-ke-size=&quot;size20&quot;&gt;&lt;b&gt;7. 빌드 성능이 영...&lt;/b&gt;&lt;/h4&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;따로 테스트 결과를 가져오진 않았는데 Vercel과 비교하여 Cloudflare Pages의 빌드 속도가 2배 가까이 느리게 느껴졌다. 최근에 Fast Build 기능이 도입되어 빌드 속도가 더 빨라졌다고 하는데... 빨라진 빌드 속도도 타 플랫폼 대비 느리게 느껴져서 약간 답답했다.&lt;b&gt;&lt;/b&gt;&lt;/p&gt;
&lt;h4 data-ke-size=&quot;size20&quot;&gt;&lt;b&gt;8. API 지원 부실&lt;/b&gt;&lt;/h4&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;Cloudflare에서 Pages 관련해서 컨트롤할 수 있는 API를 거의 제공하지 않아서, 외부에 서드파티로 뭔가를 붙여서 사용한다거나 하기에 불편해보였다. 다만 이건 시간이 해결해 줄 문제일 것 같다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;h3 data-ke-size=&quot;size23&quot;&gt;&lt;b&gt;총평/결론 - 그래서 Cloudflare Pages는 게임 체인져가 될 수 있을 것인가?&lt;/b&gt;&lt;/h3&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;기존에 Cloudflare 인프라를 이용했다면 확실히 사용하기에 편해서 강력히 추천한다. 특히 저렴한 가격과 Zero Trust 기반으로 제공되는 접근 관리는 상당히 인상적이었다. 또 Cloudflare Workers를 백엔드로 이용 중이라면 Pages만큼 좋은 선택이 없을 것이다. 하지만 개선이 필요한 부분도 확실했다. Fast하다고 하지만 Fast하지 못한 빌드 속도, 외부 서비스 연동이 상당히 제한적이라는 부분 역시 개선될 필요가 보였다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;그런 단점들에도 불구하고, 여전히 Cloudflare Pages는 매력적인 선택지이다. CI/CD 구축 과정을 단순화하고 개발 외적의 공수를 최소한으로 줄이고 개발에만 집중할 수 있는 PaaS 서비스들은 계속해서 시장의 파이를 넓혀갈 것이며, Cloudflare의 광범위한 글로벌 인프라와 지원은 Pages 플랫폼의 큰 성장으로 이어질 것이다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;그래서 결론!!&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;네트워크와 CDN 인프라의 강자 Cloudflare가 건재한 이상, Pages가 게임 체인져가 될 잠재성은 충분히 가지고 있다고 본다. 앞으로의 발전이 더욱 더 기대된다!!&lt;/p&gt;</description>
      <category>Frontend</category>
      <category>CloudFlare</category>
      <category>cloudflare pages</category>
      <category>vercel</category>
      <category>클라우드플레어</category>
      <author>HAENU</author>
      <guid isPermaLink="true">https://dokdo2013.tistory.com/7</guid>
      <comments>https://dokdo2013.tistory.com/7#entry7comment</comments>
      <pubDate>Sat, 18 Jun 2022 23:04:57 +0900</pubDate>
    </item>
    <item>
      <title>나와 가장 잘 맞는 레븐 멤버는?</title>
      <link>https://dokdo2013.tistory.com/6</link>
      <description>&lt;p&gt;&lt;figure class=&quot;imageblock alignCenter&quot; data-ke-mobileStyle=&quot;widthOrigin&quot; data-filename=&quot;제목을-입력해주세요_-001.png&quot; data-origin-width=&quot;1080&quot; data-origin-height=&quot;1080&quot;&gt;&lt;span data-url=&quot;https://blog.kakaocdn.net/dn/JHbkt/btrE5RQNjl5/5hxp4Rkc6JRCh1P1KI2kk1/img.png&quot; data-phocus=&quot;https://blog.kakaocdn.net/dn/JHbkt/btrE5RQNjl5/5hxp4Rkc6JRCh1P1KI2kk1/img.png&quot;&gt;&lt;img src=&quot;https://blog.kakaocdn.net/dn/JHbkt/btrE5RQNjl5/5hxp4Rkc6JRCh1P1KI2kk1/img.png&quot; srcset=&quot;https://img1.daumcdn.net/thumb/R1280x0/?scode=mtistory2&amp;fname=https%3A%2F%2Fblog.kakaocdn.net%2Fdn%2FJHbkt%2FbtrE5RQNjl5%2F5hxp4Rkc6JRCh1P1KI2kk1%2Fimg.png&quot; onerror=&quot;this.onerror=null; this.src='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png'; this.srcset='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png';&quot; loading=&quot;lazy&quot; width=&quot;300&quot; height=&quot;300&quot; data-filename=&quot;제목을-입력해주세요_-001.png&quot; data-origin-width=&quot;1080&quot; data-origin-height=&quot;1080&quot;/&gt;&lt;/span&gt;&lt;/figure&gt;
&lt;figure class=&quot;imageblock alignCenter&quot; data-ke-mobileStyle=&quot;widthOrigin&quot; data-filename=&quot;스크린샷_2022-06-05_20.36.04.png&quot; data-origin-width=&quot;1950&quot; data-origin-height=&quot;886&quot;&gt;&lt;span data-url=&quot;https://blog.kakaocdn.net/dn/FosOO/btrDZuW73R1/7ze4p9fkwa8cQ5PehmoGL1/img.png&quot; data-phocus=&quot;https://blog.kakaocdn.net/dn/FosOO/btrDZuW73R1/7ze4p9fkwa8cQ5PehmoGL1/img.png&quot;&gt;&lt;img src=&quot;https://blog.kakaocdn.net/dn/FosOO/btrDZuW73R1/7ze4p9fkwa8cQ5PehmoGL1/img.png&quot; srcset=&quot;https://img1.daumcdn.net/thumb/R1280x0/?scode=mtistory2&amp;fname=https%3A%2F%2Fblog.kakaocdn.net%2Fdn%2FFosOO%2FbtrDZuW73R1%2F7ze4p9fkwa8cQ5PehmoGL1%2Fimg.png&quot; onerror=&quot;this.onerror=null; this.src='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png'; this.srcset='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png';&quot; loading=&quot;lazy&quot; width=&quot;1950&quot; height=&quot;886&quot; data-filename=&quot;스크린샷_2022-06-05_20.36.04.png&quot; data-origin-width=&quot;1950&quot; data-origin-height=&quot;886&quot;/&gt;&lt;/span&gt;&lt;/figure&gt;
&lt;/p&gt;
&lt;p style=&quot;text-align: center;&quot; data-ke-size=&quot;size16&quot;&gt;&lt;b&gt;5개의 질문&lt;/b&gt;을 통해 나와 가장 잘 맞는 레븐 멤버를 찾아보는 &lt;b&gt;간단한 미니게임&lt;/b&gt;입니다!&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;h3 data-ke-size=&quot;size23&quot;&gt;여기서 잠깐! &lt;b&gt;레븐&lt;/b&gt;이 뭔가요?&lt;/h3&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;10인조 버츄얼 그룹으로, 주로 노래 커버영상 위주로 활동 중이랍니다~ 트위치에 오시면 멤버별 생방송도 볼 수 있으니 많관부!&lt;/p&gt;
&lt;figure id=&quot;og_1654446792968&quot; contenteditable=&quot;false&quot; data-ke-type=&quot;opengraph&quot; data-ke-align=&quot;alignCenter&quot; data-og-type=&quot;profile&quot; data-og-title=&quot;레븐 Leaven Official&quot; data-og-description=&quot;&quot; data-og-host=&quot;www.youtube.com&quot; data-og-source-url=&quot;https://www.youtube.com/channel/UCDLtY-WrAwy7orpK9aVWG8g&quot; data-og-url=&quot;https://www.youtube.com/channel/UCDLtY-WrAwy7orpK9aVWG8g&quot; data-og-image=&quot;https://scrap.kakaocdn.net/dn/b37Iml/hyOEiLthcN/cNFhMAHKlJYNklmTaFdlC0/img.jpg?width=900&amp;amp;height=900&amp;amp;face=0_0_900_900,https://scrap.kakaocdn.net/dn/3wgj8/hyOEj4GMeC/o49efwrjXL3KjnMrb5y3qk/img.jpg?width=900&amp;amp;height=900&amp;amp;face=0_0_900_900&quot;&gt;&lt;a href=&quot;https://www.youtube.com/channel/UCDLtY-WrAwy7orpK9aVWG8g&quot; target=&quot;_blank&quot; rel=&quot;noopener&quot; data-source-url=&quot;https://www.youtube.com/channel/UCDLtY-WrAwy7orpK9aVWG8g&quot;&gt;
&lt;div class=&quot;og-image&quot; style=&quot;background-image: url('https://scrap.kakaocdn.net/dn/b37Iml/hyOEiLthcN/cNFhMAHKlJYNklmTaFdlC0/img.jpg?width=900&amp;amp;height=900&amp;amp;face=0_0_900_900,https://scrap.kakaocdn.net/dn/3wgj8/hyOEj4GMeC/o49efwrjXL3KjnMrb5y3qk/img.jpg?width=900&amp;amp;height=900&amp;amp;face=0_0_900_900');&quot;&gt;&amp;nbsp;&lt;/div&gt;
&lt;div class=&quot;og-text&quot;&gt;
&lt;p class=&quot;og-title&quot; data-ke-size=&quot;size16&quot;&gt;레븐 Leaven Official&lt;/p&gt;
&lt;p class=&quot;og-desc&quot; data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p class=&quot;og-host&quot; data-ke-size=&quot;size16&quot;&gt;www.youtube.com&lt;/p&gt;
&lt;/div&gt;
&lt;/a&gt;&lt;/figure&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;h3 data-ke-size=&quot;size23&quot;&gt;&lt;b&gt;개발동기 및 개발과정&lt;/b&gt;&lt;/h3&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;간단한 설문형 게임이나 mbti와 유사한 검사들이 netlify 등으로 배포되어 많은 호응을 얻고 있는 걸로 알고 있다. 이에 그것들과 유사하게 질문형 미니게임을 만들어보고자 계획했다. 더불어 기술적으로 몇 가지 새로운 도전도 해봤다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;기존에는 일반적인 CRA로 제작하는 경우가 많았지만, 이번에는 Next.js(SSR)와 Tailwind CSS로 프론트 개발을 진행했다. Vercel에 배포하였으며 Sentry까지 연동하여 운영 측면까지 고려했다. 기존처럼 Cloudflare Pages에 배포할까 하다가 SSR의 장점을 살려보고싶어서 Vercel을 이용해봤다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p&gt;&lt;figure class=&quot;imageblock alignCenter&quot; data-ke-mobileStyle=&quot;widthOrigin&quot; data-filename=&quot;스크린샷 2022-06-06 01.52.46.png&quot; data-origin-width=&quot;1748&quot; data-origin-height=&quot;1228&quot;&gt;&lt;span data-url=&quot;https://blog.kakaocdn.net/dn/lJ9w6/btrD1770ROo/8wywOPhgDS5UBK0iHbqEVk/img.png&quot; data-phocus=&quot;https://blog.kakaocdn.net/dn/lJ9w6/btrD1770ROo/8wywOPhgDS5UBK0iHbqEVk/img.png&quot; data-alt=&quot;질문에 대한 답변 선택!&quot;&gt;&lt;img src=&quot;https://blog.kakaocdn.net/dn/lJ9w6/btrD1770ROo/8wywOPhgDS5UBK0iHbqEVk/img.png&quot; srcset=&quot;https://img1.daumcdn.net/thumb/R1280x0/?scode=mtistory2&amp;fname=https%3A%2F%2Fblog.kakaocdn.net%2Fdn%2FlJ9w6%2FbtrD1770ROo%2F8wywOPhgDS5UBK0iHbqEVk%2Fimg.png&quot; onerror=&quot;this.onerror=null; this.src='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png'; this.srcset='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png';&quot; loading=&quot;lazy&quot; width=&quot;500&quot; height=&quot;351&quot; data-filename=&quot;스크린샷 2022-06-06 01.52.46.png&quot; data-origin-width=&quot;1748&quot; data-origin-height=&quot;1228&quot;/&gt;&lt;/span&gt;&lt;figcaption&gt;질문에 대한 답변 선택!&lt;/figcaption&gt;
&lt;/figure&gt;
&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;질문 페이지에서 답변을 선택하려면 버튼을 누르기만 하면 된다. 답변은 localStorage에 저장되며, 마지막 /calculating 페이지에서 localStorage에 저장된 답변 5개를 가져와 계산을 하게 된다.&lt;/p&gt;
&lt;p&gt;&lt;figure class=&quot;imageblock alignCenter&quot; data-ke-mobileStyle=&quot;widthOrigin&quot; data-filename=&quot;스크린샷 2022-06-06 01.55.10.png&quot; data-origin-width=&quot;1182&quot; data-origin-height=&quot;1394&quot;&gt;&lt;span data-url=&quot;https://blog.kakaocdn.net/dn/bupu7H/btrD0skxtJh/3iRkED5h407xUcNRBRKOU1/img.png&quot; data-phocus=&quot;https://blog.kakaocdn.net/dn/bupu7H/btrD0skxtJh/3iRkED5h407xUcNRBRKOU1/img.png&quot; data-alt=&quot;결과 페이지&quot;&gt;&lt;img src=&quot;https://blog.kakaocdn.net/dn/bupu7H/btrD0skxtJh/3iRkED5h407xUcNRBRKOU1/img.png&quot; srcset=&quot;https://img1.daumcdn.net/thumb/R1280x0/?scode=mtistory2&amp;fname=https%3A%2F%2Fblog.kakaocdn.net%2Fdn%2Fbupu7H%2FbtrD0skxtJh%2F3iRkED5h407xUcNRBRKOU1%2Fimg.png&quot; onerror=&quot;this.onerror=null; this.src='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png'; this.srcset='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png';&quot; loading=&quot;lazy&quot; width=&quot;500&quot; height=&quot;590&quot; data-filename=&quot;스크린샷 2022-06-06 01.55.10.png&quot; data-origin-width=&quot;1182&quot; data-origin-height=&quot;1394&quot;/&gt;&lt;/span&gt;&lt;figcaption&gt;결과 페이지&lt;/figcaption&gt;
&lt;/figure&gt;
&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;결과 페이지에서는 &lt;b&gt;나와 가장 잘 맞는 레븐 멤버&lt;/b&gt;를 볼 수 있다. 멤버별 이미지는 Cloudflare Images에 업로드된 파일을 사용하며, Cloudflare 서버에서 Resizing된 채로 넘어오게 된다. 현재는 페이지별로 멤버가 하드코딩되어있지만, 나중에 고도화를 하게 된다면 전부 API에서 불러오도록 할 수도 있을 것이다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;공유하기 버튼은 react-copy-to-clipboard 라이브러리를 사용했고, 버튼을 누르면 미리 지정된 텍스트가 클립보드로 복사되며 Toast Message가 뜨게 된다. 토스트 메세지는 react-toastify 라이브러리를 사용했다.&lt;/p&gt;
&lt;p&gt;&lt;figure class=&quot;imageblock alignCenter&quot; data-ke-mobileStyle=&quot;widthOrigin&quot; data-filename=&quot;스크린샷 2022-06-06 01.57.23.png&quot; data-origin-width=&quot;810&quot; data-origin-height=&quot;168&quot;&gt;&lt;span data-url=&quot;https://blog.kakaocdn.net/dn/Qpqfd/btrD1gc12lj/SyH8rKXS0YouaXGmaqfdt1/img.png&quot; data-phocus=&quot;https://blog.kakaocdn.net/dn/Qpqfd/btrD1gc12lj/SyH8rKXS0YouaXGmaqfdt1/img.png&quot; data-alt=&quot;공유하기 버튼 누르면 뜨는 토스트 메세지&quot;&gt;&lt;img src=&quot;https://blog.kakaocdn.net/dn/Qpqfd/btrD1gc12lj/SyH8rKXS0YouaXGmaqfdt1/img.png&quot; srcset=&quot;https://img1.daumcdn.net/thumb/R1280x0/?scode=mtistory2&amp;fname=https%3A%2F%2Fblog.kakaocdn.net%2Fdn%2FQpqfd%2FbtrD1gc12lj%2FSyH8rKXS0YouaXGmaqfdt1%2Fimg.png&quot; onerror=&quot;this.onerror=null; this.src='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png'; this.srcset='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png';&quot; loading=&quot;lazy&quot; width=&quot;400&quot; height=&quot;83&quot; data-filename=&quot;스크린샷 2022-06-06 01.57.23.png&quot; data-origin-width=&quot;810&quot; data-origin-height=&quot;168&quot;/&gt;&lt;/span&gt;&lt;figcaption&gt;공유하기 버튼 누르면 뜨는 토스트 메세지&lt;/figcaption&gt;
&lt;/figure&gt;
&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;총 개발기간은 6~7시간 정도이다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;h3 data-ke-size=&quot;size23&quot;&gt;&lt;b&gt;몇 가지 이슈&lt;/b&gt;&lt;/h3&gt;
&lt;h4 data-ke-size=&quot;size20&quot;&gt;1. next/image로 이미지를 로딩할 때, Daisy UI Avatar와 충돌하여, 이미지가 아바타 아웃라인 밖으로 튀어나오는 현상&lt;/h4&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;상위 div에 custom-image 클래스를 넣고 스타일 속성을 override하도록 했다.&lt;/p&gt;
&lt;pre id=&quot;code_1654448460075&quot; class=&quot;css&quot; data-ke-language=&quot;css&quot; data-ke-type=&quot;codeblock&quot;&gt;&lt;code&gt;.custom-image span {
  position: static !important;
}
.custom-image span img {
  position: static !important;
  height: 100% !important;
}&lt;/code&gt;&lt;/pre&gt;
&lt;h4 data-ke-size=&quot;size20&quot;&gt;2. toastify 메세지에 글자 폰트가 적용되지 않는 현상&lt;/h4&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;해당 클래스에 !important로 font-family를 주면 된다.&lt;/p&gt;
&lt;pre id=&quot;code_1654448645532&quot; class=&quot;css&quot; data-ke-language=&quot;css&quot; data-ke-type=&quot;codeblock&quot;&gt;&lt;code&gt;.Toastify__toast-body {
  font-family: 'Unilab' !important;
}&lt;/code&gt;&lt;/pre&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;h3 data-ke-size=&quot;size23&quot;&gt;&lt;b&gt;Tailwind CSS&lt;/b&gt;가 편한가?&lt;/h3&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;솔직히 잘 모르겠다. 아직 안 익숙해서 그런건지 몰라도, 따로 디자인에 대한 가이드가 없는 사이드 프로젝트 특성상 모든 UI를 직접 그리기는 현실적으로 어렵다. 그래서 UI Library들을 함께 사용하게 되는데, Tailwind CSS 기반의 라이브러리들이 React에서 사용하기에 편한지 잘 모르겠다. UI가 조금만 복잡해지면 코드가 엄청 난잡해질 것 같아서... 프로젝트 사이즈가 조금이라도 커질 것 같으면 Chakra UI 같은 대안을 선택할 것 같다. 이번에는 Daisy UI를 써봤는데, 디자인 자체는 예쁘긴 한데 엄청 깔끔해보이진 않아서 향후에도 계속 쓸지는 고민해볼 것 같다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;h3 data-ke-size=&quot;size23&quot;&gt;&lt;b&gt;그래서 링크!&lt;/b&gt;&lt;/h3&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;웹사이트 : &lt;a href=&quot;https://minigame.leaven.team&quot;&gt;https://minigame.leaven.team&lt;/a&gt;&amp;nbsp;&lt;/p&gt;
&lt;figure id=&quot;og_1688569388022&quot; contenteditable=&quot;false&quot; data-ke-type=&quot;opengraph&quot; data-ke-align=&quot;alignCenter&quot; data-og-type=&quot;website&quot; data-og-title=&quot;나와 가장 잘 맞는 레븐 멤버는?&quot; data-og-description=&quot;LEAVEN MINIGAME 나와 가장 잘 맞는 레븐 멤버는? 시작하기 문항별 점수 구성은 실제와는 다를 수 있음을 미리 알립니다. 오로지 재미로만 즐겨주세요!&quot; data-og-host=&quot;minigame.leaven.team&quot; data-og-source-url=&quot;https://minigame.leaven.team&quot; data-og-url=&quot;https://minigame.leaven.team&quot; data-og-image=&quot;&quot;&gt;&lt;a href=&quot;https://minigame.leaven.team&quot; target=&quot;_blank&quot; rel=&quot;noopener&quot; data-source-url=&quot;https://minigame.leaven.team&quot;&gt;
&lt;div class=&quot;og-image&quot; style=&quot;background-image: url();&quot;&gt;&amp;nbsp;&lt;/div&gt;
&lt;div class=&quot;og-text&quot;&gt;
&lt;p class=&quot;og-title&quot; data-ke-size=&quot;size16&quot;&gt;나와 가장 잘 맞는 레븐 멤버는?&lt;/p&gt;
&lt;p class=&quot;og-desc&quot; data-ke-size=&quot;size16&quot;&gt;LEAVEN MINIGAME 나와 가장 잘 맞는 레븐 멤버는? 시작하기 문항별 점수 구성은 실제와는 다를 수 있음을 미리 알립니다. 오로지 재미로만 즐겨주세요!&lt;/p&gt;
&lt;p class=&quot;og-host&quot; data-ke-size=&quot;size16&quot;&gt;minigame.leaven.team&lt;/p&gt;
&lt;/div&gt;
&lt;/a&gt;&lt;/figure&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;깃허브 : &lt;a href=&quot;https://github.com/dokdo2013/leaven-minigame&quot; target=&quot;_blank&quot; rel=&quot;noopener&quot;&gt;https://github.com/dokdo2013/leaven-minigame&lt;/a&gt;&lt;/p&gt;
&lt;figure id=&quot;og_1654447902111&quot; contenteditable=&quot;false&quot; data-ke-type=&quot;opengraph&quot; data-ke-align=&quot;alignCenter&quot; data-og-type=&quot;object&quot; data-og-title=&quot;GitHub - dokdo2013/leaven-minigame: 레븐 미니게임&quot; data-og-description=&quot;레븐 미니게임. Contribute to dokdo2013/leaven-minigame development by creating an account on GitHub.&quot; data-og-host=&quot;github.com&quot; data-og-source-url=&quot;https://github.com/dokdo2013/leaven-minigame&quot; data-og-url=&quot;https://github.com/dokdo2013/leaven-minigame&quot; data-og-image=&quot;https://scrap.kakaocdn.net/dn/AsscK/hyOEc5x654/kXPH1tElcyHK7DnW0DUJyk/img.png?width=1200&amp;amp;height=600&amp;amp;face=0_0_1200_600&quot;&gt;&lt;a href=&quot;https://github.com/dokdo2013/leaven-minigame&quot; target=&quot;_blank&quot; rel=&quot;noopener&quot; data-source-url=&quot;https://github.com/dokdo2013/leaven-minigame&quot;&gt;
&lt;div class=&quot;og-image&quot; style=&quot;background-image: url('https://scrap.kakaocdn.net/dn/AsscK/hyOEc5x654/kXPH1tElcyHK7DnW0DUJyk/img.png?width=1200&amp;amp;height=600&amp;amp;face=0_0_1200_600');&quot;&gt;&amp;nbsp;&lt;/div&gt;
&lt;div class=&quot;og-text&quot;&gt;
&lt;p class=&quot;og-title&quot; data-ke-size=&quot;size16&quot;&gt;GitHub - dokdo2013/leaven-minigame: 레븐 미니게임&lt;/p&gt;
&lt;p class=&quot;og-desc&quot; data-ke-size=&quot;size16&quot;&gt;레븐 미니게임. Contribute to dokdo2013/leaven-minigame development by creating an account on GitHub.&lt;/p&gt;
&lt;p class=&quot;og-host&quot; data-ke-size=&quot;size16&quot;&gt;github.com&lt;/p&gt;
&lt;/div&gt;
&lt;/a&gt;&lt;/figure&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;</description>
      <category>사이드 프로젝트</category>
      <category>daisyui</category>
      <category>leaven</category>
      <category>nextjs</category>
      <category>Tailwind</category>
      <category>레븐</category>
      <category>사이드프로젝트</category>
      <author>HAENU</author>
      <guid isPermaLink="true">https://dokdo2013.tistory.com/6</guid>
      <comments>https://dokdo2013.tistory.com/6#entry6comment</comments>
      <pubDate>Mon, 6 Jun 2022 02:06:27 +0900</pubDate>
    </item>
  </channel>
</rss>