<?xml version="1.0" encoding="UTF-8"?>
<rss version="2.0">
  <channel>
    <title>1Dev Log</title>
    <link>https://1devlog.tistory.com/</link>
    <description>혼자서 개발하며 배우고 성장하는 공간입니다.</description>
    <language>ko</language>
    <pubDate>Sun, 6 Sep 2026 10:03:28 +0900</pubDate>
    <generator>TISTORY</generator>
    <ttl>100</ttl>
    <managingEditor>SoloQuest</managingEditor>
    <item>
      <title>프로젝트 회고록 (2) - KBO:NOTE</title>
      <link>https://1devlog.tistory.com/26</link>
      <description>&lt;p data-ke-size=&quot;size16&quot;&gt;이번 회고록은 조금 색다른 시작을 가진 프로젝트 이야기다. 팀을 직접 꾸린 것도 아니고, 학교 수업도 아니었다. 컨퍼런스에서 우연히 만난 인연으로 시작된 사이드 프로젝트, &lt;b&gt;KBO:NOTE&lt;/b&gt; 이야기다.&lt;/p&gt;
&lt;h2 data-ke-size=&quot;size26&quot;&gt;우연한 시작&lt;/h2&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;어느 날 갔던 컨퍼런스에서 우연히 한 분을 만났다. 사이드 프로젝트를 같이 만들 개발자를 구하고 있다고 하셨다. 야구 관련 앱이었고, 겨울방학 동안 만들어서 KBO 시즌이 시작되기 전에 런칭하는 게 목표라고 했다. 재밌겠다는 생각이 먼저 들었다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;사실 그 당시 이미 진행 중인 프로젝트가 두 개나 있어서 처음엔 부담스러워 거절했다. 그런데 이야기를 더 들어보니 작은 역할만 맡으면 될 것 같아서 결국 하게 됐다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;제안해주신 분이 PM을 맡았는데, 팀원을 각자 따로 모으다 보니 기술 스택이 다 다를 수 있다고 하셨다. 그래서 &lt;b&gt;MSA(마이크로서비스 아키텍처)&lt;/b&gt; 방식으로 진행한다고 했다. 이게 나에게는 꽤 매력적으로 다가왔다. MSA라는 개념 자체를 잘 모르고 있었는데, 직접 경험해볼 좋은 기회라는 생각이 들었다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;마침 PM님이 프론트엔드 팀원을 구하는 데 어려움을 겪고 계셨다. 나에게 주변에 할 만한 사람이 없냐고 물어보셨고, 백엔드를 주로 하지만 프론트에도 관심이 많던 친구를 추천했다. 그렇게 나와 친구가 함께 새로운 사이드 프로젝트에 합류하게 됐다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;팀원 전원이 대학생이었는데, 학교도 다르고 역할도 다르고 기술 스택도 다르고, 심지어 얼굴 한 번 본 적 없는 사람들과 프로젝트를 하게 됐다. 지금까지와는 확실히 다른 종류의 경험이었다.&lt;/p&gt;
&lt;h2 data-ke-size=&quot;size26&quot;&gt;맡은 역할&lt;/h2&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;내가 맡은 건 피드 페이지 조회, 피드 상세 페이지 조회, 댓글, 좋아요였다.&lt;/p&gt;
&lt;p&gt;&lt;figure class=&quot;imagegridblock&quot;&gt;
  &lt;div class=&quot;image-container&quot;&gt;&lt;span data-url=&quot;https://blog.kakaocdn.net/dn/b8IzaJ/dJMcadwm3eF/W2p5WDM3CLEYNmNmmE5NH1/img.png&quot; data-phocus=&quot;https://blog.kakaocdn.net/dn/b8IzaJ/dJMcadwm3eF/W2p5WDM3CLEYNmNmmE5NH1/img.png&quot; data-origin-width=&quot;226&quot; data-origin-height=&quot;450&quot; data-is-animation=&quot;false&quot; style=&quot;width: 34.9098%; margin-right: 10px;&quot; data-widthpercent=&quot;35.32&quot;&gt;&lt;img src=&quot;https://blog.kakaocdn.net/dn/b8IzaJ/dJMcadwm3eF/W2p5WDM3CLEYNmNmmE5NH1/img.png&quot; srcset=&quot;https://img1.daumcdn.net/thumb/R1280x0/?scode=mtistory2&amp;amp;fname=https%3A%2F%2Fblog.kakaocdn.net%2Fdn%2Fb8IzaJ%2FdJMcadwm3eF%2FW2p5WDM3CLEYNmNmmE5NH1%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;226&quot; height=&quot;450&quot;/&gt;&lt;/span&gt;&lt;span data-url=&quot;https://blog.kakaocdn.net/dn/cnZidi/dJMcahyUWp5/WoQadT9JSthSXpJ3tzzMcK/img.png&quot; data-phocus=&quot;https://blog.kakaocdn.net/dn/cnZidi/dJMcahyUWp5/WoQadT9JSthSXpJ3tzzMcK/img.png&quot; data-origin-width=&quot;458&quot; data-origin-height=&quot;498&quot; data-is-animation=&quot;false&quot; style=&quot;width: 63.9274%;&quot; data-widthpercent=&quot;64.68&quot;&gt;&lt;img src=&quot;https://blog.kakaocdn.net/dn/cnZidi/dJMcahyUWp5/WoQadT9JSthSXpJ3tzzMcK/img.png&quot; srcset=&quot;https://img1.daumcdn.net/thumb/R1280x0/?scode=mtistory2&amp;amp;fname=https%3A%2F%2Fblog.kakaocdn.net%2Fdn%2FcnZidi%2FdJMcahyUWp5%2FWoQadT9JSthSXpJ3tzzMcK%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;458&quot; height=&quot;498&quot;/&gt;&lt;/span&gt;&lt;/div&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;그 고민을 그대로 이야기했더니, MSA 방식이니 하고 싶은 대로 확장해도 된다는 답이 돌아왔다. 피드 조회에 개인화 알고리즘을 써보는 건 어떠냐는 조언도 주셨다. 그래서 그걸 중심으로 개발을 이어갔다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;이 과정에서 &lt;b&gt;블루그린 배포&lt;/b&gt;도 처음 접하게 됐다. 그동안은 자동 배포까지만 해봤지, 무중단 배포는 제대로 다뤄본 적이 없었다. 이때 처음으로 블루그린 방식의 개념과 실제 배포를 경험해볼 수 있었다.&lt;/p&gt;
&lt;h2 data-ke-size=&quot;size26&quot;&gt;3개월, 그리고 한 달의 공백&lt;/h2&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;프로젝트는 약 3개월 정도 진행됐다. 겨울방학에 시작해서 학기가 시작된 뒤에도 계속 이어갔다. 나는 맡은 역할의 개발을 기한 내에 마쳤다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;문제는 중간고사 즈음이었다. 다들 대학생이다 보니 &quot;어차피 사이드 프로젝트니 조금 널널하게 하자&quot;는 이야기가 나왔다. 그런데 팀원마다 학교가 다르다 보니 중간고사 기간도 제각각이었고, 결과적으로 한 달 가까이 공백기가 생겼다. 솔직히 그 기간엔 나도 이 프로젝트에 별로 신경 쓰지 못했다.&lt;/p&gt;
&lt;h2 data-ke-size=&quot;size26&quot;&gt;예상치 못한 종료&lt;/h2&gt;
&lt;p&gt;&lt;figure class=&quot;imageblock alignCenter&quot; data-ke-mobileStyle=&quot;widthOrigin&quot; data-origin-width=&quot;307&quot; data-origin-height=&quot;397&quot;&gt;&lt;span data-url=&quot;https://blog.kakaocdn.net/dn/bRJxws/dJMcacLetG2/dNf4Uh28FhtIOPTjZl4M71/img.png&quot; data-phocus=&quot;https://blog.kakaocdn.net/dn/bRJxws/dJMcacLetG2/dNf4Uh28FhtIOPTjZl4M71/img.png&quot;&gt;&lt;img src=&quot;https://blog.kakaocdn.net/dn/bRJxws/dJMcacLetG2/dNf4Uh28FhtIOPTjZl4M71/img.png&quot; srcset=&quot;https://img1.daumcdn.net/thumb/R1280x0/?scode=mtistory2&amp;fname=https%3A%2F%2Fblog.kakaocdn.net%2Fdn%2FbRJxws%2FdJMcacLetG2%2FdNf4Uh28FhtIOPTjZl4M71%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;307&quot; height=&quot;397&quot; data-origin-width=&quot;307&quot; data-origin-height=&quot;397&quot;/&gt;&lt;/span&gt;&lt;/figure&gt;
&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;시간이 지나 다시 회의를 하게 됐는데, PM님이 예상 밖의 이야기를 꺼내셨다. 한 달간 알아보니 우리 서비스와 거의 완전히 겹치는 서비스가 이미 있다는 걸 알게 됐다고 했다. 그래서 현재 완료된 MVP까지만 하고 여기서 멈추는 게 어떻겠냐고 제안하셨다. 아쉬웠지만, 그렇게 이 프로젝트는 마무리됐다.&lt;/p&gt;
&lt;h2 data-ke-size=&quot;size26&quot;&gt;그래도 남은 것들&lt;/h2&gt;
&lt;p data-ke-size=&quot;size16&quot;&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;MSA 방식도, 비록 내가 직접 로드밸런싱까지 맡지는 않았지만 어깨너머로 전체 구조가 어떻게 돌아가는지 볼 수 있었다. 겉핥기 수준이었지만 그것만으로도 꽤 도움이 됐다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;재밌게도 이번 방학 계절학기로 들었던 '클라우드 네이티브' 수업이 MSA 방식을 다루는 수업이었다. 계절학기라 처음 배우는 개념이었다면 꽤 힘들었을 텐데, KBO:NOTE에서 미리 겪어본 덕분에 훨씬 수월하게 따라갈 수 있었다. 오히려 MSA를 더 깊이 있게, 심화된 개념까지 배울 수 있어서 좋았다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;이 프로젝트는 MSA 구조라 서비스별로 레포가 나뉘어 있는데, 내가 맡았던 피드 조회&amp;middot;상세 페이지&amp;middot;댓글&amp;middot;좋아요 부분은 &lt;a title=&quot;깃허브 KBO:NOTE feed 서비스 링크&quot; href=&quot;https://github.com/KBO-NOTE/kbo-feed-service&quot; target=&quot;_blank&quot; rel=&quot;noopener&quot;&gt;[여기]&lt;/a&gt;에서 볼 수 있다.&lt;/p&gt;
&lt;hr data-ke-style=&quot;style1&quot; /&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;프로젝트를 한 지도 벌써 몇 개월이 지나서 기억이 온전치 않을 수도 있지만, 최대한 그때의 흐름을 되짚어 적어봤다. 얼굴도 모르는 사람들과, 서로 다른 기술 스택으로, 우연히 시작해서 예상치 못하게 끝난 프로젝트. 짧았지만 나름 밀도 있는 경험이었다.&lt;/p&gt;</description>
      <category>  프로젝트/회고록</category>
      <author>SoloQuest</author>
      <guid isPermaLink="true">https://1devlog.tistory.com/26</guid>
      <comments>https://1devlog.tistory.com/26#entry26comment</comments>
      <pubDate>Sat, 29 Aug 2026 23:37:32 +0900</pubDate>
    </item>
    <item>
      <title>프로젝트 회고록 (번외) - 자투리, 그 후</title>
      <link>https://1devlog.tistory.com/25</link>
      <description>&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;a href=&quot;https://1devlog.tistory.com/24&quot;&gt;1편&lt;/a&gt;에서 이어지는 이야기다. 이번 편은 화려한 결과물 이야기는 아니다. 오히려 그 반대에 가깝다. 왜 우리가 시작한 고도화가 흐지부지 끝났는지에 대한 기록이다.&lt;/p&gt;
&lt;h2 data-ke-size=&quot;size26&quot;&gt;다시 모인 이유&lt;/h2&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;3-2학기에 자투리 프로젝트를 마쳤지만, 짧은 시간에 밀어붙이다 보니 아쉬움이 많이 남았다. 특히 디자인 쪽이 걸렸다. 그래서 방학에 다시 모여 고도화를 진행하기로 했다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;이번엔 디자이너 한 명을 새로 영입했다. 팀은 나와 편입 동기, 기존 AI 팀원, 그리고 새로 합류한 디자이너까지 총 4명이었다. 가장 큰 목표는 명확했다. &lt;b&gt;디자인&lt;/b&gt;이었다. 디자이너가 들어온 만큼 UI를 중심으로 확 바꾸고 싶었고, 동시에 &quot;AI가 할루시네이션을 일으킬 수 있다&quot;는 피드백을 받았던 터라 이를 줄이는 것도 목표로 잡았다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;결과부터 말하면, 절반은 성공이고 절반은 실패였다.&lt;/p&gt;
&lt;h2 data-ke-size=&quot;size26&quot;&gt;디자인 &amp;mdash; 목표한 만큼은 해냈다&lt;/h2&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;먼저 잘된 것부터 말하자면, 디자인은 확실히 달라졌다. 이전엔 베이지&amp;middot;브라운 톤의 다소 밋밋한 화면이었는데, 리팩토링 후에는 화이트 배경에 오렌지 포인트 컬러를 준 톤으로 바뀌면서 훨씬 산뜻해졌다. 헤더에 로고와 검색 아이콘을 통일해서 배치하니 화면마다 따로 놀던 느낌이 사라지고 하나의 앱이라는 정체성도 생겼다. 친구 목록&amp;middot;요청을 탭으로 묶고, 마이페이지 카드 정렬을 정돈한 것도 눈에 띄는 변화였다. &quot;제목 추천&quot; 같은 작은 기능이 새로 붙은 것도 이때다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;가장 큰 목표로 잡았던 디자인만큼은 디자이너 영입이라는 결정이 제대로 통했다고 생각한다. 목적의식이 흐릿했던 프로젝트치고는 이 부분만큼은 의외로 결과가 좋았다.&lt;/p&gt;
&lt;h4 data-ke-size=&quot;size20&quot;&gt;사용자 서비스 리펙토링 전/후&lt;/h4&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;대표적으로 메인, 캘린더, 일기리스트, 친구리스트 4쌍을 가져왔다. 확실히 좀 더 깔끔해진것을 확인 할 수 있다. 바뀐 다른 부분들도 확인하고 싶다면, 1편과 부록을 확인해보시길 바란다. 1편에는 완성된 디자인과 이 글 마지막에 부록에는 리펙토링 후 완성된 디자인을 각각 첨부해 놨다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p&gt;&lt;figure class=&quot;imagegridblock&quot;&gt;
  &lt;div class=&quot;image-container&quot;&gt;&lt;span data-url=&quot;https://blog.kakaocdn.net/dn/bV6gbh/dJMcabx6unE/Pf9B1dHjDTJ4LwHX3ip4u1/img.png&quot; data-phocus=&quot;https://blog.kakaocdn.net/dn/bV6gbh/dJMcabx6unE/Pf9B1dHjDTJ4LwHX3ip4u1/img.png&quot; data-is-animation=&quot;false&quot; data-origin-width=&quot;272&quot; data-origin-height=&quot;667&quot; data-filename=&quot;자투리 3.png&quot; style=&quot;width: 46.2113%; margin-right: 10px;&quot; data-widthpercent=&quot;46.75&quot;&gt;&lt;img src=&quot;https://blog.kakaocdn.net/dn/bV6gbh/dJMcabx6unE/Pf9B1dHjDTJ4LwHX3ip4u1/img.png&quot; srcset=&quot;https://img1.daumcdn.net/thumb/R1280x0/?scode=mtistory2&amp;amp;fname=https%3A%2F%2Fblog.kakaocdn.net%2Fdn%2FbV6gbh%2FdJMcabx6unE%2FPf9B1dHjDTJ4LwHX3ip4u1%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;272&quot; height=&quot;667&quot;/&gt;&lt;/span&gt;&lt;span data-url=&quot;https://blog.kakaocdn.net/dn/bvX6vO/dJMcaixidq9/RqlP6y4IOib7kS3YYvFTy0/img.png&quot; data-phocus=&quot;https://blog.kakaocdn.net/dn/bvX6vO/dJMcaixidq9/RqlP6y4IOib7kS3YYvFTy0/img.png&quot; data-is-animation=&quot;false&quot; data-origin-width=&quot;424&quot; data-origin-height=&quot;913&quot; data-filename=&quot;메인화면.png&quot; style=&quot;width: 52.6259%;&quot; data-widthpercent=&quot;53.25&quot;&gt;&lt;img src=&quot;https://blog.kakaocdn.net/dn/bvX6vO/dJMcaixidq9/RqlP6y4IOib7kS3YYvFTy0/img.png&quot; srcset=&quot;https://img1.daumcdn.net/thumb/R1280x0/?scode=mtistory2&amp;amp;fname=https%3A%2F%2Fblog.kakaocdn.net%2Fdn%2FbvX6vO%2FdJMcaixidq9%2FRqlP6y4IOib7kS3YYvFTy0%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;424&quot; height=&quot;913&quot;/&gt;&lt;/span&gt;&lt;/div&gt;
  &lt;figcaption&gt;메인화면 전/후&lt;/figcaption&gt;
&lt;/figure&gt;
&lt;figure class=&quot;imagegridblock&quot;&gt;
  &lt;div class=&quot;image-container&quot;&gt;&lt;span data-url=&quot;https://blog.kakaocdn.net/dn/b5ZCeO/dJMb99NNake/2Aer64b2zCAUxiDQyilYV1/img.png&quot; data-phocus=&quot;https://blog.kakaocdn.net/dn/b5ZCeO/dJMb99NNake/2Aer64b2zCAUxiDQyilYV1/img.png&quot; data-is-animation=&quot;false&quot; data-origin-width=&quot;269&quot; data-origin-height=&quot;656&quot; data-filename=&quot;자투리 4.png&quot; style=&quot;width: 46.3476%; margin-right: 10px;&quot; data-widthpercent=&quot;46.89&quot;&gt;&lt;img src=&quot;https://blog.kakaocdn.net/dn/b5ZCeO/dJMb99NNake/2Aer64b2zCAUxiDQyilYV1/img.png&quot; srcset=&quot;https://img1.daumcdn.net/thumb/R1280x0/?scode=mtistory2&amp;amp;fname=https%3A%2F%2Fblog.kakaocdn.net%2Fdn%2Fb5ZCeO%2FdJMb99NNake%2F2Aer64b2zCAUxiDQyilYV1%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;269&quot; height=&quot;656&quot;/&gt;&lt;/span&gt;&lt;span data-url=&quot;https://blog.kakaocdn.net/dn/ls413/dJMcabSpb7F/NqhwKqyjOzl2ep5tZhs8a1/img.png&quot; data-phocus=&quot;https://blog.kakaocdn.net/dn/ls413/dJMcabSpb7F/NqhwKqyjOzl2ep5tZhs8a1/img.png&quot; data-is-animation=&quot;false&quot; data-origin-width=&quot;424&quot; data-origin-height=&quot;913&quot; data-filename=&quot;월별 캘린더.png&quot; style=&quot;width: 52.4896%;&quot; data-widthpercent=&quot;53.11&quot;&gt;&lt;img src=&quot;https://blog.kakaocdn.net/dn/ls413/dJMcabSpb7F/NqhwKqyjOzl2ep5tZhs8a1/img.png&quot; srcset=&quot;https://img1.daumcdn.net/thumb/R1280x0/?scode=mtistory2&amp;amp;fname=https%3A%2F%2Fblog.kakaocdn.net%2Fdn%2Fls413%2FdJMcabSpb7F%2FNqhwKqyjOzl2ep5tZhs8a1%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;424&quot; height=&quot;913&quot;/&gt;&lt;/span&gt;&lt;/div&gt;
  &lt;figcaption&gt;캘린더 전/후&lt;/figcaption&gt;
&lt;/figure&gt;
&lt;figure class=&quot;imagegridblock&quot;&gt;
  &lt;div class=&quot;image-container&quot;&gt;&lt;span data-url=&quot;https://blog.kakaocdn.net/dn/clAwrf/dJMcacw3R0N/UyKK5AQzukQMfMKJatn9oK/img.png&quot; data-phocus=&quot;https://blog.kakaocdn.net/dn/clAwrf/dJMcacw3R0N/UyKK5AQzukQMfMKJatn9oK/img.png&quot; data-is-animation=&quot;false&quot; data-origin-width=&quot;308&quot; data-origin-height=&quot;750&quot; data-filename=&quot;자투리 10.png&quot; style=&quot;width: 46.3839%; margin-right: 10px;&quot; data-widthpercent=&quot;46.93&quot;&gt;&lt;img src=&quot;https://blog.kakaocdn.net/dn/clAwrf/dJMcacw3R0N/UyKK5AQzukQMfMKJatn9oK/img.png&quot; srcset=&quot;https://img1.daumcdn.net/thumb/R1280x0/?scode=mtistory2&amp;amp;fname=https%3A%2F%2Fblog.kakaocdn.net%2Fdn%2FclAwrf%2FdJMcacw3R0N%2FUyKK5AQzukQMfMKJatn9oK%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;308&quot; height=&quot;750&quot;/&gt;&lt;/span&gt;&lt;span data-url=&quot;https://blog.kakaocdn.net/dn/cE39A5/dJMcad3JE9d/3FtBl9BNtVRggoGrGsN2E0/img.png&quot; data-phocus=&quot;https://blog.kakaocdn.net/dn/cE39A5/dJMcad3JE9d/3FtBl9BNtVRggoGrGsN2E0/img.png&quot; data-is-animation=&quot;false&quot; data-origin-width=&quot;424&quot; data-origin-height=&quot;913&quot; data-filename=&quot;일기리스트.png&quot; style=&quot;width: 52.4533%;&quot; data-widthpercent=&quot;53.07&quot;&gt;&lt;img src=&quot;https://blog.kakaocdn.net/dn/cE39A5/dJMcad3JE9d/3FtBl9BNtVRggoGrGsN2E0/img.png&quot; srcset=&quot;https://img1.daumcdn.net/thumb/R1280x0/?scode=mtistory2&amp;amp;fname=https%3A%2F%2Fblog.kakaocdn.net%2Fdn%2FcE39A5%2FdJMcad3JE9d%2F3FtBl9BNtVRggoGrGsN2E0%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;424&quot; height=&quot;913&quot;/&gt;&lt;/span&gt;&lt;/div&gt;
  &lt;figcaption&gt;일기리스트 전/후&lt;/figcaption&gt;
&lt;/figure&gt;
&lt;figure class=&quot;imagegridblock&quot;&gt;
  &lt;div class=&quot;image-container&quot;&gt;&lt;span data-url=&quot;https://blog.kakaocdn.net/dn/bco36Y/dJMcagM3ErN/YiocUBCztkcZcUHeJLtFnk/img.png&quot; data-phocus=&quot;https://blog.kakaocdn.net/dn/bco36Y/dJMcagM3ErN/YiocUBCztkcZcUHeJLtFnk/img.png&quot; data-is-animation=&quot;false&quot; data-origin-width=&quot;308&quot; data-origin-height=&quot;755&quot; data-filename=&quot;자투리 8.png&quot; data-widthpercent=&quot;30.52&quot; style=&quot;width: 29.808%; margin-right: 10px;&quot;&gt;&lt;img src=&quot;https://blog.kakaocdn.net/dn/bco36Y/dJMcagM3ErN/YiocUBCztkcZcUHeJLtFnk/img.png&quot; srcset=&quot;https://img1.daumcdn.net/thumb/R1280x0/?scode=mtistory2&amp;amp;fname=https%3A%2F%2Fblog.kakaocdn.net%2Fdn%2Fbco36Y%2FdJMcagM3ErN%2FYiocUBCztkcZcUHeJLtFnk%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;308&quot; height=&quot;755&quot;/&gt;&lt;/span&gt;&lt;span data-url=&quot;https://blog.kakaocdn.net/dn/yy67e/dJMcagM3ErO/ntALlIsMW0NujpcR2xfmq1/img.png&quot; data-phocus=&quot;https://blog.kakaocdn.net/dn/yy67e/dJMcagM3ErO/ntALlIsMW0NujpcR2xfmq1/img.png&quot; data-is-animation=&quot;false&quot; data-origin-width=&quot;424&quot; data-origin-height=&quot;913&quot; data-filename=&quot;친구리스트.png&quot; style=&quot;width: 33.9332%; margin-right: 10px;&quot; data-widthpercent=&quot;34.74&quot;&gt;&lt;img src=&quot;https://blog.kakaocdn.net/dn/yy67e/dJMcagM3ErO/ntALlIsMW0NujpcR2xfmq1/img.png&quot; srcset=&quot;https://img1.daumcdn.net/thumb/R1280x0/?scode=mtistory2&amp;amp;fname=https%3A%2F%2Fblog.kakaocdn.net%2Fdn%2Fyy67e%2FdJMcagM3ErO%2FntALlIsMW0NujpcR2xfmq1%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;424&quot; height=&quot;913&quot;/&gt;&lt;/span&gt;&lt;span data-url=&quot;https://blog.kakaocdn.net/dn/b3seVU/dJMcag7f0vN/C6tCXgwBmJdBzIIYIbYfXK/img.png&quot; data-phocus=&quot;https://blog.kakaocdn.net/dn/b3seVU/dJMcag7f0vN/C6tCXgwBmJdBzIIYIbYfXK/img.png&quot; data-is-animation=&quot;false&quot; data-origin-width=&quot;424&quot; data-origin-height=&quot;913&quot; data-filename=&quot;친구요청리스트.png&quot; style=&quot;width: 33.9332%;&quot; data-widthpercent=&quot;34.74&quot;&gt;&lt;img src=&quot;https://blog.kakaocdn.net/dn/b3seVU/dJMcag7f0vN/C6tCXgwBmJdBzIIYIbYfXK/img.png&quot; srcset=&quot;https://img1.daumcdn.net/thumb/R1280x0/?scode=mtistory2&amp;amp;fname=https%3A%2F%2Fblog.kakaocdn.net%2Fdn%2Fb3seVU%2FdJMcag7f0vN%2FC6tCXgwBmJdBzIIYIbYfXK%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;424&quot; height=&quot;913&quot;/&gt;&lt;/span&gt;&lt;/div&gt;
  &lt;figcaption&gt;친구 리스트 전/후&lt;/figcaption&gt;
&lt;/figure&gt;
&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;h2 style=&quot;color: #000000;&quot; data-ke-size=&quot;size26&quot;&gt;AI &amp;mdash; 여기서는 확인조차 못 했다&lt;/h2&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;문제는 AI 쪽이었다. 여기서는 패착이 두 가지 있었다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;b&gt;첫 번째는 목적의식 자체가 약했다는 것.&lt;/b&gt;&lt;span&gt;&amp;nbsp;&lt;/span&gt;수업은 이미 끝난 상태였다. &quot;좀 더 고도화해보자&quot;는 마음으로 다시 모이긴 했지만, 실제로 서비스화할 생각까지는 없었다. 목표가 흐릿하니 새로운 시도는 부담스럽게 느껴졌고, 다들 방학이라 바쁘다 보니 이 프로젝트는 자연스럽게 후순위로 밀렸다. 디자인은 그럼에도 눈에 보이는 결과물이 나왔지만, AI 쪽은 우선순위에서 더 쉽게 밀려난 셈이다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;b&gt;두 번째는 AI를 올릴 GPU가 없었다는 것.&lt;/b&gt;&lt;span&gt;&amp;nbsp;&lt;/span&gt;수업을 들을 때는 학교 서버를 지원받아 AI를 돌릴 수 있었다. 당연하게 여기고 있었던 부분이라 이 문제를 미리 생각하지 못했다. AI 쪽 고도화를 팀원이 다 마쳤다고는 했지만, 정작 우리가 그걸 직접 돌려서 확인할 방법이 없었다. 이때는 나도 인프라 쪽을 잘 몰랐던 시기라 클라우드를 활용하는 선택지 자체를 떠올리지 못했다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;결국 할루시네이션을 줄이겠다던 목표는 검증조차 못 한 채로 남았다.&lt;/p&gt;
&lt;h2 style=&quot;color: #000000;&quot; data-ke-size=&quot;size26&quot;&gt;마무리&lt;/h2&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;돌아보면 이 고도화는 완전한 성공도, 완전한 실패도 아니었다. 가장 중요하게 잡았던 디자인 목표는 생각보다 잘 해냈지만, AI 쪽은 시작만 하고 끝을 보지 못했다.&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;/h3&gt;
&lt;h4 data-ke-size=&quot;size20&quot;&gt;&amp;nbsp;&lt;/h4&gt;
&lt;p&gt;&lt;figure class=&quot;imageslideblock alignCenter&quot; data-image=&quot;[{&amp;quot;src&amp;quot;:&amp;quot;https://blog.kakaocdn.net/dn/oxT14/dJMcabx6t7c/54B40dmSko8OjKmuXyGkMK/img.png&amp;quot;},{&amp;quot;src&amp;quot;:&amp;quot;https://blog.kakaocdn.net/dn/c6Kljm/dJMcahLSzL4/F5siT52tN2mv0WYKJ7sci1/img.png&amp;quot;},{&amp;quot;src&amp;quot;:&amp;quot;https://blog.kakaocdn.net/dn/b6eG3D/dJMcadQbVbf/sR9m9n20pU58az6EKa6FVk/img.png&amp;quot;},{&amp;quot;src&amp;quot;:&amp;quot;https://blog.kakaocdn.net/dn/dg7UQE/dJMcabx6t7d/NwCWeJXKInAXQGn2MvnKf0/img.png&amp;quot;},{&amp;quot;src&amp;quot;:&amp;quot;https://blog.kakaocdn.net/dn/deArPC/dJMcadQbVbe/VcXuCYypzSBsgDexv7Tvh0/img.png&amp;quot;},{&amp;quot;src&amp;quot;:&amp;quot;https://blog.kakaocdn.net/dn/biy0vd/dJMcadQbVbd/loaODFZA0GkxLOiKGMp5pK/img.png&amp;quot;},{&amp;quot;src&amp;quot;:&amp;quot;https://blog.kakaocdn.net/dn/HCY9m/dJMcahLSzL5/wXlZNABKL0Zg5YGlCqWWh0/img.png&amp;quot;},{&amp;quot;src&amp;quot;:&amp;quot;https://blog.kakaocdn.net/dn/85Dsl/dJMcahZok40/ieujewz6YG5Qad9kMHJceK/img.png&amp;quot;},{&amp;quot;src&amp;quot;:&amp;quot;https://blog.kakaocdn.net/dn/qBdVd/dJMcaixicuf/yMihJBimPcSOia8J0PqIY1/img.png&amp;quot;},{&amp;quot;src&amp;quot;:&amp;quot;https://blog.kakaocdn.net/dn/o0UE3/dJMcabx6t7f/I6QtmFCS5Ui2po8bUy3jF1/img.png&amp;quot;},{&amp;quot;src&amp;quot;:&amp;quot;https://blog.kakaocdn.net/dn/eb4KeO/dJMcabx6t7g/D3NkRHoppVboT6LXN3Yus0/img.png&amp;quot;},{&amp;quot;src&amp;quot;:&amp;quot;https://blog.kakaocdn.net/dn/mLNYs/dJMcahd5O6c/IPVZVcqmC3DcniIxOl7pX1/img.png&amp;quot;},{&amp;quot;src&amp;quot;:&amp;quot;https://blog.kakaocdn.net/dn/vzGB3/dJMcabx6t7e/O8RHni3KkjlhO3fw2SlJw0/img.png&amp;quot;},{&amp;quot;src&amp;quot;:&amp;quot;https://blog.kakaocdn.net/dn/cf43gB/dJMcaa0hXP3/a8EEypSUuS4XdRIzalkC30/img.png&amp;quot;}]&quot;&gt;
  &lt;div class=&quot;image-container&quot;&gt;&lt;span class=&quot;image-wrap selected&quot; data-phocus=&quot;https://blog.kakaocdn.net/dn/oxT14/dJMcabx6t7c/54B40dmSko8OjKmuXyGkMK/img.png&quot; data-url=&quot;https://blog.kakaocdn.net/dn/oxT14/dJMcabx6t7c/54B40dmSko8OjKmuXyGkMK/img.png&quot;&gt;&lt;img src=&quot;https://blog.kakaocdn.net/dn/oxT14/dJMcabx6t7c/54B40dmSko8OjKmuXyGkMK/img.png&quot; loading=&quot;lazy&quot; srcset=&quot;https://img1.daumcdn.net/thumb/R1280x0/?scode=mtistory2&amp;amp;fname=https%3A%2F%2Fblog.kakaocdn.net%2Fdn%2FoxT14%2FdJMcabx6t7c%2F54B40dmSko8OjKmuXyGkMK%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; data-origin-width=&quot;424&quot; data-origin-height=&quot;913&quot;/&gt;&lt;/span&gt;&lt;span class=&quot;image-wrap &quot; data-phocus=&quot;https://blog.kakaocdn.net/dn/c6Kljm/dJMcahLSzL4/F5siT52tN2mv0WYKJ7sci1/img.png&quot; data-url=&quot;https://blog.kakaocdn.net/dn/c6Kljm/dJMcahLSzL4/F5siT52tN2mv0WYKJ7sci1/img.png&quot;&gt;&lt;img src=&quot;https://blog.kakaocdn.net/dn/c6Kljm/dJMcahLSzL4/F5siT52tN2mv0WYKJ7sci1/img.png&quot; loading=&quot;lazy&quot; srcset=&quot;https://img1.daumcdn.net/thumb/R1280x0/?scode=mtistory2&amp;amp;fname=https%3A%2F%2Fblog.kakaocdn.net%2Fdn%2Fc6Kljm%2FdJMcahLSzL4%2FF5siT52tN2mv0WYKJ7sci1%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; data-origin-width=&quot;424&quot; data-origin-height=&quot;913&quot;/&gt;&lt;/span&gt;&lt;span class=&quot;image-wrap &quot; data-phocus=&quot;https://blog.kakaocdn.net/dn/b6eG3D/dJMcadQbVbf/sR9m9n20pU58az6EKa6FVk/img.png&quot; data-url=&quot;https://blog.kakaocdn.net/dn/b6eG3D/dJMcadQbVbf/sR9m9n20pU58az6EKa6FVk/img.png&quot;&gt;&lt;img src=&quot;https://blog.kakaocdn.net/dn/b6eG3D/dJMcadQbVbf/sR9m9n20pU58az6EKa6FVk/img.png&quot; loading=&quot;lazy&quot; srcset=&quot;https://img1.daumcdn.net/thumb/R1280x0/?scode=mtistory2&amp;amp;fname=https%3A%2F%2Fblog.kakaocdn.net%2Fdn%2Fb6eG3D%2FdJMcadQbVbf%2FsR9m9n20pU58az6EKa6FVk%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; data-origin-width=&quot;424&quot; data-origin-height=&quot;913&quot;/&gt;&lt;/span&gt;&lt;span class=&quot;image-wrap &quot; data-phocus=&quot;https://blog.kakaocdn.net/dn/dg7UQE/dJMcabx6t7d/NwCWeJXKInAXQGn2MvnKf0/img.png&quot; data-url=&quot;https://blog.kakaocdn.net/dn/dg7UQE/dJMcabx6t7d/NwCWeJXKInAXQGn2MvnKf0/img.png&quot;&gt;&lt;img src=&quot;https://blog.kakaocdn.net/dn/dg7UQE/dJMcabx6t7d/NwCWeJXKInAXQGn2MvnKf0/img.png&quot; loading=&quot;lazy&quot; srcset=&quot;https://img1.daumcdn.net/thumb/R1280x0/?scode=mtistory2&amp;amp;fname=https%3A%2F%2Fblog.kakaocdn.net%2Fdn%2Fdg7UQE%2FdJMcabx6t7d%2FNwCWeJXKInAXQGn2MvnKf0%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; data-origin-width=&quot;424&quot; data-origin-height=&quot;913&quot;/&gt;&lt;/span&gt;&lt;span class=&quot;image-wrap &quot; data-phocus=&quot;https://blog.kakaocdn.net/dn/deArPC/dJMcadQbVbe/VcXuCYypzSBsgDexv7Tvh0/img.png&quot; data-url=&quot;https://blog.kakaocdn.net/dn/deArPC/dJMcadQbVbe/VcXuCYypzSBsgDexv7Tvh0/img.png&quot;&gt;&lt;img src=&quot;https://blog.kakaocdn.net/dn/deArPC/dJMcadQbVbe/VcXuCYypzSBsgDexv7Tvh0/img.png&quot; loading=&quot;lazy&quot; srcset=&quot;https://img1.daumcdn.net/thumb/R1280x0/?scode=mtistory2&amp;amp;fname=https%3A%2F%2Fblog.kakaocdn.net%2Fdn%2FdeArPC%2FdJMcadQbVbe%2FVcXuCYypzSBsgDexv7Tvh0%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; data-origin-width=&quot;424&quot; data-origin-height=&quot;913&quot;/&gt;&lt;/span&gt;&lt;span class=&quot;image-wrap &quot; data-phocus=&quot;https://blog.kakaocdn.net/dn/biy0vd/dJMcadQbVbd/loaODFZA0GkxLOiKGMp5pK/img.png&quot; data-url=&quot;https://blog.kakaocdn.net/dn/biy0vd/dJMcadQbVbd/loaODFZA0GkxLOiKGMp5pK/img.png&quot;&gt;&lt;img src=&quot;https://blog.kakaocdn.net/dn/biy0vd/dJMcadQbVbd/loaODFZA0GkxLOiKGMp5pK/img.png&quot; loading=&quot;lazy&quot; srcset=&quot;https://img1.daumcdn.net/thumb/R1280x0/?scode=mtistory2&amp;amp;fname=https%3A%2F%2Fblog.kakaocdn.net%2Fdn%2Fbiy0vd%2FdJMcadQbVbd%2FloaODFZA0GkxLOiKGMp5pK%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; data-origin-width=&quot;424&quot; data-origin-height=&quot;913&quot;/&gt;&lt;/span&gt;&lt;span class=&quot;image-wrap &quot; data-phocus=&quot;https://blog.kakaocdn.net/dn/HCY9m/dJMcahLSzL5/wXlZNABKL0Zg5YGlCqWWh0/img.png&quot; data-url=&quot;https://blog.kakaocdn.net/dn/HCY9m/dJMcahLSzL5/wXlZNABKL0Zg5YGlCqWWh0/img.png&quot;&gt;&lt;img src=&quot;https://blog.kakaocdn.net/dn/HCY9m/dJMcahLSzL5/wXlZNABKL0Zg5YGlCqWWh0/img.png&quot; loading=&quot;lazy&quot; srcset=&quot;https://img1.daumcdn.net/thumb/R1280x0/?scode=mtistory2&amp;amp;fname=https%3A%2F%2Fblog.kakaocdn.net%2Fdn%2FHCY9m%2FdJMcahLSzL5%2FwXlZNABKL0Zg5YGlCqWWh0%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; data-origin-width=&quot;424&quot; data-origin-height=&quot;913&quot;/&gt;&lt;/span&gt;&lt;span class=&quot;image-wrap &quot; data-phocus=&quot;https://blog.kakaocdn.net/dn/85Dsl/dJMcahZok40/ieujewz6YG5Qad9kMHJceK/img.png&quot; data-url=&quot;https://blog.kakaocdn.net/dn/85Dsl/dJMcahZok40/ieujewz6YG5Qad9kMHJceK/img.png&quot;&gt;&lt;img src=&quot;https://blog.kakaocdn.net/dn/85Dsl/dJMcahZok40/ieujewz6YG5Qad9kMHJceK/img.png&quot; loading=&quot;lazy&quot; srcset=&quot;https://img1.daumcdn.net/thumb/R1280x0/?scode=mtistory2&amp;amp;fname=https%3A%2F%2Fblog.kakaocdn.net%2Fdn%2F85Dsl%2FdJMcahZok40%2Fieujewz6YG5Qad9kMHJceK%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; data-origin-width=&quot;424&quot; data-origin-height=&quot;913&quot;/&gt;&lt;/span&gt;&lt;span class=&quot;image-wrap &quot; data-phocus=&quot;https://blog.kakaocdn.net/dn/qBdVd/dJMcaixicuf/yMihJBimPcSOia8J0PqIY1/img.png&quot; data-url=&quot;https://blog.kakaocdn.net/dn/qBdVd/dJMcaixicuf/yMihJBimPcSOia8J0PqIY1/img.png&quot;&gt;&lt;img src=&quot;https://blog.kakaocdn.net/dn/qBdVd/dJMcaixicuf/yMihJBimPcSOia8J0PqIY1/img.png&quot; loading=&quot;lazy&quot; srcset=&quot;https://img1.daumcdn.net/thumb/R1280x0/?scode=mtistory2&amp;amp;fname=https%3A%2F%2Fblog.kakaocdn.net%2Fdn%2FqBdVd%2FdJMcaixicuf%2FyMihJBimPcSOia8J0PqIY1%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; data-origin-width=&quot;424&quot; data-origin-height=&quot;913&quot;/&gt;&lt;/span&gt;&lt;span class=&quot;image-wrap &quot; data-phocus=&quot;https://blog.kakaocdn.net/dn/o0UE3/dJMcabx6t7f/I6QtmFCS5Ui2po8bUy3jF1/img.png&quot; data-url=&quot;https://blog.kakaocdn.net/dn/o0UE3/dJMcabx6t7f/I6QtmFCS5Ui2po8bUy3jF1/img.png&quot;&gt;&lt;img src=&quot;https://blog.kakaocdn.net/dn/o0UE3/dJMcabx6t7f/I6QtmFCS5Ui2po8bUy3jF1/img.png&quot; loading=&quot;lazy&quot; srcset=&quot;https://img1.daumcdn.net/thumb/R1280x0/?scode=mtistory2&amp;amp;fname=https%3A%2F%2Fblog.kakaocdn.net%2Fdn%2Fo0UE3%2FdJMcabx6t7f%2FI6QtmFCS5Ui2po8bUy3jF1%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; data-origin-width=&quot;424&quot; data-origin-height=&quot;913&quot;/&gt;&lt;/span&gt;&lt;span class=&quot;image-wrap &quot; data-phocus=&quot;https://blog.kakaocdn.net/dn/eb4KeO/dJMcabx6t7g/D3NkRHoppVboT6LXN3Yus0/img.png&quot; data-url=&quot;https://blog.kakaocdn.net/dn/eb4KeO/dJMcabx6t7g/D3NkRHoppVboT6LXN3Yus0/img.png&quot;&gt;&lt;img src=&quot;https://blog.kakaocdn.net/dn/eb4KeO/dJMcabx6t7g/D3NkRHoppVboT6LXN3Yus0/img.png&quot; loading=&quot;lazy&quot; srcset=&quot;https://img1.daumcdn.net/thumb/R1280x0/?scode=mtistory2&amp;amp;fname=https%3A%2F%2Fblog.kakaocdn.net%2Fdn%2Feb4KeO%2FdJMcabx6t7g%2FD3NkRHoppVboT6LXN3Yus0%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; data-origin-width=&quot;424&quot; data-origin-height=&quot;913&quot;/&gt;&lt;/span&gt;&lt;span class=&quot;image-wrap &quot; data-phocus=&quot;https://blog.kakaocdn.net/dn/mLNYs/dJMcahd5O6c/IPVZVcqmC3DcniIxOl7pX1/img.png&quot; data-url=&quot;https://blog.kakaocdn.net/dn/mLNYs/dJMcahd5O6c/IPVZVcqmC3DcniIxOl7pX1/img.png&quot;&gt;&lt;img src=&quot;https://blog.kakaocdn.net/dn/mLNYs/dJMcahd5O6c/IPVZVcqmC3DcniIxOl7pX1/img.png&quot; loading=&quot;lazy&quot; srcset=&quot;https://img1.daumcdn.net/thumb/R1280x0/?scode=mtistory2&amp;amp;fname=https%3A%2F%2Fblog.kakaocdn.net%2Fdn%2FmLNYs%2FdJMcahd5O6c%2FIPVZVcqmC3DcniIxOl7pX1%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; data-origin-width=&quot;424&quot; data-origin-height=&quot;913&quot;/&gt;&lt;/span&gt;&lt;span class=&quot;image-wrap &quot; data-phocus=&quot;https://blog.kakaocdn.net/dn/vzGB3/dJMcabx6t7e/O8RHni3KkjlhO3fw2SlJw0/img.png&quot; data-url=&quot;https://blog.kakaocdn.net/dn/vzGB3/dJMcabx6t7e/O8RHni3KkjlhO3fw2SlJw0/img.png&quot;&gt;&lt;img src=&quot;https://blog.kakaocdn.net/dn/vzGB3/dJMcabx6t7e/O8RHni3KkjlhO3fw2SlJw0/img.png&quot; loading=&quot;lazy&quot; srcset=&quot;https://img1.daumcdn.net/thumb/R1280x0/?scode=mtistory2&amp;amp;fname=https%3A%2F%2Fblog.kakaocdn.net%2Fdn%2FvzGB3%2FdJMcabx6t7e%2FO8RHni3KkjlhO3fw2SlJw0%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; data-origin-width=&quot;424&quot; data-origin-height=&quot;913&quot;/&gt;&lt;/span&gt;&lt;span class=&quot;image-wrap &quot; data-phocus=&quot;https://blog.kakaocdn.net/dn/cf43gB/dJMcaa0hXP3/a8EEypSUuS4XdRIzalkC30/img.png&quot; data-url=&quot;https://blog.kakaocdn.net/dn/cf43gB/dJMcaa0hXP3/a8EEypSUuS4XdRIzalkC30/img.png&quot;&gt;&lt;img src=&quot;https://blog.kakaocdn.net/dn/cf43gB/dJMcaa0hXP3/a8EEypSUuS4XdRIzalkC30/img.png&quot; loading=&quot;lazy&quot; srcset=&quot;https://img1.daumcdn.net/thumb/R1280x0/?scode=mtistory2&amp;amp;fname=https%3A%2F%2Fblog.kakaocdn.net%2Fdn%2Fcf43gB%2FdJMcaa0hXP3%2Fa8EEypSUuS4XdRIzalkC30%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; data-origin-width=&quot;424&quot; data-origin-height=&quot;913&quot;/&gt;&lt;/span&gt;&lt;button class=&quot;btn btn-prev&quot;&gt;&lt;span class=&quot;ico-prev&quot;&gt;이전&lt;/span&gt;&lt;/button&gt;&lt;button class=&quot;btn btn-next&quot;&gt;&lt;span class=&quot;ico-next&quot;&gt;다음&lt;/span&gt;&lt;/button&gt;&lt;/div&gt;
  &lt;div class=&quot;mark&quot;&gt;&lt;span data-index=&quot;0&quot;&gt;0&lt;/span&gt;&lt;span data-index=&quot;1&quot;&gt;1&lt;/span&gt;&lt;span data-index=&quot;2&quot;&gt;2&lt;/span&gt;&lt;span data-index=&quot;3&quot;&gt;3&lt;/span&gt;&lt;span data-index=&quot;4&quot;&gt;4&lt;/span&gt;&lt;span data-index=&quot;5&quot;&gt;5&lt;/span&gt;&lt;span data-index=&quot;6&quot;&gt;6&lt;/span&gt;&lt;span data-index=&quot;7&quot;&gt;7&lt;/span&gt;&lt;span data-index=&quot;8&quot;&gt;8&lt;/span&gt;&lt;span data-index=&quot;9&quot;&gt;9&lt;/span&gt;&lt;span data-index=&quot;10&quot;&gt;10&lt;/span&gt;&lt;span data-index=&quot;11&quot;&gt;11&lt;/span&gt;&lt;span data-index=&quot;12&quot;&gt;12&lt;/span&gt;&lt;span data-index=&quot;13&quot;&gt;13&lt;/span&gt;&lt;/div&gt;
&lt;/figure&gt;
&lt;/p&gt;
&lt;h2 data-ke-size=&quot;size26&quot;&gt;&amp;nbsp;&lt;/h2&gt;
&lt;h2 data-ke-size=&quot;size26&quot;&gt;&amp;nbsp;&lt;/h2&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;</description>
      <category>  프로젝트/회고록</category>
      <author>SoloQuest</author>
      <guid isPermaLink="true">https://1devlog.tistory.com/25</guid>
      <comments>https://1devlog.tistory.com/25#entry25comment</comments>
      <pubDate>Mon, 13 Jul 2026 00:28:00 +0900</pubDate>
    </item>
    <item>
      <title>프로젝트 회고록 (1) - 자투리</title>
      <link>https://1devlog.tistory.com/24</link>
      <description>&lt;p data-ke-size=&quot;size16&quot;&gt;오늘부터 그동안 진행했던 프로젝트들에 대한 회고록을 하나씩 써보려고 한다. 진작 했어야 했는데, 계속 미루다 보니 지금까지 와버렸다. 많지 않은 프로젝트지만 천천히 하나씩 정리하면서 그때의 감정도 다시 꺼내보려 한다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;이번 글은 회고록 시리즈의 첫 번째, &lt;b&gt;편입 후 처음 하게 된 프로젝트&lt;/b&gt; 이야기다.&lt;/p&gt;
&lt;h2 data-ke-size=&quot;size26&quot;&gt;배경&lt;/h2&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;작년에 편입을 했는데, 1학기는 학교에 적응하느라 정신이 없었다. 그렇게 1학기를 보내고 2학기가 되자 필수 수업으로 'P-실무 프로젝트'라는 수업을 듣게 됐다. 나는 백엔드를 지망하고 있었기 때문에, 팀도 백엔드 위주로 구성하고 싶었다.&lt;/p&gt;
&lt;h2 data-ke-size=&quot;size26&quot;&gt;팀 구성&lt;/h2&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;총 5명이 함께하게 됐다. AI 1명, 프론트 2명, 백엔드 2명. 같은 편입 동기 한 명을 설득해서 함께 백엔드를 맡기로 했고, 나머지 3명은 학교 커뮤니티 에브리타임에서 구했다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;이 수업은 원래 3-2학기 12주간 정규 수업을 듣고, 13주차부터 한 달 동안 프로젝트에만 몰입해서 완성하는 방식이었다. 하지만 나와 동기는 편입생이라 챙겨야 할 15주 수업도 꽤 있었다. 한 달이라는 짧은 시간에 다 해내기엔 무리라고 판단해서, 팀원들과 합의 후 여름방학부터 조금씩 미리 진행하기로 했다.&lt;/p&gt;
&lt;h2 data-ke-size=&quot;size26&quot;&gt;진행 방식&lt;/h2&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;방학 때 모여 가장 먼저 주제를 정했고, 이 자리에서 자연스럽게 내가 팀장을 맡았다. 학점 욕심도 있었던 터라, 내가 끌고 가서 좋은 결과를 만들고 싶은 마음에 자진했다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;막상 시작하니 쉽지 않았다. 몇 년 전 실무 경험이 있긴 했지만 AI가 등장하기 전 이야기였고, 그마저도 편입 공부하느라 다 잊은 상태였다. 팀원 대부분도 프로젝트 자체가 처음이라고 했다. 쉽지 않았지만 어떻게든 끌고 가보고 싶었다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;우리 방식은 단순했다. 2주에 한 번 정해진 날짜에 비대면 회의를 하고, 각자 역할을 기준으로 해올 것을 정한 뒤, 회의 날 완료한 것을 공유하고 다음 할 일을 다시 부여하는 방식이었다.&lt;/p&gt;
&lt;h2 data-ke-size=&quot;size26&quot;&gt;흔들리기 시작한 일정&lt;/h2&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;h2 data-ke-size=&quot;size26&quot;&gt;13주차, 본격 시작&lt;/h2&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;13주차가 되면서 수업이 시작됐다. 매주 한 번 교수님께 진행 상황을 보고하고 피드백을 받는 방식이었고, 마지막 주에는 전체 발표가 예정돼 있었다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;수업이 시작된 만큼 회의 방식도 바꿨다. 2주에 한 번 비대면 회의를, 매주 한 번 대면 회의로 전환했다. 마지막 일주일은 테스트 기간으로 잡았지만, 이마저 계속 밀려서 발표를 3일 앞두고서야 개발이 끝났다. 그런데 완성된 결과물이 내가 생각했던 기능과는 다르게 만들어져 있었다. 결국 마지막 3일은 밤새며 프론트 작업을 함께 도왔고, 마지막 발표도 내가 맡으며 수업을 마무리했다.&lt;/p&gt;
&lt;h2 data-ke-size=&quot;size26&quot;&gt;결과&lt;/h2&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;결과적으로는 나쁘지 않았다. 팀원 모두 A+를 받았고, 장려상도 받았다.(원래는 우수상까지만 시상 예정이였으나, 교수님이 장려상까지 주셨다.)&lt;/p&gt;
&lt;p&gt;&lt;figure class=&quot;imageslideblock alignCenter&quot; data-image=&quot;[{&amp;quot;src&amp;quot;:&amp;quot;https://blog.kakaocdn.net/dn/VaM5X/dJMcagsFcvA/IaGSnHeeDy1tEAlbhza4v0/img.png&amp;quot;},{&amp;quot;src&amp;quot;:&amp;quot;https://blog.kakaocdn.net/dn/Zd48v/dJMcaf1ymaK/jdjnRTSGR7bcxT52e6YyS1/img.png&amp;quot;}]&quot;&gt;
  &lt;div class=&quot;image-container&quot;&gt;&lt;span class=&quot;image-wrap selected&quot; data-phocus=&quot;https://blog.kakaocdn.net/dn/VaM5X/dJMcagsFcvA/IaGSnHeeDy1tEAlbhza4v0/img.png&quot; data-url=&quot;https://blog.kakaocdn.net/dn/VaM5X/dJMcagsFcvA/IaGSnHeeDy1tEAlbhza4v0/img.png&quot;&gt;&lt;img src=&quot;https://blog.kakaocdn.net/dn/VaM5X/dJMcagsFcvA/IaGSnHeeDy1tEAlbhza4v0/img.png&quot; loading=&quot;lazy&quot; srcset=&quot;https://img1.daumcdn.net/thumb/R1280x0/?scode=mtistory2&amp;amp;fname=https%3A%2F%2Fblog.kakaocdn.net%2Fdn%2FVaM5X%2FdJMcagsFcvA%2FIaGSnHeeDy1tEAlbhza4v0%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; data-origin-width=&quot;918&quot; data-origin-height=&quot;476&quot;/&gt;&lt;/span&gt;&lt;span class=&quot;image-wrap &quot; data-phocus=&quot;https://blog.kakaocdn.net/dn/Zd48v/dJMcaf1ymaK/jdjnRTSGR7bcxT52e6YyS1/img.png&quot; data-url=&quot;https://blog.kakaocdn.net/dn/Zd48v/dJMcaf1ymaK/jdjnRTSGR7bcxT52e6YyS1/img.png&quot;&gt;&lt;img src=&quot;https://blog.kakaocdn.net/dn/Zd48v/dJMcaf1ymaK/jdjnRTSGR7bcxT52e6YyS1/img.png&quot; loading=&quot;lazy&quot; srcset=&quot;https://img1.daumcdn.net/thumb/R1280x0/?scode=mtistory2&amp;amp;fname=https%3A%2F%2Fblog.kakaocdn.net%2Fdn%2FZd48v%2FdJMcaf1ymaK%2FjdjnRTSGR7bcxT52e6YyS1%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; data-origin-width=&quot;918&quot; data-origin-height=&quot;476&quot;/&gt;&lt;/span&gt;&lt;button class=&quot;btn btn-prev&quot;&gt;&lt;span class=&quot;ico-prev&quot;&gt;이전&lt;/span&gt;&lt;/button&gt;&lt;button class=&quot;btn btn-next&quot;&gt;&lt;span class=&quot;ico-next&quot;&gt;다음&lt;/span&gt;&lt;/button&gt;&lt;/div&gt;
  &lt;div class=&quot;mark&quot;&gt;&lt;span data-index=&quot;0&quot;&gt;0&lt;/span&gt;&lt;span data-index=&quot;1&quot;&gt;1&lt;/span&gt;&lt;/div&gt;
&lt;/figure&gt;
&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;일찍 시작한 덕에 다른 학우들이 한 달 내내 거의 매일 밤새며 회의했다는 이야기를 들었을 때, 우리는 끝까지 주 1회 회의만으로 프로젝트를 마무리할 수 있었다.&lt;/p&gt;
&lt;h2 data-ke-size=&quot;size26&quot;&gt;아쉬웠던 점&lt;/h2&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;분명 아쉬운 점도 있었다. 팀장으로서 진행 상황과 요구사항을 더 자주 확인하지 못한 책임이 컸다고 생각한다. &lt;b&gt;우리 팀은 소통이 너무 부족했다.&lt;/b&gt; 서로 맞춰가며 소통했어야 했는데, 각자에게 역할만 부여하고 2주에 한 번 확인만 하다 보니 진행 상황도, 요구사항도 제대로 파악하기 어려웠다.&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;h2 data-ke-size=&quot;size26&quot;&gt;자투리&lt;/h2&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;이것이 우리가 만든 프로젝트, &lt;b&gt;자투리&lt;/b&gt;다.&lt;/p&gt;
&lt;h4 data-ke-size=&quot;size20&quot;&gt;사용자 서비스&lt;/h4&gt;
&lt;p&gt;&lt;figure class=&quot;imageslideblock alignCenter&quot; data-image=&quot;[{&amp;quot;src&amp;quot;:&amp;quot;https://blog.kakaocdn.net/dn/3JOwO/dJMcac4QQ6F/77psO6LiwQkLV0kIKdiOmk/img.png&amp;quot;},{&amp;quot;src&amp;quot;:&amp;quot;https://blog.kakaocdn.net/dn/djWuzC/dJMcaalCZph/V69QouoqyHol4orKcaH8i1/img.png&amp;quot;},{&amp;quot;src&amp;quot;:&amp;quot;https://blog.kakaocdn.net/dn/k8oMF/dJMcaalCZpl/hsqvqJS7NFLbKHppIwmfkK/img.png&amp;quot;},{&amp;quot;src&amp;quot;:&amp;quot;https://blog.kakaocdn.net/dn/b3IZjS/dJMcaalCZpk/hoBFONwsxxTAkVUhCfM9p0/img.png&amp;quot;},{&amp;quot;src&amp;quot;:&amp;quot;https://blog.kakaocdn.net/dn/bjMfE0/dJMcaalCZpj/fdEyGe0e3a3MwWAgkdGsA1/img.png&amp;quot;},{&amp;quot;src&amp;quot;:&amp;quot;https://blog.kakaocdn.net/dn/xCHol/dJMcac4QQ6E/NvDd2QSihRENudIjJnmJQ0/img.png&amp;quot;},{&amp;quot;src&amp;quot;:&amp;quot;https://blog.kakaocdn.net/dn/vyG5l/dJMcaalCZpi/8KO5ki0d3EvguXjkKvQsek/img.png&amp;quot;},{&amp;quot;src&amp;quot;:&amp;quot;https://blog.kakaocdn.net/dn/boKCuQ/dJMcac4QQ6C/thZXbsUwHVwzvjl2QV06l0/img.png&amp;quot;},{&amp;quot;src&amp;quot;:&amp;quot;https://blog.kakaocdn.net/dn/egaHtP/dJMcac4QQ6D/ntmpefbj9cJ5ItxGnWI0Y1/img.png&amp;quot;},{&amp;quot;src&amp;quot;:&amp;quot;https://blog.kakaocdn.net/dn/cO4esP/dJMcaalCZpm/1BctDd6cq5g6Dwg0eNBBmK/img.png&amp;quot;}]&quot;&gt;
  &lt;div class=&quot;image-container&quot;&gt;&lt;span class=&quot;image-wrap selected&quot; data-phocus=&quot;https://blog.kakaocdn.net/dn/3JOwO/dJMcac4QQ6F/77psO6LiwQkLV0kIKdiOmk/img.png&quot; data-url=&quot;https://blog.kakaocdn.net/dn/3JOwO/dJMcac4QQ6F/77psO6LiwQkLV0kIKdiOmk/img.png&quot;&gt;&lt;img src=&quot;https://blog.kakaocdn.net/dn/3JOwO/dJMcac4QQ6F/77psO6LiwQkLV0kIKdiOmk/img.png&quot; loading=&quot;lazy&quot; srcset=&quot;https://img1.daumcdn.net/thumb/R1280x0/?scode=mtistory2&amp;amp;fname=https%3A%2F%2Fblog.kakaocdn.net%2Fdn%2F3JOwO%2FdJMcac4QQ6F%2F77psO6LiwQkLV0kIKdiOmk%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; data-origin-width=&quot;246&quot; data-origin-height=&quot;601&quot; data-is-animation=&quot;false&quot;/&gt;&lt;/span&gt;&lt;span class=&quot;image-wrap &quot; data-phocus=&quot;https://blog.kakaocdn.net/dn/djWuzC/dJMcaalCZph/V69QouoqyHol4orKcaH8i1/img.png&quot; data-url=&quot;https://blog.kakaocdn.net/dn/djWuzC/dJMcaalCZph/V69QouoqyHol4orKcaH8i1/img.png&quot;&gt;&lt;img src=&quot;https://blog.kakaocdn.net/dn/djWuzC/dJMcaalCZph/V69QouoqyHol4orKcaH8i1/img.png&quot; loading=&quot;lazy&quot; srcset=&quot;https://img1.daumcdn.net/thumb/R1280x0/?scode=mtistory2&amp;amp;fname=https%3A%2F%2Fblog.kakaocdn.net%2Fdn%2FdjWuzC%2FdJMcaalCZph%2FV69QouoqyHol4orKcaH8i1%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; data-origin-width=&quot;246&quot; data-origin-height=&quot;601&quot; data-is-animation=&quot;false&quot;/&gt;&lt;/span&gt;&lt;span class=&quot;image-wrap &quot; data-phocus=&quot;https://blog.kakaocdn.net/dn/k8oMF/dJMcaalCZpl/hsqvqJS7NFLbKHppIwmfkK/img.png&quot; data-url=&quot;https://blog.kakaocdn.net/dn/k8oMF/dJMcaalCZpl/hsqvqJS7NFLbKHppIwmfkK/img.png&quot;&gt;&lt;img src=&quot;https://blog.kakaocdn.net/dn/k8oMF/dJMcaalCZpl/hsqvqJS7NFLbKHppIwmfkK/img.png&quot; loading=&quot;lazy&quot; srcset=&quot;https://img1.daumcdn.net/thumb/R1280x0/?scode=mtistory2&amp;amp;fname=https%3A%2F%2Fblog.kakaocdn.net%2Fdn%2Fk8oMF%2FdJMcaalCZpl%2FhsqvqJS7NFLbKHppIwmfkK%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; data-origin-width=&quot;246&quot; data-origin-height=&quot;601&quot; data-is-animation=&quot;false&quot;/&gt;&lt;/span&gt;&lt;span class=&quot;image-wrap &quot; data-phocus=&quot;https://blog.kakaocdn.net/dn/b3IZjS/dJMcaalCZpk/hoBFONwsxxTAkVUhCfM9p0/img.png&quot; data-url=&quot;https://blog.kakaocdn.net/dn/b3IZjS/dJMcaalCZpk/hoBFONwsxxTAkVUhCfM9p0/img.png&quot;&gt;&lt;img src=&quot;https://blog.kakaocdn.net/dn/b3IZjS/dJMcaalCZpk/hoBFONwsxxTAkVUhCfM9p0/img.png&quot; loading=&quot;lazy&quot; srcset=&quot;https://img1.daumcdn.net/thumb/R1280x0/?scode=mtistory2&amp;amp;fname=https%3A%2F%2Fblog.kakaocdn.net%2Fdn%2Fb3IZjS%2FdJMcaalCZpk%2FhoBFONwsxxTAkVUhCfM9p0%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; data-origin-width=&quot;246&quot; data-origin-height=&quot;601&quot; data-is-animation=&quot;false&quot;/&gt;&lt;/span&gt;&lt;span class=&quot;image-wrap &quot; data-phocus=&quot;https://blog.kakaocdn.net/dn/bjMfE0/dJMcaalCZpj/fdEyGe0e3a3MwWAgkdGsA1/img.png&quot; data-url=&quot;https://blog.kakaocdn.net/dn/bjMfE0/dJMcaalCZpj/fdEyGe0e3a3MwWAgkdGsA1/img.png&quot;&gt;&lt;img src=&quot;https://blog.kakaocdn.net/dn/bjMfE0/dJMcaalCZpj/fdEyGe0e3a3MwWAgkdGsA1/img.png&quot; loading=&quot;lazy&quot; srcset=&quot;https://img1.daumcdn.net/thumb/R1280x0/?scode=mtistory2&amp;amp;fname=https%3A%2F%2Fblog.kakaocdn.net%2Fdn%2FbjMfE0%2FdJMcaalCZpj%2FfdEyGe0e3a3MwWAgkdGsA1%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; data-origin-width=&quot;246&quot; data-origin-height=&quot;601&quot; data-is-animation=&quot;false&quot;/&gt;&lt;/span&gt;&lt;span class=&quot;image-wrap &quot; data-phocus=&quot;https://blog.kakaocdn.net/dn/xCHol/dJMcac4QQ6E/NvDd2QSihRENudIjJnmJQ0/img.png&quot; data-url=&quot;https://blog.kakaocdn.net/dn/xCHol/dJMcac4QQ6E/NvDd2QSihRENudIjJnmJQ0/img.png&quot;&gt;&lt;img src=&quot;https://blog.kakaocdn.net/dn/xCHol/dJMcac4QQ6E/NvDd2QSihRENudIjJnmJQ0/img.png&quot; loading=&quot;lazy&quot; srcset=&quot;https://img1.daumcdn.net/thumb/R1280x0/?scode=mtistory2&amp;amp;fname=https%3A%2F%2Fblog.kakaocdn.net%2Fdn%2FxCHol%2FdJMcac4QQ6E%2FNvDd2QSihRENudIjJnmJQ0%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; data-origin-width=&quot;246&quot; data-origin-height=&quot;601&quot; data-is-animation=&quot;false&quot;/&gt;&lt;/span&gt;&lt;span class=&quot;image-wrap &quot; data-phocus=&quot;https://blog.kakaocdn.net/dn/vyG5l/dJMcaalCZpi/8KO5ki0d3EvguXjkKvQsek/img.png&quot; data-url=&quot;https://blog.kakaocdn.net/dn/vyG5l/dJMcaalCZpi/8KO5ki0d3EvguXjkKvQsek/img.png&quot;&gt;&lt;img src=&quot;https://blog.kakaocdn.net/dn/vyG5l/dJMcaalCZpi/8KO5ki0d3EvguXjkKvQsek/img.png&quot; loading=&quot;lazy&quot; srcset=&quot;https://img1.daumcdn.net/thumb/R1280x0/?scode=mtistory2&amp;amp;fname=https%3A%2F%2Fblog.kakaocdn.net%2Fdn%2FvyG5l%2FdJMcaalCZpi%2F8KO5ki0d3EvguXjkKvQsek%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; data-origin-width=&quot;246&quot; data-origin-height=&quot;601&quot; data-is-animation=&quot;false&quot;/&gt;&lt;/span&gt;&lt;span class=&quot;image-wrap &quot; data-phocus=&quot;https://blog.kakaocdn.net/dn/boKCuQ/dJMcac4QQ6C/thZXbsUwHVwzvjl2QV06l0/img.png&quot; data-url=&quot;https://blog.kakaocdn.net/dn/boKCuQ/dJMcac4QQ6C/thZXbsUwHVwzvjl2QV06l0/img.png&quot;&gt;&lt;img src=&quot;https://blog.kakaocdn.net/dn/boKCuQ/dJMcac4QQ6C/thZXbsUwHVwzvjl2QV06l0/img.png&quot; loading=&quot;lazy&quot; srcset=&quot;https://img1.daumcdn.net/thumb/R1280x0/?scode=mtistory2&amp;amp;fname=https%3A%2F%2Fblog.kakaocdn.net%2Fdn%2FboKCuQ%2FdJMcac4QQ6C%2FthZXbsUwHVwzvjl2QV06l0%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; data-origin-width=&quot;246&quot; data-origin-height=&quot;601&quot; data-is-animation=&quot;false&quot;/&gt;&lt;/span&gt;&lt;span class=&quot;image-wrap &quot; data-phocus=&quot;https://blog.kakaocdn.net/dn/egaHtP/dJMcac4QQ6D/ntmpefbj9cJ5ItxGnWI0Y1/img.png&quot; data-url=&quot;https://blog.kakaocdn.net/dn/egaHtP/dJMcac4QQ6D/ntmpefbj9cJ5ItxGnWI0Y1/img.png&quot;&gt;&lt;img src=&quot;https://blog.kakaocdn.net/dn/egaHtP/dJMcac4QQ6D/ntmpefbj9cJ5ItxGnWI0Y1/img.png&quot; loading=&quot;lazy&quot; srcset=&quot;https://img1.daumcdn.net/thumb/R1280x0/?scode=mtistory2&amp;amp;fname=https%3A%2F%2Fblog.kakaocdn.net%2Fdn%2FegaHtP%2FdJMcac4QQ6D%2Fntmpefbj9cJ5ItxGnWI0Y1%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; data-origin-width=&quot;246&quot; data-origin-height=&quot;601&quot; data-is-animation=&quot;false&quot;/&gt;&lt;/span&gt;&lt;span class=&quot;image-wrap &quot; data-phocus=&quot;https://blog.kakaocdn.net/dn/cO4esP/dJMcaalCZpm/1BctDd6cq5g6Dwg0eNBBmK/img.png&quot; data-url=&quot;https://blog.kakaocdn.net/dn/cO4esP/dJMcaalCZpm/1BctDd6cq5g6Dwg0eNBBmK/img.png&quot;&gt;&lt;img src=&quot;https://blog.kakaocdn.net/dn/cO4esP/dJMcaalCZpm/1BctDd6cq5g6Dwg0eNBBmK/img.png&quot; loading=&quot;lazy&quot; srcset=&quot;https://img1.daumcdn.net/thumb/R1280x0/?scode=mtistory2&amp;amp;fname=https%3A%2F%2Fblog.kakaocdn.net%2Fdn%2FcO4esP%2FdJMcaalCZpm%2F1BctDd6cq5g6Dwg0eNBBmK%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; data-origin-width=&quot;246&quot; data-origin-height=&quot;601&quot; data-is-animation=&quot;false&quot;/&gt;&lt;/span&gt;&lt;button class=&quot;btn btn-prev&quot;&gt;&lt;span class=&quot;ico-prev&quot;&gt;이전&lt;/span&gt;&lt;/button&gt;&lt;button class=&quot;btn btn-next&quot;&gt;&lt;span class=&quot;ico-next&quot;&gt;다음&lt;/span&gt;&lt;/button&gt;&lt;/div&gt;
  &lt;div class=&quot;mark&quot;&gt;&lt;span data-index=&quot;0&quot;&gt;0&lt;/span&gt;&lt;span data-index=&quot;1&quot;&gt;1&lt;/span&gt;&lt;span data-index=&quot;2&quot;&gt;2&lt;/span&gt;&lt;span data-index=&quot;3&quot;&gt;3&lt;/span&gt;&lt;span data-index=&quot;4&quot;&gt;4&lt;/span&gt;&lt;span data-index=&quot;5&quot;&gt;5&lt;/span&gt;&lt;span data-index=&quot;6&quot;&gt;6&lt;/span&gt;&lt;span data-index=&quot;7&quot;&gt;7&lt;/span&gt;&lt;span data-index=&quot;8&quot;&gt;8&lt;/span&gt;&lt;span data-index=&quot;9&quot;&gt;9&lt;/span&gt;&lt;/div&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;/h4&gt;
&lt;p&gt;&lt;figure class=&quot;imageslideblock alignCenter&quot; data-image=&quot;[{&amp;quot;src&amp;quot;:&amp;quot;https://blog.kakaocdn.net/dn/bFmz4t/dJMcajwbbyZ/FiwAlKtngu0CuK8obh8c8K/img.png&amp;quot;},{&amp;quot;src&amp;quot;:&amp;quot;https://blog.kakaocdn.net/dn/bU44t4/dJMcabrm1P4/7W5Ypvpblwlboam5ZuJmH1/img.png&amp;quot;},{&amp;quot;src&amp;quot;:&amp;quot;https://blog.kakaocdn.net/dn/AX1J7/dJMcagzp24l/GdbquLngFhHtkVLHUDK9Jk/img.png&amp;quot;},{&amp;quot;src&amp;quot;:&amp;quot;https://blog.kakaocdn.net/dn/bManFT/dJMcabrm1P5/ClfekkKOovWbQ9mwuOSBwk/img.png&amp;quot;}]&quot;&gt;
  &lt;div class=&quot;image-container&quot;&gt;&lt;span class=&quot;image-wrap selected&quot; data-phocus=&quot;https://blog.kakaocdn.net/dn/bFmz4t/dJMcajwbbyZ/FiwAlKtngu0CuK8obh8c8K/img.png&quot; data-url=&quot;https://blog.kakaocdn.net/dn/bFmz4t/dJMcajwbbyZ/FiwAlKtngu0CuK8obh8c8K/img.png&quot;&gt;&lt;img src=&quot;https://blog.kakaocdn.net/dn/bFmz4t/dJMcajwbbyZ/FiwAlKtngu0CuK8obh8c8K/img.png&quot; loading=&quot;lazy&quot; srcset=&quot;https://img1.daumcdn.net/thumb/R1280x0/?scode=mtistory2&amp;amp;fname=https%3A%2F%2Fblog.kakaocdn.net%2Fdn%2FbFmz4t%2FdJMcajwbbyZ%2FFiwAlKtngu0CuK8obh8c8K%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; data-origin-width=&quot;641&quot; data-origin-height=&quot;361&quot; data-is-animation=&quot;false&quot;/&gt;&lt;/span&gt;&lt;span class=&quot;image-wrap &quot; data-phocus=&quot;https://blog.kakaocdn.net/dn/bU44t4/dJMcabrm1P4/7W5Ypvpblwlboam5ZuJmH1/img.png&quot; data-url=&quot;https://blog.kakaocdn.net/dn/bU44t4/dJMcabrm1P4/7W5Ypvpblwlboam5ZuJmH1/img.png&quot;&gt;&lt;img src=&quot;https://blog.kakaocdn.net/dn/bU44t4/dJMcabrm1P4/7W5Ypvpblwlboam5ZuJmH1/img.png&quot; loading=&quot;lazy&quot; srcset=&quot;https://img1.daumcdn.net/thumb/R1280x0/?scode=mtistory2&amp;amp;fname=https%3A%2F%2Fblog.kakaocdn.net%2Fdn%2FbU44t4%2FdJMcabrm1P4%2F7W5Ypvpblwlboam5ZuJmH1%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; data-origin-width=&quot;641&quot; data-origin-height=&quot;361&quot; data-is-animation=&quot;false&quot;/&gt;&lt;/span&gt;&lt;span class=&quot;image-wrap &quot; data-phocus=&quot;https://blog.kakaocdn.net/dn/AX1J7/dJMcagzp24l/GdbquLngFhHtkVLHUDK9Jk/img.png&quot; data-url=&quot;https://blog.kakaocdn.net/dn/AX1J7/dJMcagzp24l/GdbquLngFhHtkVLHUDK9Jk/img.png&quot;&gt;&lt;img src=&quot;https://blog.kakaocdn.net/dn/AX1J7/dJMcagzp24l/GdbquLngFhHtkVLHUDK9Jk/img.png&quot; loading=&quot;lazy&quot; srcset=&quot;https://img1.daumcdn.net/thumb/R1280x0/?scode=mtistory2&amp;amp;fname=https%3A%2F%2Fblog.kakaocdn.net%2Fdn%2FAX1J7%2FdJMcagzp24l%2FGdbquLngFhHtkVLHUDK9Jk%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; data-origin-width=&quot;641&quot; data-origin-height=&quot;361&quot; data-is-animation=&quot;false&quot;/&gt;&lt;/span&gt;&lt;span class=&quot;image-wrap &quot; data-phocus=&quot;https://blog.kakaocdn.net/dn/bManFT/dJMcabrm1P5/ClfekkKOovWbQ9mwuOSBwk/img.png&quot; data-url=&quot;https://blog.kakaocdn.net/dn/bManFT/dJMcabrm1P5/ClfekkKOovWbQ9mwuOSBwk/img.png&quot;&gt;&lt;img src=&quot;https://blog.kakaocdn.net/dn/bManFT/dJMcabrm1P5/ClfekkKOovWbQ9mwuOSBwk/img.png&quot; loading=&quot;lazy&quot; srcset=&quot;https://img1.daumcdn.net/thumb/R1280x0/?scode=mtistory2&amp;amp;fname=https%3A%2F%2Fblog.kakaocdn.net%2Fdn%2FbManFT%2FdJMcabrm1P5%2FClfekkKOovWbQ9mwuOSBwk%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; data-origin-width=&quot;641&quot; data-origin-height=&quot;361&quot; data-is-animation=&quot;false&quot;/&gt;&lt;/span&gt;&lt;button class=&quot;btn btn-prev&quot;&gt;&lt;span class=&quot;ico-prev&quot;&gt;이전&lt;/span&gt;&lt;/button&gt;&lt;button class=&quot;btn btn-next&quot;&gt;&lt;span class=&quot;ico-next&quot;&gt;다음&lt;/span&gt;&lt;/button&gt;&lt;/div&gt;
  &lt;div class=&quot;mark&quot;&gt;&lt;span data-index=&quot;0&quot;&gt;0&lt;/span&gt;&lt;span data-index=&quot;1&quot;&gt;1&lt;/span&gt;&lt;span data-index=&quot;2&quot;&gt;2&lt;/span&gt;&lt;span data-index=&quot;3&quot;&gt;3&lt;/span&gt;&lt;/div&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;깃허브 주소: &lt;a href=&quot;https://github.com/jatuli-25-2&quot; target=&quot;_blank&quot; rel=&quot;noopener&amp;nbsp;noreferrer&quot;&gt;https://github.com/jatuli-25-2&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;완벽한 프로젝트는 아니었지만, 처음으로 팀을 이끌며 개발보다 사람과 일정, 소통을 관리하는 일이 더 어렵다는 것을 배웠다. 이후 프로젝트를 진행할 때마다 이 경험을 기준으로 내 방식을 돌아보게 됐다. 오랜만에 하는 프로젝트라 힘들었지만, 그만큼 의미있던 첫 프로젝트였던거같다. 하지만 자투리 이야기는 여기서 완전히 끝나지 않았다. 완성된 결과에 남은 아쉬움 때문에, 방학이 되자 일부 팀원들과 다시 모여 프로젝트를 손보기 시작했다. 이 이야기는 &lt;a href=&quot;https://1devlog.tistory.com/25&quot; target=&quot;_blank&quot; rel=&quot;noopener&quot;&gt;2편&lt;/a&gt;에서 이어진다.&lt;/p&gt;</description>
      <category>  프로젝트/회고록</category>
      <author>SoloQuest</author>
      <guid isPermaLink="true">https://1devlog.tistory.com/24</guid>
      <comments>https://1devlog.tistory.com/24#entry24comment</comments>
      <pubDate>Mon, 13 Jul 2026 00:06:05 +0900</pubDate>
    </item>
    <item>
      <title>[QRoad] 서버는 빠른데 로컬만 느린 API, N+1 병목 줄이기</title>
      <link>https://1devlog.tistory.com/23</link>
      <description>&lt;p style=&quot;color: #d4d4d4; text-align: start;&quot; data-ke-size=&quot;size16&quot;&gt;&lt;span style=&quot;color: #000000;&quot;&gt;최근&amp;nbsp;&lt;span style=&quot;background-color: oklab(0.845175 0.0000383258 0.0000169277 / 0.1);&quot;&gt;/api/admin/publications/{paperId}&lt;/span&gt;&amp;nbsp;상세 조회 API를 확인하던 중, 서버에서는 크게 느리지 않은데 로컬에서 유독 지연이 큰 현상을 발견했다.&lt;/span&gt;&lt;br /&gt;&lt;span style=&quot;color: #000000;&quot;&gt;처음에는 단순 환경 차이일 수 있다고 생각했지만, 데이터를 바꿔가며 호출해 보니 패턴이 분명했다.&lt;/span&gt;&lt;/p&gt;
&lt;ul style=&quot;list-style-type: disc; color: #d4d4d4; text-align: start;&quot; data-ke-list-type=&quot;disc&quot;&gt;
&lt;li&gt;&lt;span style=&quot;color: #000000;&quot;&gt;기사 38개인 케이스: 약 18초&lt;/span&gt;&lt;/li&gt;
&lt;li&gt;&lt;span style=&quot;color: #000000;&quot;&gt;기사 51개인 케이스: 약 28초&lt;/span&gt;&lt;/li&gt;
&lt;/ul&gt;
&lt;p style=&quot;color: #d4d4d4; text-align: start;&quot; data-ke-size=&quot;size16&quot;&gt;&lt;span style=&quot;color: #000000;&quot;&gt;측정값 자체는 환경에 따라 달라질 수 있다.&lt;/span&gt;&lt;br /&gt;&lt;span style=&quot;color: #000000;&quot;&gt;다만 기사 수가 늘어날수록 지연이 크게 증가한다는 점이 명확해서, 이 구간을 병목으로 보고 로직을 점검했다.&lt;/span&gt;&lt;/p&gt;
&lt;hr contenteditable=&quot;false&quot; data-ke-type=&quot;horizontalRule&quot; data-ke-style=&quot;style5&quot; /&gt;
&lt;h2 style=&quot;color: #d4d4d4; text-align: start;&quot; data-ke-size=&quot;size26&quot;&gt;&lt;span style=&quot;color: #000000;&quot;&gt;원인: N+1 쿼리 패턴&lt;/span&gt;&lt;/h2&gt;
&lt;p style=&quot;color: #d4d4d4; text-align: start;&quot; data-ke-size=&quot;size16&quot;&gt;&lt;span style=&quot;color: #000000;&quot;&gt;상세 조회 로직에서 기사 목록을 가져온 뒤, 각 기사마다 키워드를 개별 조회하고 있었다.&lt;/span&gt;&lt;br /&gt;&lt;span style=&quot;color: #000000;&quot;&gt;즉, 기사 N개면 키워드 조회가 N번 추가되는 구조였다.&lt;/span&gt;&lt;/p&gt;
&lt;p style=&quot;color: #d4d4d4; text-align: start;&quot; data-ke-size=&quot;size16&quot;&gt;&lt;span style=&quot;color: #000000;&quot;&gt;이런 N+1 패턴은 데이터가 적을 때는 잘 드러나지 않지만, 데이터가 쌓일수록 응답 시간이 빠르게 나빠질 수 있다.&lt;/span&gt;&lt;/p&gt;
&lt;hr contenteditable=&quot;false&quot; data-ke-type=&quot;horizontalRule&quot; data-ke-style=&quot;style5&quot; /&gt;
&lt;h2 style=&quot;color: #d4d4d4; text-align: start;&quot; data-ke-size=&quot;size26&quot;&gt;&lt;span style=&quot;color: #000000;&quot;&gt;개선: 배치 조회로 전환&lt;/span&gt;&lt;/h2&gt;
&lt;p style=&quot;color: #d4d4d4; text-align: start;&quot; data-ke-size=&quot;size16&quot;&gt;&lt;span style=&quot;color: #000000;&quot;&gt;개선 방향은 단순했다.&lt;/span&gt;&lt;/p&gt;
&lt;ol style=&quot;list-style-type: decimal; color: #d4d4d4; text-align: start;&quot; data-ke-list-type=&quot;decimal&quot;&gt;
&lt;li&gt;&lt;span style=&quot;color: #000000;&quot;&gt;기사별 키워드 개별 조회 제거&lt;/span&gt;&lt;/li&gt;
&lt;li&gt;&lt;span style=&quot;color: #000000;&quot;&gt;기사 ID 목록을 모아&amp;nbsp;&lt;span style=&quot;background-color: oklab(0.845175 0.0000383258 0.0000169277 / 0.1);&quot;&gt;IN (...)&lt;/span&gt;으로 키워드를 한 번에 조회&lt;/span&gt;&lt;/li&gt;
&lt;li&gt;&lt;span style=&quot;color: #000000;&quot;&gt;조회 결과를&amp;nbsp;&lt;span style=&quot;background-color: oklab(0.845175 0.0000383258 0.0000169277 / 0.1);&quot;&gt;articleId -&amp;gt; keywords&lt;/span&gt;&amp;nbsp;형태로 그룹핑해 DTO 조합&lt;/span&gt;&lt;/li&gt;
&lt;/ol&gt;
&lt;p style=&quot;color: #d4d4d4; text-align: start;&quot; data-ke-size=&quot;size16&quot;&gt;&lt;span style=&quot;color: #000000;&quot;&gt;Before:&lt;/span&gt;&lt;/p&gt;
&lt;ul style=&quot;list-style-type: disc; color: #d4d4d4; text-align: start;&quot; data-ke-list-type=&quot;disc&quot;&gt;
&lt;li&gt;&lt;span style=&quot;color: #000000;&quot;&gt;기사마다 키워드 조회 반복&lt;/span&gt;&lt;/li&gt;
&lt;/ul&gt;
&lt;p style=&quot;color: #d4d4d4; text-align: start;&quot; data-ke-size=&quot;size16&quot;&gt;&lt;span style=&quot;color: #000000;&quot;&gt;After:&lt;/span&gt;&lt;/p&gt;
&lt;ul style=&quot;list-style-type: disc; color: #d4d4d4; text-align: start;&quot; data-ke-list-type=&quot;disc&quot;&gt;
&lt;li&gt;&lt;span style=&quot;color: #000000;&quot;&gt;키워드 일괄 조회 후 메모리 그룹핑&lt;/span&gt;&lt;/li&gt;
&lt;/ul&gt;
&lt;p style=&quot;color: #d4d4d4; text-align: start;&quot; data-ke-size=&quot;size16&quot;&gt;&lt;span style=&quot;color: #000000;&quot;&gt;핵심은 반복 조회를 묶음 조회로 바꿔 DB 왕복 횟수를 줄인 것이다.&lt;/span&gt;&lt;/p&gt;
&lt;hr contenteditable=&quot;false&quot; data-ke-type=&quot;horizontalRule&quot; data-ke-style=&quot;style5&quot; /&gt;
&lt;h2 style=&quot;color: #d4d4d4; text-align: start;&quot; data-ke-size=&quot;size26&quot;&gt;&lt;span style=&quot;color: #000000;&quot;&gt;적용 코드 (요약)&lt;/span&gt;&lt;/h2&gt;
&lt;h3 style=&quot;color: #d4d4d4; text-align: start;&quot; data-ke-size=&quot;size23&quot;&gt;&lt;span style=&quot;color: #000000;&quot;&gt;Repository: 기사 ID 목록으로 키워드 일괄 조회&lt;/span&gt;&lt;/h3&gt;
&lt;div style=&quot;background-color: #000000; color: #d4d4d4; text-align: start;&quot; data-theme=&quot;dark&quot;&gt;
&lt;div&gt;@Query(&quot;&quot;&quot; &lt;br /&gt;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;SELECT&amp;nbsp;ak.article.id,&amp;nbsp;ak.keyword.name &lt;br /&gt;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;FROM&amp;nbsp;ArticleKeywordEntity&amp;nbsp;ak &lt;br /&gt;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;WHERE&amp;nbsp;ak.article.id&amp;nbsp;IN&amp;nbsp;:articleIds &lt;br /&gt;&quot;&quot;&quot;) &lt;br /&gt;List&amp;lt;Object[]&amp;gt;&amp;nbsp;findArticleIdAndKeywordNameByArticleIds(@Param(&quot;articleIds&quot;)&amp;nbsp;List&amp;lt;Long&amp;gt;&amp;nbsp;articleIds); &lt;/div&gt;
&lt;div&gt;&amp;nbsp;&lt;/div&gt;
&lt;/div&gt;
&lt;h3 style=&quot;color: #d4d4d4; text-align: start;&quot; data-ke-size=&quot;size23&quot;&gt;&lt;span style=&quot;color: #000000;&quot;&gt;Service: 조회 결과를 articleId 기준으로 그룹핑&lt;/span&gt;&lt;/h3&gt;
&lt;div style=&quot;background-color: #000000; color: #d4d4d4; text-align: start;&quot; data-theme=&quot;dark&quot;&gt;private&amp;nbsp;Map&amp;lt;Long,&amp;nbsp;List&amp;lt;String&amp;gt;&amp;gt;&amp;nbsp;getKeywordsForArticles(List&amp;lt;ArticleEntity&amp;gt;&amp;nbsp;articles)&amp;nbsp;{ &lt;br /&gt;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;if&amp;nbsp;(articles&amp;nbsp;==&amp;nbsp;null&amp;nbsp;||&amp;nbsp;articles.isEmpty())&amp;nbsp;{ &lt;br /&gt;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;return&amp;nbsp;Collections.emptyMap(); &lt;br /&gt;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;} &lt;br /&gt;&lt;br /&gt;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;List&amp;lt;Long&amp;gt;&amp;nbsp;articleIds&amp;nbsp;=&amp;nbsp;articles.stream() &lt;br /&gt;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;.map(ArticleEntity::getId) &lt;br /&gt;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;.toList(); &lt;br /&gt;&lt;br /&gt;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;List&amp;lt;Object[]&amp;gt;&amp;nbsp;rows&amp;nbsp;=&amp;nbsp;articleKeywordRepository.findArticleIdAndKeywordNameByArticleIds(articleIds); &lt;br /&gt;&lt;br /&gt;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;Map&amp;lt;Long,&amp;nbsp;List&amp;lt;String&amp;gt;&amp;gt;&amp;nbsp;keywordsByArticleId&amp;nbsp;=&amp;nbsp;new&amp;nbsp;HashMap&amp;lt;&amp;gt;(); &lt;br /&gt;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;for&amp;nbsp;(Object[]&amp;nbsp;row&amp;nbsp;:&amp;nbsp;rows)&amp;nbsp;{ &lt;br /&gt;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;Long&amp;nbsp;articleId&amp;nbsp;=&amp;nbsp;((Number)&amp;nbsp;row[0]).longValue(); &lt;br /&gt;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;String&amp;nbsp;keywordName&amp;nbsp;=&amp;nbsp;(String)&amp;nbsp;row[1]; &lt;br /&gt;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;keywordsByArticleId &lt;br /&gt;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;.computeIfAbsent(articleId,&amp;nbsp;ignored&amp;nbsp;-&amp;gt;&amp;nbsp;new&amp;nbsp;ArrayList&amp;lt;&amp;gt;()) &lt;br /&gt;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;.add(keywordName); &lt;br /&gt;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;} &lt;br /&gt;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;return&amp;nbsp;keywordsByArticleId; &lt;br /&gt;} &lt;/div&gt;
&lt;hr contenteditable=&quot;false&quot; data-ke-type=&quot;horizontalRule&quot; data-ke-style=&quot;style5&quot; /&gt;
&lt;h2 style=&quot;color: #d4d4d4; text-align: start;&quot; data-ke-size=&quot;size26&quot;&gt;&lt;span style=&quot;color: #000000;&quot;&gt;정리&lt;/span&gt;&lt;/h2&gt;
&lt;p style=&quot;color: #d4d4d4; text-align: start;&quot; data-ke-size=&quot;size16&quot;&gt;&lt;span style=&quot;color: #000000;&quot;&gt;이번 개선의 핵심은 &amp;ldquo;몇 초 줄였다&amp;rdquo;보다,&lt;/span&gt;&lt;br /&gt;&lt;span style=&quot;color: #000000;&quot;&gt;데이터 증가에 따라 지연이 커지던 구조를 안정적으로 바꿨다는 점에 있다.&lt;/span&gt;&lt;/p&gt;
&lt;p style=&quot;color: #d4d4d4; text-align: start;&quot; data-ke-size=&quot;size16&quot;&gt;&lt;span style=&quot;color: #000000;&quot;&gt;성능 이슈는 큰 장애가 나서 확인하기보다,&lt;/span&gt;&lt;br /&gt;&lt;span style=&quot;color: #000000;&quot;&gt;작은 이상 패턴을 보였을 때 바로 구조를 점검하는 게 훨씬 효과적이라는 걸 다시 느꼈다.&lt;/span&gt;&lt;/p&gt;</description>
      <category>  프로젝트/트러블 슈팅</category>
      <author>SoloQuest</author>
      <guid isPermaLink="true">https://1devlog.tistory.com/23</guid>
      <comments>https://1devlog.tistory.com/23#entry23comment</comments>
      <pubDate>Sat, 4 Apr 2026 04:26:55 +0900</pubDate>
    </item>
    <item>
      <title>서버 git pull 배포에서 GHCR 기반 Docker 배포로 전환한 기록</title>
      <link>https://1devlog.tistory.com/22</link>
      <description>&lt;p style=&quot;color: #d4d4d4; text-align: start;&quot; data-ke-size=&quot;size16&quot;&gt;&lt;span style=&quot;color: #000000;&quot;&gt;배포 방식을 기존의&amp;nbsp;&lt;span style=&quot;background-color: oklab(0.845175 0.0000383258 0.0000169277 / 0.1);&quot;&gt;서버에서 git pull + 빌드&lt;/span&gt;&amp;nbsp;방식에서,&lt;/span&gt;&lt;br /&gt;&lt;span style=&quot;color: #000000;&quot;&gt;&lt;span style=&quot;background-color: oklab(0.845175 0.0000383258 0.0000169277 / 0.1);&quot;&gt;CI에서 이미지 빌드 + GHCR 푸시 + 서버는 pull만&lt;/span&gt;&amp;nbsp;하는 구조로 바꿨다.&lt;/span&gt;&lt;/p&gt;
&lt;p style=&quot;color: #d4d4d4; text-align: start;&quot; data-ke-size=&quot;size16&quot;&gt;&lt;span style=&quot;color: #000000;&quot;&gt;작지만 확실한 개선이었고, 실제로 겪은 오류와 해결 과정을 정리해본다.&lt;/span&gt;&lt;/p&gt;
&lt;h2 style=&quot;color: #d4d4d4; text-align: start;&quot; data-ke-size=&quot;size26&quot;&gt;&lt;span style=&quot;color: #000000;&quot;&gt;왜 바꿨나?&lt;/span&gt;&lt;/h2&gt;
&lt;p style=&quot;color: #d4d4d4; text-align: start;&quot; data-ke-size=&quot;size16&quot;&gt;&lt;span style=&quot;color: #000000;&quot;&gt;기존 방식은 서버가 직접 빌드했다.&lt;/span&gt;&lt;/p&gt;
&lt;ul style=&quot;list-style-type: disc; color: #d4d4d4; text-align: start;&quot; data-ke-list-type=&quot;disc&quot;&gt;
&lt;li&gt;&lt;span style=&quot;color: #000000;&quot;&gt;서버 상태(자바/그레이들/캐시)에 따라 결과가 달라질 수 있음&lt;/span&gt;&lt;/li&gt;
&lt;li&gt;&lt;span style=&quot;color: #000000;&quot;&gt;&lt;span style=&quot;background-color: oklab(0.845175 0.0000383258 0.0000169277 / 0.1);&quot;&gt;/home/ubuntu/BE&lt;/span&gt;&amp;nbsp;같은 경로 의존성이 큼&lt;/span&gt;&lt;/li&gt;
&lt;li&gt;&lt;span style=&quot;color: #000000;&quot;&gt;배포 실패 시 원인 추적이 어려움&lt;/span&gt;&lt;/li&gt;
&lt;li&gt;&lt;span style=&quot;color: #000000;&quot;&gt;롤백이 명확하지 않음&lt;/span&gt;&lt;/li&gt;
&lt;/ul&gt;
&lt;p style=&quot;color: #d4d4d4; text-align: start;&quot; data-ke-size=&quot;size16&quot;&gt;&lt;span style=&quot;color: #000000;&quot;&gt;그래서 목표를 이렇게 잡았다.&lt;/span&gt;&lt;/p&gt;
&lt;ul style=&quot;list-style-type: disc; color: #d4d4d4; text-align: start;&quot; data-ke-list-type=&quot;disc&quot;&gt;
&lt;li&gt;&lt;span style=&quot;color: #000000;&quot;&gt;빌드는 CI에서만&lt;/span&gt;&lt;/li&gt;
&lt;li&gt;&lt;span style=&quot;color: #000000;&quot;&gt;서버는 이미지 pull/run만&lt;/span&gt;&lt;/li&gt;
&lt;li&gt;&lt;span style=&quot;color: #000000;&quot;&gt;커밋 태그 기반 배포&lt;/span&gt;&lt;/li&gt;
&lt;li&gt;&lt;span style=&quot;color: #000000;&quot;&gt;실패 시 자동 롤백&lt;/span&gt;&lt;/li&gt;
&lt;/ul&gt;
&lt;hr contenteditable=&quot;false&quot; data-ke-type=&quot;horizontalRule&quot; data-ke-style=&quot;style5&quot; /&gt;
&lt;h2 style=&quot;color: #d4d4d4; text-align: start;&quot; data-ke-size=&quot;size26&quot;&gt;&lt;span style=&quot;color: #000000;&quot;&gt;기존 방식 (Before)&lt;/span&gt;&lt;/h2&gt;
&lt;ol style=&quot;list-style-type: decimal; color: #d4d4d4; text-align: start;&quot; data-ke-list-type=&quot;decimal&quot;&gt;
&lt;li&gt;&lt;span style=&quot;color: #000000;&quot;&gt;GitHub Actions가 SSH로 서버 접속&lt;/span&gt;&lt;/li&gt;
&lt;li&gt;&lt;span style=&quot;color: #000000;&quot;&gt;서버에서&amp;nbsp;&lt;span style=&quot;background-color: oklab(0.845175 0.0000383258 0.0000169277 / 0.1);&quot;&gt;git pull&lt;/span&gt;&lt;/span&gt;&lt;/li&gt;
&lt;li&gt;&lt;span style=&quot;background-color: oklab(0.845175 0.0000383258 0.0000169277 / 0.1); color: #000000;&quot;&gt;./gradlew clean build -x test&lt;/span&gt;&lt;/li&gt;
&lt;li&gt;&lt;span style=&quot;color: #000000;&quot;&gt;기존 프로세스 kill 후&amp;nbsp;&lt;span style=&quot;background-color: oklab(0.845175 0.0000383258 0.0000169277 / 0.1);&quot;&gt;java -jar&lt;/span&gt;&amp;nbsp;실행&lt;/span&gt;&lt;/li&gt;
&lt;/ol&gt;
&lt;p style=&quot;color: #d4d4d4; text-align: start;&quot; data-ke-size=&quot;size16&quot;&gt;&lt;span style=&quot;color: #000000;&quot;&gt;즉, 서버가 코드/빌드/실행을 모두 담당했다.&lt;/span&gt;&lt;/p&gt;
&lt;hr contenteditable=&quot;false&quot; data-ke-type=&quot;horizontalRule&quot; data-ke-style=&quot;style5&quot; /&gt;
&lt;h2 style=&quot;color: #d4d4d4; text-align: start;&quot; data-ke-size=&quot;size26&quot;&gt;&lt;span style=&quot;color: #000000;&quot;&gt;변경한 방식 (After)&lt;/span&gt;&lt;/h2&gt;
&lt;h3 style=&quot;color: #d4d4d4; text-align: start;&quot; data-ke-size=&quot;size23&quot;&gt;&lt;span style=&quot;color: #000000;&quot;&gt;핵심 구조&lt;/span&gt;&lt;/h3&gt;
&lt;ol style=&quot;list-style-type: decimal; color: #d4d4d4; text-align: start;&quot; data-ke-list-type=&quot;decimal&quot;&gt;
&lt;li&gt;&lt;span style=&quot;color: #000000;&quot;&gt;&lt;span style=&quot;background-color: oklab(0.845175 0.0000383258 0.0000169277 / 0.1);&quot;&gt;build_and_push&lt;/span&gt;&amp;nbsp;Job&lt;/span&gt;
&lt;ul style=&quot;list-style-type: disc;&quot; data-ke-list-type=&quot;disc&quot;&gt;
&lt;li&gt;&lt;span style=&quot;color: #000000;&quot;&gt;Docker 이미지 빌드&lt;/span&gt;&lt;/li&gt;
&lt;li&gt;&lt;span style=&quot;color: #000000;&quot;&gt;GHCR에 두 태그 푸시&lt;/span&gt;
&lt;ul style=&quot;list-style-type: disc;&quot; data-ke-list-type=&quot;disc&quot;&gt;
&lt;li&gt;&lt;span style=&quot;background-color: oklab(0.845175 0.0000383258 0.0000169277 / 0.1); color: #000000;&quot;&gt;ghcr.io/&amp;lt;org&amp;gt;/&amp;lt;repo&amp;gt;:&amp;lt;sha12&amp;gt;&lt;/span&gt;&lt;/li&gt;
&lt;li&gt;&lt;span style=&quot;background-color: oklab(0.845175 0.0000383258 0.0000169277 / 0.1); color: #000000;&quot;&gt;ghcr.io/&amp;lt;org&amp;gt;/&amp;lt;repo&amp;gt;:latest&lt;/span&gt;&lt;/li&gt;
&lt;/ul&gt;
&lt;/li&gt;
&lt;/ul&gt;
&lt;/li&gt;
&lt;li&gt;&lt;span style=&quot;color: #000000;&quot;&gt;&lt;span style=&quot;background-color: oklab(0.845175 0.0000383258 0.0000169277 / 0.1);&quot;&gt;deploy&lt;/span&gt;&amp;nbsp;Job&lt;/span&gt;
&lt;ul style=&quot;list-style-type: disc;&quot; data-ke-list-type=&quot;disc&quot;&gt;
&lt;li&gt;&lt;span style=&quot;color: #000000;&quot;&gt;SSH 허용 IP를 보안그룹에 임시 추가&lt;/span&gt;&lt;/li&gt;
&lt;li&gt;&lt;span style=&quot;color: #000000;&quot;&gt;서버에&amp;nbsp;&lt;span style=&quot;background-color: oklab(0.845175 0.0000383258 0.0000169277 / 0.1);&quot;&gt;docker-compose.prod.yml&lt;/span&gt;,&amp;nbsp;&lt;span style=&quot;background-color: oklab(0.845175 0.0000383258 0.0000169277 / 0.1);&quot;&gt;deploy.sh&lt;/span&gt;&amp;nbsp;전송(&lt;span style=&quot;background-color: oklab(0.845175 0.0000383258 0.0000169277 / 0.1);&quot;&gt;scp&lt;/span&gt;)&lt;/span&gt;&lt;/li&gt;
&lt;li&gt;&lt;span style=&quot;color: #000000;&quot;&gt;서버에서 배포 스크립트 실행&lt;/span&gt;
&lt;ul style=&quot;list-style-type: disc;&quot; data-ke-list-type=&quot;disc&quot;&gt;
&lt;li&gt;&lt;span style=&quot;color: #000000;&quot;&gt;commit 태그 pull 우선&lt;/span&gt;&lt;/li&gt;
&lt;li&gt;&lt;span style=&quot;color: #000000;&quot;&gt;실패 시 latest fallback&lt;/span&gt;&lt;/li&gt;
&lt;li&gt;&lt;span style=&quot;background-color: oklab(0.845175 0.0000383258 0.0000169277 / 0.1); color: #000000;&quot;&gt;docker compose up -d&lt;/span&gt;&lt;/li&gt;
&lt;li&gt;&lt;span style=&quot;color: #000000;&quot;&gt;health check 실패 시 이전 이미지 롤백&lt;/span&gt;&lt;/li&gt;
&lt;/ul&gt;
&lt;/li&gt;
&lt;li&gt;&lt;span style=&quot;color: #000000;&quot;&gt;마지막에 보안그룹 IP 제거&lt;/span&gt;&lt;/li&gt;
&lt;/ul&gt;
&lt;/li&gt;
&lt;/ol&gt;
&lt;hr contenteditable=&quot;false&quot; data-ke-type=&quot;horizontalRule&quot; data-ke-style=&quot;style5&quot; /&gt;
&lt;h2 style=&quot;color: #d4d4d4; text-align: start;&quot; data-ke-size=&quot;size26&quot;&gt;&lt;span style=&quot;color: #000000;&quot;&gt;서버 레포 의존성 제거&lt;/span&gt;&lt;/h2&gt;
&lt;p style=&quot;color: #d4d4d4; text-align: start;&quot; data-ke-size=&quot;size16&quot;&gt;&lt;span style=&quot;color: #000000;&quot;&gt;처음에는 서버에&amp;nbsp;&lt;span style=&quot;background-color: oklab(0.845175 0.0000383258 0.0000169277 / 0.1);&quot;&gt;/home/ubuntu/BE&lt;/span&gt;가 있어야 했는데,&lt;/span&gt;&lt;br /&gt;&lt;span style=&quot;color: #000000;&quot;&gt;배포 중 아래 오류를 만났다.&lt;/span&gt;&lt;/p&gt;
&lt;div style=&quot;background-color: #000000; color: #d4d4d4; text-align: start;&quot; data-theme=&quot;dark&quot;&gt;
&lt;div&gt;&lt;span&gt;&lt;span&gt;&lt;span style=&quot;color: #e9950c;&quot;&gt;cd&lt;/span&gt;&lt;/span&gt;&lt;span&gt;: /home/ubuntu/BE: No such file or directory &lt;/span&gt;&lt;/span&gt;&lt;/div&gt;
&lt;/div&gt;
&lt;p style=&quot;color: #d4d4d4; text-align: start;&quot; data-ke-size=&quot;size16&quot;&gt;&lt;span style=&quot;color: #000000;&quot;&gt;해결은 간단했다.&lt;/span&gt;&lt;/p&gt;
&lt;ul style=&quot;list-style-type: disc; color: #d4d4d4; text-align: start;&quot; data-ke-list-type=&quot;disc&quot;&gt;
&lt;li&gt;&lt;span style=&quot;color: #000000;&quot;&gt;서버 고정 배포 디렉토리를&amp;nbsp;&lt;span style=&quot;background-color: oklab(0.845175 0.0000383258 0.0000169277 / 0.1);&quot;&gt;/opt/qroad&lt;/span&gt;로 변경&lt;/span&gt;&lt;/li&gt;
&lt;li&gt;&lt;span style=&quot;color: #000000;&quot;&gt;매번 필요한 파일(&lt;span style=&quot;background-color: oklab(0.845175 0.0000383258 0.0000169277 / 0.1);&quot;&gt;deploy.sh&lt;/span&gt;,&amp;nbsp;&lt;span style=&quot;background-color: oklab(0.845175 0.0000383258 0.0000169277 / 0.1);&quot;&gt;docker-compose.prod.yml&lt;/span&gt;)을 CI가&amp;nbsp;&lt;span style=&quot;background-color: oklab(0.845175 0.0000383258 0.0000169277 / 0.1);&quot;&gt;scp&lt;/span&gt;로 전달&lt;/span&gt;&lt;/li&gt;
&lt;li&gt;&lt;span style=&quot;color: #000000;&quot;&gt;서버에 git clone/pull 필요 없도록 전환&lt;/span&gt;&lt;/li&gt;
&lt;/ul&gt;
&lt;hr contenteditable=&quot;false&quot; data-ke-type=&quot;horizontalRule&quot; data-ke-style=&quot;style5&quot; /&gt;
&lt;h2 style=&quot;color: #d4d4d4; text-align: start;&quot; data-ke-size=&quot;size26&quot;&gt;&lt;span style=&quot;color: #000000;&quot;&gt;로그 경로도 정리&lt;/span&gt;&lt;/h2&gt;
&lt;p style=&quot;color: #d4d4d4; text-align: start;&quot; data-ke-size=&quot;size16&quot;&gt;&lt;span style=&quot;color: #000000;&quot;&gt;처음엔&amp;nbsp;&lt;span style=&quot;background-color: oklab(0.845175 0.0000383258 0.0000169277 / 0.1);&quot;&gt;/log/qroad-be&lt;/span&gt;를 썼다가 운영 편의상&amp;nbsp;&lt;span style=&quot;background-color: oklab(0.845175 0.0000383258 0.0000169277 / 0.1);&quot;&gt;~/log/qroad-be&lt;/span&gt;로 변경했다.&lt;/span&gt;&lt;/p&gt;
&lt;ul style=&quot;list-style-type: disc; color: #d4d4d4; text-align: start;&quot; data-ke-list-type=&quot;disc&quot;&gt;
&lt;li&gt;&lt;span style=&quot;color: #000000;&quot;&gt;컨테이너 내부:&amp;nbsp;&lt;span style=&quot;background-color: oklab(0.845175 0.0000383258 0.0000169277 / 0.1);&quot;&gt;/log&lt;/span&gt;&lt;/span&gt;&lt;/li&gt;
&lt;li&gt;&lt;span style=&quot;color: #000000;&quot;&gt;호스트:&amp;nbsp;&lt;span style=&quot;background-color: oklab(0.845175 0.0000383258 0.0000169277 / 0.1);&quot;&gt;/home/ubuntu/log/qroad-be&lt;/span&gt;&lt;/span&gt;&lt;/li&gt;
&lt;li&gt;&lt;span style=&quot;color: #000000;&quot;&gt;파일명:&amp;nbsp;&lt;span style=&quot;background-color: oklab(0.845175 0.0000383258 0.0000169277 / 0.1);&quot;&gt;YYYY-MM-DD.log&lt;/span&gt;&lt;/span&gt;&lt;/li&gt;
&lt;/ul&gt;
&lt;hr contenteditable=&quot;false&quot; data-ke-type=&quot;horizontalRule&quot; data-ke-style=&quot;style5&quot; /&gt;
&lt;h2 style=&quot;color: #d4d4d4; text-align: start;&quot; data-ke-size=&quot;size26&quot;&gt;&lt;span style=&quot;color: #000000;&quot;&gt;실제로 겪은 오류와 해결&lt;/span&gt;&lt;/h2&gt;
&lt;h3 style=&quot;color: #d4d4d4; text-align: start;&quot; data-ke-size=&quot;size23&quot;&gt;&lt;span style=&quot;color: #000000;&quot;&gt;1) ECR 로그인 권한 오류&lt;/span&gt;&lt;/h3&gt;
&lt;p style=&quot;color: #d4d4d4; text-align: start;&quot; data-ke-size=&quot;size16&quot;&gt;&lt;span style=&quot;color: #000000;&quot;&gt;기존 ECR 시도 때&amp;nbsp;&lt;span style=&quot;background-color: oklab(0.845175 0.0000383258 0.0000169277 / 0.1);&quot;&gt;ecr:GetAuthorizationToken&lt;/span&gt;&amp;nbsp;권한 에러 발생.&lt;/span&gt;&lt;/p&gt;
&lt;p style=&quot;color: #d4d4d4; text-align: start;&quot; data-ke-size=&quot;size16&quot;&gt;&lt;span style=&quot;color: #000000;&quot;&gt;&amp;rarr; GHCR(private)로 전환해서 해결.&lt;/span&gt;&lt;/p&gt;
&lt;hr contenteditable=&quot;false&quot; data-ke-type=&quot;horizontalRule&quot; data-ke-style=&quot;style5&quot; /&gt;
&lt;h3 style=&quot;color: #d4d4d4; text-align: start;&quot; data-ke-size=&quot;size23&quot;&gt;&lt;span style=&quot;color: #000000;&quot;&gt;2) deploy job에서 scp 실패&lt;/span&gt;&lt;/h3&gt;
&lt;p style=&quot;color: #d4d4d4; text-align: start;&quot; data-ke-size=&quot;size16&quot;&gt;&lt;span style=&quot;color: #000000;&quot;&gt;오류:&lt;/span&gt;&lt;/p&gt;
&lt;div style=&quot;background-color: #000000; color: #d4d4d4; text-align: start;&quot; data-theme=&quot;dark&quot;&gt;
&lt;div&gt;&lt;span&gt;&lt;span&gt;scp: &lt;/span&gt;&lt;span&gt;&lt;span style=&quot;color: #e9950c;&quot;&gt;stat&lt;/span&gt;&lt;/span&gt;&lt;span&gt; &lt;/span&gt;&lt;span&gt;&lt;span style=&quot;color: #e9950c;&quot;&gt;local&lt;/span&gt;&lt;/span&gt;&lt;span&gt; &lt;/span&gt;&lt;span&gt;&lt;span style=&quot;color: #00a67d;&quot;&gt;&quot;docker-compose.prod.yml&quot;&lt;/span&gt;&lt;/span&gt;&lt;span&gt;: No such file or directory &lt;/span&gt;&lt;/span&gt;&lt;/div&gt;
&lt;/div&gt;
&lt;p style=&quot;color: #d4d4d4; text-align: start;&quot; data-ke-size=&quot;size16&quot;&gt;&lt;span style=&quot;color: #000000;&quot;&gt;원인:&lt;/span&gt;&lt;/p&gt;
&lt;ul style=&quot;list-style-type: disc; color: #d4d4d4; text-align: start;&quot; data-ke-list-type=&quot;disc&quot;&gt;
&lt;li&gt;&lt;span style=&quot;color: #000000;&quot;&gt;&lt;span style=&quot;background-color: oklab(0.845175 0.0000383258 0.0000169277 / 0.1);&quot;&gt;deploy&lt;/span&gt;&amp;nbsp;job에 checkout이 없어서 워크스페이스에 파일이 없었음&lt;/span&gt;&lt;/li&gt;
&lt;/ul&gt;
&lt;p style=&quot;color: #d4d4d4; text-align: start;&quot; data-ke-size=&quot;size16&quot;&gt;&lt;span style=&quot;color: #000000;&quot;&gt;해결:&lt;/span&gt;&lt;/p&gt;
&lt;ul style=&quot;list-style-type: disc; color: #d4d4d4; text-align: start;&quot; data-ke-list-type=&quot;disc&quot;&gt;
&lt;li&gt;&lt;span style=&quot;color: #000000;&quot;&gt;&lt;span style=&quot;background-color: oklab(0.845175 0.0000383258 0.0000169277 / 0.1);&quot;&gt;deploy&lt;/span&gt;&amp;nbsp;job에도&amp;nbsp;&lt;span style=&quot;background-color: oklab(0.845175 0.0000383258 0.0000169277 / 0.1);&quot;&gt;actions/checkout@v4&lt;/span&gt;&amp;nbsp;추가&lt;/span&gt;&lt;/li&gt;
&lt;/ul&gt;
&lt;hr contenteditable=&quot;false&quot; data-ke-type=&quot;horizontalRule&quot; data-ke-style=&quot;style5&quot; /&gt;
&lt;h3 style=&quot;color: #d4d4d4; text-align: start;&quot; data-ke-size=&quot;size23&quot;&gt;&lt;span style=&quot;color: #000000;&quot;&gt;3) Logback&amp;nbsp;&lt;span style=&quot;background-color: oklab(0.845175 0.0000383258 0.0000169277 / 0.1);&quot;&gt;%clr&lt;/span&gt;&amp;nbsp;에러로 앱 부팅 실패&lt;/span&gt;&lt;/h3&gt;
&lt;p style=&quot;color: #d4d4d4; text-align: start;&quot; data-ke-size=&quot;size16&quot;&gt;&lt;span style=&quot;color: #000000;&quot;&gt;오류:&lt;/span&gt;&lt;/p&gt;
&lt;div style=&quot;background-color: #000000; color: #d4d4d4; text-align: start;&quot; data-theme=&quot;dark&quot;&gt;
&lt;div&gt;&lt;span&gt;&lt;span&gt;There is no conversion class registered &lt;/span&gt;&lt;span&gt;&lt;span style=&quot;color: #2e95d3;&quot;&gt;for&lt;/span&gt;&lt;/span&gt;&lt;span&gt; composite conversion word [clr] &lt;/span&gt;&lt;/span&gt;&lt;/div&gt;
&lt;/div&gt;
&lt;p style=&quot;color: #d4d4d4; text-align: start;&quot; data-ke-size=&quot;size16&quot;&gt;&lt;span style=&quot;color: #000000;&quot;&gt;원인:&lt;/span&gt;&lt;/p&gt;
&lt;ul style=&quot;list-style-type: disc; color: #d4d4d4; text-align: start;&quot; data-ke-list-type=&quot;disc&quot;&gt;
&lt;li&gt;&lt;span style=&quot;color: #000000;&quot;&gt;&lt;span style=&quot;background-color: oklab(0.845175 0.0000383258 0.0000169277 / 0.1);&quot;&gt;logback-spring.xml&lt;/span&gt;에&amp;nbsp;&lt;span style=&quot;background-color: oklab(0.845175 0.0000383258 0.0000169277 / 0.1);&quot;&gt;%clr&lt;/span&gt;&amp;nbsp;사용하면서 conversionRule 미등록&lt;/span&gt;&lt;/li&gt;
&lt;/ul&gt;
&lt;p style=&quot;color: #d4d4d4; text-align: start;&quot; data-ke-size=&quot;size16&quot;&gt;&lt;span style=&quot;color: #000000;&quot;&gt;해결:&lt;/span&gt;&lt;/p&gt;
&lt;div style=&quot;background-color: #000000; color: #d4d4d4; text-align: start;&quot; data-theme=&quot;dark&quot;&gt;
&lt;div&gt;&lt;span&gt;&lt;span&gt;&lt;span&gt;&amp;lt;&lt;span&gt;conversionRule&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span&gt; &lt;/span&gt;&lt;span&gt;&lt;span style=&quot;color: #df3079;&quot;&gt;conversionWord&lt;/span&gt;&lt;/span&gt;&lt;span&gt;=&lt;/span&gt;&lt;span&gt;&lt;span style=&quot;color: #00a67d;&quot;&gt;&quot;clr&quot;&lt;/span&gt;&lt;/span&gt;&lt;span&gt; &lt;/span&gt;&lt;span&gt;&lt;span style=&quot;color: #df3079;&quot;&gt;class&lt;/span&gt;&lt;/span&gt;&lt;span&gt;=&lt;/span&gt;&lt;span&gt;&lt;span style=&quot;color: #00a67d;&quot;&gt;&quot;org.springframework.boot.logging.logback.ColorConverter&quot;&lt;/span&gt;&lt;/span&gt;&lt;span&gt;/&amp;gt;&lt;/span&gt;&lt;/span&gt;&lt;/div&gt;
&lt;/div&gt;
&lt;hr contenteditable=&quot;false&quot; data-ke-type=&quot;horizontalRule&quot; data-ke-style=&quot;style5&quot; /&gt;
&lt;h2 style=&quot;color: #d4d4d4; text-align: start;&quot; data-ke-size=&quot;size26&quot;&gt;&lt;span style=&quot;color: #000000;&quot;&gt;감정 공감 기능도 같이 개선&lt;/span&gt;&lt;/h2&gt;
&lt;p style=&quot;color: #d4d4d4; text-align: start;&quot; data-ke-size=&quot;size16&quot;&gt;&lt;span style=&quot;color: #000000;&quot;&gt;새로고침하면 상태가 풀리는 이슈가 있어 점검했다.&lt;/span&gt;&lt;/p&gt;
&lt;p style=&quot;color: #d4d4d4; text-align: start;&quot; data-ke-size=&quot;size16&quot;&gt;&lt;span style=&quot;color: #000000;&quot;&gt;기존:&lt;/span&gt;&lt;/p&gt;
&lt;ul style=&quot;list-style-type: disc; color: #d4d4d4; text-align: start;&quot; data-ke-list-type=&quot;disc&quot;&gt;
&lt;li&gt;&lt;span style=&quot;color: #000000;&quot;&gt;감정 식별자 기반이 IP&lt;/span&gt;&lt;/li&gt;
&lt;/ul&gt;
&lt;p style=&quot;color: #d4d4d4; text-align: start;&quot; data-ke-size=&quot;size16&quot;&gt;&lt;span style=&quot;color: #000000;&quot;&gt;개선:&lt;/span&gt;&lt;/p&gt;
&lt;ul style=&quot;list-style-type: disc; color: #d4d4d4; text-align: start;&quot; data-ke-list-type=&quot;disc&quot;&gt;
&lt;li&gt;&lt;span style=&quot;color: #000000;&quot;&gt;&lt;span style=&quot;background-color: oklab(0.845175 0.0000383258 0.0000169277 / 0.1);&quot;&gt;qroad_uid&lt;/span&gt;&amp;nbsp;쿠키(UUID) 기반으로 통일&lt;/span&gt;&lt;/li&gt;
&lt;li&gt;&lt;span style=&quot;color: #000000;&quot;&gt;기사 상세 응답에&amp;nbsp;&lt;span style=&quot;background-color: oklab(0.845175 0.0000383258 0.0000169277 / 0.1);&quot;&gt;myEmotion&lt;/span&gt;&amp;nbsp;추가해서 프론트가 새로고침 후 상태 복원 가능하게 변경&lt;/span&gt;&lt;/li&gt;
&lt;/ul&gt;
&lt;p style=&quot;color: #d4d4d4; text-align: start;&quot; data-ke-size=&quot;size16&quot;&gt;&lt;span style=&quot;color: #000000;&quot;&gt;DB 스키마 변경은 필요 없었다.&lt;/span&gt;&lt;/p&gt;
&lt;hr contenteditable=&quot;false&quot; data-ke-type=&quot;horizontalRule&quot; data-ke-style=&quot;style5&quot; /&gt;
&lt;h2 style=&quot;color: #d4d4d4; text-align: start;&quot; data-ke-size=&quot;size26&quot;&gt;&lt;span style=&quot;color: #000000;&quot;&gt;보안 체크&lt;/span&gt;&lt;/h2&gt;
&lt;p style=&quot;color: #d4d4d4; text-align: start;&quot; data-ke-size=&quot;size16&quot;&gt;&lt;span style=&quot;color: #000000;&quot;&gt;배포/로깅 수정 후&amp;nbsp;&lt;span style=&quot;background-color: oklab(0.845175 0.0000383258 0.0000169277 / 0.1);&quot;&gt;gitleaks&lt;/span&gt;로 히스토리 스캔도 수행했다.&lt;/span&gt;&lt;/p&gt;
&lt;ul style=&quot;list-style-type: disc; color: #d4d4d4; text-align: start;&quot; data-ke-list-type=&quot;disc&quot;&gt;
&lt;li&gt;&lt;span style=&quot;color: #000000;&quot;&gt;결과:&amp;nbsp;&lt;span style=&quot;background-color: oklab(0.845175 0.0000383258 0.0000169277 / 0.1);&quot;&gt;no leaks found&lt;/span&gt;&lt;/span&gt;&lt;/li&gt;
&lt;/ul&gt;
&lt;hr contenteditable=&quot;false&quot; data-ke-type=&quot;horizontalRule&quot; data-ke-style=&quot;style5&quot; /&gt;
&lt;h2 style=&quot;color: #d4d4d4; text-align: start;&quot; data-ke-size=&quot;size26&quot;&gt;&lt;span style=&quot;color: #000000;&quot;&gt;전환 후 체감 효과&lt;/span&gt;&lt;/h2&gt;
&lt;ul style=&quot;list-style-type: disc; color: #d4d4d4; text-align: start;&quot; data-ke-list-type=&quot;disc&quot;&gt;
&lt;li&gt;&lt;span style=&quot;color: #000000;&quot;&gt;서버 환경 의존도 감소&lt;/span&gt;&lt;/li&gt;
&lt;li&gt;&lt;span style=&quot;color: #000000;&quot;&gt;배포 재현성 향상&lt;/span&gt;&lt;/li&gt;
&lt;li&gt;&lt;span style=&quot;color: #000000;&quot;&gt;롤백 전략 명확&lt;/span&gt;&lt;/li&gt;
&lt;li&gt;&lt;span style=&quot;color: #000000;&quot;&gt;경로/권한 이슈 디버깅 쉬워짐&lt;/span&gt;&lt;/li&gt;
&lt;li&gt;&lt;span style=&quot;color: #000000;&quot;&gt;운영 로그 관리 편해짐&lt;/span&gt;&lt;/li&gt;
&lt;/ul&gt;
&lt;hr contenteditable=&quot;false&quot; data-ke-type=&quot;horizontalRule&quot; data-ke-style=&quot;style5&quot; /&gt;
&lt;h2 style=&quot;color: #d4d4d4; text-align: start;&quot; data-ke-size=&quot;size26&quot;&gt;&lt;span style=&quot;color: #000000;&quot;&gt;이 방식이 특히 좋았던 이유&lt;/span&gt;&lt;/h2&gt;
&lt;p style=&quot;color: #d4d4d4; text-align: start;&quot; data-ke-size=&quot;size16&quot;&gt;&lt;span style=&quot;color: #000000;&quot;&gt;핵심은 한 줄이다.&lt;/span&gt;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;hr contenteditable=&quot;false&quot; data-ke-type=&quot;horizontalRule&quot; data-ke-style=&quot;style5&quot; /&gt;
&lt;h2 style=&quot;color: #d4d4d4; text-align: start;&quot; data-ke-size=&quot;size26&quot;&gt;&lt;span style=&quot;color: #000000;&quot;&gt;마무리&lt;/span&gt;&lt;/h2&gt;
&lt;p style=&quot;color: #d4d4d4; text-align: start;&quot; data-ke-size=&quot;size16&quot;&gt;&lt;span style=&quot;color: #000000;&quot;&gt;이번 전환에서 얻은 결론:&lt;/span&gt;&lt;/p&gt;
&lt;ul style=&quot;list-style-type: disc; color: #d4d4d4; text-align: start;&quot; data-ke-list-type=&quot;disc&quot;&gt;
&lt;li&gt;&lt;span style=&quot;color: #000000;&quot;&gt;CI에서 빌드하고&lt;/span&gt;&lt;/li&gt;
&lt;li&gt;&lt;span style=&quot;color: #000000;&quot;&gt;레지스트리에 버전 고정하고&lt;/span&gt;&lt;/li&gt;
&lt;li&gt;&lt;span style=&quot;color: #000000;&quot;&gt;서버는 pull/run만 하자&lt;/span&gt;&lt;/li&gt;
&lt;/ul&gt;
&lt;p style=&quot;color: #d4d4d4; text-align: start;&quot; data-ke-size=&quot;size16&quot;&gt;&lt;span style=&quot;color: #000000;&quot;&gt;비슷한 구조를 쓰는 팀이라면, 작은 프로젝트라도 이 패턴으로 빨리 옮기는 걸 추천한다.&lt;/span&gt;&lt;/p&gt;</description>
      <category>  프로젝트/QRoad</category>
      <author>SoloQuest</author>
      <guid isPermaLink="true">https://1devlog.tistory.com/22</guid>
      <comments>https://1devlog.tistory.com/22#entry22comment</comments>
      <pubDate>Mon, 30 Mar 2026 03:22:20 +0900</pubDate>
    </item>
    <item>
      <title>[KBO:NOTE] Docker Blue-Green 배포 구현 중 발생한 트러블슈팅 정리</title>
      <link>https://1devlog.tistory.com/21</link>
      <description>&lt;p data-end=&quot;298&quot; data-start=&quot;195&quot; data-ke-size=&quot;size16&quot;&gt;최근 KBO:NOTE 프로젝트에서 &lt;b&gt;Docker + Nginx + GitHub Actions 기반 Blue-Green 무중단 배포 환경&lt;/b&gt;을 구축하면서 몇 가지 예상하지 못한 문제를 겪었다.&lt;/p&gt;
&lt;p data-end=&quot;346&quot; data-start=&quot;300&quot; data-ke-size=&quot;size16&quot;&gt;이번 글에서는 배포 과정에서 실제로 발생했던 문제와 해결 과정을 정리해보려고 한다.&lt;/p&gt;
&lt;hr contenteditable=&quot;false&quot; data-ke-type=&quot;horizontalRule&quot; data-ke-style=&quot;style5&quot; /&gt;
&lt;h1 data-end=&quot;380&quot; data-start=&quot;353&quot; data-section-id=&quot;mwix4m&quot;&gt;1. Health Check 실패로 배포 중단&lt;/h1&gt;
&lt;h2 data-end=&quot;390&quot; data-start=&quot;382&quot; data-section-id=&quot;10a385l&quot; data-ke-size=&quot;size26&quot;&gt;문제 상황&lt;/h2&gt;
&lt;p data-end=&quot;429&quot; data-start=&quot;392&quot; data-ke-size=&quot;size16&quot;&gt;배포 로그에서 Health Check 단계에서 반복적으로 실패했다.&lt;/p&gt;
&lt;p data-end=&quot;436&quot; data-start=&quot;431&quot; data-ke-size=&quot;size16&quot;&gt;예시 로그&lt;/p&gt;
&lt;div&gt;
&lt;div&gt;
&lt;div&gt;
&lt;div&gt;
&lt;div&gt;
&lt;div&gt;
&lt;div&gt;
&lt;div&gt;
&lt;div&gt;
&lt;div id=&quot;code-block-viewer&quot;&gt;
&lt;div&gt;
&lt;blockquote data-ke-style=&quot;style3&quot;&gt;===== Health Check =====&lt;br /&gt;&lt;span&gt;Health check retry...&lt;/span&gt;&lt;br /&gt;&lt;span&gt;Health check retry...&lt;/span&gt;&lt;br /&gt;&lt;span&gt;Process exited with status 1&lt;/span&gt;&lt;/blockquote&gt;
&lt;/div&gt;
&lt;/div&gt;
&lt;/div&gt;
&lt;/div&gt;
&lt;/div&gt;
&lt;/div&gt;
&lt;/div&gt;
&lt;/div&gt;
&lt;div&gt;&amp;nbsp;&lt;/div&gt;
&lt;/div&gt;
&lt;/div&gt;
&lt;/div&gt;
&lt;p data-end=&quot;583&quot; data-start=&quot;545&quot; data-ke-size=&quot;size16&quot;&gt;하지만 서버에서 직접 확인해보면 서비스는 정상적으로 실행되고 있었다.&lt;/p&gt;
&lt;div&gt;
&lt;div&gt;
&lt;div&gt;
&lt;div&gt;
&lt;div&gt;
&lt;div&gt;
&lt;div&gt;
&lt;div&gt;
&lt;div&gt;
&lt;div id=&quot;code-block-viewer&quot;&gt;
&lt;div&gt;
&lt;blockquote data-ke-style=&quot;style3&quot;&gt;curl localhost:8081/api/v1/feeds/health&lt;/blockquote&gt;
&lt;/div&gt;
&lt;/div&gt;
&lt;/div&gt;
&lt;/div&gt;
&lt;/div&gt;
&lt;/div&gt;
&lt;/div&gt;
&lt;/div&gt;
&lt;div&gt;&amp;nbsp;&lt;/div&gt;
&lt;/div&gt;
&lt;/div&gt;
&lt;/div&gt;
&lt;p data-end=&quot;636&quot; data-start=&quot;634&quot; data-ke-size=&quot;size16&quot;&gt;응답&lt;/p&gt;
&lt;div&gt;
&lt;div&gt;
&lt;div&gt;
&lt;div&gt;
&lt;div&gt;
&lt;div&gt;
&lt;div&gt;
&lt;div&gt;
&lt;div&gt;
&lt;div id=&quot;code-block-viewer&quot;&gt;
&lt;div&gt;
&lt;blockquote data-ke-style=&quot;style3&quot;&gt;{&quot;service&quot;:&quot;kbo-feed-service&quot;,&quot;status&quot;:&quot;UP&quot;}&lt;/blockquote&gt;
&lt;/div&gt;
&lt;/div&gt;
&lt;/div&gt;
&lt;/div&gt;
&lt;/div&gt;
&lt;/div&gt;
&lt;/div&gt;
&lt;/div&gt;
&lt;div&gt;&amp;nbsp;&lt;/div&gt;
&lt;/div&gt;
&lt;/div&gt;
&lt;/div&gt;
&lt;p data-end=&quot;732&quot; data-start=&quot;692&quot; data-ke-size=&quot;size16&quot;&gt;즉 &lt;b&gt;서비스는 정상인데 배포 스크립트는 실패로 판단하는 상황&lt;/b&gt;이었다.&lt;/p&gt;
&lt;hr contenteditable=&quot;false&quot; data-ke-type=&quot;horizontalRule&quot; data-ke-style=&quot;style5&quot; /&gt;
&lt;h2 data-end=&quot;744&quot; data-start=&quot;739&quot; data-section-id=&quot;1mbk5p&quot; data-ke-size=&quot;size26&quot;&gt;원인&lt;/h2&gt;
&lt;p data-end=&quot;778&quot; data-start=&quot;746&quot; data-ke-size=&quot;size16&quot;&gt;Health Check 검증을 다음 코드로 구현해 두었다.&lt;/p&gt;
&lt;div&gt;
&lt;div&gt;
&lt;div&gt;
&lt;div&gt;
&lt;div&gt;
&lt;div&gt;
&lt;div&gt;
&lt;div&gt;
&lt;div&gt;
&lt;div id=&quot;code-block-viewer&quot;&gt;
&lt;div&gt;
&lt;blockquote data-ke-style=&quot;style3&quot;&gt;STATUS&lt;span&gt;=&lt;/span&gt;&lt;span&gt;$(curl -s http://localhost:&lt;/span&gt;&lt;span&gt;$TARGET_PORT&lt;/span&gt;&lt;span&gt;/api/v1/feeds/health | grep &lt;/span&gt;&lt;span&gt;'&quot;status&quot;:&quot;UP&quot;'&lt;/span&gt;&lt;span&gt;)&lt;/span&gt;&lt;/blockquote&gt;
&lt;/div&gt;
&lt;/div&gt;
&lt;/div&gt;
&lt;/div&gt;
&lt;/div&gt;
&lt;/div&gt;
&lt;/div&gt;
&lt;/div&gt;
&lt;div&gt;&amp;nbsp;&lt;/div&gt;
&lt;/div&gt;
&lt;/div&gt;
&lt;/div&gt;
&lt;p data-end=&quot;931&quot; data-start=&quot;884&quot; data-ke-size=&quot;size16&quot;&gt;이 방식은 &lt;b&gt;문자열 매칭에 의존&lt;/b&gt;하기 때문에 환경에 따라 매칭이 실패할 수 있다.&lt;/p&gt;
&lt;p data-end=&quot;944&quot; data-start=&quot;933&quot; data-ke-size=&quot;size16&quot;&gt;특히 CI 환경에서는&lt;/p&gt;
&lt;ul style=&quot;list-style-type: disc;&quot; data-end=&quot;987&quot; data-start=&quot;946&quot; data-ke-list-type=&quot;disc&quot;&gt;
&lt;li data-end=&quot;966&quot; data-start=&quot;946&quot; data-section-id=&quot;1rdczdn&quot;&gt;JSON formatting 차이&lt;/li&gt;
&lt;li data-end=&quot;974&quot; data-start=&quot;967&quot; data-section-id=&quot;1tjjt54&quot;&gt;공백 문제&lt;/li&gt;
&lt;li data-end=&quot;987&quot; data-start=&quot;975&quot; data-section-id=&quot;1c5az2l&quot;&gt;grep 매칭 실패&lt;/li&gt;
&lt;/ul&gt;
&lt;p data-end=&quot;1023&quot; data-start=&quot;989&quot; data-ke-size=&quot;size16&quot;&gt;등으로 인해 STATUS 변수가 비어있는 경우가 발생했다.&lt;/p&gt;
&lt;p data-end=&quot;1058&quot; data-start=&quot;1025&quot; data-ke-size=&quot;size16&quot;&gt;그 결과 스크립트는 다음 로직에 의해 배포 실패로 판단했다.&lt;/p&gt;
&lt;div&gt;
&lt;div&gt;
&lt;div&gt;
&lt;div&gt;
&lt;div&gt;
&lt;div&gt;
&lt;div&gt;
&lt;div&gt;
&lt;div&gt;
&lt;div id=&quot;code-block-viewer&quot;&gt;
&lt;div&gt;
&lt;blockquote data-ke-style=&quot;style3&quot;&gt;if [ -z &quot;$STATUS&quot; ]; then&lt;br /&gt;&lt;span&gt;&amp;nbsp; &amp;nbsp; exit 1&lt;/span&gt;&lt;br /&gt;&lt;span&gt;fi&lt;/span&gt;&lt;/blockquote&gt;
&lt;/div&gt;
&lt;/div&gt;
&lt;/div&gt;
&lt;/div&gt;
&lt;/div&gt;
&lt;/div&gt;
&lt;/div&gt;
&lt;/div&gt;
&lt;div&gt;&amp;nbsp;&lt;/div&gt;
&lt;/div&gt;
&lt;/div&gt;
&lt;/div&gt;
&lt;hr contenteditable=&quot;false&quot; data-ke-type=&quot;horizontalRule&quot; data-ke-style=&quot;style5&quot; /&gt;
&lt;h2 data-end=&quot;1122&quot; data-start=&quot;1114&quot; data-section-id=&quot;ib841p&quot; data-ke-size=&quot;size26&quot;&gt;해결 방법&lt;/h2&gt;
&lt;p data-end=&quot;1230&quot; data-start=&quot;1124&quot; data-ke-size=&quot;size16&quot;&gt;Health endpoint 자체가 정상인지 직접 확인하여 문제 원인을 파악했고,&lt;br /&gt;Health Check 로직을 retry 기반으로 유지하여 애플리케이션 기동 시간을 고려하도록 구성했다.&lt;/p&gt;
&lt;hr contenteditable=&quot;false&quot; data-ke-type=&quot;horizontalRule&quot; data-ke-style=&quot;style5&quot; /&gt;
&lt;h1 data-end=&quot;1272&quot; data-start=&quot;1237&quot; data-section-id=&quot;1liu9dk&quot;&gt;2. Blue / Green 컨테이너가 동시에 실행되는 문제&lt;/h1&gt;
&lt;h2 data-end=&quot;1282&quot; data-start=&quot;1274&quot; data-section-id=&quot;10a385l&quot; data-ke-size=&quot;size26&quot;&gt;문제 상황&lt;/h2&gt;
&lt;p data-end=&quot;1334&quot; data-start=&quot;1284&quot; data-ke-size=&quot;size16&quot;&gt;배포 후 서버 상태를 확인했을 때 다음과 같이 두 개의 컨테이너가 동시에 실행되고 있었다.&lt;/p&gt;
&lt;div&gt;
&lt;div&gt;
&lt;div&gt;
&lt;div&gt;
&lt;div&gt;
&lt;div&gt;
&lt;div&gt;
&lt;div&gt;
&lt;div&gt;
&lt;div id=&quot;code-block-viewer&quot;&gt;
&lt;div&gt;
&lt;blockquote data-ke-style=&quot;style3&quot;&gt;docker ps&lt;/blockquote&gt;
&lt;/div&gt;
&lt;/div&gt;
&lt;/div&gt;
&lt;/div&gt;
&lt;/div&gt;
&lt;/div&gt;
&lt;/div&gt;
&lt;/div&gt;
&lt;div&gt;&amp;nbsp;&lt;/div&gt;
&lt;/div&gt;
&lt;/div&gt;
&lt;/div&gt;
&lt;div&gt;
&lt;div&gt;
&lt;div&gt;
&lt;div&gt;
&lt;div&gt;
&lt;div&gt;
&lt;div&gt;
&lt;div&gt;
&lt;div&gt;
&lt;div id=&quot;code-block-viewer&quot;&gt;
&lt;div&gt;
&lt;blockquote data-ke-style=&quot;style3&quot;&gt;feed-blue&lt;br /&gt;&lt;span&gt;feed-green&lt;/span&gt;&lt;/blockquote&gt;
&lt;/div&gt;
&lt;/div&gt;
&lt;/div&gt;
&lt;/div&gt;
&lt;/div&gt;
&lt;/div&gt;
&lt;/div&gt;
&lt;/div&gt;
&lt;div&gt;&amp;nbsp;&lt;/div&gt;
&lt;/div&gt;
&lt;/div&gt;
&lt;/div&gt;
&lt;p data-end=&quot;1432&quot; data-start=&quot;1385&quot; data-ke-size=&quot;size16&quot;&gt;Blue-Green 배포에서는 &lt;b&gt;항상 하나의 컨테이너만 실행되는 것이 정상&lt;/b&gt;이다.&lt;/p&gt;
&lt;hr contenteditable=&quot;false&quot; data-ke-type=&quot;horizontalRule&quot; data-ke-style=&quot;style5&quot; /&gt;
&lt;h2 data-end=&quot;1444&quot; data-start=&quot;1439&quot; data-section-id=&quot;1mbk5p&quot; data-ke-size=&quot;size26&quot;&gt;원인&lt;/h2&gt;
&lt;p data-end=&quot;1466&quot; data-start=&quot;1446&quot; data-ke-size=&quot;size16&quot;&gt;배포 스크립트의 흐름은 다음과 같다.&lt;/p&gt;
&lt;div&gt;
&lt;div&gt;
&lt;div&gt;
&lt;div&gt;
&lt;div&gt;
&lt;div&gt;
&lt;div&gt;
&lt;div&gt;
&lt;div&gt;
&lt;div id=&quot;code-block-viewer&quot;&gt;
&lt;div&gt;
&lt;blockquote data-ke-style=&quot;style3&quot;&gt;새 컨테이너 실행&lt;br /&gt;&lt;span&gt;&amp;rarr; Health Check&lt;/span&gt;&lt;br /&gt;&lt;span&gt;&amp;rarr; Nginx 트래픽 전환&lt;/span&gt;&lt;br /&gt;&lt;span&gt;&amp;rarr; 이전 컨테이너 제거&lt;/span&gt;&lt;/blockquote&gt;
&lt;/div&gt;
&lt;/div&gt;
&lt;/div&gt;
&lt;/div&gt;
&lt;/div&gt;
&lt;/div&gt;
&lt;/div&gt;
&lt;/div&gt;
&lt;div&gt;&amp;nbsp;&lt;/div&gt;
&lt;/div&gt;
&lt;/div&gt;
&lt;/div&gt;
&lt;p data-end=&quot;1577&quot; data-start=&quot;1530&quot; data-ke-size=&quot;size16&quot;&gt;하지만 Health Check 단계에서 배포가 실패하면 스크립트가 중간에서 종료된다.&lt;/p&gt;
&lt;div&gt;
&lt;div&gt;
&lt;div&gt;
&lt;div&gt;
&lt;div&gt;
&lt;div&gt;
&lt;div&gt;
&lt;div&gt;
&lt;div&gt;
&lt;div id=&quot;code-block-viewer&quot;&gt;
&lt;div&gt;
&lt;blockquote data-ke-style=&quot;style3&quot;&gt;exit 1&lt;/blockquote&gt;
&lt;/div&gt;
&lt;/div&gt;
&lt;/div&gt;
&lt;/div&gt;
&lt;/div&gt;
&lt;/div&gt;
&lt;/div&gt;
&lt;/div&gt;
&lt;div&gt;&amp;nbsp;&lt;/div&gt;
&lt;/div&gt;
&lt;/div&gt;
&lt;/div&gt;
&lt;p data-end=&quot;1636&quot; data-start=&quot;1595&quot; data-ke-size=&quot;size16&quot;&gt;이 경우 마지막 단계인 &lt;b&gt;이전 컨테이너 제거 코드가 실행되지 않는다.&lt;/b&gt;&lt;/p&gt;
&lt;div&gt;
&lt;div&gt;
&lt;div&gt;
&lt;div&gt;
&lt;div&gt;
&lt;div&gt;
&lt;div&gt;
&lt;div&gt;
&lt;div&gt;
&lt;div id=&quot;code-block-viewer&quot;&gt;
&lt;div&gt;
&lt;blockquote data-ke-style=&quot;style3&quot;&gt;docker stop $CURRENT&lt;br /&gt;&lt;span&gt;docker rm $CURRENT&lt;/span&gt;&lt;/blockquote&gt;
&lt;/div&gt;
&lt;/div&gt;
&lt;/div&gt;
&lt;/div&gt;
&lt;/div&gt;
&lt;/div&gt;
&lt;/div&gt;
&lt;/div&gt;
&lt;div&gt;&amp;nbsp;&lt;/div&gt;
&lt;/div&gt;
&lt;/div&gt;
&lt;/div&gt;
&lt;p data-end=&quot;1695&quot; data-start=&quot;1687&quot; data-ke-size=&quot;size16&quot;&gt;그래서 서버에는&lt;/p&gt;
&lt;div&gt;
&lt;div&gt;
&lt;div&gt;
&lt;div&gt;
&lt;div&gt;
&lt;div&gt;
&lt;div&gt;
&lt;div&gt;
&lt;div&gt;
&lt;div id=&quot;code-block-viewer&quot;&gt;
&lt;div&gt;
&lt;blockquote data-ke-style=&quot;style3&quot;&gt;feed-blue&lt;br /&gt;&lt;span&gt;feed-green&lt;/span&gt;&lt;/blockquote&gt;
&lt;/div&gt;
&lt;/div&gt;
&lt;/div&gt;
&lt;/div&gt;
&lt;/div&gt;
&lt;/div&gt;
&lt;/div&gt;
&lt;/div&gt;
&lt;div&gt;&amp;nbsp;&lt;/div&gt;
&lt;/div&gt;
&lt;/div&gt;
&lt;/div&gt;
&lt;p data-end=&quot;1746&quot; data-start=&quot;1727&quot; data-ke-size=&quot;size16&quot;&gt;두 컨테이너가 동시에 남게 되었다.&lt;/p&gt;
&lt;hr contenteditable=&quot;false&quot; data-ke-type=&quot;horizontalRule&quot; data-ke-style=&quot;style5&quot; /&gt;
&lt;h2 data-end=&quot;1761&quot; data-start=&quot;1753&quot; data-section-id=&quot;ib841p&quot; data-ke-size=&quot;size26&quot;&gt;해결 방법&lt;/h2&gt;
&lt;p data-end=&quot;1828&quot; data-start=&quot;1763&quot; data-ke-size=&quot;size16&quot;&gt;Health Check 문제를 해결한 뒤 배포가 정상적으로 끝나면서 이전 컨테이너 제거 로직이 정상적으로 실행되었다.&lt;/p&gt;
&lt;p data-end=&quot;1863&quot; data-start=&quot;1830&quot; data-ke-size=&quot;size16&quot;&gt;이후에는 항상 하나의 컨테이너만 실행되는 상태가 유지되었다.&lt;/p&gt;
&lt;hr contenteditable=&quot;false&quot; data-ke-type=&quot;horizontalRule&quot; data-ke-style=&quot;style5&quot; /&gt;
&lt;h1 data-end=&quot;240&quot; data-start=&quot;213&quot; data-section-id=&quot;1cu8zcw&quot;&gt;3. Nginx 설정 파일 포트가 깨지는 문제&lt;/h1&gt;
&lt;h2 data-end=&quot;250&quot; data-start=&quot;242&quot; data-section-id=&quot;10a385l&quot; data-ke-size=&quot;size26&quot;&gt;문제 상황&lt;/h2&gt;
&lt;p data-end=&quot;294&quot; data-start=&quot;252&quot; data-ke-size=&quot;size16&quot;&gt;배포 과정에서 Nginx 설정 테스트 단계에서 다음과 같은 에러가 발생했다.&lt;/p&gt;
&lt;div&gt;
&lt;div&gt;
&lt;div&gt;
&lt;div&gt;
&lt;div&gt;
&lt;div&gt;
&lt;div&gt;
&lt;div&gt;
&lt;div&gt;
&lt;div id=&quot;code-block-viewer&quot;&gt;
&lt;div&gt;
&lt;blockquote data-ke-style=&quot;style3&quot;&gt;invalid port in &quot;9808180818081&quot; of the &quot;listen&quot; directive&lt;/blockquote&gt;
&lt;/div&gt;
&lt;/div&gt;
&lt;/div&gt;
&lt;/div&gt;
&lt;/div&gt;
&lt;/div&gt;
&lt;/div&gt;
&lt;/div&gt;
&lt;div&gt;&amp;nbsp;&lt;/div&gt;
&lt;/div&gt;
&lt;/div&gt;
&lt;/div&gt;
&lt;p data-end=&quot;365&quot; data-start=&quot;363&quot; data-ke-size=&quot;size16&quot;&gt;또는&lt;/p&gt;
&lt;div&gt;
&lt;div&gt;
&lt;div&gt;
&lt;div&gt;
&lt;div&gt;
&lt;div&gt;
&lt;div&gt;
&lt;div&gt;
&lt;div&gt;
&lt;div id=&quot;code-block-viewer&quot;&gt;
&lt;div&gt;
&lt;blockquote data-ke-style=&quot;style3&quot;&gt;nginx: configuration file test failed&lt;/blockquote&gt;
&lt;/div&gt;
&lt;/div&gt;
&lt;/div&gt;
&lt;/div&gt;
&lt;/div&gt;
&lt;/div&gt;
&lt;/div&gt;
&lt;/div&gt;
&lt;div&gt;&amp;nbsp;&lt;/div&gt;
&lt;/div&gt;
&lt;/div&gt;
&lt;/div&gt;
&lt;p data-end=&quot;472&quot; data-start=&quot;414&quot; data-ke-size=&quot;size16&quot;&gt;Nginx 설정 파일을 확인해보니 listen 포트가 다음과 같이 비정상적인 값으로 변경되어 있었다.&lt;/p&gt;
&lt;div&gt;
&lt;div&gt;
&lt;div&gt;
&lt;div&gt;
&lt;div&gt;
&lt;div&gt;
&lt;div&gt;
&lt;div&gt;
&lt;div&gt;
&lt;div id=&quot;code-block-viewer&quot;&gt;
&lt;div&gt;
&lt;blockquote data-ke-style=&quot;style3&quot;&gt;listen 9808180818081;&lt;/blockquote&gt;
&lt;/div&gt;
&lt;/div&gt;
&lt;/div&gt;
&lt;/div&gt;
&lt;/div&gt;
&lt;/div&gt;
&lt;/div&gt;
&lt;/div&gt;
&lt;div&gt;&amp;nbsp;&lt;/div&gt;
&lt;/div&gt;
&lt;/div&gt;
&lt;/div&gt;
&lt;p data-end=&quot;525&quot; data-start=&quot;505&quot; data-ke-size=&quot;size16&quot;&gt;정상적인 설정은 다음과 같아야 한다.&lt;/p&gt;
&lt;div&gt;
&lt;div&gt;
&lt;div&gt;
&lt;div&gt;
&lt;div&gt;
&lt;div&gt;
&lt;div&gt;
&lt;div&gt;
&lt;div&gt;
&lt;div id=&quot;code-block-viewer&quot;&gt;
&lt;div&gt;
&lt;blockquote data-ke-style=&quot;style3&quot;&gt;listen 9000;&lt;/blockquote&gt;
&lt;/div&gt;
&lt;/div&gt;
&lt;/div&gt;
&lt;/div&gt;
&lt;/div&gt;
&lt;/div&gt;
&lt;/div&gt;
&lt;/div&gt;
&lt;/div&gt;
&lt;/div&gt;
&lt;/div&gt;
&lt;hr contenteditable=&quot;false&quot; data-ke-type=&quot;horizontalRule&quot; data-ke-style=&quot;style5&quot; /&gt;
&lt;h2 data-end=&quot;559&quot; data-start=&quot;554&quot; data-section-id=&quot;1mbk5p&quot; data-ke-size=&quot;size26&quot;&gt;원인&lt;/h2&gt;
&lt;p data-end=&quot;614&quot; data-start=&quot;561&quot; data-ke-size=&quot;size16&quot;&gt;배포 스크립트에서 Nginx 설정을 변경하기 위해 다음과 같은 sed 명령어를 사용하고 있었다.&lt;/p&gt;
&lt;div&gt;
&lt;div&gt;
&lt;div&gt;
&lt;div&gt;
&lt;div&gt;
&lt;div&gt;
&lt;div&gt;
&lt;div&gt;
&lt;div&gt;
&lt;div id=&quot;code-block-viewer&quot;&gt;
&lt;div&gt;
&lt;blockquote data-ke-style=&quot;style3&quot;&gt;sed&lt;span&gt; &lt;/span&gt;&lt;span&gt;-i&lt;/span&gt;&lt;span&gt; &lt;/span&gt;&lt;span&gt;&quot;s/&lt;/span&gt;&lt;span&gt;$CURRENT_PORT&lt;/span&gt;&lt;span&gt;/&lt;/span&gt;&lt;span&gt;$TARGET_PORT&lt;/span&gt;&lt;span&gt;/g&quot;&lt;/span&gt;&lt;span&gt; &lt;/span&gt;&lt;span&gt;$NGINX_CONFIG&lt;/span&gt;&lt;/blockquote&gt;
&lt;/div&gt;
&lt;/div&gt;
&lt;/div&gt;
&lt;/div&gt;
&lt;/div&gt;
&lt;/div&gt;
&lt;/div&gt;
&lt;/div&gt;
&lt;div&gt;&amp;nbsp;&lt;/div&gt;
&lt;/div&gt;
&lt;/div&gt;
&lt;/div&gt;
&lt;p data-end=&quot;720&quot; data-start=&quot;683&quot; data-ke-size=&quot;size16&quot;&gt;이 명령어는 설정 파일 전체에서 &lt;b&gt;해당 숫자를 모두 치환&lt;/b&gt;한다.&lt;/p&gt;
&lt;p data-end=&quot;744&quot; data-start=&quot;722&quot; data-ke-size=&quot;size16&quot;&gt;예를 들어 설정 파일이 다음과 같을 경우&lt;/p&gt;
&lt;div&gt;
&lt;div&gt;
&lt;div&gt;
&lt;div&gt;
&lt;div&gt;
&lt;div&gt;
&lt;div&gt;
&lt;div&gt;
&lt;div&gt;
&lt;div id=&quot;code-block-viewer&quot;&gt;
&lt;div&gt;
&lt;blockquote data-ke-style=&quot;style3&quot;&gt;listen 9000;&lt;br /&gt;&lt;span&gt;proxy_pass http://127.0.0.1:8081;&lt;/span&gt;&lt;/blockquote&gt;
&lt;/div&gt;
&lt;/div&gt;
&lt;/div&gt;
&lt;/div&gt;
&lt;/div&gt;
&lt;/div&gt;
&lt;/div&gt;
&lt;/div&gt;
&lt;div&gt;&amp;nbsp;&lt;/div&gt;
&lt;/div&gt;
&lt;/div&gt;
&lt;/div&gt;
&lt;p data-end=&quot;863&quot; data-start=&quot;802&quot; data-ke-size=&quot;size16&quot;&gt;여러 번 배포가 반복되면서 sed 명령이 계속 실행되고 숫자가 누적되면서 다음과 같은 값이 만들어질 수 있다.&lt;/p&gt;
&lt;div&gt;
&lt;div&gt;
&lt;div&gt;
&lt;div&gt;
&lt;div&gt;
&lt;div&gt;
&lt;div&gt;
&lt;div&gt;
&lt;div&gt;
&lt;div id=&quot;code-block-viewer&quot;&gt;
&lt;div&gt;
&lt;blockquote data-ke-style=&quot;style3&quot;&gt;listen 9808180818081;&lt;/blockquote&gt;
&lt;/div&gt;
&lt;/div&gt;
&lt;/div&gt;
&lt;/div&gt;
&lt;/div&gt;
&lt;/div&gt;
&lt;/div&gt;
&lt;/div&gt;
&lt;div&gt;&amp;nbsp;&lt;/div&gt;
&lt;/div&gt;
&lt;/div&gt;
&lt;/div&gt;
&lt;p data-end=&quot;942&quot; data-start=&quot;896&quot; data-ke-size=&quot;size16&quot;&gt;즉 &lt;b&gt;포트 치환 범위가 너무 넓어서 Nginx 설정이 깨지는 문제가 발생했다.&lt;/b&gt;&lt;/p&gt;
&lt;hr contenteditable=&quot;false&quot; data-ke-type=&quot;horizontalRule&quot; data-ke-style=&quot;style5&quot; /&gt;
&lt;h2 data-end=&quot;957&quot; data-start=&quot;949&quot; data-section-id=&quot;14so77o&quot; data-ke-size=&quot;size26&quot;&gt;임시 복구&lt;/h2&gt;
&lt;p data-end=&quot;995&quot; data-start=&quot;959&quot; data-ke-size=&quot;size16&quot;&gt;우선 Nginx 설정 파일을 직접 수정하여 정상 값으로 복구했다.&lt;/p&gt;
&lt;div&gt;
&lt;div&gt;
&lt;div&gt;
&lt;div&gt;
&lt;div&gt;
&lt;div&gt;
&lt;div&gt;
&lt;div&gt;
&lt;div&gt;
&lt;div id=&quot;code-block-viewer&quot;&gt;
&lt;div&gt;
&lt;blockquote data-ke-style=&quot;style3&quot;&gt;listen 9000;&lt;br /&gt;&lt;span&gt;proxy_pass http://127.0.0.1:8081;&lt;/span&gt;&lt;/blockquote&gt;
&lt;/div&gt;
&lt;/div&gt;
&lt;/div&gt;
&lt;/div&gt;
&lt;/div&gt;
&lt;/div&gt;
&lt;/div&gt;
&lt;/div&gt;
&lt;div&gt;&amp;nbsp;&lt;/div&gt;
&lt;/div&gt;
&lt;/div&gt;
&lt;/div&gt;
&lt;p data-end=&quot;1070&quot; data-start=&quot;1053&quot; data-ke-size=&quot;size16&quot;&gt;이후 설정이 정상인지 확인했다.&lt;/p&gt;
&lt;div&gt;
&lt;div&gt;
&lt;div&gt;
&lt;div&gt;
&lt;div&gt;
&lt;div&gt;
&lt;div&gt;
&lt;div&gt;
&lt;div&gt;
&lt;div id=&quot;code-block-viewer&quot;&gt;
&lt;div&gt;
&lt;blockquote data-ke-style=&quot;style3&quot;&gt;sudo nginx -t&lt;/blockquote&gt;
&lt;/div&gt;
&lt;/div&gt;
&lt;/div&gt;
&lt;/div&gt;
&lt;/div&gt;
&lt;/div&gt;
&lt;/div&gt;
&lt;/div&gt;
&lt;div&gt;&amp;nbsp;&lt;/div&gt;
&lt;/div&gt;
&lt;/div&gt;
&lt;/div&gt;
&lt;p data-end=&quot;1100&quot; data-start=&quot;1095&quot; data-ke-size=&quot;size16&quot;&gt;정상 결과&lt;/p&gt;
&lt;div&gt;
&lt;div&gt;
&lt;div&gt;
&lt;div&gt;
&lt;div&gt;
&lt;div&gt;
&lt;div&gt;
&lt;div&gt;
&lt;div&gt;
&lt;div id=&quot;code-block-viewer&quot;&gt;
&lt;div&gt;
&lt;blockquote data-ke-style=&quot;style3&quot;&gt;syntax is ok&lt;br /&gt;&lt;span&gt;test is successful&lt;/span&gt;&lt;/blockquote&gt;
&lt;/div&gt;
&lt;/div&gt;
&lt;/div&gt;
&lt;/div&gt;
&lt;/div&gt;
&lt;/div&gt;
&lt;/div&gt;
&lt;/div&gt;
&lt;/div&gt;
&lt;/div&gt;
&lt;/div&gt;
&lt;hr contenteditable=&quot;false&quot; data-ke-type=&quot;horizontalRule&quot; data-ke-style=&quot;style5&quot; /&gt;
&lt;h2 data-end=&quot;1156&quot; data-start=&quot;1148&quot; data-section-id=&quot;1fuednp&quot; data-ke-size=&quot;size26&quot;&gt;근본 해결&lt;/h2&gt;
&lt;p data-end=&quot;1182&quot; data-start=&quot;1158&quot; data-ke-size=&quot;size16&quot;&gt;문제의 원인이었던 sed 명령어를 수정했다.&lt;/p&gt;
&lt;p data-end=&quot;1189&quot; data-start=&quot;1184&quot; data-ke-size=&quot;size16&quot;&gt;기존 방식&lt;/p&gt;
&lt;div&gt;
&lt;div&gt;
&lt;div&gt;
&lt;div&gt;
&lt;div&gt;
&lt;div&gt;
&lt;div&gt;
&lt;div&gt;
&lt;div&gt;
&lt;div id=&quot;code-block-viewer&quot;&gt;
&lt;div&gt;
&lt;blockquote data-ke-style=&quot;style3&quot;&gt;sed -i &quot;s/$CURRENT_PORT/$TARGET_PORT/g&quot; $NGINX_CONFIG&lt;/blockquote&gt;
&lt;/div&gt;
&lt;/div&gt;
&lt;/div&gt;
&lt;/div&gt;
&lt;/div&gt;
&lt;/div&gt;
&lt;/div&gt;
&lt;/div&gt;
&lt;div&gt;&amp;nbsp;&lt;/div&gt;
&lt;/div&gt;
&lt;/div&gt;
&lt;/div&gt;
&lt;p data-end=&quot;1284&quot; data-start=&quot;1254&quot; data-ke-size=&quot;size16&quot;&gt;이 방식은 설정 파일 전체를 치환하기 때문에 위험하다.&lt;/p&gt;
&lt;p data-end=&quot;1321&quot; data-start=&quot;1286&quot; data-ke-size=&quot;size16&quot;&gt;대신 proxy_pass 라인만 변경하도록 범위를 제한했다.&lt;/p&gt;
&lt;div&gt;
&lt;div&gt;
&lt;div&gt;
&lt;div&gt;
&lt;div&gt;
&lt;div&gt;
&lt;div&gt;
&lt;div&gt;
&lt;div&gt;
&lt;div id=&quot;code-block-viewer&quot;&gt;
&lt;div&gt;
&lt;blockquote data-ke-style=&quot;style3&quot;&gt;sed -i &quot;s|proxy_pass http://127.0.0.1:$CURRENT_PORT|proxy_pass http://127.0.0.1:$TARGET_PORT|&quot; $NGINX_CONFIG&lt;/blockquote&gt;
&lt;/div&gt;
&lt;/div&gt;
&lt;/div&gt;
&lt;/div&gt;
&lt;/div&gt;
&lt;/div&gt;
&lt;/div&gt;
&lt;/div&gt;
&lt;div&gt;&amp;nbsp;&lt;/div&gt;
&lt;/div&gt;
&lt;/div&gt;
&lt;/div&gt;
&lt;p data-end=&quot;1501&quot; data-start=&quot;1441&quot; data-ke-size=&quot;size16&quot;&gt;이렇게 수정하면 listen 포트에는 영향을 주지 않고 &lt;b&gt;트래픽을 전달하는 포트만 변경할 수 있다.&lt;/b&gt;&lt;/p&gt;
&lt;hr contenteditable=&quot;false&quot; data-ke-type=&quot;horizontalRule&quot; data-ke-style=&quot;style5&quot; /&gt;
&lt;h1 data-end=&quot;2610&quot; data-start=&quot;2586&quot; data-section-id=&quot;1d1ce3&quot;&gt;4. Health Check 타이밍 문제&lt;/h1&gt;
&lt;h2 data-end=&quot;2620&quot; data-start=&quot;2612&quot; data-section-id=&quot;10a385l&quot; data-ke-size=&quot;size26&quot;&gt;문제 상황&lt;/h2&gt;
&lt;p data-end=&quot;2662&quot; data-start=&quot;2622&quot; data-ke-size=&quot;size16&quot;&gt;새 컨테이너 실행 직후 Health Check가 실패하는 경우가 있었다.&lt;/p&gt;
&lt;div&gt;
&lt;div&gt;
&lt;div&gt;
&lt;div&gt;
&lt;div&gt;
&lt;div&gt;
&lt;div&gt;
&lt;div&gt;
&lt;div&gt;
&lt;div id=&quot;code-block-viewer&quot;&gt;
&lt;div&gt;
&lt;blockquote data-ke-style=&quot;style3&quot;&gt;Health check retry...&lt;br /&gt;&lt;span&gt;Health check retry...&lt;/span&gt;&lt;br /&gt;&lt;span&gt;서비스 정상&lt;/span&gt;&lt;/blockquote&gt;
&lt;/div&gt;
&lt;/div&gt;
&lt;/div&gt;
&lt;/div&gt;
&lt;/div&gt;
&lt;/div&gt;
&lt;/div&gt;
&lt;/div&gt;
&lt;/div&gt;
&lt;/div&gt;
&lt;/div&gt;
&lt;hr contenteditable=&quot;false&quot; data-ke-type=&quot;horizontalRule&quot; data-ke-style=&quot;style5&quot; /&gt;
&lt;h2 data-end=&quot;2734&quot; data-start=&quot;2729&quot; data-section-id=&quot;1mbk5p&quot; data-ke-size=&quot;size26&quot;&gt;원인&lt;/h2&gt;
&lt;p data-end=&quot;2801&quot; data-start=&quot;2736&quot; data-ke-size=&quot;size16&quot;&gt;Spring Boot 애플리케이션이 완전히 기동되기 전에&lt;br /&gt;Health Check 요청이 먼저 실행되었기 때문이다.&lt;/p&gt;
&lt;p data-end=&quot;2834&quot; data-start=&quot;2803&quot; data-ke-size=&quot;size16&quot;&gt;즉 &lt;b&gt;애플리케이션 startup time 문제&lt;/b&gt;였다.&lt;/p&gt;
&lt;hr contenteditable=&quot;false&quot; data-ke-type=&quot;horizontalRule&quot; data-ke-style=&quot;style5&quot; /&gt;
&lt;h2 data-end=&quot;2849&quot; data-start=&quot;2841&quot; data-section-id=&quot;ib841p&quot; data-ke-size=&quot;size26&quot;&gt;해결 방법&lt;/h2&gt;
&lt;p data-end=&quot;2883&quot; data-start=&quot;2851&quot; data-ke-size=&quot;size16&quot;&gt;Health Check 로직에 retry 구조를 적용했다.&lt;/p&gt;
&lt;div&gt;
&lt;div&gt;
&lt;div&gt;
&lt;div&gt;
&lt;div&gt;
&lt;div&gt;
&lt;div&gt;
&lt;div&gt;
&lt;div&gt;
&lt;div id=&quot;code-block-viewer&quot;&gt;
&lt;div&gt;
&lt;blockquote data-ke-style=&quot;style3&quot;&gt;for&lt;span&gt; i &lt;/span&gt;&lt;span&gt;in&lt;/span&gt;&lt;span&gt; {1..10}&lt;/span&gt;&lt;br /&gt;&lt;span&gt;do&lt;/span&gt;&lt;br /&gt;&lt;span&gt; &lt;/span&gt;&lt;span&gt;sleep&lt;/span&gt;&lt;span&gt; &lt;/span&gt;&lt;span&gt;3&lt;/span&gt;&lt;/blockquote&gt;
&lt;/div&gt;
&lt;/div&gt;
&lt;/div&gt;
&lt;/div&gt;
&lt;/div&gt;
&lt;/div&gt;
&lt;/div&gt;
&lt;/div&gt;
&lt;div&gt;&amp;nbsp;&lt;/div&gt;
&lt;/div&gt;
&lt;/div&gt;
&lt;/div&gt;
&lt;p data-end=&quot;2962&quot; data-start=&quot;2930&quot; data-ke-size=&quot;size16&quot;&gt;최대 30초 동안 애플리케이션 기동을 기다리도록 구성했다.&lt;/p&gt;
&lt;hr contenteditable=&quot;false&quot; data-ke-type=&quot;horizontalRule&quot; data-ke-style=&quot;style5&quot; /&gt;
&lt;h1 data-end=&quot;2974&quot; data-start=&quot;2969&quot; data-section-id=&quot;20h5rq&quot;&gt;마무리&lt;/h1&gt;
&lt;p data-end=&quot;3033&quot; data-start=&quot;2976&quot; data-ke-size=&quot;size16&quot;&gt;Blue-Green 배포 자체는 구조가 단순해 보이지만 실제로 구현해보면 다양한 문제를 마주하게 된다.&lt;/p&gt;
&lt;p data-end=&quot;3060&quot; data-start=&quot;3035&quot; data-ke-size=&quot;size16&quot;&gt;특히 다음 세 가지가 중요하다는 것을 느꼈다.&lt;/p&gt;
&lt;ul style=&quot;list-style-type: disc;&quot; data-end=&quot;3116&quot; data-start=&quot;3062&quot; data-ke-list-type=&quot;disc&quot;&gt;
&lt;li data-end=&quot;3083&quot; data-start=&quot;3062&quot; data-section-id=&quot;13ucqsj&quot;&gt;Health Check 로직 안정성&lt;/li&gt;
&lt;li data-end=&quot;3099&quot; data-start=&quot;3084&quot; data-section-id=&quot;16ojwlx&quot;&gt;배포 실패 시 상태 관리&lt;/li&gt;
&lt;li data-end=&quot;3116&quot; data-start=&quot;3100&quot; data-section-id=&quot;92j3ie&quot;&gt;Nginx 설정 변경 방식&lt;/li&gt;
&lt;/ul&gt;
&lt;p data-end=&quot;3181&quot; data-start=&quot;3118&quot; data-ke-size=&quot;size16&quot;&gt;이번 경험을 통해 &lt;b&gt;무중단 배포 환경을 실제로 운영하면서 발생할 수 있는 문제들을 직접 해결해볼 수 있었다.&lt;/b&gt;&lt;/p&gt;
&lt;hr contenteditable=&quot;false&quot; data-ke-type=&quot;horizontalRule&quot; data-ke-style=&quot;style5&quot; /&gt;</description>
      <category>  프로젝트/트러블 슈팅</category>
      <author>SoloQuest</author>
      <guid isPermaLink="true">https://1devlog.tistory.com/21</guid>
      <comments>https://1devlog.tistory.com/21#entry21comment</comments>
      <pubDate>Sun, 15 Mar 2026 01:56:53 +0900</pubDate>
    </item>
    <item>
      <title>Docker + Nginx 기반 Blue-Green 배포 구축 (3) &amp;mdash; GitHub Actions CI/CD와 자동 배포 스크립트 구축</title>
      <link>https://1devlog.tistory.com/20</link>
      <description>&lt;h1 data-end=&quot;218&quot; data-start=&quot;212&quot; data-section-id=&quot;1ucxt1i&quot;&gt;들어가며&lt;/h1&gt;
&lt;p data-end=&quot;341&quot; data-start=&quot;220&quot; data-ke-size=&quot;size16&quot;&gt;이전 글에서는 Docker 이미지를 GitHub Container Registry(GHCR)에 업로드하고&lt;br /&gt;Blue-Green 컨테이너 환경을 구성하여 &lt;b&gt;수동 배포 방식으로 Blue-Green 전환을 검증&lt;/b&gt;했다.&lt;/p&gt;
&lt;p data-end=&quot;366&quot; data-start=&quot;343&quot; data-ke-size=&quot;size16&quot;&gt;이 과정에서 다음과 같은 작업을 완료했다.&lt;/p&gt;
&lt;ul style=&quot;list-style-type: disc;&quot; data-end=&quot;508&quot; data-start=&quot;368&quot; data-ke-list-type=&quot;disc&quot;&gt;
&lt;li data-end=&quot;392&quot; data-start=&quot;368&quot; data-section-id=&quot;vpkjs9&quot;&gt;Dockerfile 작성 및 이미지 빌드&lt;/li&gt;
&lt;li data-end=&quot;429&quot; data-start=&quot;393&quot; data-section-id=&quot;19arl39&quot;&gt;GitHub Container Registry(GHCR) 사용&lt;/li&gt;
&lt;li data-end=&quot;449&quot; data-start=&quot;430&quot; data-section-id=&quot;34nk6d&quot;&gt;서버에서 이미지 Pull 테스트&lt;/li&gt;
&lt;li data-end=&quot;475&quot; data-start=&quot;450&quot; data-section-id=&quot;1q5kute&quot;&gt;Blue / Green 컨테이너 환경 구성&lt;/li&gt;
&lt;li data-end=&quot;508&quot; data-start=&quot;476&quot; data-section-id=&quot;1jq0ftv&quot;&gt;NGINX Reverse Proxy 트래픽 전환 테스트&lt;/li&gt;
&lt;/ul&gt;
&lt;p data-end=&quot;556&quot; data-start=&quot;510&quot; data-ke-size=&quot;size16&quot;&gt;즉 &lt;b&gt;Blue-Green 배포가 실제로 동작하는 환경&lt;/b&gt;은 이미 구축된 상태였다.&lt;/p&gt;
&lt;p data-end=&quot;591&quot; data-start=&quot;558&quot; data-ke-size=&quot;size16&quot;&gt;하지만 실제 운영 환경에서는 다음과 같은 문제가 남아 있다.&lt;/p&gt;
&lt;div&gt;
&lt;div&gt;
&lt;div&gt;
&lt;div&gt;
&lt;div&gt;
&lt;div&gt;
&lt;div&gt;
&lt;div&gt;
&lt;div&gt;
&lt;div id=&quot;code-block-viewer&quot;&gt;
&lt;div&gt;
&lt;blockquote data-ke-style=&quot;style3&quot;&gt;코드 변경&lt;br /&gt;&lt;span&gt;&amp;darr;&lt;/span&gt;&lt;br /&gt;&lt;span&gt;Docker 이미지 빌드&lt;/span&gt;&lt;br /&gt;&lt;span&gt;&amp;darr;&lt;/span&gt;&lt;br /&gt;&lt;span&gt;이미지 Push&lt;/span&gt;&lt;br /&gt;&lt;span&gt;&amp;darr;&lt;/span&gt;&lt;br /&gt;&lt;span&gt;서버 접속&lt;/span&gt;&lt;br /&gt;&lt;span&gt;&amp;darr;&lt;/span&gt;&lt;br /&gt;&lt;span&gt;컨테이너 실행&lt;/span&gt;&lt;br /&gt;&lt;span&gt;&amp;darr;&lt;/span&gt;&lt;br /&gt;&lt;span&gt;NGINX 전환&lt;/span&gt;&lt;/blockquote&gt;
&lt;/div&gt;
&lt;/div&gt;
&lt;/div&gt;
&lt;/div&gt;
&lt;/div&gt;
&lt;/div&gt;
&lt;/div&gt;
&lt;/div&gt;
&lt;div&gt;&amp;nbsp;&lt;/div&gt;
&lt;/div&gt;
&lt;/div&gt;
&lt;/div&gt;
&lt;p data-end=&quot;699&quot; data-start=&quot;668&quot; data-ke-size=&quot;size16&quot;&gt;이 과정을 &lt;b&gt;모두 수동으로 실행해야 한다는 점&lt;/b&gt;이다.&lt;/p&gt;
&lt;p data-end=&quot;758&quot; data-start=&quot;701&quot; data-ke-size=&quot;size16&quot;&gt;따라서 이번 글에서는 이 과정을 자동화하여 다음과 같은 &lt;b&gt;완전한 자동 배포 파이프라인&lt;/b&gt;을 구축한다.&lt;/p&gt;
&lt;div&gt;
&lt;div&gt;
&lt;div&gt;
&lt;div&gt;
&lt;div&gt;
&lt;div&gt;
&lt;div&gt;
&lt;div&gt;
&lt;div&gt;
&lt;div id=&quot;code-block-viewer&quot;&gt;
&lt;div&gt;
&lt;blockquote data-ke-style=&quot;style3&quot;&gt;GitHub Push&lt;br /&gt;&lt;span&gt;&amp;darr;&lt;/span&gt;&lt;br /&gt;&lt;span&gt;GitHub Actions&lt;/span&gt;&lt;br /&gt;&lt;span&gt;&amp;darr;&lt;/span&gt;&lt;br /&gt;&lt;span&gt;Docker Image Build&lt;/span&gt;&lt;br /&gt;&lt;span&gt;&amp;darr;&lt;/span&gt;&lt;br /&gt;&lt;span&gt;GHCR Push&lt;/span&gt;&lt;br /&gt;&lt;span&gt;&amp;darr;&lt;/span&gt;&lt;br /&gt;&lt;span&gt;Server Deploy Script&lt;/span&gt;&lt;br /&gt;&lt;span&gt;&amp;darr;&lt;/span&gt;&lt;br /&gt;&lt;span&gt;Blue-Green 배포&lt;/span&gt;&lt;br /&gt;&lt;span&gt;&amp;darr;&lt;/span&gt;&lt;br /&gt;&lt;span&gt;NGINX 자동 전환&lt;/span&gt;&lt;/blockquote&gt;
&lt;/div&gt;
&lt;/div&gt;
&lt;/div&gt;
&lt;/div&gt;
&lt;/div&gt;
&lt;/div&gt;
&lt;/div&gt;
&lt;/div&gt;
&lt;/div&gt;
&lt;/div&gt;
&lt;/div&gt;
&lt;hr contenteditable=&quot;false&quot; data-ke-type=&quot;horizontalRule&quot; data-ke-style=&quot;style5&quot; /&gt;
&lt;h1 data-end=&quot;906&quot; data-start=&quot;893&quot; data-section-id=&quot;clnmz3&quot;&gt;자동 배포 구조 설계&lt;/h1&gt;
&lt;p data-end=&quot;932&quot; data-start=&quot;908&quot; data-ke-size=&quot;size16&quot;&gt;자동 배포의 핵심 구성 요소는 다음과 같다.&lt;/p&gt;
&lt;p data-end=&quot;1067&quot; data-start=&quot;934&quot; data-ke-size=&quot;size16&quot;&gt;1️⃣ GitHub Actions CI/CD&lt;br /&gt;2️⃣ Docker Image Version 관리&lt;br /&gt;3️⃣ 서버 배포 스크립트 (deploy.sh)&lt;br /&gt;4️⃣ Health Check 자동화&lt;br /&gt;5️⃣ NGINX 트래픽 전환 자동화&lt;/p&gt;
&lt;p data-end=&quot;1083&quot; data-start=&quot;1069&quot; data-ke-size=&quot;size16&quot;&gt;전체 구조는 다음과 같다.&lt;/p&gt;
&lt;div&gt;
&lt;div&gt;
&lt;div&gt;
&lt;div&gt;
&lt;div&gt;
&lt;div&gt;
&lt;div&gt;
&lt;div&gt;
&lt;div&gt;
&lt;div id=&quot;code-block-viewer&quot;&gt;
&lt;div&gt;
&lt;blockquote data-ke-style=&quot;style3&quot;&gt;Developer&lt;br /&gt;&lt;span&gt; │&lt;/span&gt;&lt;br /&gt;&lt;span&gt; │ git push&lt;/span&gt;&lt;br /&gt;&lt;span&gt; ▼&lt;/span&gt;&lt;br /&gt;&lt;span&gt;GitHub Repository&lt;/span&gt;&lt;br /&gt;&lt;span&gt; │&lt;/span&gt;&lt;br /&gt;&lt;span&gt; ▼&lt;/span&gt;&lt;br /&gt;&lt;span&gt;GitHub Actions&lt;/span&gt;&lt;br /&gt;&lt;span&gt; │&lt;/span&gt;&lt;br /&gt;&lt;span&gt; │ Docker Build&lt;/span&gt;&lt;br /&gt;&lt;span&gt; │&lt;/span&gt;&lt;br /&gt;&lt;span&gt; ▼&lt;/span&gt;&lt;br /&gt;&lt;span&gt;GitHub Container Registry (GHCR)&lt;/span&gt;&lt;br /&gt;&lt;span&gt; │&lt;/span&gt;&lt;br /&gt;&lt;span&gt; │ docker pull&lt;/span&gt;&lt;br /&gt;&lt;span&gt; ▼&lt;/span&gt;&lt;br /&gt;&lt;span&gt;Server&lt;/span&gt;&lt;br /&gt;&lt;span&gt; │&lt;/span&gt;&lt;br /&gt;&lt;span&gt; │ deploy.sh&lt;/span&gt;&lt;br /&gt;&lt;span&gt; ▼&lt;/span&gt;&lt;br /&gt;&lt;span&gt;Blue / Green Container&lt;/span&gt;&lt;br /&gt;&lt;span&gt; │&lt;/span&gt;&lt;br /&gt;&lt;span&gt; ▼&lt;/span&gt;&lt;br /&gt;&lt;span&gt;NGINX&lt;/span&gt;&lt;br /&gt;&lt;span&gt; │&lt;/span&gt;&lt;br /&gt;&lt;span&gt; ▼&lt;/span&gt;&lt;br /&gt;&lt;span&gt;Client&lt;/span&gt;&lt;/blockquote&gt;
&lt;/div&gt;
&lt;/div&gt;
&lt;/div&gt;
&lt;/div&gt;
&lt;/div&gt;
&lt;/div&gt;
&lt;/div&gt;
&lt;/div&gt;
&lt;div&gt;&amp;nbsp;&lt;/div&gt;
&lt;/div&gt;
&lt;/div&gt;
&lt;/div&gt;
&lt;p data-end=&quot;1403&quot; data-start=&quot;1356&quot; data-ke-size=&quot;size16&quot;&gt;즉 코드가 Push되면 &lt;b&gt;자동으로 서버까지 배포되는 구조&lt;/b&gt;를 만드는 것이 목표다.&lt;/p&gt;
&lt;hr contenteditable=&quot;false&quot; data-ke-type=&quot;horizontalRule&quot; data-ke-style=&quot;style5&quot; /&gt;
&lt;h1 data-end=&quot;1428&quot; data-start=&quot;1410&quot; data-section-id=&quot;6khi4v&quot;&gt;Docker 이미지 버전 전략&lt;/h1&gt;
&lt;p data-end=&quot;1473&quot; data-start=&quot;1430&quot; data-ke-size=&quot;size16&quot;&gt;초기에는 Docker 이미지를 다음과 같이 latest 태그로만 관리했다.&lt;/p&gt;
&lt;div&gt;
&lt;div&gt;
&lt;div&gt;
&lt;div&gt;
&lt;div&gt;
&lt;div&gt;
&lt;div&gt;
&lt;div&gt;
&lt;div&gt;
&lt;div id=&quot;code-block-viewer&quot;&gt;
&lt;div&gt;
&lt;blockquote data-ke-style=&quot;style3&quot;&gt;ghcr.io/kbo-note/kbo-note-feed-service:latest&lt;/blockquote&gt;
&lt;/div&gt;
&lt;/div&gt;
&lt;/div&gt;
&lt;/div&gt;
&lt;/div&gt;
&lt;/div&gt;
&lt;/div&gt;
&lt;/div&gt;
&lt;div&gt;&amp;nbsp;&lt;/div&gt;
&lt;/div&gt;
&lt;/div&gt;
&lt;/div&gt;
&lt;p data-end=&quot;1552&quot; data-start=&quot;1534&quot; data-ke-size=&quot;size16&quot;&gt;하지만 이 방식에는 문제가 있다.&lt;/p&gt;
&lt;div&gt;
&lt;div&gt;
&lt;div&gt;
&lt;div&gt;
&lt;div&gt;
&lt;div&gt;
&lt;div&gt;
&lt;div&gt;
&lt;div&gt;
&lt;div id=&quot;code-block-viewer&quot;&gt;
&lt;div&gt;
&lt;blockquote data-ke-style=&quot;style3&quot;&gt;새로운 배포가 latest를 덮어씀&lt;br /&gt;&lt;span&gt;&amp;darr;&lt;/span&gt;&lt;br /&gt;&lt;span&gt;기존 컨테이너 재시작 시 다른 코드 실행 가능&lt;/span&gt;&lt;br /&gt;&lt;span&gt;&amp;darr;&lt;/span&gt;&lt;br /&gt;&lt;span&gt;버전 추적 어려움&lt;/span&gt;&lt;/blockquote&gt;
&lt;/div&gt;
&lt;/div&gt;
&lt;/div&gt;
&lt;/div&gt;
&lt;/div&gt;
&lt;/div&gt;
&lt;/div&gt;
&lt;/div&gt;
&lt;div&gt;&amp;nbsp;&lt;/div&gt;
&lt;/div&gt;
&lt;/div&gt;
&lt;/div&gt;
&lt;p data-end=&quot;1668&quot; data-start=&quot;1627&quot; data-ke-size=&quot;size16&quot;&gt;이를 해결하기 위해 &lt;b&gt;Commit 기반 이미지 태그 전략&lt;/b&gt;을 적용했다.&lt;/p&gt;
&lt;p data-end=&quot;1730&quot; data-start=&quot;1670&quot; data-ke-size=&quot;size16&quot;&gt;GitHub Actions에서 Docker 이미지를 빌드할 때 다음과 같이 두 개의 태그를 동시에 생성한다.&lt;/p&gt;
&lt;div&gt;
&lt;div&gt;
&lt;div&gt;
&lt;div&gt;
&lt;div&gt;
&lt;div&gt;
&lt;div&gt;
&lt;div&gt;
&lt;div&gt;
&lt;div id=&quot;code-block-viewer&quot;&gt;
&lt;div&gt;
&lt;blockquote data-ke-style=&quot;style3&quot;&gt;docker build \&lt;br /&gt;&lt;span&gt; &lt;/span&gt;&lt;span&gt;-t&lt;/span&gt;&lt;span&gt; ghcr.io/kbo-note/kbo-note-feed-service:&lt;/span&gt;&lt;span&gt;${{ github.sha }}&lt;/span&gt;&lt;span&gt; \&lt;/span&gt;&lt;br /&gt;&lt;span&gt; &lt;/span&gt;&lt;span&gt;-t&lt;/span&gt;&lt;span&gt; ghcr.io/kbo-note/kbo-note-feed-service:latest .&lt;/span&gt;&lt;/blockquote&gt;
&lt;/div&gt;
&lt;/div&gt;
&lt;/div&gt;
&lt;/div&gt;
&lt;/div&gt;
&lt;/div&gt;
&lt;/div&gt;
&lt;/div&gt;
&lt;div&gt;&amp;nbsp;&lt;/div&gt;
&lt;/div&gt;
&lt;/div&gt;
&lt;/div&gt;
&lt;p data-end=&quot;1916&quot; data-start=&quot;1877&quot; data-ke-size=&quot;size16&quot;&gt;이렇게 하면 Registry에는 다음과 같은 형태로 이미지가 저장된다.&lt;/p&gt;
&lt;div&gt;
&lt;div&gt;
&lt;div&gt;
&lt;div&gt;
&lt;div&gt;
&lt;div&gt;
&lt;div&gt;
&lt;div&gt;
&lt;div&gt;
&lt;div id=&quot;code-block-viewer&quot;&gt;
&lt;div&gt;
&lt;blockquote data-ke-style=&quot;style3&quot;&gt;kbo-note-feed-service&lt;br /&gt;&lt;span&gt; ├ latest&lt;/span&gt;&lt;br /&gt;&lt;span&gt; ├ 3f91c2a&lt;/span&gt;&lt;br /&gt;&lt;span&gt; ├ 8ad21f3&lt;/span&gt;&lt;br /&gt;&lt;span&gt; └ b71d9c1&lt;/span&gt;&lt;/blockquote&gt;
&lt;/div&gt;
&lt;/div&gt;
&lt;/div&gt;
&lt;/div&gt;
&lt;/div&gt;
&lt;/div&gt;
&lt;/div&gt;
&lt;/div&gt;
&lt;div&gt;&amp;nbsp;&lt;/div&gt;
&lt;/div&gt;
&lt;/div&gt;
&lt;/div&gt;
&lt;p data-end=&quot;2013&quot; data-start=&quot;1996&quot; data-ke-size=&quot;size16&quot;&gt;이 전략의 장점은 다음과 같다.&lt;/p&gt;
&lt;ul style=&quot;list-style-type: disc;&quot; data-end=&quot;2065&quot; data-start=&quot;2015&quot; data-ke-list-type=&quot;disc&quot;&gt;
&lt;li data-end=&quot;2028&quot; data-start=&quot;2015&quot; data-section-id=&quot;1nllxl0&quot;&gt;배포 버전 추적 가능&lt;/li&gt;
&lt;li data-end=&quot;2042&quot; data-start=&quot;2029&quot; data-section-id=&quot;hg0tsk&quot;&gt;특정 버전 롤백 가능&lt;/li&gt;
&lt;li data-end=&quot;2065&quot; data-start=&quot;2043&quot; data-section-id=&quot;jcq190&quot;&gt;이미지 변경으로 인한 코드 꼬임 방지&lt;/li&gt;
&lt;/ul&gt;
&lt;hr contenteditable=&quot;false&quot; data-ke-type=&quot;horizontalRule&quot; data-ke-style=&quot;style5&quot; /&gt;
&lt;h1 data-end=&quot;2097&quot; data-start=&quot;2072&quot; data-section-id=&quot;1afpiz9&quot;&gt;GitHub Actions CI/CD 구축&lt;/h1&gt;
&lt;p data-end=&quot;2132&quot; data-start=&quot;2099&quot; data-ke-size=&quot;size16&quot;&gt;이제 GitHub Actions Workflow를 작성한다.&lt;/p&gt;
&lt;p data-end=&quot;2148&quot; data-start=&quot;2134&quot; data-ke-size=&quot;size16&quot;&gt;Workflow 파일 위치&lt;/p&gt;
&lt;div&gt;
&lt;div&gt;
&lt;div&gt;
&lt;div&gt;
&lt;div&gt;
&lt;div&gt;
&lt;div&gt;
&lt;div&gt;
&lt;div&gt;
&lt;div id=&quot;code-block-viewer&quot;&gt;
&lt;div&gt;
&lt;blockquote data-ke-style=&quot;style3&quot;&gt;.github/workflows/deploy.yml&lt;/blockquote&gt;
&lt;/div&gt;
&lt;/div&gt;
&lt;/div&gt;
&lt;/div&gt;
&lt;/div&gt;
&lt;/div&gt;
&lt;/div&gt;
&lt;/div&gt;
&lt;div&gt;&amp;nbsp;&lt;/div&gt;
&lt;/div&gt;
&lt;/div&gt;
&lt;/div&gt;
&lt;p data-end=&quot;2212&quot; data-start=&quot;2192&quot; data-ke-size=&quot;size16&quot;&gt;핵심 Workflow는 다음과 같다.&lt;/p&gt;
&lt;div&gt;
&lt;div&gt;
&lt;div&gt;
&lt;div&gt;
&lt;div&gt;
&lt;div&gt;
&lt;div&gt;
&lt;div&gt;
&lt;div&gt;
&lt;div id=&quot;code-block-viewer&quot;&gt;
&lt;div&gt;
&lt;blockquote data-ke-style=&quot;style3&quot;&gt;name: Feed Service CI/CD&lt;br /&gt;&lt;br /&gt;&lt;span&gt;on:&lt;/span&gt;&lt;br /&gt;&lt;span&gt; push:&lt;/span&gt;&lt;br /&gt;&lt;span&gt; branches:&lt;/span&gt;&lt;br /&gt;&lt;span&gt; - main&lt;/span&gt;&lt;br /&gt;&lt;br /&gt;permissions:&lt;br /&gt;contents: read&lt;br /&gt;packages: write&lt;br /&gt;&lt;br /&gt;&lt;span&gt;jobs:&lt;/span&gt;&lt;br /&gt;&lt;span&gt; deploy:&lt;/span&gt;&lt;br /&gt;&lt;br /&gt;&lt;span&gt; runs-on: ubuntu-latest&lt;/span&gt;&lt;br /&gt;&lt;br /&gt;&lt;span&gt; steps:&lt;/span&gt;&lt;br /&gt;&lt;br /&gt;&lt;span&gt; - name: Checkout&lt;/span&gt;&lt;br /&gt;&lt;span&gt; uses: actions/checkout@v4&lt;/span&gt;&lt;br /&gt;&lt;br /&gt;&lt;span&gt; - name: Login to GHCR&lt;/span&gt;&lt;br /&gt;&lt;span&gt; run: echo &quot;${{ secrets.GITHUB_TOKEN }}&quot; | docker login ghcr.io -u ${{ github.actor }} --password-stdin&lt;/span&gt;&lt;br /&gt;&lt;br /&gt;&lt;span&gt; - name: Build Docker Image&lt;/span&gt;&lt;br /&gt;&lt;span&gt; run: &lt;/span&gt;&lt;span&gt;|&lt;/span&gt;&lt;br /&gt;&lt;span&gt; docker build \&lt;/span&gt;&lt;br /&gt;&lt;span&gt; -t ghcr.io/kbo-note/kbo-note-feed-service:${{ github.sha }} \&lt;/span&gt;&lt;br /&gt;&lt;span&gt; -t ghcr.io/kbo-note/kbo-note-feed-service:latest .&lt;/span&gt;&lt;br /&gt;&lt;br /&gt;&lt;span&gt; - name: Push Docker Image&lt;/span&gt;&lt;br /&gt;&lt;span&gt; run: &lt;/span&gt;&lt;span&gt;|&lt;/span&gt;&lt;br /&gt;&lt;span&gt; docker push ghcr.io/kbo-note/kbo-note-feed-service:${{ github.sha }}&lt;/span&gt;&lt;br /&gt;&lt;span&gt; docker push ghcr.io/kbo-note/kbo-note-feed-service:latest&lt;/span&gt;&lt;br /&gt;&lt;br /&gt;&lt;span&gt; - name: Deploy to Server&lt;/span&gt;&lt;br /&gt;&lt;span&gt; uses: appleboy/ssh-action@v1&lt;/span&gt;&lt;br /&gt;&lt;span&gt; with:&lt;/span&gt;&lt;br /&gt;&lt;span&gt; host: ${{ secrets.SERVER_HOST }}&lt;/span&gt;&lt;br /&gt;&lt;span&gt; username: ${{ secrets.SERVER_USERNAME }}&lt;/span&gt;&lt;br /&gt;&lt;span&gt; password: ${{ secrets.SERVER_PASSWORD }}&lt;/span&gt;&lt;br /&gt;&lt;span&gt; port: 22&lt;/span&gt;&lt;br /&gt;&lt;span&gt; script: &lt;/span&gt;&lt;span&gt;|&lt;/span&gt;&lt;br /&gt;&lt;span&gt; cd ~/kbo-feed-service/deploy&lt;/span&gt;&lt;br /&gt;&lt;span&gt; ./deploy.sh ${{ github.sha }}&lt;/span&gt;&lt;/blockquote&gt;
&lt;/div&gt;
&lt;/div&gt;
&lt;/div&gt;
&lt;/div&gt;
&lt;/div&gt;
&lt;/div&gt;
&lt;/div&gt;
&lt;/div&gt;
&lt;div&gt;&amp;nbsp;&lt;/div&gt;
&lt;/div&gt;
&lt;/div&gt;
&lt;/div&gt;
&lt;p data-end=&quot;3388&quot; data-start=&quot;3360&quot; data-ke-size=&quot;size16&quot;&gt;이 Workflow는 다음과 같은 작업을 수행한다.&lt;/p&gt;
&lt;div&gt;
&lt;div&gt;
&lt;div&gt;
&lt;div&gt;
&lt;div&gt;
&lt;div&gt;
&lt;div&gt;
&lt;div&gt;
&lt;div&gt;
&lt;div id=&quot;code-block-viewer&quot;&gt;
&lt;div&gt;
&lt;blockquote data-ke-style=&quot;style3&quot;&gt;코드 Checkout&lt;br /&gt;&lt;span&gt;&amp;darr;&lt;/span&gt;&lt;br /&gt;&lt;span&gt;Docker 이미지 빌드&lt;/span&gt;&lt;br /&gt;&lt;span&gt;&amp;darr;&lt;/span&gt;&lt;br /&gt;&lt;span&gt;GHCR Push&lt;/span&gt;&lt;br /&gt;&lt;span&gt;&amp;darr;&lt;/span&gt;&lt;br /&gt;&lt;span&gt;SSH 서버 접속&lt;/span&gt;&lt;br /&gt;&lt;span&gt;&amp;darr;&lt;/span&gt;&lt;br /&gt;&lt;span&gt;deploy.sh 실행&lt;/span&gt;&lt;/blockquote&gt;
&lt;/div&gt;
&lt;/div&gt;
&lt;/div&gt;
&lt;/div&gt;
&lt;/div&gt;
&lt;/div&gt;
&lt;/div&gt;
&lt;/div&gt;
&lt;/div&gt;
&lt;/div&gt;
&lt;/div&gt;
&lt;hr contenteditable=&quot;false&quot; data-ke-type=&quot;horizontalRule&quot; data-ke-style=&quot;style5&quot; /&gt;
&lt;h1 data-end=&quot;3501&quot; data-start=&quot;3475&quot; data-section-id=&quot;152nvqu&quot;&gt;Blue-Green 자동 배포 스크립트 작성&lt;/h1&gt;
&lt;p data-end=&quot;3539&quot; data-start=&quot;3503&quot; data-ke-size=&quot;size16&quot;&gt;다음으로 서버에서 실행되는 &lt;b&gt;자동 배포 스크립트&lt;/b&gt;를 작성한다.&lt;/p&gt;
&lt;p data-end=&quot;3546&quot; data-start=&quot;3541&quot; data-ke-size=&quot;size16&quot;&gt;파일 위치&lt;/p&gt;
&lt;div&gt;
&lt;div&gt;
&lt;div&gt;
&lt;div&gt;
&lt;div&gt;
&lt;div&gt;
&lt;div&gt;
&lt;div&gt;
&lt;div&gt;
&lt;div id=&quot;code-block-viewer&quot;&gt;
&lt;div&gt;
&lt;blockquote data-ke-style=&quot;style3&quot;&gt;~/kbo-feed-service/deploy/deploy.sh&lt;/blockquote&gt;
&lt;/div&gt;
&lt;/div&gt;
&lt;/div&gt;
&lt;/div&gt;
&lt;/div&gt;
&lt;/div&gt;
&lt;/div&gt;
&lt;/div&gt;
&lt;div&gt;&amp;nbsp;&lt;/div&gt;
&lt;/div&gt;
&lt;/div&gt;
&lt;/div&gt;
&lt;p data-end=&quot;3617&quot; data-start=&quot;3597&quot; data-ke-size=&quot;size16&quot;&gt;스크립트의 주요 역할은 다음과 같다.&lt;/p&gt;
&lt;div&gt;
&lt;div&gt;
&lt;div&gt;
&lt;div&gt;
&lt;div&gt;
&lt;div&gt;
&lt;div&gt;
&lt;div&gt;
&lt;div&gt;
&lt;div id=&quot;code-block-viewer&quot;&gt;
&lt;div&gt;
&lt;blockquote data-ke-style=&quot;style3&quot;&gt;현재 실행 중인 컨테이너 확인&lt;br /&gt;&lt;span&gt;&amp;darr;&lt;/span&gt;&lt;br /&gt;&lt;span&gt;반대 환경 컨테이너 실행&lt;/span&gt;&lt;br /&gt;&lt;span&gt;&amp;darr;&lt;/span&gt;&lt;br /&gt;&lt;span&gt;Health Check 수행&lt;/span&gt;&lt;br /&gt;&lt;span&gt;&amp;darr;&lt;/span&gt;&lt;br /&gt;&lt;span&gt;NGINX 트래픽 전환&lt;/span&gt;&lt;br /&gt;&lt;span&gt;&amp;darr;&lt;/span&gt;&lt;br /&gt;&lt;span&gt;기존 컨테이너 종료&lt;/span&gt;&lt;/blockquote&gt;
&lt;/div&gt;
&lt;/div&gt;
&lt;/div&gt;
&lt;/div&gt;
&lt;/div&gt;
&lt;/div&gt;
&lt;/div&gt;
&lt;/div&gt;
&lt;div&gt;&amp;nbsp;&lt;/div&gt;
&lt;/div&gt;
&lt;/div&gt;
&lt;/div&gt;
&lt;p data-end=&quot;3731&quot; data-start=&quot;3711&quot; data-ke-size=&quot;size16&quot;&gt;예를 들어 현재 상태가 다음과 같다면&lt;/p&gt;
&lt;div&gt;
&lt;div&gt;
&lt;div&gt;
&lt;div&gt;
&lt;div&gt;
&lt;div&gt;
&lt;div&gt;
&lt;div&gt;
&lt;div&gt;
&lt;div id=&quot;code-block-viewer&quot;&gt;
&lt;div&gt;
&lt;blockquote data-ke-style=&quot;style3&quot;&gt;feed-blue &amp;rarr; 8081&lt;/blockquote&gt;
&lt;/div&gt;
&lt;/div&gt;
&lt;/div&gt;
&lt;/div&gt;
&lt;/div&gt;
&lt;/div&gt;
&lt;/div&gt;
&lt;/div&gt;
&lt;div&gt;&amp;nbsp;&lt;/div&gt;
&lt;/div&gt;
&lt;/div&gt;
&lt;/div&gt;
&lt;p data-end=&quot;3780&quot; data-start=&quot;3763&quot; data-ke-size=&quot;size16&quot;&gt;배포 시 다음과 같이 진행된다.&lt;/p&gt;
&lt;div&gt;
&lt;div&gt;
&lt;div&gt;
&lt;div&gt;
&lt;div&gt;
&lt;div&gt;
&lt;div&gt;
&lt;div&gt;
&lt;div&gt;
&lt;div id=&quot;code-block-viewer&quot;&gt;
&lt;div&gt;
&lt;blockquote data-ke-style=&quot;style3&quot;&gt;새 버전 &amp;rarr; feed-green (8082)&lt;br /&gt;&lt;span&gt;&amp;darr;&lt;/span&gt;&lt;br /&gt;&lt;span&gt;Health Check&lt;/span&gt;&lt;br /&gt;&lt;span&gt;&amp;darr;&lt;/span&gt;&lt;br /&gt;&lt;span&gt;NGINX 전환&lt;/span&gt;&lt;br /&gt;&lt;span&gt;&amp;darr;&lt;/span&gt;&lt;br /&gt;&lt;span&gt;feed-blue 종료&lt;/span&gt;&lt;/blockquote&gt;
&lt;/div&gt;
&lt;/div&gt;
&lt;/div&gt;
&lt;/div&gt;
&lt;/div&gt;
&lt;/div&gt;
&lt;/div&gt;
&lt;/div&gt;
&lt;/div&gt;
&lt;/div&gt;
&lt;/div&gt;
&lt;hr contenteditable=&quot;false&quot; data-ke-type=&quot;horizontalRule&quot; data-ke-style=&quot;style5&quot; /&gt;
&lt;h1 data-end=&quot;3884&quot; data-start=&quot;3866&quot; data-section-id=&quot;74tk0x&quot;&gt;Health Check 자동화&lt;/h1&gt;
&lt;p data-end=&quot;3939&quot; data-start=&quot;3886&quot; data-ke-size=&quot;size16&quot;&gt;새 컨테이너가 정상적으로 실행되었는지 확인하기 위해&lt;br /&gt;Health Check 로직을 추가했다.&lt;/p&gt;
&lt;p data-end=&quot;3984&quot; data-start=&quot;3941&quot; data-ke-size=&quot;size16&quot;&gt;Feed Server에는 다음과 같은 health endpoint가 존재한다.&lt;/p&gt;
&lt;div&gt;
&lt;div&gt;
&lt;div&gt;
&lt;div&gt;
&lt;div&gt;
&lt;div&gt;
&lt;div&gt;
&lt;div&gt;
&lt;div&gt;
&lt;div id=&quot;code-block-viewer&quot;&gt;
&lt;div&gt;
&lt;blockquote data-ke-style=&quot;style3&quot;&gt;/api/v1/feeds/health&lt;/blockquote&gt;
&lt;/div&gt;
&lt;/div&gt;
&lt;/div&gt;
&lt;/div&gt;
&lt;/div&gt;
&lt;/div&gt;
&lt;/div&gt;
&lt;/div&gt;
&lt;div&gt;&amp;nbsp;&lt;/div&gt;
&lt;/div&gt;
&lt;/div&gt;
&lt;/div&gt;
&lt;p data-end=&quot;4025&quot; data-start=&quot;4020&quot; data-ke-size=&quot;size16&quot;&gt;응답 예시&lt;/p&gt;
&lt;div&gt;
&lt;div&gt;
&lt;div&gt;
&lt;div&gt;
&lt;div&gt;
&lt;div&gt;
&lt;div&gt;
&lt;div&gt;
&lt;div&gt;
&lt;div id=&quot;code-block-viewer&quot;&gt;
&lt;div&gt;
&lt;blockquote data-ke-style=&quot;style3&quot;&gt;{&lt;br /&gt;&lt;span&gt; &quot;service&quot;: &lt;/span&gt;&lt;span&gt;&quot;kbo-feed-service&quot;&lt;/span&gt;&lt;span&gt;,&lt;/span&gt;&lt;br /&gt;&lt;span&gt; &quot;status&quot;: &lt;/span&gt;&lt;span&gt;&quot;UP&quot;&lt;/span&gt;&lt;br /&gt;&lt;span&gt;}&lt;/span&gt;&lt;/blockquote&gt;
&lt;/div&gt;
&lt;/div&gt;
&lt;/div&gt;
&lt;/div&gt;
&lt;/div&gt;
&lt;/div&gt;
&lt;/div&gt;
&lt;/div&gt;
&lt;div&gt;&amp;nbsp;&lt;/div&gt;
&lt;/div&gt;
&lt;/div&gt;
&lt;/div&gt;
&lt;p data-end=&quot;4130&quot; data-start=&quot;4094&quot; data-ke-size=&quot;size16&quot;&gt;deploy.sh에서는 이 값을 확인하여 서비스 상태를 검증한다.&lt;/p&gt;
&lt;div&gt;
&lt;div&gt;
&lt;div&gt;
&lt;div&gt;
&lt;div&gt;
&lt;div&gt;
&lt;div&gt;
&lt;div&gt;
&lt;div&gt;
&lt;div id=&quot;code-block-viewer&quot;&gt;
&lt;div&gt;
&lt;blockquote data-ke-style=&quot;style3&quot;&gt;curl&lt;span&gt; http://localhost:&lt;/span&gt;&lt;span&gt;$PORT&lt;/span&gt;&lt;span&gt;/api/v1/feeds/health&lt;/span&gt;&lt;/blockquote&gt;
&lt;/div&gt;
&lt;/div&gt;
&lt;/div&gt;
&lt;/div&gt;
&lt;/div&gt;
&lt;/div&gt;
&lt;/div&gt;
&lt;/div&gt;
&lt;div&gt;&amp;nbsp;&lt;/div&gt;
&lt;/div&gt;
&lt;/div&gt;
&lt;/div&gt;
&lt;p data-end=&quot;4214&quot; data-start=&quot;4193&quot; data-ke-size=&quot;size16&quot;&gt;만약 Health Check가 실패하면&lt;/p&gt;
&lt;div&gt;
&lt;div&gt;
&lt;div&gt;
&lt;div&gt;
&lt;div&gt;
&lt;div&gt;
&lt;div&gt;
&lt;div&gt;
&lt;div&gt;
&lt;div id=&quot;code-block-viewer&quot;&gt;
&lt;div&gt;
&lt;blockquote data-ke-style=&quot;style3&quot;&gt;1. NGINX 전환 중단&lt;br /&gt;&lt;span&gt;2. 새 컨테이너 제거&lt;/span&gt;&lt;br /&gt;&lt;span&gt;3. 배포 실패 처리&lt;/span&gt;&lt;/blockquote&gt;
&lt;/div&gt;
&lt;/div&gt;
&lt;/div&gt;
&lt;/div&gt;
&lt;/div&gt;
&lt;/div&gt;
&lt;/div&gt;
&lt;/div&gt;
&lt;div&gt;&amp;nbsp;&lt;/div&gt;
&lt;/div&gt;
&lt;/div&gt;
&lt;/div&gt;
&lt;p data-end=&quot;4269&quot; data-start=&quot;4260&quot; data-ke-size=&quot;size16&quot;&gt;하도록 구성했다.&lt;/p&gt;
&lt;hr contenteditable=&quot;false&quot; data-ke-type=&quot;horizontalRule&quot; data-ke-style=&quot;style5&quot; /&gt;
&lt;h1 data-end=&quot;4286&quot; data-start=&quot;4276&quot; data-section-id=&quot;1xazd9v&quot;&gt;배포 로그 기록&lt;/h1&gt;
&lt;p data-end=&quot;4333&quot; data-start=&quot;4288&quot; data-ke-size=&quot;size16&quot;&gt;추가로 배포 히스토리를 확인할 수 있도록 &lt;b&gt;배포 로그 기록 기능&lt;/b&gt;도 추가했다.&lt;/p&gt;
&lt;div&gt;
&lt;div&gt;
&lt;div&gt;
&lt;div&gt;
&lt;div&gt;
&lt;div&gt;
&lt;div&gt;
&lt;div&gt;
&lt;div&gt;
&lt;div id=&quot;code-block-viewer&quot;&gt;
&lt;div&gt;
&lt;blockquote data-ke-style=&quot;style3&quot;&gt;echo&lt;span&gt; &lt;/span&gt;&lt;span&gt;&quot;&lt;/span&gt;&lt;span&gt;$(date &lt;/span&gt;&lt;span&gt;'+%Y-%m-%d %H:%M:%S'&lt;/span&gt;&lt;span&gt;)&lt;/span&gt;&lt;span&gt; deploy &lt;/span&gt;&lt;span&gt;$IMAGE_TAG&lt;/span&gt;&lt;span&gt;&quot;&lt;/span&gt;&lt;span&gt; &amp;gt;&amp;gt; deploy.log&lt;/span&gt;&lt;/blockquote&gt;
&lt;/div&gt;
&lt;/div&gt;
&lt;/div&gt;
&lt;/div&gt;
&lt;/div&gt;
&lt;/div&gt;
&lt;/div&gt;
&lt;/div&gt;
&lt;div&gt;&amp;nbsp;&lt;/div&gt;
&lt;/div&gt;
&lt;/div&gt;
&lt;/div&gt;
&lt;p data-end=&quot;4451&quot; data-start=&quot;4416&quot; data-ke-size=&quot;size16&quot;&gt;이렇게 하면 서버에서 다음과 같이 배포 기록을 확인할 수 있다.&lt;/p&gt;
&lt;div&gt;
&lt;div&gt;
&lt;div&gt;
&lt;div&gt;
&lt;div&gt;
&lt;div&gt;
&lt;div&gt;
&lt;div&gt;
&lt;div&gt;
&lt;div id=&quot;code-block-viewer&quot;&gt;
&lt;div&gt;
&lt;blockquote data-ke-style=&quot;style3&quot;&gt;2026-03-15 01:12 deploy 3f91c2a&lt;br /&gt;&lt;span&gt;2026-03-15 01:40 deploy 8ad21f3&lt;/span&gt;&lt;/blockquote&gt;
&lt;/div&gt;
&lt;/div&gt;
&lt;/div&gt;
&lt;/div&gt;
&lt;/div&gt;
&lt;/div&gt;
&lt;/div&gt;
&lt;/div&gt;
&lt;/div&gt;
&lt;/div&gt;
&lt;/div&gt;
&lt;hr contenteditable=&quot;false&quot; data-ke-type=&quot;horizontalRule&quot; data-ke-style=&quot;style5&quot; /&gt;
&lt;h1 data-end=&quot;4548&quot; data-start=&quot;4535&quot; data-section-id=&quot;17mqz1u&quot;&gt;자동 배포 동작 과정&lt;/h1&gt;
&lt;p data-end=&quot;4570&quot; data-start=&quot;4550&quot; data-ke-size=&quot;size16&quot;&gt;이제 실제 배포 흐름은 다음과 같다.&lt;/p&gt;
&lt;div&gt;
&lt;div&gt;
&lt;div&gt;
&lt;div&gt;
&lt;div&gt;
&lt;div&gt;
&lt;div&gt;
&lt;div&gt;
&lt;div&gt;
&lt;div id=&quot;code-block-viewer&quot;&gt;
&lt;div&gt;
&lt;blockquote data-ke-style=&quot;style3&quot;&gt;git push&lt;br /&gt;&lt;span&gt;&amp;darr;&lt;/span&gt;&lt;br /&gt;&lt;span&gt;GitHub Actions 실행&lt;/span&gt;&lt;br /&gt;&lt;span&gt;&amp;darr;&lt;/span&gt;&lt;br /&gt;&lt;span&gt;Docker Image Build&lt;/span&gt;&lt;br /&gt;&lt;span&gt;&amp;darr;&lt;/span&gt;&lt;br /&gt;&lt;span&gt;GHCR Push&lt;/span&gt;&lt;br /&gt;&lt;span&gt;&amp;darr;&lt;/span&gt;&lt;br /&gt;&lt;span&gt;SSH 서버 접속&lt;/span&gt;&lt;br /&gt;&lt;span&gt;&amp;darr;&lt;/span&gt;&lt;br /&gt;&lt;span&gt;deploy.sh 실행&lt;/span&gt;&lt;br /&gt;&lt;span&gt;&amp;darr;&lt;/span&gt;&lt;br /&gt;&lt;span&gt;Blue-Green 배포&lt;/span&gt;&lt;br /&gt;&lt;span&gt;&amp;darr;&lt;/span&gt;&lt;br /&gt;&lt;span&gt;NGINX 트래픽 전환&lt;/span&gt;&lt;/blockquote&gt;
&lt;/div&gt;
&lt;/div&gt;
&lt;/div&gt;
&lt;/div&gt;
&lt;/div&gt;
&lt;/div&gt;
&lt;/div&gt;
&lt;/div&gt;
&lt;div&gt;&amp;nbsp;&lt;/div&gt;
&lt;/div&gt;
&lt;/div&gt;
&lt;/div&gt;
&lt;p data-end=&quot;4731&quot; data-start=&quot;4705&quot; data-ke-size=&quot;size16&quot;&gt;즉 개발자는 단순히 다음 명령만 실행하면 된다.&lt;/p&gt;
&lt;div&gt;
&lt;div&gt;
&lt;div&gt;
&lt;div&gt;
&lt;div&gt;
&lt;div&gt;
&lt;div&gt;
&lt;div&gt;
&lt;div&gt;
&lt;div id=&quot;code-block-viewer&quot;&gt;
&lt;div&gt;
&lt;blockquote data-ke-style=&quot;style3&quot;&gt;git&lt;span&gt; push&lt;/span&gt;&lt;/blockquote&gt;
&lt;/div&gt;
&lt;/div&gt;
&lt;/div&gt;
&lt;/div&gt;
&lt;/div&gt;
&lt;/div&gt;
&lt;/div&gt;
&lt;/div&gt;
&lt;div&gt;&amp;nbsp;&lt;/div&gt;
&lt;/div&gt;
&lt;/div&gt;
&lt;/div&gt;
&lt;p data-end=&quot;4774&quot; data-start=&quot;4755&quot; data-ke-size=&quot;size16&quot;&gt;그러면 자동으로 서버까지 배포된다.&lt;/p&gt;
&lt;hr contenteditable=&quot;false&quot; data-ke-type=&quot;horizontalRule&quot; data-ke-style=&quot;style5&quot; /&gt;
&lt;h1 data-end=&quot;4791&quot; data-start=&quot;4781&quot; data-section-id=&quot;1ayjyov&quot;&gt;실제 배포 검증&lt;/h1&gt;
&lt;p data-end=&quot;4846&quot; data-start=&quot;4793&quot; data-ke-size=&quot;size16&quot;&gt;자동 배포가 정상적으로 동작하는지 확인하기 위해&lt;br /&gt;테스트 커밋을 Push하여 배포를 실행했다.&lt;/p&gt;
&lt;p data-end=&quot;4885&quot; data-start=&quot;4848&quot; data-ke-size=&quot;size16&quot;&gt;GitHub Actions 로그에서는 다음과 같은 단계가 확인된다.&lt;/p&gt;
&lt;div&gt;
&lt;div&gt;
&lt;div&gt;
&lt;div&gt;
&lt;div&gt;
&lt;div&gt;
&lt;div&gt;
&lt;div&gt;
&lt;div&gt;
&lt;div id=&quot;code-block-viewer&quot;&gt;
&lt;div&gt;
&lt;blockquote data-ke-style=&quot;style3&quot;&gt;Checkout&lt;br /&gt;&lt;span&gt;Login to GHCR&lt;/span&gt;&lt;br /&gt;&lt;span&gt;Build Docker Image&lt;/span&gt;&lt;br /&gt;&lt;span&gt;Push Docker Image&lt;/span&gt;&lt;br /&gt;&lt;span&gt;Deploy to Server&lt;/span&gt;&lt;/blockquote&gt;
&lt;/div&gt;
&lt;/div&gt;
&lt;/div&gt;
&lt;/div&gt;
&lt;/div&gt;
&lt;/div&gt;
&lt;/div&gt;
&lt;/div&gt;
&lt;div&gt;&amp;nbsp;&lt;/div&gt;
&lt;/div&gt;
&lt;/div&gt;
&lt;/div&gt;
&lt;p data-end=&quot;5008&quot; data-start=&quot;4977&quot; data-ke-size=&quot;size16&quot;&gt;서버에서도 컨테이너가 정상적으로 전환되는 것을 확인했다.&lt;/p&gt;
&lt;div&gt;
&lt;div&gt;
&lt;div&gt;
&lt;div&gt;
&lt;div&gt;
&lt;div&gt;
&lt;div&gt;
&lt;div&gt;
&lt;div&gt;
&lt;div id=&quot;code-block-viewer&quot;&gt;
&lt;div&gt;
&lt;blockquote data-ke-style=&quot;style3&quot;&gt;docker &lt;span&gt;ps&lt;/span&gt;&lt;/blockquote&gt;
&lt;/div&gt;
&lt;/div&gt;
&lt;/div&gt;
&lt;/div&gt;
&lt;/div&gt;
&lt;/div&gt;
&lt;/div&gt;
&lt;/div&gt;
&lt;div&gt;&amp;nbsp;&lt;/div&gt;
&lt;/div&gt;
&lt;/div&gt;
&lt;/div&gt;
&lt;p data-end=&quot;5055&quot; data-start=&quot;5033&quot; data-ke-size=&quot;size16&quot;&gt;컨테이너는 다음과 같이 번갈아 실행된다.&lt;/p&gt;
&lt;div&gt;
&lt;div&gt;
&lt;div&gt;
&lt;div&gt;
&lt;div&gt;
&lt;div&gt;
&lt;div&gt;
&lt;div&gt;
&lt;div&gt;
&lt;div id=&quot;code-block-viewer&quot;&gt;
&lt;div&gt;
&lt;blockquote data-ke-style=&quot;style3&quot;&gt;feed-blue&lt;br /&gt;&lt;span&gt;feed-green&lt;/span&gt;&lt;/blockquote&gt;
&lt;/div&gt;
&lt;/div&gt;
&lt;/div&gt;
&lt;/div&gt;
&lt;/div&gt;
&lt;/div&gt;
&lt;/div&gt;
&lt;/div&gt;
&lt;div&gt;&amp;nbsp;&lt;/div&gt;
&lt;/div&gt;
&lt;/div&gt;
&lt;/div&gt;
&lt;p data-end=&quot;5150&quot; data-start=&quot;5091&quot; data-ke-size=&quot;size16&quot;&gt;이로써 &lt;b&gt;GitHub Push &amp;rarr; 자동 Blue-Green 배포&lt;/b&gt;가 정상적으로 동작하는 것을 확인했다.&lt;/p&gt;
&lt;hr contenteditable=&quot;false&quot; data-ke-type=&quot;horizontalRule&quot; data-ke-style=&quot;style5&quot; /&gt;
&lt;h1 data-end=&quot;5169&quot; data-start=&quot;5157&quot; data-section-id=&quot;uxkxwn&quot;&gt;현재까지 진행 상황&lt;/h1&gt;
&lt;p data-end=&quot;5196&quot; data-start=&quot;5171&quot; data-ke-size=&quot;size16&quot;&gt;지금까지 진행한 작업을 정리하면 다음과 같다.&lt;/p&gt;
&lt;p data-end=&quot;5423&quot; data-start=&quot;5198&quot; data-ke-size=&quot;size16&quot;&gt;✔ Docker 기반 서비스 환경 구축&lt;br /&gt;✔ GitHub Container Registry 사용&lt;br /&gt;✔ Blue-Green 배포 환경 구성&lt;br /&gt;✔ NGINX Reverse Proxy 설정&lt;br /&gt;✔ 수동 Blue-Green 배포 검증&lt;br /&gt;✔ GitHub Actions CI/CD 구축&lt;br /&gt;✔ Blue-Green 자동 배포 스크립트 작성&lt;br /&gt;✔ Health Check 자동화&lt;br /&gt;✔ 자동 배포 검증 완료&lt;/p&gt;
&lt;hr contenteditable=&quot;false&quot; data-ke-type=&quot;horizontalRule&quot; data-ke-style=&quot;style5&quot; /&gt;
&lt;h1 data-end=&quot;5435&quot; data-start=&quot;5430&quot; data-section-id=&quot;20h5rq&quot;&gt;마무리&lt;/h1&gt;
&lt;p data-end=&quot;5504&quot; data-start=&quot;5437&quot; data-ke-size=&quot;size16&quot;&gt;이번 글에서는 GitHub Actions를 이용해 &lt;b&gt;Blue-Green 자동 배포 환경을 구축하는 과정&lt;/b&gt;을 정리했다.&lt;/p&gt;
&lt;p data-end=&quot;5524&quot; data-start=&quot;5506&quot; data-ke-size=&quot;size16&quot;&gt;특히 다음 세 가지가 핵심이었다.&lt;/p&gt;
&lt;ul style=&quot;list-style-type: disc;&quot; data-end=&quot;5612&quot; data-start=&quot;5526&quot; data-ke-list-type=&quot;disc&quot;&gt;
&lt;li data-end=&quot;5554&quot; data-start=&quot;5526&quot; data-section-id=&quot;9e9w43&quot;&gt;GitHub Actions 기반 CI/CD 구축&lt;/li&gt;
&lt;li data-end=&quot;5585&quot; data-start=&quot;5555&quot; data-section-id=&quot;1bwv1ri&quot;&gt;Commit 기반 Docker Image 버전 관리&lt;/li&gt;
&lt;li data-end=&quot;5612&quot; data-start=&quot;5586&quot; data-section-id=&quot;5z8efk&quot;&gt;Blue-Green 자동 배포 스크립트 구현&lt;/li&gt;
&lt;/ul&gt;
&lt;p data-end=&quot;5670&quot; data-start=&quot;5614&quot; data-ke-size=&quot;size16&quot;&gt;이 구조를 통해 &lt;b&gt;코드 Push만으로 서버 배포가 자동으로 이루어지는 환경&lt;/b&gt;을 구축할 수 있었다.&lt;/p&gt;
&lt;p data-end=&quot;5733&quot; data-start=&quot;5672&quot; data-ke-size=&quot;size16&quot;&gt;앞으로 서비스 규모가 커지더라도&lt;br /&gt;이 배포 구조를 기반으로 안정적인 배포를 유지할 수 있을 것으로 기대한다&lt;/p&gt;
&lt;hr contenteditable=&quot;false&quot; data-ke-type=&quot;horizontalRule&quot; data-ke-style=&quot;style5&quot; /&gt;</description>
      <category>  프로젝트/KBO-NOTE</category>
      <author>SoloQuest</author>
      <guid isPermaLink="true">https://1devlog.tistory.com/20</guid>
      <comments>https://1devlog.tistory.com/20#entry20comment</comments>
      <pubDate>Sun, 15 Mar 2026 01:42:07 +0900</pubDate>
    </item>
    <item>
      <title>Docker + Nginx 기반 Blue-Green 배포 구축 (2) &amp;mdash; Docker 이미지 배포와 Blue-Green 환경 구축</title>
      <link>https://1devlog.tistory.com/19</link>
      <description>&lt;h2 data-end=&quot;362&quot; data-start=&quot;355&quot; data-section-id=&quot;1fudykl&quot; data-ke-size=&quot;size26&quot;&gt;들어가며&lt;/h2&gt;
&lt;p data-end=&quot;434&quot; data-start=&quot;364&quot; data-ke-size=&quot;size16&quot;&gt;이전 글에서는 현재 Feed Server의 서버 구조를 분석하고&lt;br /&gt;Blue-Green 배포 전략을 설계하는 과정을 정리했다.&lt;/p&gt;
&lt;p data-end=&quot;455&quot; data-start=&quot;436&quot; data-ke-size=&quot;size16&quot;&gt;특히 다음과 같은 사항을 확인했다.&lt;/p&gt;
&lt;ul style=&quot;list-style-type: disc;&quot; data-end=&quot;561&quot; data-start=&quot;457&quot; data-ke-list-type=&quot;disc&quot;&gt;
&lt;li data-end=&quot;475&quot; data-start=&quot;457&quot; data-section-id=&quot;1cwkbhq&quot;&gt;Docker 기반 MSA 환경&lt;/li&gt;
&lt;li data-end=&quot;500&quot; data-start=&quot;476&quot; data-section-id=&quot;c72bbi&quot;&gt;NGINX Reverse Proxy 구조&lt;/li&gt;
&lt;li data-end=&quot;525&quot; data-start=&quot;501&quot; data-section-id=&quot;18q4l5z&quot;&gt;Docker host network 사용&lt;/li&gt;
&lt;li data-end=&quot;561&quot; data-start=&quot;526&quot; data-section-id=&quot;1j03rrc&quot;&gt;Blue-Green 포트 전략 설계 (8081 / 8082)&lt;/li&gt;
&lt;/ul&gt;
&lt;p data-end=&quot;621&quot; data-start=&quot;563&quot; data-ke-size=&quot;size16&quot;&gt;이번 글에서는 이 구조를 기반으로 실제 Blue-Green 배포 환경을 구축하는 과정을 정리하려고 한다.&lt;/p&gt;
&lt;p data-end=&quot;646&quot; data-start=&quot;623&quot; data-ke-size=&quot;size16&quot;&gt;이번 단계에서 진행한 작업은 다음과 같다.&lt;/p&gt;
&lt;ul style=&quot;list-style-type: disc;&quot; data-end=&quot;765&quot; data-start=&quot;648&quot; data-ke-list-type=&quot;disc&quot;&gt;
&lt;li data-end=&quot;663&quot; data-start=&quot;648&quot; data-section-id=&quot;1b7cf18&quot;&gt;Docker 이미지 생성&lt;/li&gt;
&lt;li data-end=&quot;700&quot; data-start=&quot;664&quot; data-section-id=&quot;19arl39&quot;&gt;GitHub Container Registry(GHCR) 사용&lt;/li&gt;
&lt;li data-end=&quot;720&quot; data-start=&quot;701&quot; data-section-id=&quot;34nk6d&quot;&gt;서버에서 이미지 Pull 테스트&lt;/li&gt;
&lt;li data-end=&quot;746&quot; data-start=&quot;721&quot; data-section-id=&quot;1q5kute&quot;&gt;Blue / Green 컨테이너 환경 구성&lt;/li&gt;
&lt;li data-end=&quot;765&quot; data-start=&quot;747&quot; data-section-id=&quot;11clhzj&quot;&gt;NGINX 트래픽 전환 테스트&lt;/li&gt;
&lt;/ul&gt;
&lt;p data-end=&quot;816&quot; data-start=&quot;767&quot; data-ke-size=&quot;size16&quot;&gt;즉 &lt;b&gt;Blue-Green 배포가 실제로 동작하는 환경을 구축하고 검증하는 과정&lt;/b&gt;이다.&lt;/p&gt;
&lt;hr contenteditable=&quot;false&quot; data-ke-type=&quot;horizontalRule&quot; data-ke-style=&quot;style5&quot; /&gt;
&lt;h1 data-end=&quot;838&quot; data-start=&quot;823&quot; data-section-id=&quot;125k6xe&quot;&gt;Docker 이미지 생성&lt;/h1&gt;
&lt;p data-end=&quot;895&quot; data-start=&quot;840&quot; data-ke-size=&quot;size16&quot;&gt;먼저 Feed Server를 Docker 이미지로 빌드할 수 있도록 Dockerfile을 작성했다.&lt;/p&gt;
&lt;p data-end=&quot;956&quot; data-start=&quot;897&quot; data-ke-size=&quot;size16&quot;&gt;이번 Dockerfile은 &lt;b&gt;멀티 스테이지 빌드(Multi-stage build)&lt;/b&gt; 방식으로 구성했다.&lt;/p&gt;
&lt;div&gt;
&lt;div&gt;
&lt;div&gt;
&lt;div&gt;
&lt;div&gt;
&lt;div&gt;
&lt;div&gt;
&lt;div&gt;
&lt;div&gt;&amp;nbsp;&lt;/div&gt;
&lt;/div&gt;
&lt;div&gt;
&lt;div&gt;
&lt;div id=&quot;code-block-viewer&quot;&gt;
&lt;div&gt;
&lt;blockquote data-ke-style=&quot;style3&quot;&gt;FROM&lt;span&gt; eclipse-temurin:17-jdk &lt;/span&gt;&lt;span&gt;AS&lt;/span&gt;&lt;span&gt; builder&lt;/span&gt;&lt;br /&gt;&lt;span&gt;WORKDIR&lt;/span&gt;&lt;span&gt; /workspace&lt;/span&gt;&lt;br /&gt;&lt;br /&gt;&lt;span&gt;COPY&lt;/span&gt;&lt;span&gt; gradlew gradlew&lt;/span&gt;&lt;br /&gt;&lt;span&gt;COPY&lt;/span&gt;&lt;span&gt; gradle gradle&lt;/span&gt;&lt;br /&gt;&lt;span&gt;COPY&lt;/span&gt;&lt;span&gt; build.gradle settings.gradle ./&lt;/span&gt;&lt;br /&gt;&lt;span&gt;RUN&lt;/span&gt;&lt;span&gt; chmod +x gradlew&lt;/span&gt;&lt;br /&gt;&lt;br /&gt;&lt;span&gt;COPY&lt;/span&gt;&lt;span&gt; src src&lt;/span&gt;&lt;br /&gt;&lt;span&gt;RUN&lt;/span&gt;&lt;span&gt; ./gradlew clean bootJar --no-daemon&lt;/span&gt;&lt;br /&gt;&lt;br /&gt;&lt;span&gt;FROM&lt;/span&gt;&lt;span&gt; eclipse-temurin:17-jre&lt;/span&gt;&lt;br /&gt;&lt;span&gt;WORKDIR&lt;/span&gt;&lt;span&gt; /app&lt;/span&gt;&lt;br /&gt;&lt;br /&gt;&lt;span&gt;COPY&lt;/span&gt;&lt;span&gt; --from=builder /workspace/build/libs/*.jar app.jar&lt;/span&gt;&lt;br /&gt;&lt;br /&gt;&lt;span&gt;EXPOSE&lt;/span&gt;&lt;span&gt; &lt;/span&gt;&lt;span&gt;8080&lt;/span&gt;&lt;br /&gt;&lt;span&gt;ENTRYPOINT&lt;/span&gt;&lt;span&gt; [&lt;/span&gt;&lt;span&gt;&quot;java&quot;&lt;/span&gt;&lt;span&gt;, &lt;/span&gt;&lt;span&gt;&quot;-jar&quot;&lt;/span&gt;&lt;span&gt;, &lt;/span&gt;&lt;span&gt;&quot;app.jar&quot;&lt;/span&gt;&lt;span&gt;]&lt;/span&gt;&lt;/blockquote&gt;
&lt;/div&gt;
&lt;/div&gt;
&lt;/div&gt;
&lt;/div&gt;
&lt;/div&gt;
&lt;/div&gt;
&lt;/div&gt;
&lt;/div&gt;
&lt;div&gt;&amp;nbsp;&lt;/div&gt;
&lt;/div&gt;
&lt;/div&gt;
&lt;/div&gt;
&lt;p data-end=&quot;1370&quot; data-start=&quot;1339&quot; data-ke-size=&quot;size16&quot;&gt;이 Dockerfile은 크게 두 단계로 구성되어 있다.&lt;/p&gt;
&lt;h3 data-end=&quot;1391&quot; data-start=&quot;1372&quot; data-section-id=&quot;c0xzc9&quot; data-ke-size=&quot;size23&quot;&gt;1️⃣ Build Stage&lt;/h3&gt;
&lt;p data-end=&quot;1441&quot; data-start=&quot;1393&quot; data-ke-size=&quot;size16&quot;&gt;첫 번째 단계에서는 Gradle을 사용해 Spring Boot 애플리케이션을 빌드한다.&lt;/p&gt;
&lt;div&gt;
&lt;div&gt;
&lt;div&gt;
&lt;div&gt;
&lt;div&gt;
&lt;div&gt;
&lt;div&gt;
&lt;div&gt;
&lt;div&gt;
&lt;div id=&quot;code-block-viewer&quot;&gt;
&lt;div&gt;
&lt;blockquote data-ke-style=&quot;style3&quot;&gt;FROM&lt;span&gt; eclipse-temurin:17-jdk &lt;/span&gt;&lt;span&gt;AS&lt;/span&gt;&lt;span&gt; builder&lt;/span&gt;&lt;/blockquote&gt;
&lt;/div&gt;
&lt;/div&gt;
&lt;/div&gt;
&lt;/div&gt;
&lt;/div&gt;
&lt;/div&gt;
&lt;/div&gt;
&lt;/div&gt;
&lt;div&gt;&amp;nbsp;&lt;/div&gt;
&lt;/div&gt;
&lt;/div&gt;
&lt;/div&gt;
&lt;p data-end=&quot;1543&quot; data-start=&quot;1501&quot; data-ke-size=&quot;size16&quot;&gt;JDK 환경에서 Gradle Wrapper를 이용해 애플리케이션을 빌드한다.&lt;/p&gt;
&lt;div&gt;
&lt;div&gt;
&lt;div&gt;
&lt;div&gt;
&lt;div&gt;
&lt;div&gt;
&lt;div&gt;
&lt;div&gt;
&lt;div&gt;
&lt;div id=&quot;code-block-viewer&quot;&gt;
&lt;div&gt;
&lt;blockquote data-ke-style=&quot;style3&quot;&gt;RUN&lt;span&gt; ./gradlew clean bootJar --no-daemon&lt;/span&gt;&lt;/blockquote&gt;
&lt;/div&gt;
&lt;/div&gt;
&lt;/div&gt;
&lt;/div&gt;
&lt;/div&gt;
&lt;/div&gt;
&lt;/div&gt;
&lt;/div&gt;
&lt;div&gt;&amp;nbsp;&lt;/div&gt;
&lt;/div&gt;
&lt;/div&gt;
&lt;/div&gt;
&lt;p data-end=&quot;1642&quot; data-start=&quot;1604&quot; data-ke-size=&quot;size16&quot;&gt;이 과정이 완료되면 다음 위치에 실행 가능한 JAR 파일이 생성된다.&lt;/p&gt;
&lt;div&gt;
&lt;div&gt;
&lt;div&gt;
&lt;div&gt;
&lt;div&gt;
&lt;div&gt;
&lt;div&gt;
&lt;div&gt;
&lt;div&gt;
&lt;div id=&quot;code-block-viewer&quot;&gt;
&lt;div&gt;
&lt;blockquote data-ke-style=&quot;style3&quot;&gt;build/libs/&lt;/blockquote&gt;
&lt;/div&gt;
&lt;/div&gt;
&lt;/div&gt;
&lt;/div&gt;
&lt;/div&gt;
&lt;/div&gt;
&lt;/div&gt;
&lt;/div&gt;
&lt;/div&gt;
&lt;/div&gt;
&lt;/div&gt;
&lt;hr contenteditable=&quot;false&quot; data-ke-type=&quot;horizontalRule&quot; data-ke-style=&quot;style5&quot; /&gt;
&lt;h3 data-end=&quot;1691&quot; data-start=&quot;1670&quot; data-section-id=&quot;1fdn39c&quot; data-ke-size=&quot;size23&quot;&gt;2️⃣ Runtime Stage&lt;/h3&gt;
&lt;p data-end=&quot;1735&quot; data-start=&quot;1693&quot; data-ke-size=&quot;size16&quot;&gt;두 번째 단계에서는 실제 실행에 필요한 최소 환경만 포함한 이미지를 만든다.&lt;/p&gt;
&lt;div&gt;
&lt;div&gt;
&lt;div&gt;
&lt;div&gt;
&lt;div&gt;
&lt;div&gt;
&lt;div&gt;
&lt;div&gt;
&lt;div&gt;
&lt;div id=&quot;code-block-viewer&quot;&gt;
&lt;div&gt;
&lt;blockquote data-ke-style=&quot;style3&quot;&gt;FROM&lt;span&gt; eclipse-temurin:17-jre&lt;/span&gt;&lt;/blockquote&gt;
&lt;/div&gt;
&lt;/div&gt;
&lt;/div&gt;
&lt;/div&gt;
&lt;/div&gt;
&lt;/div&gt;
&lt;/div&gt;
&lt;/div&gt;
&lt;div&gt;&amp;nbsp;&lt;/div&gt;
&lt;/div&gt;
&lt;/div&gt;
&lt;/div&gt;
&lt;p data-end=&quot;1809&quot; data-start=&quot;1784&quot; data-ke-size=&quot;size16&quot;&gt;빌드 단계에서 생성된 JAR 파일을 복사한다.&lt;/p&gt;
&lt;div&gt;
&lt;div&gt;
&lt;div&gt;
&lt;div&gt;
&lt;div&gt;
&lt;div&gt;
&lt;div&gt;
&lt;div&gt;
&lt;div&gt;
&lt;div id=&quot;code-block-viewer&quot;&gt;
&lt;div&gt;
&lt;blockquote data-ke-style=&quot;style3&quot;&gt;COPY&lt;span&gt; --from=builder /workspace/build/libs/*.jar app.jar&lt;/span&gt;&lt;/blockquote&gt;
&lt;/div&gt;
&lt;/div&gt;
&lt;/div&gt;
&lt;/div&gt;
&lt;/div&gt;
&lt;/div&gt;
&lt;/div&gt;
&lt;/div&gt;
&lt;div&gt;&amp;nbsp;&lt;/div&gt;
&lt;/div&gt;
&lt;/div&gt;
&lt;/div&gt;
&lt;p data-end=&quot;1915&quot; data-start=&quot;1886&quot; data-ke-size=&quot;size16&quot;&gt;그리고 Spring Boot 애플리케이션을 실행한다.&lt;/p&gt;
&lt;div&gt;
&lt;div&gt;
&lt;div&gt;
&lt;div&gt;
&lt;div&gt;
&lt;div&gt;
&lt;div&gt;
&lt;div&gt;
&lt;div&gt;
&lt;div id=&quot;code-block-viewer&quot;&gt;
&lt;div&gt;
&lt;blockquote data-ke-style=&quot;style3&quot;&gt;ENTRYPOINT&lt;span&gt; [&lt;/span&gt;&lt;span&gt;&quot;java&quot;&lt;/span&gt;&lt;span&gt;, &lt;/span&gt;&lt;span&gt;&quot;-jar&quot;&lt;/span&gt;&lt;span&gt;, &lt;/span&gt;&lt;span&gt;&quot;app.jar&quot;&lt;/span&gt;&lt;span&gt;]&lt;/span&gt;&lt;/blockquote&gt;
&lt;/div&gt;
&lt;/div&gt;
&lt;/div&gt;
&lt;/div&gt;
&lt;/div&gt;
&lt;/div&gt;
&lt;/div&gt;
&lt;/div&gt;
&lt;div&gt;&amp;nbsp;&lt;/div&gt;
&lt;/div&gt;
&lt;/div&gt;
&lt;/div&gt;
&lt;p data-end=&quot;2006&quot; data-start=&quot;1975&quot; data-ke-size=&quot;size16&quot;&gt;멀티 스테이지 빌드를 사용하면 다음과 같은 장점이 있다.&lt;/p&gt;
&lt;ul style=&quot;list-style-type: disc;&quot; data-end=&quot;2067&quot; data-start=&quot;2008&quot; data-ke-list-type=&quot;disc&quot;&gt;
&lt;li data-end=&quot;2033&quot; data-start=&quot;2008&quot; data-section-id=&quot;3btlji&quot;&gt;빌드 도구(Gradle)가 포함되지 않는다&lt;/li&gt;
&lt;li data-end=&quot;2051&quot; data-start=&quot;2034&quot; data-section-id=&quot;buqhf8&quot;&gt;최종 이미지 크기가 작아진다&lt;/li&gt;
&lt;li data-end=&quot;2067&quot; data-start=&quot;2052&quot; data-section-id=&quot;xhc4yd&quot;&gt;보안적으로도 더 안전하다&lt;/li&gt;
&lt;/ul&gt;
&lt;hr contenteditable=&quot;false&quot; data-ke-type=&quot;horizontalRule&quot; data-ke-style=&quot;style5&quot; /&gt;
&lt;h1 data-end=&quot;2089&quot; data-start=&quot;2074&quot; data-section-id=&quot;125t3da&quot;&gt;Docker 이미지 빌드&lt;/h1&gt;
&lt;p data-end=&quot;2119&quot; data-start=&quot;2091&quot; data-ke-size=&quot;size16&quot;&gt;Dockerfile을 작성한 후 이미지를 빌드한다.&lt;/p&gt;
&lt;div&gt;
&lt;div&gt;
&lt;div&gt;
&lt;div&gt;
&lt;div&gt;
&lt;div&gt;
&lt;div&gt;
&lt;div&gt;
&lt;div&gt;
&lt;div id=&quot;code-block-viewer&quot;&gt;
&lt;div&gt;
&lt;blockquote data-ke-style=&quot;style3&quot;&gt;docker build &lt;span&gt;-t&lt;/span&gt;&lt;span&gt; feed-service:test .&lt;/span&gt;&lt;/blockquote&gt;
&lt;/div&gt;
&lt;/div&gt;
&lt;/div&gt;
&lt;/div&gt;
&lt;/div&gt;
&lt;/div&gt;
&lt;/div&gt;
&lt;/div&gt;
&lt;div&gt;&amp;nbsp;&lt;/div&gt;
&lt;/div&gt;
&lt;/div&gt;
&lt;/div&gt;
&lt;p data-end=&quot;2202&quot; data-start=&quot;2170&quot; data-ke-size=&quot;size16&quot;&gt;빌드가 완료되면 로컬에서 컨테이너 실행 테스트를 진행한다.&lt;/p&gt;
&lt;div&gt;
&lt;div&gt;
&lt;div&gt;
&lt;div&gt;
&lt;div&gt;
&lt;div&gt;
&lt;div&gt;
&lt;div&gt;
&lt;div&gt;
&lt;div id=&quot;code-block-viewer&quot;&gt;
&lt;div&gt;
&lt;blockquote data-ke-style=&quot;style3&quot;&gt;docker run &lt;span&gt;-p&lt;/span&gt;&lt;span&gt; &lt;/span&gt;&lt;span&gt;8080&lt;/span&gt;&lt;span&gt;:8080 feed-service:test&lt;/span&gt;&lt;/blockquote&gt;
&lt;/div&gt;
&lt;/div&gt;
&lt;/div&gt;
&lt;/div&gt;
&lt;/div&gt;
&lt;/div&gt;
&lt;/div&gt;
&lt;/div&gt;
&lt;div&gt;&amp;nbsp;&lt;/div&gt;
&lt;/div&gt;
&lt;/div&gt;
&lt;/div&gt;
&lt;p data-end=&quot;2298&quot; data-start=&quot;2259&quot; data-ke-size=&quot;size16&quot;&gt;Spring Boot 애플리케이션이 정상적으로 실행되는 것을 확인했다.&lt;/p&gt;
&lt;hr contenteditable=&quot;false&quot; data-ke-type=&quot;horizontalRule&quot; data-ke-style=&quot;style5&quot; /&gt;
&lt;h1 data-end=&quot;2335&quot; data-start=&quot;2305&quot; data-section-id=&quot;1lh8ho4&quot;&gt;GitHub Container Registry 사용&lt;/h1&gt;
&lt;p data-end=&quot;2396&quot; data-start=&quot;2337&quot; data-ke-size=&quot;size16&quot;&gt;자동 배포 환경을 구축하기 위해 Docker 이미지를 Container Registry에 업로드해야 한다.&lt;/p&gt;
&lt;p data-end=&quot;2452&quot; data-start=&quot;2398&quot; data-ke-size=&quot;size16&quot;&gt;이번 프로젝트에서는 &lt;b&gt;GitHub Container Registry(GHCR)&lt;/b&gt; 를 사용했다.&lt;/p&gt;
&lt;p data-end=&quot;2479&quot; data-start=&quot;2454&quot; data-ke-size=&quot;size16&quot;&gt;이미지 이름은 다음과 같은 형식으로 구성했다.&lt;/p&gt;
&lt;div&gt;
&lt;div&gt;
&lt;div&gt;
&lt;div&gt;
&lt;div&gt;
&lt;div&gt;
&lt;div&gt;
&lt;div&gt;
&lt;div&gt;
&lt;div id=&quot;code-block-viewer&quot;&gt;
&lt;div&gt;
&lt;blockquote data-ke-style=&quot;style3&quot;&gt;ghcr.io/&amp;lt;organization&amp;gt;/&amp;lt;repository&amp;gt;:latest&lt;/blockquote&gt;
&lt;/div&gt;
&lt;/div&gt;
&lt;/div&gt;
&lt;/div&gt;
&lt;/div&gt;
&lt;/div&gt;
&lt;/div&gt;
&lt;/div&gt;
&lt;div&gt;&amp;nbsp;&lt;/div&gt;
&lt;/div&gt;
&lt;/div&gt;
&lt;/div&gt;
&lt;p data-end=&quot;2564&quot; data-start=&quot;2533&quot; data-ke-size=&quot;size16&quot;&gt;우리 프로젝트에서는 다음과 같은 이미지 이름을 사용했다.&lt;/p&gt;
&lt;div&gt;
&lt;div&gt;
&lt;div&gt;
&lt;div&gt;
&lt;div&gt;
&lt;div&gt;
&lt;div&gt;
&lt;div&gt;
&lt;div&gt;
&lt;div id=&quot;code-block-viewer&quot;&gt;
&lt;div&gt;
&lt;blockquote data-ke-style=&quot;style3&quot;&gt;ghcr.io/kbo-note/kbo-note-feed-service:latest&lt;/blockquote&gt;
&lt;/div&gt;
&lt;/div&gt;
&lt;/div&gt;
&lt;/div&gt;
&lt;/div&gt;
&lt;/div&gt;
&lt;/div&gt;
&lt;/div&gt;
&lt;div&gt;&amp;nbsp;&lt;/div&gt;
&lt;/div&gt;
&lt;/div&gt;
&lt;/div&gt;
&lt;p data-end=&quot;2642&quot; data-start=&quot;2616&quot; data-ke-size=&quot;size16&quot;&gt;먼저 로컬 이미지를 GHCR 형식으로 태깅한다.&lt;/p&gt;
&lt;div&gt;
&lt;div&gt;
&lt;div&gt;
&lt;div&gt;
&lt;div&gt;
&lt;div&gt;
&lt;div&gt;
&lt;div&gt;
&lt;div&gt;
&lt;div id=&quot;code-block-viewer&quot;&gt;
&lt;div&gt;
&lt;blockquote data-ke-style=&quot;style3&quot;&gt;docker tag feed-service:test ghcr.io/kbo-note/kbo-note-feed-service:latest&lt;/blockquote&gt;
&lt;/div&gt;
&lt;/div&gt;
&lt;/div&gt;
&lt;/div&gt;
&lt;/div&gt;
&lt;/div&gt;
&lt;/div&gt;
&lt;/div&gt;
&lt;div&gt;&amp;nbsp;&lt;/div&gt;
&lt;/div&gt;
&lt;/div&gt;
&lt;/div&gt;
&lt;p data-end=&quot;2765&quot; data-start=&quot;2727&quot; data-ke-size=&quot;size16&quot;&gt;다음으로 GitHub Container Registry에 로그인한다.&lt;/p&gt;
&lt;div&gt;
&lt;div&gt;
&lt;div&gt;
&lt;div&gt;
&lt;div&gt;
&lt;div&gt;
&lt;div&gt;
&lt;div&gt;
&lt;div&gt;
&lt;div id=&quot;code-block-viewer&quot;&gt;
&lt;div&gt;
&lt;blockquote data-ke-style=&quot;style3&quot;&gt;docker login ghcr.io&lt;/blockquote&gt;
&lt;/div&gt;
&lt;/div&gt;
&lt;/div&gt;
&lt;/div&gt;
&lt;/div&gt;
&lt;/div&gt;
&lt;/div&gt;
&lt;/div&gt;
&lt;div&gt;&amp;nbsp;&lt;/div&gt;
&lt;/div&gt;
&lt;/div&gt;
&lt;/div&gt;
&lt;p data-end=&quot;2847&quot; data-start=&quot;2801&quot; data-ke-size=&quot;size16&quot;&gt;로그인에는 GitHub Personal Access Token(PAT)을 사용했다.&lt;/p&gt;
&lt;p data-end=&quot;2874&quot; data-start=&quot;2849&quot; data-ke-size=&quot;size16&quot;&gt;이후 이미지를 Registry에 Push한다.&lt;/p&gt;
&lt;div&gt;
&lt;div&gt;
&lt;div&gt;
&lt;div&gt;
&lt;div&gt;
&lt;div&gt;
&lt;div&gt;
&lt;div&gt;
&lt;div&gt;
&lt;div id=&quot;code-block-viewer&quot;&gt;
&lt;div&gt;
&lt;blockquote data-ke-style=&quot;style3&quot;&gt;docker push ghcr.io/kbo-note/kbo-note-feed-service:latest&lt;/blockquote&gt;
&lt;/div&gt;
&lt;/div&gt;
&lt;/div&gt;
&lt;/div&gt;
&lt;/div&gt;
&lt;/div&gt;
&lt;/div&gt;
&lt;/div&gt;
&lt;div&gt;&amp;nbsp;&lt;/div&gt;
&lt;/div&gt;
&lt;/div&gt;
&lt;/div&gt;
&lt;p data-end=&quot;3009&quot; data-start=&quot;2942&quot; data-ke-size=&quot;size16&quot;&gt;Push가 완료되면 GitHub Organization의 Packages 영역에서 이미지가 생성된 것을 확인할 수 있다.&lt;/p&gt;
&lt;hr contenteditable=&quot;false&quot; data-ke-type=&quot;horizontalRule&quot; data-ke-style=&quot;style5&quot; /&gt;
&lt;h1 data-end=&quot;3035&quot; data-start=&quot;3016&quot; data-section-id=&quot;8jr1bf&quot;&gt;서버에서 이미지 Pull 테스트&lt;/h1&gt;
&lt;p data-end=&quot;3076&quot; data-start=&quot;3037&quot; data-ke-size=&quot;size16&quot;&gt;다음 단계는 서버에서 이미지를 직접 가져올 수 있는지 확인하는 것이다.&lt;/p&gt;
&lt;p data-end=&quot;3101&quot; data-start=&quot;3078&quot; data-ke-size=&quot;size16&quot;&gt;먼저 서버에서 GHCR 로그인을 수행한다.&lt;/p&gt;
&lt;div&gt;
&lt;div&gt;
&lt;div&gt;
&lt;div&gt;
&lt;div&gt;
&lt;div&gt;
&lt;div&gt;
&lt;div&gt;
&lt;div&gt;
&lt;div id=&quot;code-block-viewer&quot;&gt;
&lt;div&gt;
&lt;blockquote data-ke-style=&quot;style3&quot;&gt;docker login ghcr.io&lt;/blockquote&gt;
&lt;/div&gt;
&lt;/div&gt;
&lt;/div&gt;
&lt;/div&gt;
&lt;/div&gt;
&lt;/div&gt;
&lt;/div&gt;
&lt;/div&gt;
&lt;div&gt;&amp;nbsp;&lt;/div&gt;
&lt;/div&gt;
&lt;/div&gt;
&lt;/div&gt;
&lt;p data-end=&quot;3153&quot; data-start=&quot;3137&quot; data-ke-size=&quot;size16&quot;&gt;이후 이미지를 Pull 한다.&lt;/p&gt;
&lt;div&gt;
&lt;div&gt;
&lt;div&gt;
&lt;div&gt;
&lt;div&gt;
&lt;div&gt;
&lt;div&gt;
&lt;div&gt;
&lt;div&gt;
&lt;div id=&quot;code-block-viewer&quot;&gt;
&lt;div&gt;
&lt;blockquote data-ke-style=&quot;style3&quot;&gt;docker pull ghcr.io/kbo-note/kbo-feed-service:latest&lt;/blockquote&gt;
&lt;/div&gt;
&lt;/div&gt;
&lt;/div&gt;
&lt;/div&gt;
&lt;/div&gt;
&lt;/div&gt;
&lt;/div&gt;
&lt;/div&gt;
&lt;div&gt;&amp;nbsp;&lt;/div&gt;
&lt;/div&gt;
&lt;/div&gt;
&lt;/div&gt;
&lt;p data-end=&quot;3255&quot; data-start=&quot;3221&quot; data-ke-size=&quot;size16&quot;&gt;이미지가 정상적으로 다운로드되면 다음 단계로 넘어갈 수 있다.&lt;/p&gt;
&lt;p data-end=&quot;3335&quot; data-start=&quot;3257&quot; data-ke-size=&quot;size16&quot;&gt;이 단계는 매우 중요한데&lt;br /&gt;&lt;b&gt;Container Registry &amp;rarr; 서버 배포 경로가 정상적으로 동작하는지 확인하는 과정&lt;/b&gt;이기 때문이다.&lt;/p&gt;
&lt;hr contenteditable=&quot;false&quot; data-ke-type=&quot;horizontalRule&quot; data-ke-style=&quot;style5&quot; /&gt;
&lt;h1 data-end=&quot;3367&quot; data-start=&quot;3342&quot; data-section-id=&quot;188jv64&quot;&gt;Blue / Green 컨테이너 환경 구성&lt;/h1&gt;
&lt;p data-end=&quot;3395&quot; data-start=&quot;3369&quot; data-ke-size=&quot;size16&quot;&gt;이제 실제 Blue-Green 환경을 구성한다.&lt;/p&gt;
&lt;p data-end=&quot;3427&quot; data-start=&quot;3397&quot; data-ke-size=&quot;size16&quot;&gt;이번 프로젝트에서는 다음과 같은 포트 전략을 사용했다.&lt;/p&gt;
&lt;div&gt;
&lt;table style=&quot;border-collapse: collapse; width: 25.5814%; height: 51px;&quot; border=&quot;1&quot; data-end=&quot;3469&quot; data-start=&quot;3429&quot; data-ke-align=&quot;alignLeft&quot; data-ke-style=&quot;style12&quot;&gt;
&lt;tbody&gt;
&lt;tr style=&quot;height: 17px;&quot;&gt;
&lt;td style=&quot;height: 17px; width: 48.6364%; text-align: center;&quot;&gt;환경&lt;/td&gt;
&lt;td style=&quot;height: 17px; width: 50.9091%; text-align: center;&quot;&gt;포트&lt;/td&gt;
&lt;/tr&gt;
&lt;tr style=&quot;height: 17px;&quot; data-end=&quot;3456&quot; data-start=&quot;3445&quot;&gt;
&lt;td style=&quot;height: 17px; width: 48.6364%; text-align: center;&quot; data-col-size=&quot;sm&quot; data-end=&quot;3450&quot; data-start=&quot;3445&quot;&gt;Blue&lt;/td&gt;
&lt;td style=&quot;height: 17px; width: 50.9091%; text-align: center;&quot; data-col-size=&quot;sm&quot; data-end=&quot;3456&quot; data-start=&quot;3450&quot;&gt;8081&lt;/td&gt;
&lt;/tr&gt;
&lt;tr style=&quot;height: 17px;&quot; data-end=&quot;3469&quot; data-start=&quot;3457&quot;&gt;
&lt;td style=&quot;height: 17px; width: 48.6364%; text-align: center;&quot; data-col-size=&quot;sm&quot; data-end=&quot;3463&quot; data-start=&quot;3457&quot;&gt;Green&lt;/td&gt;
&lt;td style=&quot;height: 17px; width: 50.9091%; text-align: center;&quot; data-col-size=&quot;sm&quot; data-end=&quot;3469&quot; data-start=&quot;3463&quot;&gt;8082&lt;/td&gt;
&lt;/tr&gt;
&lt;/tbody&gt;
&lt;/table&gt;
&lt;/div&gt;
&lt;p data-end=&quot;3495&quot; data-start=&quot;3471&quot; data-ke-size=&quot;size16&quot;&gt;또한 컨테이너 이름도 다음과 같이 분리했다.&lt;/p&gt;
&lt;div&gt;
&lt;div&gt;
&lt;div&gt;
&lt;div&gt;
&lt;div&gt;
&lt;div&gt;
&lt;div&gt;
&lt;div&gt;
&lt;div&gt;
&lt;div id=&quot;code-block-viewer&quot;&gt;
&lt;div&gt;
&lt;blockquote data-ke-style=&quot;style3&quot;&gt;feed-blue&lt;br /&gt;&lt;span&gt;feed-green&lt;/span&gt;&lt;/blockquote&gt;
&lt;/div&gt;
&lt;/div&gt;
&lt;/div&gt;
&lt;/div&gt;
&lt;/div&gt;
&lt;/div&gt;
&lt;/div&gt;
&lt;/div&gt;
&lt;div&gt;&amp;nbsp;&lt;/div&gt;
&lt;/div&gt;
&lt;/div&gt;
&lt;/div&gt;
&lt;p data-end=&quot;3544&quot; data-start=&quot;3527&quot; data-ke-size=&quot;size16&quot;&gt;먼저 Blue 환경을 실행한다.&lt;/p&gt;
&lt;div&gt;
&lt;div&gt;
&lt;div&gt;
&lt;div&gt;
&lt;div&gt;
&lt;div&gt;
&lt;div&gt;
&lt;div&gt;
&lt;div&gt;&amp;nbsp;&lt;/div&gt;
&lt;/div&gt;
&lt;div&gt;
&lt;div&gt;
&lt;div id=&quot;code-block-viewer&quot;&gt;
&lt;div&gt;
&lt;blockquote data-ke-style=&quot;style3&quot;&gt;docker run &lt;span&gt;-d&lt;/span&gt;&lt;span&gt; \&lt;/span&gt;&lt;br /&gt;&lt;span&gt;--name&lt;/span&gt;&lt;span&gt; feed-blue \&lt;/span&gt;&lt;br /&gt;&lt;span&gt;--network&lt;/span&gt;&lt;span&gt; host \&lt;/span&gt;&lt;br /&gt;&lt;span&gt;-e&lt;/span&gt;&lt;span&gt; &lt;/span&gt;&lt;span&gt;SERVER_PORT&lt;/span&gt;&lt;span&gt;=&lt;/span&gt;&lt;span&gt;8081&lt;/span&gt;&lt;span&gt; \&lt;/span&gt;&lt;br /&gt;&lt;span&gt;ghcr.io/kbo-note/kbo-note-feed-service:latest&lt;/span&gt;&lt;/blockquote&gt;
&lt;/div&gt;
&lt;/div&gt;
&lt;/div&gt;
&lt;/div&gt;
&lt;/div&gt;
&lt;/div&gt;
&lt;/div&gt;
&lt;/div&gt;
&lt;div&gt;&amp;nbsp;&lt;/div&gt;
&lt;/div&gt;
&lt;/div&gt;
&lt;/div&gt;
&lt;p data-end=&quot;3697&quot; data-start=&quot;3674&quot; data-ke-size=&quot;size16&quot;&gt;컨테이너가 정상적으로 실행되는지 확인한다.&lt;/p&gt;
&lt;div&gt;
&lt;div&gt;
&lt;div&gt;
&lt;div&gt;
&lt;div&gt;
&lt;div&gt;
&lt;div&gt;
&lt;div&gt;
&lt;div&gt;
&lt;div id=&quot;code-block-viewer&quot;&gt;
&lt;div&gt;
&lt;blockquote data-ke-style=&quot;style3&quot;&gt;docker &lt;span&gt;ps&lt;/span&gt;&lt;/blockquote&gt;
&lt;/div&gt;
&lt;/div&gt;
&lt;/div&gt;
&lt;/div&gt;
&lt;/div&gt;
&lt;/div&gt;
&lt;/div&gt;
&lt;/div&gt;
&lt;div&gt;&amp;nbsp;&lt;/div&gt;
&lt;/div&gt;
&lt;/div&gt;
&lt;/div&gt;
&lt;p data-end=&quot;3742&quot; data-start=&quot;3722&quot; data-ke-size=&quot;size16&quot;&gt;서버에서 직접 요청을 보내 확인했다.&lt;/p&gt;
&lt;div&gt;
&lt;div&gt;
&lt;div&gt;
&lt;div&gt;
&lt;div&gt;
&lt;div&gt;
&lt;div&gt;
&lt;div&gt;
&lt;div&gt;
&lt;div id=&quot;code-block-viewer&quot;&gt;
&lt;div&gt;
&lt;blockquote data-ke-style=&quot;style3&quot;&gt;curl&lt;span&gt; localhost:8081&lt;/span&gt;&lt;/blockquote&gt;
&lt;/div&gt;
&lt;/div&gt;
&lt;/div&gt;
&lt;/div&gt;
&lt;/div&gt;
&lt;/div&gt;
&lt;/div&gt;
&lt;/div&gt;
&lt;div&gt;&amp;nbsp;&lt;/div&gt;
&lt;/div&gt;
&lt;/div&gt;
&lt;/div&gt;
&lt;p data-end=&quot;3849&quot; data-start=&quot;3777&quot; data-ke-size=&quot;size16&quot;&gt;Spring Boot 루트 경로가 정의되어 있지 않아 404 응답이 반환되지만&lt;br /&gt;이는 서버가 정상적으로 실행되고 있다는 의미다.&lt;/p&gt;
&lt;hr contenteditable=&quot;false&quot; data-ke-type=&quot;horizontalRule&quot; data-ke-style=&quot;style5&quot; /&gt;
&lt;h1 data-end=&quot;3869&quot; data-start=&quot;3856&quot; data-section-id=&quot;1b1c6ph&quot;&gt;Green 환경 실행&lt;/h1&gt;
&lt;p data-end=&quot;3899&quot; data-start=&quot;3871&quot; data-ke-size=&quot;size16&quot;&gt;이제 동일한 이미지를 Green 환경으로 실행한다.&lt;/p&gt;
&lt;div&gt;
&lt;div&gt;
&lt;div&gt;
&lt;div&gt;
&lt;div&gt;
&lt;div&gt;
&lt;div&gt;
&lt;div&gt;
&lt;div&gt;
&lt;div id=&quot;code-block-viewer&quot;&gt;
&lt;div&gt;
&lt;blockquote data-ke-style=&quot;style3&quot;&gt;docker run &lt;span&gt;-d&lt;/span&gt;&lt;span&gt; \&lt;/span&gt;&lt;br /&gt;&lt;span&gt;--name&lt;/span&gt;&lt;span&gt; feed-green \&lt;/span&gt;&lt;br /&gt;&lt;span&gt;--network&lt;/span&gt;&lt;span&gt; host \&lt;/span&gt;&lt;br /&gt;&lt;span&gt;-e&lt;/span&gt;&lt;span&gt; &lt;/span&gt;&lt;span&gt;SERVER_PORT&lt;/span&gt;&lt;span&gt;=&lt;/span&gt;&lt;span&gt;8082&lt;/span&gt;&lt;span&gt; \&lt;/span&gt;&lt;br /&gt;&lt;span&gt;ghcr.io/kbo-note/kbo-feed-service:latest&lt;/span&gt;&lt;/blockquote&gt;
&lt;/div&gt;
&lt;/div&gt;
&lt;/div&gt;
&lt;/div&gt;
&lt;/div&gt;
&lt;/div&gt;
&lt;/div&gt;
&lt;/div&gt;
&lt;div&gt;&amp;nbsp;&lt;/div&gt;
&lt;/div&gt;
&lt;/div&gt;
&lt;/div&gt;
&lt;p data-end=&quot;4047&quot; data-start=&quot;4030&quot; data-ke-size=&quot;size16&quot;&gt;현재 서버 상태는 다음과 같다.&lt;/p&gt;
&lt;div&gt;
&lt;div&gt;
&lt;div&gt;
&lt;div&gt;
&lt;div&gt;
&lt;div&gt;
&lt;div&gt;
&lt;div&gt;
&lt;div&gt;
&lt;div id=&quot;code-block-viewer&quot;&gt;
&lt;div&gt;
&lt;blockquote data-ke-style=&quot;style3&quot;&gt;feed-blue &amp;rarr; 8081&lt;br /&gt;&lt;span&gt;feed-green &amp;rarr; 8082&lt;/span&gt;&lt;/blockquote&gt;
&lt;/div&gt;
&lt;/div&gt;
&lt;/div&gt;
&lt;/div&gt;
&lt;/div&gt;
&lt;/div&gt;
&lt;/div&gt;
&lt;/div&gt;
&lt;div&gt;&amp;nbsp;&lt;/div&gt;
&lt;/div&gt;
&lt;/div&gt;
&lt;/div&gt;
&lt;p data-end=&quot;4122&quot; data-start=&quot;4094&quot; data-ke-size=&quot;size16&quot;&gt;즉 두 개의 애플리케이션이 동시에 실행되는 상태다.&lt;/p&gt;
&lt;p data-end=&quot;4145&quot; data-start=&quot;4124&quot; data-ke-size=&quot;size16&quot;&gt;Green 환경도 동일하게 테스트한다.&lt;/p&gt;
&lt;div&gt;
&lt;div&gt;
&lt;div&gt;
&lt;div&gt;
&lt;div&gt;
&lt;div&gt;
&lt;div&gt;
&lt;div&gt;
&lt;div&gt;
&lt;div id=&quot;code-block-viewer&quot;&gt;
&lt;div&gt;
&lt;blockquote data-ke-style=&quot;style3&quot;&gt;curl&lt;span&gt; localhost:8082&lt;/span&gt;&lt;/blockquote&gt;
&lt;/div&gt;
&lt;/div&gt;
&lt;/div&gt;
&lt;/div&gt;
&lt;/div&gt;
&lt;/div&gt;
&lt;/div&gt;
&lt;/div&gt;
&lt;/div&gt;
&lt;/div&gt;
&lt;/div&gt;
&lt;hr contenteditable=&quot;false&quot; data-ke-type=&quot;horizontalRule&quot; data-ke-style=&quot;style5&quot; /&gt;
&lt;h1 data-end=&quot;4203&quot; data-start=&quot;4185&quot; data-section-id=&quot;1nmbo8x&quot;&gt;NGINX 트래픽 전환 테스트&lt;/h1&gt;
&lt;p data-end=&quot;4225&quot; data-start=&quot;4205&quot; data-ke-size=&quot;size16&quot;&gt;현재 NGINX 설정은 다음과 같다.&lt;/p&gt;
&lt;div&gt;
&lt;div&gt;
&lt;div&gt;
&lt;div&gt;
&lt;div&gt;
&lt;div&gt;
&lt;div&gt;
&lt;div&gt;
&lt;div&gt;
&lt;div id=&quot;code-block-viewer&quot;&gt;
&lt;div&gt;
&lt;blockquote data-ke-style=&quot;style3&quot;&gt;proxy_pass http://127.0.0.1:8081;&lt;/blockquote&gt;
&lt;/div&gt;
&lt;/div&gt;
&lt;/div&gt;
&lt;/div&gt;
&lt;/div&gt;
&lt;/div&gt;
&lt;/div&gt;
&lt;/div&gt;
&lt;div&gt;&amp;nbsp;&lt;/div&gt;
&lt;/div&gt;
&lt;/div&gt;
&lt;/div&gt;
&lt;p data-end=&quot;4298&quot; data-start=&quot;4270&quot; data-ke-size=&quot;size16&quot;&gt;즉 현재 트래픽은 Blue 환경으로 전달되고 있다.&lt;/p&gt;
&lt;p data-end=&quot;4337&quot; data-start=&quot;4300&quot; data-ke-size=&quot;size16&quot;&gt;Blue-Green 전환 테스트를 위해 NGINX 설정을 수정했다.&lt;/p&gt;
&lt;p data-end=&quot;4344&quot; data-start=&quot;4339&quot; data-ke-size=&quot;size16&quot;&gt;기존 설정&lt;/p&gt;
&lt;div&gt;
&lt;div&gt;
&lt;div&gt;
&lt;div&gt;
&lt;div&gt;
&lt;div&gt;
&lt;div&gt;
&lt;div&gt;
&lt;div&gt;
&lt;div id=&quot;code-block-viewer&quot;&gt;
&lt;div&gt;
&lt;blockquote data-ke-style=&quot;style3&quot;&gt;proxy_pass http://127.0.0.1:8081;&lt;/blockquote&gt;
&lt;/div&gt;
&lt;/div&gt;
&lt;/div&gt;
&lt;/div&gt;
&lt;/div&gt;
&lt;/div&gt;
&lt;/div&gt;
&lt;/div&gt;
&lt;div&gt;&amp;nbsp;&lt;/div&gt;
&lt;/div&gt;
&lt;/div&gt;
&lt;/div&gt;
&lt;p data-end=&quot;4393&quot; data-start=&quot;4389&quot; data-ke-size=&quot;size16&quot;&gt;변경 후&lt;/p&gt;
&lt;div&gt;
&lt;div&gt;
&lt;div&gt;
&lt;div&gt;
&lt;div&gt;
&lt;div&gt;
&lt;div&gt;
&lt;div&gt;
&lt;div&gt;
&lt;div id=&quot;code-block-viewer&quot;&gt;
&lt;div&gt;
&lt;blockquote data-ke-style=&quot;style3&quot;&gt;proxy_pass http://127.0.0.1:8082;&lt;/blockquote&gt;
&lt;/div&gt;
&lt;/div&gt;
&lt;/div&gt;
&lt;/div&gt;
&lt;/div&gt;
&lt;/div&gt;
&lt;/div&gt;
&lt;/div&gt;
&lt;div&gt;&amp;nbsp;&lt;/div&gt;
&lt;/div&gt;
&lt;/div&gt;
&lt;/div&gt;
&lt;p data-end=&quot;4463&quot; data-start=&quot;4438&quot; data-ke-size=&quot;size16&quot;&gt;설정을 변경한 후 NGINX 설정을 검증했다.&lt;/p&gt;
&lt;div&gt;
&lt;div&gt;
&lt;div&gt;
&lt;div&gt;
&lt;div&gt;
&lt;div&gt;
&lt;div&gt;
&lt;div&gt;
&lt;div&gt;
&lt;div id=&quot;code-block-viewer&quot;&gt;
&lt;div&gt;
&lt;blockquote data-ke-style=&quot;style3&quot;&gt;sudo&lt;span&gt; nginx &lt;/span&gt;&lt;span&gt;-t&lt;/span&gt;&lt;/blockquote&gt;
&lt;/div&gt;
&lt;/div&gt;
&lt;/div&gt;
&lt;/div&gt;
&lt;/div&gt;
&lt;/div&gt;
&lt;/div&gt;
&lt;/div&gt;
&lt;div&gt;&amp;nbsp;&lt;/div&gt;
&lt;/div&gt;
&lt;/div&gt;
&lt;/div&gt;
&lt;p data-end=&quot;4514&quot; data-start=&quot;4492&quot; data-ke-size=&quot;size16&quot;&gt;문제가 없다면 설정을 reload 한다.&lt;/p&gt;
&lt;div&gt;
&lt;div&gt;
&lt;div&gt;
&lt;div&gt;
&lt;div&gt;
&lt;div&gt;
&lt;div&gt;
&lt;div&gt;
&lt;div&gt;
&lt;div id=&quot;code-block-viewer&quot;&gt;
&lt;div&gt;
&lt;blockquote data-ke-style=&quot;style3&quot;&gt;sudo&lt;span&gt; systemctl reload nginx&lt;/span&gt;&lt;/blockquote&gt;
&lt;/div&gt;
&lt;/div&gt;
&lt;/div&gt;
&lt;/div&gt;
&lt;/div&gt;
&lt;/div&gt;
&lt;/div&gt;
&lt;/div&gt;
&lt;div&gt;&amp;nbsp;&lt;/div&gt;
&lt;/div&gt;
&lt;/div&gt;
&lt;/div&gt;
&lt;p data-end=&quot;4581&quot; data-start=&quot;4557&quot; data-ke-size=&quot;size16&quot;&gt;이제 트래픽은 Green 환경으로 전달된다.&lt;/p&gt;
&lt;div&gt;
&lt;div&gt;
&lt;div&gt;
&lt;div&gt;
&lt;div&gt;
&lt;div&gt;
&lt;div&gt;
&lt;div&gt;
&lt;div&gt;
&lt;div id=&quot;code-block-viewer&quot;&gt;
&lt;div&gt;
&lt;blockquote data-ke-style=&quot;style3&quot;&gt;Client&lt;br /&gt;&lt;span&gt;&amp;darr;&lt;/span&gt;&lt;br /&gt;&lt;span&gt;NGINX :9000&lt;/span&gt;&lt;br /&gt;&lt;span&gt;&amp;darr;&lt;/span&gt;&lt;br /&gt;&lt;span&gt;Feed Server (Green :8082)&lt;/span&gt;&lt;/blockquote&gt;
&lt;/div&gt;
&lt;/div&gt;
&lt;/div&gt;
&lt;/div&gt;
&lt;/div&gt;
&lt;/div&gt;
&lt;/div&gt;
&lt;/div&gt;
&lt;div&gt;&amp;nbsp;&lt;/div&gt;
&lt;/div&gt;
&lt;/div&gt;
&lt;/div&gt;
&lt;p data-end=&quot;4666&quot; data-start=&quot;4641&quot; data-ke-size=&quot;size16&quot;&gt;마지막으로 기존 Blue 컨테이너를 정리한다.&lt;/p&gt;
&lt;div&gt;
&lt;div&gt;
&lt;div&gt;
&lt;div&gt;
&lt;div&gt;
&lt;div&gt;
&lt;div&gt;
&lt;div&gt;
&lt;div&gt;
&lt;div id=&quot;code-block-viewer&quot;&gt;
&lt;div&gt;
&lt;blockquote data-ke-style=&quot;style3&quot;&gt;docker &lt;span&gt;stop&lt;/span&gt;&lt;span&gt; feed-blue&lt;/span&gt;&lt;br /&gt;&lt;span&gt;docker &lt;/span&gt;&lt;span&gt;rm&lt;/span&gt;&lt;span&gt; feed-blue&lt;/span&gt;&lt;/blockquote&gt;
&lt;/div&gt;
&lt;/div&gt;
&lt;/div&gt;
&lt;/div&gt;
&lt;/div&gt;
&lt;/div&gt;
&lt;/div&gt;
&lt;/div&gt;
&lt;div&gt;&amp;nbsp;&lt;/div&gt;
&lt;/div&gt;
&lt;/div&gt;
&lt;/div&gt;
&lt;p data-end=&quot;4748&quot; data-start=&quot;4723&quot; data-ke-size=&quot;size16&quot;&gt;이로써 Blue-Green 전환이 완료되었다.&lt;/p&gt;
&lt;hr contenteditable=&quot;false&quot; data-ke-type=&quot;horizontalRule&quot; data-ke-style=&quot;style5&quot; /&gt;
&lt;h1 data-end=&quot;4767&quot; data-start=&quot;4755&quot; data-section-id=&quot;uxkxwn&quot;&gt;현재까지 진행 상황&lt;/h1&gt;
&lt;p data-end=&quot;4794&quot; data-start=&quot;4769&quot; data-ke-size=&quot;size16&quot;&gt;지금까지 진행한 작업을 정리하면 다음과 같다.&lt;/p&gt;
&lt;p data-end=&quot;4956&quot; data-start=&quot;4796&quot; data-ke-size=&quot;size16&quot;&gt;✔ Dockerfile 작성&lt;br /&gt;✔ Docker 이미지 빌드&lt;br /&gt;✔ GitHub Container Registry 사용&lt;br /&gt;✔ 서버에서 이미지 Pull 확인&lt;br /&gt;✔ Blue / Green 컨테이너 환경 구축&lt;br /&gt;✔ NGINX 트래픽 전환 테스트&lt;br /&gt;✔ 수동 Blue-Green 배포 검증&lt;/p&gt;
&lt;p data-end=&quot;5005&quot; data-start=&quot;4958&quot; data-ke-size=&quot;size16&quot;&gt;즉 &lt;b&gt;Blue-Green 배포가 실제로 동작하는 환경을 구축하고 검증한 상태&lt;/b&gt;다.&lt;/p&gt;
&lt;hr contenteditable=&quot;false&quot; data-ke-type=&quot;horizontalRule&quot; data-ke-style=&quot;style5&quot; /&gt;
&lt;h1 data-end=&quot;5026&quot; data-start=&quot;5012&quot; data-section-id=&quot;1aq0syz&quot;&gt;다음 글에서 다룰 내용&lt;/h1&gt;
&lt;p data-end=&quot;5078&quot; data-start=&quot;5028&quot; data-ke-size=&quot;size16&quot;&gt;이번 글에서는 Blue-Green 배포를 &lt;b&gt;수동으로 실행하고 검증하는 과정&lt;/b&gt;을 다뤘다.&lt;/p&gt;
&lt;p data-end=&quot;5112&quot; data-start=&quot;5080&quot; data-ke-size=&quot;size16&quot;&gt;하지만 실제 운영 환경에서는 배포 과정을 자동화해야 한다.&lt;/p&gt;
&lt;p data-end=&quot;5138&quot; data-start=&quot;5114&quot; data-ke-size=&quot;size16&quot;&gt;다음 글에서는 다음 내용을 정리할 예정이다.&lt;/p&gt;
&lt;ul style=&quot;list-style-type: disc;&quot; data-end=&quot;5241&quot; data-start=&quot;5140&quot; data-ke-list-type=&quot;disc&quot;&gt;
&lt;li data-end=&quot;5175&quot; data-start=&quot;5140&quot; data-section-id=&quot;1mix2by&quot;&gt;Blue-Green 배포 스크립트 작성 (deploy.sh)&lt;/li&gt;
&lt;li data-end=&quot;5194&quot; data-start=&quot;5176&quot; data-section-id=&quot;1s2jdnz&quot;&gt;Health Check 자동화&lt;/li&gt;
&lt;li data-end=&quot;5212&quot; data-start=&quot;5195&quot; data-section-id=&quot;17susnq&quot;&gt;NGINX 트래픽 자동 전환&lt;/li&gt;
&lt;li data-end=&quot;5241&quot; data-start=&quot;5213&quot; data-section-id=&quot;1xx643o&quot;&gt;GitHub Actions 기반 자동 배포 구축&lt;/li&gt;
&lt;/ul&gt;
&lt;p data-end=&quot;5271&quot; data-start=&quot;5243&quot; data-ke-size=&quot;size16&quot;&gt;최종 목표는 다음과 같은 자동 배포 파이프라인이다.&lt;/p&gt;
&lt;div&gt;
&lt;div&gt;
&lt;div&gt;
&lt;div&gt;
&lt;div&gt;
&lt;div&gt;
&lt;div&gt;
&lt;div&gt;
&lt;div&gt;
&lt;div id=&quot;code-block-viewer&quot;&gt;
&lt;div&gt;
&lt;blockquote data-ke-style=&quot;style3&quot;&gt;GitHub Push&lt;br /&gt;&lt;span&gt;&amp;darr;&lt;/span&gt;&lt;br /&gt;&lt;span&gt;GitHub Actions&lt;/span&gt;&lt;br /&gt;&lt;span&gt;&amp;darr;&lt;/span&gt;&lt;br /&gt;&lt;span&gt;Docker Image Build&lt;/span&gt;&lt;br /&gt;&lt;span&gt;&amp;darr;&lt;/span&gt;&lt;br /&gt;&lt;span&gt;GHCR Push&lt;/span&gt;&lt;br /&gt;&lt;span&gt;&amp;darr;&lt;/span&gt;&lt;br /&gt;&lt;span&gt;Server Deploy Script&lt;/span&gt;&lt;br /&gt;&lt;span&gt;&amp;darr;&lt;/span&gt;&lt;br /&gt;&lt;span&gt;Blue-Green 배포&lt;/span&gt;&lt;br /&gt;&lt;span&gt;&amp;darr;&lt;/span&gt;&lt;br /&gt;&lt;span&gt;NGINX 자동 전환&lt;/span&gt;&lt;/blockquote&gt;
&lt;/div&gt;
&lt;/div&gt;
&lt;/div&gt;
&lt;/div&gt;
&lt;/div&gt;
&lt;/div&gt;
&lt;/div&gt;
&lt;/div&gt;
&lt;/div&gt;
&lt;/div&gt;
&lt;/div&gt;
&lt;hr contenteditable=&quot;false&quot; data-ke-type=&quot;horizontalRule&quot; data-ke-style=&quot;style5&quot; /&gt;</description>
      <category>  프로젝트/KBO-NOTE</category>
      <author>SoloQuest</author>
      <guid isPermaLink="true">https://1devlog.tistory.com/19</guid>
      <comments>https://1devlog.tistory.com/19#entry19comment</comments>
      <pubDate>Sun, 15 Mar 2026 01:29:48 +0900</pubDate>
    </item>
    <item>
      <title>Docker + Nginx 기반 Blue-Green 배포 구축 (1) &amp;mdash; 서버 구조 정리와 배포 전략 설계</title>
      <link>https://1devlog.tistory.com/18</link>
      <description>&lt;h2 data-end=&quot;242&quot; data-start=&quot;235&quot; data-section-id=&quot;1fudykl&quot; data-ke-size=&quot;size26&quot;&gt;들어가며&lt;/h2&gt;
&lt;p data-end=&quot;358&quot; data-start=&quot;244&quot; data-ke-size=&quot;size16&quot;&gt;현재 진행 중인 프로젝트는 &lt;b&gt;MSA(Microservice Architecture)&lt;/b&gt; 기반으로 구성되어 있고,&lt;br /&gt;각 서비스는 &lt;b&gt;서비스별 GitHub Repository로 분리&lt;/b&gt;되어 관리되고 있다.&lt;/p&gt;
&lt;p data-end=&quot;433&quot; data-start=&quot;360&quot; data-ke-size=&quot;size16&quot;&gt;나는 이 중 &lt;b&gt;Feed Server&lt;/b&gt;를 담당하고 있으며, 초기 개발 단계에서 &lt;b&gt;Docker 컨테이너 환경도 직접 구성&lt;/b&gt;했다.&lt;/p&gt;
&lt;p data-end=&quot;511&quot; data-start=&quot;435&quot; data-ke-size=&quot;size16&quot;&gt;이번에 새로 진행하려는 작업은 &lt;b&gt;Feed Server에 Blue-Green 배포 전략을 적용하고 자동 배포 환경을 구축하는 것&lt;/b&gt;이다.&lt;/p&gt;
&lt;p data-end=&quot;540&quot; data-start=&quot;513&quot; data-ke-size=&quot;size16&quot;&gt;최종적으로 목표로 하는 배포 구조는 다음과 같다.&lt;/p&gt;
&lt;div&gt;
&lt;div&gt;
&lt;div&gt;
&lt;div&gt;
&lt;div&gt;
&lt;div&gt;
&lt;div&gt;
&lt;div&gt;
&lt;div&gt;
&lt;div id=&quot;code-block-viewer&quot;&gt;
&lt;div&gt;
&lt;blockquote data-ke-style=&quot;style3&quot;&gt;GitHub Push&lt;br /&gt;&lt;span&gt; &amp;darr;&lt;/span&gt;&lt;br /&gt;&lt;span&gt;GitHub Actions&lt;/span&gt;&lt;br /&gt;&lt;span&gt; &amp;darr;&lt;/span&gt;&lt;br /&gt;&lt;span&gt;Docker Image Build&lt;/span&gt;&lt;br /&gt;&lt;span&gt; &amp;darr;&lt;/span&gt;&lt;br /&gt;&lt;span&gt;Container Registry Push&lt;/span&gt;&lt;br /&gt;&lt;span&gt; &amp;darr;&lt;/span&gt;&lt;br /&gt;&lt;span&gt;Server Pull&lt;/span&gt;&lt;br /&gt;&lt;span&gt; &amp;darr;&lt;/span&gt;&lt;br /&gt;&lt;span&gt;Blue-Green 배포&lt;/span&gt;&lt;br /&gt;&lt;span&gt; &amp;darr;&lt;/span&gt;&lt;br /&gt;&lt;span&gt;NGINX 트래픽 전환&lt;/span&gt;&lt;/blockquote&gt;
&lt;/div&gt;
&lt;/div&gt;
&lt;/div&gt;
&lt;/div&gt;
&lt;/div&gt;
&lt;/div&gt;
&lt;/div&gt;
&lt;/div&gt;
&lt;div&gt;&amp;nbsp;&lt;/div&gt;
&lt;/div&gt;
&lt;/div&gt;
&lt;/div&gt;
&lt;p data-end=&quot;746&quot; data-start=&quot;694&quot; data-ke-size=&quot;size16&quot;&gt;하지만 자동 배포를 구축하기 전에 &lt;b&gt;현재 서버 구조를 먼저 정확히 정리할 필요가 있었다.&lt;/b&gt;&lt;/p&gt;
&lt;p data-end=&quot;835&quot; data-start=&quot;748&quot; data-ke-size=&quot;size16&quot;&gt;이번 글에서는 &lt;b&gt;현재 Feed Server가 어떤 구조로 운영되고 있는지 정리하고,&lt;br /&gt;Blue-Green 배포 전략을 설계한 과정&lt;/b&gt;을 기록하려고 한다.&lt;/p&gt;
&lt;hr contenteditable=&quot;false&quot; data-ke-type=&quot;horizontalRule&quot; data-ke-style=&quot;style5&quot; /&gt;
&lt;h1 data-end=&quot;852&quot; data-start=&quot;842&quot; data-section-id=&quot;1if262b&quot;&gt;현재 서버 환경&lt;/h1&gt;
&lt;p data-end=&quot;887&quot; data-start=&quot;854&quot; data-ke-size=&quot;size16&quot;&gt;먼저 서버에 접속해서 현재 실행 중인 컨테이너들을 확인했다.&lt;/p&gt;
&lt;div&gt;
&lt;div&gt;
&lt;div&gt;
&lt;div&gt;
&lt;div&gt;
&lt;div&gt;
&lt;div&gt;
&lt;div&gt;
&lt;div&gt;
&lt;div id=&quot;code-block-viewer&quot;&gt;
&lt;div&gt;
&lt;blockquote data-ke-style=&quot;style3&quot;&gt;docker &lt;span&gt;ps&lt;/span&gt;&lt;/blockquote&gt;
&lt;/div&gt;
&lt;/div&gt;
&lt;/div&gt;
&lt;/div&gt;
&lt;/div&gt;
&lt;/div&gt;
&lt;/div&gt;
&lt;/div&gt;
&lt;div&gt;&amp;nbsp;&lt;/div&gt;
&lt;/div&gt;
&lt;/div&gt;
&lt;/div&gt;
&lt;p data-end=&quot;960&quot; data-start=&quot;912&quot; data-ke-size=&quot;size16&quot;&gt;현재 서버는 여러 서비스가 동시에 실행되는 &lt;b&gt;Docker 기반 MSA 환경&lt;/b&gt;이었다.&lt;/p&gt;
&lt;p data-end=&quot;1036&quot; data-start=&quot;962&quot; data-ke-size=&quot;size16&quot;&gt;여러 서비스 컨테이너가 실행 중이었고,&lt;br /&gt;내가 담당하는 &lt;b&gt;Feed Server도 Docker 컨테이너 형태로 실행되고 있었다.&lt;/b&gt;&lt;/p&gt;
&lt;p data-end=&quot;1094&quot; data-start=&quot;1038&quot; data-ke-size=&quot;size16&quot;&gt;Feed Server 컨테이너 내부에서는 &lt;b&gt;Spring Boot 애플리케이션이 실행되는 구조&lt;/b&gt;다.&lt;/p&gt;
&lt;p data-end=&quot;1112&quot; data-start=&quot;1096&quot; data-ke-size=&quot;size16&quot;&gt;즉 전체 구조는 다음과 같다.&lt;/p&gt;
&lt;div&gt;
&lt;div&gt;
&lt;div&gt;
&lt;div&gt;
&lt;div&gt;
&lt;div&gt;
&lt;div&gt;
&lt;div&gt;
&lt;div&gt;
&lt;div id=&quot;code-block-viewer&quot;&gt;
&lt;div&gt;
&lt;blockquote data-ke-style=&quot;style3&quot;&gt;Docker Container&lt;br /&gt;&lt;span&gt; &amp;darr;&lt;/span&gt;&lt;br /&gt;&lt;span&gt;Spring Boot Application&lt;/span&gt;&lt;br /&gt;&lt;span&gt; &amp;darr;&lt;/span&gt;&lt;br /&gt;&lt;span&gt;Feed Service&lt;/span&gt;&lt;/blockquote&gt;
&lt;/div&gt;
&lt;/div&gt;
&lt;/div&gt;
&lt;/div&gt;
&lt;/div&gt;
&lt;/div&gt;
&lt;/div&gt;
&lt;/div&gt;
&lt;div&gt;&amp;nbsp;&lt;/div&gt;
&lt;/div&gt;
&lt;/div&gt;
&lt;/div&gt;
&lt;p data-end=&quot;1263&quot; data-start=&quot;1197&quot; data-ke-size=&quot;size16&quot;&gt;Feed Server는 처음 개발할 때 &lt;b&gt;Docker 이미지로 빌드하고 컨테이너 형태로 실행되도록 구성&lt;/b&gt;해 두었다.&lt;/p&gt;
&lt;hr contenteditable=&quot;false&quot; data-ke-type=&quot;horizontalRule&quot; data-ke-style=&quot;style5&quot; /&gt;
&lt;h1 data-end=&quot;1291&quot; data-start=&quot;1270&quot; data-section-id=&quot;184sj7l&quot;&gt;Feed Server 네트워크 구조&lt;/h1&gt;
&lt;p data-end=&quot;1317&quot; data-start=&quot;1293&quot; data-ke-size=&quot;size16&quot;&gt;다음으로 컨테이너 네트워크 설정을 확인했다.&lt;/p&gt;
&lt;p data-end=&quot;1381&quot; data-start=&quot;1319&quot; data-ke-size=&quot;size16&quot;&gt;확인해 보니 Feed Server 컨테이너는 &lt;b&gt;Docker host network 모드&lt;/b&gt;로 실행되고 있었다.&lt;/p&gt;
&lt;p data-end=&quot;1411&quot; data-start=&quot;1383&quot; data-ke-size=&quot;size16&quot;&gt;host network 모드의 특징은 다음과 같다.&lt;/p&gt;
&lt;div&gt;
&lt;div&gt;
&lt;div&gt;
&lt;div&gt;
&lt;div&gt;
&lt;div&gt;
&lt;div&gt;
&lt;div&gt;
&lt;div&gt;
&lt;div id=&quot;code-block-viewer&quot;&gt;
&lt;div&gt;
&lt;blockquote data-ke-style=&quot;style3&quot;&gt;컨테이너 포트 = 서버 포트&lt;/blockquote&gt;
&lt;/div&gt;
&lt;/div&gt;
&lt;/div&gt;
&lt;/div&gt;
&lt;/div&gt;
&lt;/div&gt;
&lt;/div&gt;
&lt;/div&gt;
&lt;div&gt;&amp;nbsp;&lt;/div&gt;
&lt;/div&gt;
&lt;/div&gt;
&lt;/div&gt;
&lt;p data-end=&quot;1479&quot; data-start=&quot;1442&quot; data-ke-size=&quot;size16&quot;&gt;일반적인 Docker 실행 방식에서는 다음과 같이 포트를 매핑한다.&lt;/p&gt;
&lt;div&gt;
&lt;div&gt;
&lt;div&gt;
&lt;div&gt;
&lt;div&gt;
&lt;div&gt;
&lt;div&gt;
&lt;div&gt;
&lt;div&gt;
&lt;div id=&quot;code-block-viewer&quot;&gt;
&lt;div&gt;
&lt;blockquote data-ke-style=&quot;style3&quot;&gt;hostPort:containerPort&lt;/blockquote&gt;
&lt;/div&gt;
&lt;/div&gt;
&lt;/div&gt;
&lt;/div&gt;
&lt;/div&gt;
&lt;/div&gt;
&lt;/div&gt;
&lt;/div&gt;
&lt;div&gt;&amp;nbsp;&lt;/div&gt;
&lt;/div&gt;
&lt;/div&gt;
&lt;/div&gt;
&lt;p data-end=&quot;1591&quot; data-start=&quot;1517&quot; data-ke-size=&quot;size16&quot;&gt;하지만 host network 모드에서는 컨테이너가 &lt;b&gt;서버 네트워크를 그대로 사용하기 때문에 포트 매핑이 따로 존재하지 않는다.&lt;/b&gt;&lt;/p&gt;
&lt;p data-end=&quot;1662&quot; data-start=&quot;1593&quot; data-ke-size=&quot;size16&quot;&gt;이 구조는 설정이 단순하다는 장점이 있지만, &lt;b&gt;여러 서비스가 동시에 실행되는 환경에서는 포트 충돌에 주의해야 한다.&lt;/b&gt;&lt;/p&gt;
&lt;hr contenteditable=&quot;false&quot; data-ke-type=&quot;horizontalRule&quot; data-ke-style=&quot;style5&quot; /&gt;
&lt;h1 data-end=&quot;1686&quot; data-start=&quot;1669&quot; data-section-id=&quot;vtjxmc&quot;&gt;NGINX 트래픽 흐름 확인&lt;/h1&gt;
&lt;p data-end=&quot;1728&quot; data-start=&quot;1688&quot; data-ke-size=&quot;size16&quot;&gt;다음으로 외부 요청이 Feed Server로 어떻게 전달되는지 확인했다.&lt;/p&gt;
&lt;p data-end=&quot;1774&quot; data-start=&quot;1730&quot; data-ke-size=&quot;size16&quot;&gt;현재 서버에서는 &lt;b&gt;NGINX가 Reverse Proxy 역할을 하고 있다.&lt;/b&gt;&lt;/p&gt;
&lt;p data-end=&quot;1787&quot; data-start=&quot;1776&quot; data-ke-size=&quot;size16&quot;&gt;구조는 다음과 같다.&lt;/p&gt;
&lt;div&gt;
&lt;div&gt;
&lt;div&gt;
&lt;div&gt;
&lt;div&gt;
&lt;div&gt;
&lt;div&gt;
&lt;div&gt;
&lt;div&gt;
&lt;div id=&quot;code-block-viewer&quot;&gt;
&lt;div&gt;
&lt;blockquote data-ke-style=&quot;style3&quot;&gt;Client&lt;br /&gt;&lt;span&gt; &amp;darr;&lt;/span&gt;&lt;br /&gt;&lt;span&gt;API Gateway&lt;/span&gt;&lt;br /&gt;&lt;span&gt; &amp;darr;&lt;/span&gt;&lt;br /&gt;&lt;span&gt;NGINX&lt;/span&gt;&lt;br /&gt;&lt;span&gt; &amp;darr;&lt;/span&gt;&lt;br /&gt;&lt;span&gt;Feed Server&lt;/span&gt;&lt;/blockquote&gt;
&lt;/div&gt;
&lt;/div&gt;
&lt;/div&gt;
&lt;/div&gt;
&lt;/div&gt;
&lt;/div&gt;
&lt;/div&gt;
&lt;/div&gt;
&lt;div&gt;&amp;nbsp;&lt;/div&gt;
&lt;/div&gt;
&lt;/div&gt;
&lt;/div&gt;
&lt;p data-end=&quot;1883&quot; data-start=&quot;1854&quot; data-ke-size=&quot;size16&quot;&gt;NGINX 설정을 확인해 보니 다음과 같은 구조였다.&lt;/p&gt;
&lt;div&gt;
&lt;div&gt;
&lt;div&gt;
&lt;div&gt;
&lt;div&gt;
&lt;div&gt;
&lt;div&gt;
&lt;div&gt;
&lt;div&gt;
&lt;div id=&quot;code-block-viewer&quot;&gt;
&lt;div&gt;
&lt;blockquote data-ke-style=&quot;style3&quot;&gt;listen 9000&lt;br /&gt;&lt;span&gt;proxy_pass &amp;rarr; 127.0.0.1:8081&lt;/span&gt;&lt;/blockquote&gt;
&lt;/div&gt;
&lt;/div&gt;
&lt;/div&gt;
&lt;/div&gt;
&lt;/div&gt;
&lt;/div&gt;
&lt;/div&gt;
&lt;/div&gt;
&lt;div&gt;&amp;nbsp;&lt;/div&gt;
&lt;/div&gt;
&lt;/div&gt;
&lt;/div&gt;
&lt;p data-end=&quot;1958&quot; data-start=&quot;1938&quot; data-ke-size=&quot;size16&quot;&gt;즉 현재 트래픽 흐름은 다음과 같다.&lt;/p&gt;
&lt;div&gt;
&lt;div&gt;
&lt;div&gt;
&lt;div&gt;
&lt;div&gt;
&lt;div&gt;
&lt;div&gt;
&lt;div&gt;
&lt;div&gt;
&lt;div id=&quot;code-block-viewer&quot;&gt;
&lt;div&gt;
&lt;blockquote data-ke-style=&quot;style3&quot;&gt;API Gateway&lt;br /&gt;&lt;span&gt; &amp;darr;&lt;/span&gt;&lt;br /&gt;&lt;span&gt;NGINX (9000)&lt;/span&gt;&lt;br /&gt;&lt;span&gt; &amp;darr;&lt;/span&gt;&lt;br /&gt;&lt;span&gt;Feed Server (8081)&lt;/span&gt;&lt;/blockquote&gt;
&lt;/div&gt;
&lt;/div&gt;
&lt;/div&gt;
&lt;/div&gt;
&lt;/div&gt;
&lt;/div&gt;
&lt;/div&gt;
&lt;/div&gt;
&lt;div&gt;&amp;nbsp;&lt;/div&gt;
&lt;/div&gt;
&lt;/div&gt;
&lt;/div&gt;
&lt;p data-end=&quot;2059&quot; data-start=&quot;2033&quot; data-ke-size=&quot;size16&quot;&gt;실제로 연결이 정상적으로 동작하는지도 확인했다.&lt;/p&gt;
&lt;div&gt;
&lt;div&gt;
&lt;div&gt;
&lt;div&gt;
&lt;div&gt;
&lt;div&gt;
&lt;div&gt;
&lt;div&gt;
&lt;div&gt;
&lt;div id=&quot;code-block-viewer&quot;&gt;
&lt;div&gt;
&lt;blockquote data-ke-style=&quot;style3&quot;&gt;curl&lt;span&gt; localhost:9000&lt;/span&gt;&lt;br /&gt;&lt;span&gt;curl&lt;/span&gt;&lt;span&gt; localhost:8081&lt;/span&gt;&lt;/blockquote&gt;
&lt;/div&gt;
&lt;/div&gt;
&lt;/div&gt;
&lt;/div&gt;
&lt;/div&gt;
&lt;/div&gt;
&lt;/div&gt;
&lt;/div&gt;
&lt;div&gt;&amp;nbsp;&lt;/div&gt;
&lt;/div&gt;
&lt;/div&gt;
&lt;/div&gt;
&lt;p data-end=&quot;2187&quot; data-start=&quot;2114&quot; data-ke-size=&quot;size16&quot;&gt;Spring Boot 애플리케이션의 루트 경로가 정의되어 있지 않아 &lt;b&gt;404 응답이 반환&lt;/b&gt;되었지만, 이는 정상적인 동작이다.&lt;/p&gt;
&lt;p data-end=&quot;2236&quot; data-start=&quot;2189&quot; data-ke-size=&quot;size16&quot;&gt;즉 &lt;b&gt;NGINX &amp;rarr; Feed Server 연결 구조는 문제없이 동작하고 있었다.&lt;/b&gt;&lt;/p&gt;
&lt;hr contenteditable=&quot;false&quot; data-ke-type=&quot;horizontalRule&quot; data-ke-style=&quot;style5&quot; /&gt;
&lt;h1 data-end=&quot;2264&quot; data-start=&quot;2243&quot; data-section-id=&quot;udniwz&quot;&gt;Blue-Green 배포 전략 설계&lt;/h1&gt;
&lt;p data-end=&quot;2305&quot; data-start=&quot;2266&quot; data-ke-size=&quot;size16&quot;&gt;현재 구조를 기반으로 &lt;b&gt;Blue-Green 배포 전략을 설계했다.&lt;/b&gt;&lt;/p&gt;
&lt;p data-end=&quot;2363&quot; data-start=&quot;2307&quot; data-ke-size=&quot;size16&quot;&gt;Blue-Green 배포의 핵심은 &lt;b&gt;두 개의 동일한 환경을 준비한 뒤 트래픽을 전환하는 것&lt;/b&gt;이다.&lt;/p&gt;
&lt;p data-end=&quot;2399&quot; data-start=&quot;2365&quot; data-ke-size=&quot;size16&quot;&gt;이번 프로젝트에서는 다음과 같은 포트 전략을 사용하기로 했다.&lt;/p&gt;
&lt;table style=&quot;border-collapse: collapse; width: 22.2081%;&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: 21.1784%; text-align: center;&quot;&gt;환경&lt;/td&gt;
&lt;td style=&quot;width: 27.3555%; text-align: center;&quot;&gt;포트&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td style=&quot;width: 21.1784%; text-align: center;&quot;&gt;Blue&lt;/td&gt;
&lt;td style=&quot;width: 27.3555%; text-align: center;&quot;&gt;8081&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td style=&quot;width: 21.1784%; text-align: center;&quot;&gt;Green&lt;/td&gt;
&lt;td style=&quot;width: 27.3555%; text-align: center;&quot;&gt;8082&lt;/td&gt;
&lt;/tr&gt;
&lt;/tbody&gt;
&lt;/table&gt;
&lt;p data-end=&quot;2476&quot; data-start=&quot;2462&quot; data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-end=&quot;2476&quot; data-start=&quot;2462&quot; data-ke-size=&quot;size16&quot;&gt;현재 상태는 다음과 같다.&lt;/p&gt;
&lt;div&gt;
&lt;div&gt;
&lt;div&gt;
&lt;div&gt;
&lt;div&gt;
&lt;div&gt;
&lt;div&gt;
&lt;div&gt;
&lt;div&gt;
&lt;div id=&quot;code-block-viewer&quot;&gt;
&lt;div&gt;
&lt;blockquote data-ke-style=&quot;style3&quot;&gt;NGINX &amp;rarr; 8081&lt;/blockquote&gt;
&lt;/div&gt;
&lt;/div&gt;
&lt;/div&gt;
&lt;/div&gt;
&lt;/div&gt;
&lt;/div&gt;
&lt;/div&gt;
&lt;/div&gt;
&lt;div&gt;&amp;nbsp;&lt;/div&gt;
&lt;/div&gt;
&lt;/div&gt;
&lt;/div&gt;
&lt;p data-end=&quot;2548&quot; data-start=&quot;2504&quot; data-ke-size=&quot;size16&quot;&gt;즉 현재 Feed Server는 &lt;b&gt;Blue 환경으로 실행되고 있는 상태&lt;/b&gt;다.&lt;/p&gt;
&lt;p data-end=&quot;2589&quot; data-start=&quot;2550&quot; data-ke-size=&quot;size16&quot;&gt;Blue-Green 배포 시에는 다음과 같은 흐름으로 배포가 진행된다.&lt;/p&gt;
&lt;div&gt;
&lt;div&gt;
&lt;div&gt;
&lt;div&gt;
&lt;div&gt;
&lt;div&gt;
&lt;div&gt;
&lt;div&gt;
&lt;div&gt;
&lt;div id=&quot;code-block-viewer&quot;&gt;
&lt;div&gt;
&lt;blockquote data-ke-style=&quot;style3&quot;&gt;1. 새로운 버전을 Green 환경(8082)에 실행&lt;br /&gt;&lt;span&gt;2. Health Check 수행&lt;/span&gt;&lt;br /&gt;&lt;span&gt;3. NGINX 트래픽을 8082로 전환&lt;/span&gt;&lt;br /&gt;&lt;span&gt;4. 기존 Blue 환경 종료&lt;/span&gt;&lt;/blockquote&gt;
&lt;/div&gt;
&lt;/div&gt;
&lt;/div&gt;
&lt;/div&gt;
&lt;/div&gt;
&lt;/div&gt;
&lt;/div&gt;
&lt;/div&gt;
&lt;div&gt;&amp;nbsp;&lt;/div&gt;
&lt;/div&gt;
&lt;/div&gt;
&lt;/div&gt;
&lt;p data-end=&quot;2730&quot; data-start=&quot;2693&quot; data-ke-size=&quot;size16&quot;&gt;이 방식의 가장 큰 장점은 &lt;b&gt;무중단 배포가 가능하다는 것&lt;/b&gt;이다.&lt;/p&gt;
&lt;hr contenteditable=&quot;false&quot; data-ke-type=&quot;horizontalRule&quot; data-ke-style=&quot;style5&quot; /&gt;
&lt;h1 data-end=&quot;2752&quot; data-start=&quot;2737&quot; data-section-id=&quot;53hm6u&quot;&gt;컨테이너 구조 변경 계획&lt;/h1&gt;
&lt;p data-end=&quot;2795&quot; data-start=&quot;2754&quot; data-ke-size=&quot;size16&quot;&gt;기존에는 Feed Server 컨테이너가 하나의 이름으로 실행되고 있었다.&lt;/p&gt;
&lt;p data-end=&quot;2853&quot; data-start=&quot;2797&quot; data-ke-size=&quot;size16&quot;&gt;하지만 Blue-Green 배포를 위해 다음과 같이 &lt;b&gt;두 개의 컨테이너 구조로 변경&lt;/b&gt;할 계획이다.&lt;/p&gt;
&lt;div&gt;
&lt;div&gt;
&lt;div&gt;
&lt;div&gt;
&lt;div&gt;
&lt;div&gt;
&lt;div&gt;
&lt;div&gt;
&lt;div&gt;
&lt;div id=&quot;code-block-viewer&quot;&gt;
&lt;div&gt;
&lt;blockquote data-ke-style=&quot;style3&quot;&gt;feed-blue&lt;br /&gt;&lt;span&gt;feed-green&lt;/span&gt;&lt;/blockquote&gt;
&lt;/div&gt;
&lt;/div&gt;
&lt;/div&gt;
&lt;/div&gt;
&lt;/div&gt;
&lt;/div&gt;
&lt;/div&gt;
&lt;/div&gt;
&lt;div&gt;&amp;nbsp;&lt;/div&gt;
&lt;/div&gt;
&lt;/div&gt;
&lt;/div&gt;
&lt;p data-end=&quot;2906&quot; data-start=&quot;2889&quot; data-ke-size=&quot;size16&quot;&gt;예를 들어 다음과 같은 구조다.&lt;/p&gt;
&lt;div&gt;
&lt;div&gt;
&lt;div&gt;
&lt;div&gt;
&lt;div&gt;
&lt;div&gt;
&lt;div&gt;
&lt;div&gt;
&lt;div&gt;
&lt;div id=&quot;code-block-viewer&quot;&gt;
&lt;div&gt;
&lt;blockquote data-ke-style=&quot;style3&quot;&gt;feed-blue &amp;rarr; 8081&lt;br /&gt;&lt;span&gt;feed-green &amp;rarr; 8082&lt;/span&gt;&lt;/blockquote&gt;
&lt;/div&gt;
&lt;/div&gt;
&lt;/div&gt;
&lt;/div&gt;
&lt;/div&gt;
&lt;/div&gt;
&lt;/div&gt;
&lt;/div&gt;
&lt;div&gt;&amp;nbsp;&lt;/div&gt;
&lt;/div&gt;
&lt;/div&gt;
&lt;/div&gt;
&lt;p data-end=&quot;3013&quot; data-start=&quot;2957&quot; data-ke-size=&quot;size16&quot;&gt;이렇게 구성하면 두 버전을 동시에 실행할 수 있고&lt;br /&gt;&lt;b&gt;트래픽을 원하는 시점에 전환할 수 있다.&lt;/b&gt;&lt;/p&gt;
&lt;hr contenteditable=&quot;false&quot; data-ke-type=&quot;horizontalRule&quot; data-ke-style=&quot;style5&quot; /&gt;
&lt;h1 data-end=&quot;3038&quot; data-start=&quot;3020&quot; data-section-id=&quot;1pdxe4b&quot;&gt;컨테이너 실행 중 발생한 문제&lt;/h1&gt;
&lt;p data-end=&quot;3076&quot; data-start=&quot;3040&quot; data-ke-size=&quot;size16&quot;&gt;Blue 환경을 테스트하는 과정에서 다음과 같은 문제가 발생했다.&lt;/p&gt;
&lt;div&gt;
&lt;div&gt;
&lt;div&gt;
&lt;div&gt;
&lt;div&gt;
&lt;div&gt;
&lt;div&gt;
&lt;div&gt;
&lt;div&gt;
&lt;div id=&quot;code-block-viewer&quot;&gt;
&lt;div&gt;
&lt;blockquote data-ke-style=&quot;style3&quot;&gt;Web server failed to start. Port 8080 was already in use.&lt;/blockquote&gt;
&lt;/div&gt;
&lt;/div&gt;
&lt;/div&gt;
&lt;/div&gt;
&lt;/div&gt;
&lt;/div&gt;
&lt;/div&gt;
&lt;/div&gt;
&lt;div&gt;&amp;nbsp;&lt;/div&gt;
&lt;/div&gt;
&lt;/div&gt;
&lt;/div&gt;
&lt;p data-end=&quot;3216&quot; data-start=&quot;3149&quot; data-ke-size=&quot;size16&quot;&gt;Spring Boot 기본 포트는 &lt;b&gt;8080&lt;/b&gt;인데&lt;br /&gt;이미 서버에서 해당 포트를 사용하는 서비스가 존재했기 때문이다.&lt;/p&gt;
&lt;p data-end=&quot;3216&quot; data-start=&quot;3149&quot; data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-end=&quot;3305&quot; data-start=&quot;3218&quot; data-ke-size=&quot;size16&quot;&gt;또한 Feed Server 컨테이너는 &lt;b&gt;host network 모드&lt;/b&gt;를 사용하고 있기 때문에&lt;br /&gt;컨테이너 내부 포트 역시 서버 포트와 동일하게 사용된다.&lt;/p&gt;
&lt;p data-end=&quot;3305&quot; data-start=&quot;3218&quot; data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-end=&quot;3353&quot; data-start=&quot;3307&quot; data-ke-size=&quot;size16&quot;&gt;즉 포트를 명시적으로 지정하지 않으면 &lt;b&gt;포트 충돌이 발생할 수 있는 구조였다.&lt;/b&gt;&lt;/p&gt;
&lt;hr contenteditable=&quot;false&quot; data-ke-type=&quot;horizontalRule&quot; data-ke-style=&quot;style5&quot; /&gt;
&lt;h1 data-end=&quot;3367&quot; data-start=&quot;3360&quot; data-section-id=&quot;njn1jy&quot;&gt;해결 방법&lt;/h1&gt;
&lt;p data-end=&quot;3414&quot; data-start=&quot;3369&quot; data-ke-size=&quot;size16&quot;&gt;이 문제는 &lt;b&gt;Spring Boot 실행 포트를 명시적으로 지정&lt;/b&gt;하여 해결했다.&lt;/p&gt;
&lt;p data-end=&quot;3423&quot; data-start=&quot;3416&quot; data-ke-size=&quot;size16&quot;&gt;Blue 환경&lt;/p&gt;
&lt;div&gt;
&lt;div&gt;
&lt;div&gt;
&lt;div&gt;
&lt;div&gt;
&lt;div&gt;
&lt;div&gt;
&lt;div&gt;
&lt;div&gt;
&lt;div id=&quot;code-block-viewer&quot;&gt;
&lt;div&gt;
&lt;blockquote data-ke-style=&quot;style3&quot;&gt;SERVER_PORT=8081&lt;/blockquote&gt;
&lt;/div&gt;
&lt;/div&gt;
&lt;/div&gt;
&lt;/div&gt;
&lt;/div&gt;
&lt;/div&gt;
&lt;/div&gt;
&lt;/div&gt;
&lt;div&gt;&amp;nbsp;&lt;/div&gt;
&lt;/div&gt;
&lt;/div&gt;
&lt;/div&gt;
&lt;p data-end=&quot;3463&quot; data-start=&quot;3455&quot; data-ke-size=&quot;size16&quot;&gt;Green 환경&lt;/p&gt;
&lt;div&gt;
&lt;div&gt;
&lt;div&gt;
&lt;div&gt;
&lt;div&gt;
&lt;div&gt;
&lt;div&gt;
&lt;div&gt;
&lt;div&gt;
&lt;div id=&quot;code-block-viewer&quot;&gt;
&lt;div&gt;
&lt;blockquote data-ke-style=&quot;style3&quot;&gt;SERVER_PORT=8082&lt;/blockquote&gt;
&lt;/div&gt;
&lt;/div&gt;
&lt;/div&gt;
&lt;/div&gt;
&lt;/div&gt;
&lt;/div&gt;
&lt;/div&gt;
&lt;/div&gt;
&lt;div&gt;&amp;nbsp;&lt;/div&gt;
&lt;/div&gt;
&lt;/div&gt;
&lt;/div&gt;
&lt;p data-end=&quot;3549&quot; data-start=&quot;3495&quot; data-ke-size=&quot;size16&quot;&gt;이렇게 실행하면 두 환경이 서로 다른 포트를 사용하게 되어&lt;br /&gt;충돌 없이 동시에 실행할 수 있다.&lt;/p&gt;
&lt;hr contenteditable=&quot;false&quot; data-ke-type=&quot;horizontalRule&quot; data-ke-style=&quot;style5&quot; /&gt;
&lt;h1 data-end=&quot;3568&quot; data-start=&quot;3556&quot; data-section-id=&quot;uxkxwn&quot;&gt;현재까지 진행 상황&lt;/h1&gt;
&lt;p data-end=&quot;3595&quot; data-start=&quot;3570&quot; data-ke-size=&quot;size16&quot;&gt;지금까지 진행한 작업을 정리하면 다음과 같다.&lt;/p&gt;
&lt;div&gt;
&lt;div&gt;
&lt;div&gt;
&lt;div&gt;
&lt;div&gt;
&lt;div&gt;
&lt;div&gt;
&lt;div&gt;
&lt;div&gt;
&lt;div id=&quot;code-block-viewer&quot;&gt;
&lt;div&gt;
&lt;blockquote data-ke-style=&quot;style3&quot;&gt;✔ Feed Server Docker 환경 확인&lt;br /&gt;&lt;span&gt;✔ 서버 네트워크 구조 분석&lt;/span&gt;&lt;br /&gt;&lt;span&gt;✔ NGINX Reverse Proxy 구조 확인&lt;/span&gt;&lt;br /&gt;&lt;span&gt;✔ Blue-Green 포트 전략 설계&lt;/span&gt;&lt;br /&gt;&lt;span&gt;✔ 컨테이너 구조 변경 계획&lt;/span&gt;&lt;br /&gt;&lt;span&gt;✔ 포트 충돌 문제 해결&lt;/span&gt;&lt;/blockquote&gt;
&lt;/div&gt;
&lt;/div&gt;
&lt;/div&gt;
&lt;/div&gt;
&lt;/div&gt;
&lt;/div&gt;
&lt;/div&gt;
&lt;/div&gt;
&lt;div&gt;&amp;nbsp;&lt;/div&gt;
&lt;/div&gt;
&lt;/div&gt;
&lt;/div&gt;
&lt;hr contenteditable=&quot;false&quot; data-ke-type=&quot;horizontalRule&quot; data-ke-style=&quot;style5&quot; /&gt;
&lt;h1 data-end=&quot;3752&quot; data-start=&quot;3738&quot; data-section-id=&quot;1aq0syz&quot;&gt;다음 글에서 다룰 내용&lt;/h1&gt;
&lt;p data-end=&quot;3805&quot; data-start=&quot;3754&quot; data-ke-size=&quot;size16&quot;&gt;다음 글에서는 &lt;b&gt;실제 Blue-Green 자동 배포 환경 구축 과정&lt;/b&gt;을 정리할 예정이다.&lt;/p&gt;
&lt;p data-end=&quot;3821&quot; data-start=&quot;3807&quot; data-ke-size=&quot;size16&quot;&gt;다룰 내용은 다음과 같다.&lt;/p&gt;
&lt;ul style=&quot;list-style-type: disc;&quot; data-end=&quot;3933&quot; data-start=&quot;3823&quot; data-ke-list-type=&quot;disc&quot;&gt;
&lt;li data-end=&quot;3838&quot; data-start=&quot;3823&quot; data-section-id=&quot;1b6tyws&quot;&gt;Docker 이미지 관리&lt;/li&gt;
&lt;li data-end=&quot;3862&quot; data-start=&quot;3839&quot; data-section-id=&quot;9135y7&quot;&gt;Container Registry 사용&lt;/li&gt;
&lt;li data-end=&quot;3888&quot; data-start=&quot;3863&quot; data-section-id=&quot;exi0uz&quot;&gt;GitHub Actions CI/CD 구축&lt;/li&gt;
&lt;li data-end=&quot;3915&quot; data-start=&quot;3889&quot; data-section-id=&quot;5zhb5k&quot;&gt;Blue-Green 자동 배포 스크립트 작성&lt;/li&gt;
&lt;li data-end=&quot;3933&quot; data-start=&quot;3916&quot; data-section-id=&quot;17susnq&quot;&gt;NGINX 트래픽 자동 전환&lt;/li&gt;
&lt;/ul&gt;
&lt;hr contenteditable=&quot;false&quot; data-ke-type=&quot;horizontalRule&quot; data-ke-style=&quot;style5&quot; /&gt;
&lt;h1 data-end=&quot;3945&quot; data-start=&quot;3940&quot; data-section-id=&quot;20h5rq&quot;&gt;마무리&lt;/h1&gt;
&lt;p data-end=&quot;4005&quot; data-start=&quot;3947&quot; data-ke-size=&quot;size16&quot;&gt;Blue-Green 배포를 구축하기 전에 &lt;b&gt;현재 서버 구조를 정확히 이해하는 과정이 매우 중요했다.&lt;/b&gt;&lt;/p&gt;
&lt;p data-end=&quot;4018&quot; data-start=&quot;4007&quot; data-ke-size=&quot;size16&quot;&gt;특히 이번 환경에서는&lt;/p&gt;
&lt;ul style=&quot;list-style-type: disc;&quot; data-end=&quot;4089&quot; data-start=&quot;4020&quot; data-ke-list-type=&quot;disc&quot;&gt;
&lt;li data-end=&quot;4041&quot; data-start=&quot;4020&quot; data-section-id=&quot;3ay1fm&quot;&gt;Docker host network&lt;/li&gt;
&lt;li data-end=&quot;4063&quot; data-start=&quot;4042&quot; data-section-id=&quot;12z73n6&quot;&gt;NGINX Reverse Proxy&lt;/li&gt;
&lt;li data-end=&quot;4089&quot; data-start=&quot;4064&quot; data-section-id=&quot;1xxrsm2&quot;&gt;여러 서비스가 동시에 실행되는 MSA 환경&lt;/li&gt;
&lt;/ul&gt;
&lt;p data-end=&quot;4113&quot; data-start=&quot;4091&quot; data-ke-size=&quot;size16&quot;&gt;이 세 가지 요소를 먼저 정리해야 했다.&lt;/p&gt;
&lt;p data-end=&quot;4172&quot; data-start=&quot;4115&quot; data-ke-size=&quot;size16&quot;&gt;다음 글에서는 이 구조를 기반으로 &lt;b&gt;실제 자동 배포 파이프라인을 구축하는 과정&lt;/b&gt;을 정리할 예정이다.&lt;/p&gt;
&lt;hr contenteditable=&quot;false&quot; data-ke-type=&quot;horizontalRule&quot; data-ke-style=&quot;style5&quot; /&gt;</description>
      <category>  프로젝트/KBO-NOTE</category>
      <author>SoloQuest</author>
      <guid isPermaLink="true">https://1devlog.tistory.com/18</guid>
      <comments>https://1devlog.tistory.com/18#entry18comment</comments>
      <pubDate>Sat, 14 Mar 2026 23:59:11 +0900</pubDate>
    </item>
    <item>
      <title>[QRoad]운영 전 API 부하 테스트를 진행하며 403을 마주하다 (문제 추적 과정 정리)</title>
      <link>https://1devlog.tistory.com/17</link>
      <description>&lt;h2 data-end=&quot;227&quot; data-start=&quot;219&quot; data-ke-size=&quot;size26&quot;&gt;1. 배경&lt;/h2&gt;
&lt;p data-end=&quot;279&quot; data-start=&quot;229&quot; data-ke-size=&quot;size16&quot;&gt;지역신문 QR 유입을 대비하여 운영 전 API 안정성을 점검하고자 부하 테스트를 진행했다.&lt;/p&gt;
&lt;ul style=&quot;list-style-type: disc;&quot; data-end=&quot;390&quot; data-start=&quot;281&quot; data-ke-list-type=&quot;disc&quot;&gt;
&lt;li data-end=&quot;296&quot; data-start=&quot;281&quot;&gt;프론트엔드: Vercel&lt;/li&gt;
&lt;li data-end=&quot;315&quot; data-start=&quot;297&quot;&gt;백엔드: EC2 + Nginx&lt;/li&gt;
&lt;li data-end=&quot;328&quot; data-start=&quot;316&quot;&gt;테스트 도구: k6&lt;/li&gt;
&lt;li data-end=&quot;390&quot; data-start=&quot;329&quot;&gt;테스트 대상 API:&lt;/li&gt;
&lt;/ul&gt;
&lt;div&gt;
&lt;div&gt;
&lt;div&gt;
&lt;div&gt;
&lt;div&gt;
&lt;div&gt;
&lt;div&gt;
&lt;div&gt;
&lt;div id=&quot;code-block-viewer&quot;&gt;
&lt;div&gt;
&lt;blockquote data-ke-style=&quot;style3&quot;&gt;https://api.qroad.info/api/qr/3&lt;/blockquote&gt;
&lt;/div&gt;
&lt;/div&gt;
&lt;/div&gt;
&lt;/div&gt;
&lt;/div&gt;
&lt;/div&gt;
&lt;/div&gt;
&lt;/div&gt;
&lt;div&gt;&amp;nbsp;&lt;/div&gt;
&lt;/div&gt;
&lt;/div&gt;
&lt;p data-end=&quot;404&quot; data-start=&quot;392&quot; data-ke-size=&quot;size16&quot;&gt;목표는 다음과 같았다.&lt;/p&gt;
&lt;ul style=&quot;list-style-type: disc;&quot; data-end=&quot;463&quot; data-start=&quot;406&quot; data-ke-list-type=&quot;disc&quot;&gt;
&lt;li data-end=&quot;430&quot; data-start=&quot;406&quot;&gt;동시 사용자 30~50명 시나리오 테스트&lt;/li&gt;
&lt;li data-end=&quot;448&quot; data-start=&quot;431&quot;&gt;p95 500ms 이하 유지&lt;/li&gt;
&lt;li data-end=&quot;463&quot; data-start=&quot;449&quot;&gt;에러율 1% 미만 유지&lt;/li&gt;
&lt;/ul&gt;
&lt;hr contenteditable=&quot;false&quot; data-ke-type=&quot;horizontalRule&quot; data-ke-style=&quot;style5&quot; /&gt;
&lt;h2 data-end=&quot;494&quot; data-start=&quot;470&quot; data-ke-size=&quot;size26&quot;&gt;2. 1차 실수: 잘못된 레이어 테스트&lt;/h2&gt;
&lt;p data-end=&quot;514&quot; data-start=&quot;496&quot; data-ke-size=&quot;size16&quot;&gt;처음에는 다음 주소를 테스트했다.&lt;/p&gt;
&lt;div&gt;
&lt;div&gt;
&lt;div&gt;
&lt;div&gt;
&lt;div&gt;
&lt;div&gt;
&lt;div&gt;
&lt;div&gt;
&lt;div&gt;
&lt;div id=&quot;code-block-viewer&quot;&gt;
&lt;div&gt;
&lt;blockquote data-ke-style=&quot;style3&quot;&gt;https://www.qroad.info/a/3&lt;/blockquote&gt;
&lt;/div&gt;
&lt;/div&gt;
&lt;/div&gt;
&lt;/div&gt;
&lt;/div&gt;
&lt;/div&gt;
&lt;/div&gt;
&lt;/div&gt;
&lt;div&gt;&amp;nbsp;&lt;/div&gt;
&lt;/div&gt;
&lt;/div&gt;
&lt;/div&gt;
&lt;p data-end=&quot;571&quot; data-start=&quot;552&quot; data-ke-size=&quot;size16&quot;&gt;하지만 curl 응답을 확인해보니:&lt;/p&gt;
&lt;div&gt;
&lt;div&gt;
&lt;div&gt;
&lt;div&gt;
&lt;div&gt;
&lt;div&gt;
&lt;div&gt;
&lt;div&gt;
&lt;div&gt;
&lt;div id=&quot;code-block-viewer&quot;&gt;
&lt;div&gt;
&lt;blockquote data-ke-style=&quot;style3&quot;&gt;Server: Vercel&lt;/blockquote&gt;
&lt;/div&gt;
&lt;/div&gt;
&lt;/div&gt;
&lt;/div&gt;
&lt;/div&gt;
&lt;/div&gt;
&lt;/div&gt;
&lt;/div&gt;
&lt;div&gt;&amp;nbsp;&lt;/div&gt;
&lt;/div&gt;
&lt;/div&gt;
&lt;/div&gt;
&lt;p data-end=&quot;660&quot; data-start=&quot;597&quot; data-ke-size=&quot;size16&quot;&gt;즉, 프론트엔드 정적 페이지를 테스트한 것이었다.&lt;br /&gt;DB 조회 API가 아닌 레이어를 테스트하고 있었던 것이다.&lt;/p&gt;
&lt;p data-end=&quot;693&quot; data-start=&quot;662&quot; data-ke-size=&quot;size16&quot;&gt;&amp;rarr; 테스트 대상을 &lt;b&gt;api.qroad.info&lt;/b&gt;로 수정.&lt;/p&gt;
&lt;hr contenteditable=&quot;false&quot; data-ke-type=&quot;horizontalRule&quot; data-ke-style=&quot;style5&quot; /&gt;
&lt;h2 data-end=&quot;719&quot; data-start=&quot;700&quot; data-ke-size=&quot;size26&quot;&gt;3. 실제 API 테스트 결과&lt;/h2&gt;
&lt;p data-end=&quot;755&quot; data-start=&quot;721&quot; data-ke-size=&quot;size16&quot;&gt;k6로 &lt;b&gt;/api/qr/3&lt;/b&gt;를 호출하자 결과는 다음과 같았다.&lt;/p&gt;
&lt;div&gt;
&lt;div&gt;
&lt;div&gt;
&lt;div&gt;
&lt;div&gt;
&lt;div&gt;
&lt;div&gt;
&lt;div&gt;
&lt;div&gt;
&lt;div id=&quot;code-block-viewer&quot;&gt;
&lt;div&gt;
&lt;blockquote data-ke-style=&quot;style3&quot;&gt;http_req_failed: 100%&lt;br /&gt;&lt;span&gt;status is 200: 실패&lt;/span&gt;&lt;/blockquote&gt;
&lt;/div&gt;
&lt;/div&gt;
&lt;/div&gt;
&lt;/div&gt;
&lt;/div&gt;
&lt;/div&gt;
&lt;/div&gt;
&lt;/div&gt;
&lt;div&gt;&amp;nbsp;&lt;/div&gt;
&lt;/div&gt;
&lt;/div&gt;
&lt;/div&gt;
&lt;p data-end=&quot;823&quot; data-start=&quot;806&quot; data-ke-size=&quot;size16&quot;&gt;모든 요청이 실패로 집계되었다.&lt;/p&gt;
&lt;p data-end=&quot;843&quot; data-start=&quot;825&quot; data-ke-size=&quot;size16&quot;&gt;하지만 응답 시간은 매우 짧았다.&lt;/p&gt;
&lt;div&gt;
&lt;div&gt;
&lt;div&gt;
&lt;div&gt;
&lt;div&gt;
&lt;div&gt;
&lt;div&gt;
&lt;div&gt;
&lt;div&gt;
&lt;div id=&quot;code-block-viewer&quot;&gt;
&lt;div&gt;
&lt;blockquote data-ke-style=&quot;style3&quot;&gt;avg: 3~5ms&lt;/blockquote&gt;
&lt;/div&gt;
&lt;/div&gt;
&lt;/div&gt;
&lt;/div&gt;
&lt;/div&gt;
&lt;/div&gt;
&lt;/div&gt;
&lt;/div&gt;
&lt;div&gt;&amp;nbsp;&lt;/div&gt;
&lt;/div&gt;
&lt;/div&gt;
&lt;/div&gt;
&lt;p data-end=&quot;899&quot; data-start=&quot;865&quot; data-ke-size=&quot;size16&quot;&gt;이는 성능 문제가 아니라 &lt;b&gt;즉시 거부 응답&lt;/b&gt;임을 의미한다.&lt;/p&gt;
&lt;hr contenteditable=&quot;false&quot; data-ke-type=&quot;horizontalRule&quot; data-ke-style=&quot;style5&quot; /&gt;
&lt;h2 data-end=&quot;920&quot; data-start=&quot;906&quot; data-ke-size=&quot;size26&quot;&gt;4. 원인 추적 과정&lt;/h2&gt;
&lt;h3 data-end=&quot;937&quot; data-start=&quot;922&quot; data-ke-size=&quot;size23&quot;&gt;(1) 보안그룹 확인&lt;/h3&gt;
&lt;ul style=&quot;list-style-type: disc;&quot; data-end=&quot;969&quot; data-start=&quot;939&quot; data-ke-list-type=&quot;disc&quot;&gt;
&lt;li data-end=&quot;956&quot; data-start=&quot;939&quot;&gt;443 포트 전체 허용 상태&lt;/li&gt;
&lt;li data-end=&quot;969&quot; data-start=&quot;957&quot;&gt;네트워크 차단 아님&lt;/li&gt;
&lt;/ul&gt;
&lt;h3 data-end=&quot;990&quot; data-start=&quot;971&quot; data-ke-size=&quot;size23&quot;&gt;(2) nginx 설정 확인&lt;/h3&gt;
&lt;div&gt;
&lt;div&gt;
&lt;div&gt;
&lt;div&gt;
&lt;div&gt;
&lt;div&gt;
&lt;div&gt;
&lt;div&gt;
&lt;div&gt;
&lt;div id=&quot;code-block-viewer&quot;&gt;
&lt;div&gt;
&lt;blockquote data-ke-style=&quot;style3&quot;&gt;sudo grep -R &quot;deny&quot; /etc/nginx&lt;/blockquote&gt;
&lt;/div&gt;
&lt;/div&gt;
&lt;/div&gt;
&lt;/div&gt;
&lt;/div&gt;
&lt;/div&gt;
&lt;/div&gt;
&lt;/div&gt;
&lt;div&gt;&amp;nbsp;&lt;/div&gt;
&lt;/div&gt;
&lt;/div&gt;
&lt;/div&gt;
&lt;p data-end=&quot;1049&quot; data-start=&quot;1032&quot; data-ke-size=&quot;size16&quot;&gt;결과: 실제 deny 설정 없음&lt;/p&gt;
&lt;p data-end=&quot;1067&quot; data-start=&quot;1051&quot; data-ke-size=&quot;size16&quot;&gt;&amp;rarr; nginx 레벨 차단 아님&lt;/p&gt;
&lt;h3 data-end=&quot;1085&quot; data-start=&quot;1069&quot; data-ke-size=&quot;size23&quot;&gt;(3) curl 테스트&lt;/h3&gt;
&lt;div&gt;
&lt;div&gt;
&lt;div&gt;
&lt;div&gt;
&lt;div&gt;
&lt;div&gt;
&lt;div&gt;
&lt;div&gt;
&lt;div&gt;
&lt;div id=&quot;code-block-viewer&quot;&gt;
&lt;div&gt;
&lt;blockquote data-ke-style=&quot;style3&quot;&gt;curl -i https://api.qroad.info/api/qr/3&lt;/blockquote&gt;
&lt;/div&gt;
&lt;/div&gt;
&lt;/div&gt;
&lt;/div&gt;
&lt;/div&gt;
&lt;/div&gt;
&lt;/div&gt;
&lt;/div&gt;
&lt;div&gt;&amp;nbsp;&lt;/div&gt;
&lt;/div&gt;
&lt;/div&gt;
&lt;/div&gt;
&lt;p data-end=&quot;1139&quot; data-start=&quot;1136&quot; data-ke-size=&quot;size16&quot;&gt;결과:&lt;/p&gt;
&lt;div&gt;
&lt;div&gt;
&lt;div&gt;
&lt;div&gt;
&lt;div&gt;
&lt;div&gt;
&lt;div&gt;
&lt;div&gt;
&lt;div&gt;
&lt;div id=&quot;code-block-viewer&quot;&gt;
&lt;div&gt;
&lt;blockquote data-ke-style=&quot;style3&quot;&gt;HTTP/1.1 403&lt;/blockquote&gt;
&lt;/div&gt;
&lt;/div&gt;
&lt;/div&gt;
&lt;/div&gt;
&lt;/div&gt;
&lt;/div&gt;
&lt;/div&gt;
&lt;/div&gt;
&lt;div&gt;&amp;nbsp;&lt;/div&gt;
&lt;/div&gt;
&lt;/div&gt;
&lt;/div&gt;
&lt;p data-end=&quot;1197&quot; data-start=&quot;1163&quot; data-ke-size=&quot;size16&quot;&gt;Origin, Referer 헤더를 추가해도 동일하게 403.&lt;/p&gt;
&lt;hr contenteditable=&quot;false&quot; data-ke-type=&quot;horizontalRule&quot; data-ke-style=&quot;style5&quot; /&gt;
&lt;h2 data-end=&quot;1218&quot; data-start=&quot;1204&quot; data-ke-size=&quot;size26&quot;&gt;5. 현재 추정 원인&lt;/h2&gt;
&lt;ul style=&quot;list-style-type: disc;&quot; data-end=&quot;1309&quot; data-start=&quot;1220&quot; data-ke-list-type=&quot;disc&quot;&gt;
&lt;li data-end=&quot;1258&quot; data-start=&quot;1220&quot;&gt;애플리케이션 레벨(Spring Security 등)에서 접근 차단&lt;/li&gt;
&lt;li data-end=&quot;1281&quot; data-start=&quot;1259&quot;&gt;세션 또는 쿠키 기반 접근 허용 구조&lt;/li&gt;
&lt;li data-end=&quot;1309&quot; data-start=&quot;1282&quot;&gt;브라우저 정상 흐름에서만 접근 가능하도록 설계&lt;/li&gt;
&lt;/ul&gt;
&lt;p data-end=&quot;1366&quot; data-start=&quot;1311&quot; data-ke-size=&quot;size16&quot;&gt;즉, 서버 성능 문제 이전에&lt;br /&gt;&lt;b&gt;API 접근 정책 설계 문제를 먼저 정리해야 하는 상태&lt;/b&gt;였다.&lt;/p&gt;
&lt;hr contenteditable=&quot;false&quot; data-ke-type=&quot;horizontalRule&quot; data-ke-style=&quot;style5&quot; /&gt;
&lt;h2 data-end=&quot;1383&quot; data-start=&quot;1373&quot; data-ke-size=&quot;size26&quot;&gt;6. 배운 점&lt;/h2&gt;
&lt;h3 data-end=&quot;1420&quot; data-start=&quot;1385&quot; data-ke-size=&quot;size23&quot;&gt;1) 부하 테스트는 단순히 트래픽을 때리는 작업이 아니다&lt;/h3&gt;
&lt;p data-end=&quot;1459&quot; data-start=&quot;1422&quot; data-ke-size=&quot;size16&quot;&gt;레이어를 정확히 이해하지 못하면&lt;br /&gt;엉뚱한 부분을 테스트하게 된다.&lt;/p&gt;
&lt;h3 data-end=&quot;1482&quot; data-start=&quot;1461&quot; data-ke-size=&quot;size23&quot;&gt;2) 403도 의미 있는 결과다&lt;/h3&gt;
&lt;ul style=&quot;list-style-type: disc;&quot; data-end=&quot;1522&quot; data-start=&quot;1484&quot; data-ke-list-type=&quot;disc&quot;&gt;
&lt;li data-end=&quot;1495&quot; data-start=&quot;1484&quot;&gt;네트워크 차단인지&lt;/li&gt;
&lt;li data-end=&quot;1508&quot; data-start=&quot;1496&quot;&gt;nginx 레벨인지&lt;/li&gt;
&lt;li data-end=&quot;1522&quot; data-start=&quot;1509&quot;&gt;애플리케이션 레벨인지&lt;/li&gt;
&lt;/ul&gt;
&lt;p data-end=&quot;1550&quot; data-start=&quot;1524&quot; data-ke-size=&quot;size16&quot;&gt;단계적으로 좁혀가는 과정 자체가 트러블슈팅이다.&lt;/p&gt;
&lt;h3 data-end=&quot;1570&quot; data-start=&quot;1552&quot; data-ke-size=&quot;size23&quot;&gt;3) 운영 전 점검의 가치&lt;/h3&gt;
&lt;p data-end=&quot;1581&quot; data-start=&quot;1572&quot; data-ke-size=&quot;size16&quot;&gt;실제 운영 전에:&lt;/p&gt;
&lt;ul style=&quot;list-style-type: disc;&quot; data-end=&quot;1622&quot; data-start=&quot;1583&quot; data-ke-list-type=&quot;disc&quot;&gt;
&lt;li data-end=&quot;1600&quot; data-start=&quot;1583&quot;&gt;API 접근 정책 문제 발견&lt;/li&gt;
&lt;li data-end=&quot;1622&quot; data-start=&quot;1601&quot;&gt;인증/보안 설계 재검토 필요성 인지&lt;/li&gt;
&lt;/ul&gt;
&lt;p data-end=&quot;1642&quot; data-start=&quot;1624&quot; data-ke-size=&quot;size16&quot;&gt;이 자체가 리스크 관리 경험이다.&lt;/p&gt;
&lt;hr contenteditable=&quot;false&quot; data-ke-type=&quot;horizontalRule&quot; data-ke-style=&quot;style5&quot; /&gt;
&lt;h2 data-end=&quot;1660&quot; data-start=&quot;1649&quot; data-ke-size=&quot;size26&quot;&gt;7. 다음 단계&lt;/h2&gt;
&lt;ul style=&quot;list-style-type: disc;&quot; data-end=&quot;1731&quot; data-start=&quot;1662&quot; data-ke-list-type=&quot;disc&quot;&gt;
&lt;li data-end=&quot;1685&quot; data-start=&quot;1662&quot;&gt;Spring Security 설정 점검&lt;/li&gt;
&lt;li data-end=&quot;1710&quot; data-start=&quot;1686&quot;&gt;&lt;b&gt;/api/qr/**&lt;/b&gt; 접근 정책 명확화&lt;/li&gt;
&lt;li data-end=&quot;1731&quot; data-start=&quot;1711&quot;&gt;인증 전략 정리 후 재테스트 진행&lt;/li&gt;
&lt;/ul&gt;
&lt;hr contenteditable=&quot;false&quot; data-ke-type=&quot;horizontalRule&quot; data-ke-style=&quot;style5&quot; /&gt;
&lt;h1 data-end=&quot;1743&quot; data-start=&quot;1738&quot;&gt;마무리&lt;/h1&gt;
&lt;p data-end=&quot;1795&quot; data-start=&quot;1745&quot; data-ke-size=&quot;size16&quot;&gt;이번 부하 테스트는 성능 검증 이전에&lt;br /&gt;&amp;ldquo;API 접근 구조를 재점검하는 과정&amp;rdquo;이 되었다.&lt;/p&gt;
&lt;p data-end=&quot;1863&quot; data-start=&quot;1797&quot; data-ke-size=&quot;size16&quot;&gt;단순한 부하 테스트 실행이 아니라,&lt;br /&gt;문제 정의 &amp;rarr; 가설 수립 &amp;rarr; 원인 축소 &amp;rarr; 구조 분석의 흐름을 경험한 사례였다.&lt;/p&gt;
&lt;p data-end=&quot;1914&quot; data-start=&quot;1865&quot; data-ke-size=&quot;size16&quot;&gt;운영 전 점검은&lt;br /&gt;장애 발생 이후 대응보다 훨씬 가치 있는 작업임을 체감한 순간이었다.&lt;/p&gt;</description>
      <category>  프로젝트/트러블 슈팅</category>
      <author>SoloQuest</author>
      <guid isPermaLink="true">https://1devlog.tistory.com/17</guid>
      <comments>https://1devlog.tistory.com/17#entry17comment</comments>
      <pubDate>Mon, 2 Mar 2026 22:54:14 +0900</pubDate>
    </item>
  </channel>
</rss>