<?xml version="1.0" encoding="UTF-8"?>
<rss version="2.0">
  <channel>
    <title>tioon's Devlog</title>
    <link>https://tioon.tistory.com/</link>
    <description></description>
    <language>ko</language>
    <pubDate>Thu, 16 Apr 2026 15:26:22 +0900</pubDate>
    <generator>TISTORY</generator>
    <ttl>100</ttl>
    <managingEditor>tioon</managingEditor>
    <image>
      <title>tioon's Devlog</title>
      <url>https://tistory1.daumcdn.net/tistory/6243738/attach/401cba711f114da69e5cbb125f847b4e</url>
      <link>https://tioon.tistory.com</link>
    </image>
    <item>
      <title>무중단 배포를 위한 Nginx 도입기</title>
      <link>https://tioon.tistory.com/199</link>
      <description>&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;기존의 로드밸런서만을 사용하던 프로젝트에서 docker를 이용한 무중단 배포를 적용을 해야했습니다.&lt;br /&gt;이때, 추가적으로 Nginx를 도입하면서 했던 생각들과 여기서 어떤 방식이 제일 효율적이며 안정적인가 고민을 하면서 도입을 했는데 이번에 이를 정리해보려 합니다.&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;기존 프로젝트에선 로드밸런서에 ACM과 Route53을 통해 https를 적용하고 뒷단의 서버는 ci-cd로 중단 배포가 적용되었습니다. 이를 그림으로 간단하게 보면 다음과 같습니다.&lt;/p&gt;
&lt;p&gt;&lt;figure class=&quot;imageblock alignCenter&quot; data-ke-mobileStyle=&quot;widthOrigin&quot; data-origin-width=&quot;560&quot; data-origin-height=&quot;391&quot;&gt;&lt;span data-url=&quot;https://blog.kakaocdn.net/dn/ctVU3v/btsIiOKbBcy/IJ3mXKqarSJLuaMfksdHNk/img.png&quot; data-phocus=&quot;https://blog.kakaocdn.net/dn/ctVU3v/btsIiOKbBcy/IJ3mXKqarSJLuaMfksdHNk/img.png&quot;&gt;&lt;img src=&quot;https://blog.kakaocdn.net/dn/ctVU3v/btsIiOKbBcy/IJ3mXKqarSJLuaMfksdHNk/img.png&quot; srcset=&quot;https://img1.daumcdn.net/thumb/R1280x0/?scode=mtistory2&amp;fname=https%3A%2F%2Fblog.kakaocdn.net%2Fdn%2FctVU3v%2FbtsIiOKbBcy%2FIJ3mXKqarSJLuaMfksdHNk%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;560&quot; height=&quot;391&quot; data-origin-width=&quot;560&quot; data-origin-height=&quot;391&quot;/&gt;&lt;/span&gt;&lt;/figure&gt;
&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;AWS ELB가 앞단에 존재하고, 요청을 받게 되면 뒤에 있는 EC2로 요청을 전달하는 과정으로 통신이 진행됩니다.&lt;br /&gt;이때, ELB에는 Route53을 통해 기존에 저희가 사두었던 도메인이 적용되어 있으며, ACM을 통해 SSL 인증서가 적용되어 앞단의 ELB는 443포트(HTTPS)로의 요청을 받을 수 있었습니다.&lt;br /&gt;또한 ci-cd는 깃허브액션으로 ssh를 기반으로 배포하는 방식으로 이때 서버가 20~40초 정도 중단되는 문제가 있었습니다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;blockquote data-ke-style=&quot;style1&quot;&gt;&lt;span style=&quot;font-family: 'Noto Serif KR';&quot;&gt;&lt;span style=&quot;color: #000000;&quot;&gt;&lt;b&gt;무중단 배포 도입 방식 논의&lt;/b&gt;&lt;/span&gt;&lt;br /&gt;&lt;/span&gt;&lt;/blockquote&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;저희는 배포시에 서버가 다운되는 기존의 구조에서 사용성을 개선시키기 위해 무중단 배포를 도입을 해야했습니다. 이때 저희는 무중단 배포를 도입하기위해 생각한 방식은 총 두가지였는데 다음과 같습니다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;ol style=&quot;list-style-type: decimal;&quot; data-ke-list-type=&quot;decimal&quot;&gt;
&lt;li&gt;&lt;span style=&quot;color: #000000;&quot;&gt;&lt;b&gt;CodeDeploy를 활용해 EC2 서버 기반 블루/그린 무중단 배포&lt;/b&gt;&lt;/span&gt;&lt;/li&gt;
&lt;li&gt;&lt;span style=&quot;color: #000000;&quot;&gt;&lt;b&gt;Nginx를 활용한 도커 기반 블루/그린 무중단 배포&lt;/b&gt;&lt;/span&gt;&lt;/li&gt;
&lt;/ol&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;첫번째 방법은 배포시에 새로운 EC2를 띄우고 ELB를 기준으로 블루/그린 배포를 하는 방식입니다. 따라서, 이 방식을 적용하게 되면 CodeDeploy와 2개의 EC2가 필수적으로 필요했습니다. 하지만 저희는 이 서버를 사비로 운영하고 있었고, 두개의 EC2를 사용하게 될 경우 프리티어를 사용하지 못하여 비용이 추가적으로 부과된다는 점에서 부담을 느꼈습니다.&lt;br /&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;두번째 방법은 한개의 EC2 내부에서 두개의 port를 통해 Nginx를 기준으로 블루/그린 배포를 하는 방식입니다. 따라서, 이방식을 적용하게 되면 한개의 EC2 내부에 Nginx 및 두개의 서버를 띄울 수 있어, 프리티어로 충분히 유지할 수 있다는 장점이 있었습니다. 따라서 저희는 비용부담이 적은 두번째 방법을 선택을 하게됐고, 이번에 Nginx를 추가적으로 도입하여 무중단 배포를 구현하기로 하였습니다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;blockquote data-ke-style=&quot;style1&quot;&gt;&lt;span style=&quot;color: #000000;&quot;&gt;&lt;b&gt;Nginx 도입 과정&lt;/b&gt;&lt;/span&gt;&lt;/blockquote&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;앞전에 두번째 방법으로 무중단배포를 구현하려고 했기에 Nginx를 도입해야 했습니다. 근데 Nginx를 도입하기위해 아키텍처를 수정하던 도중 다음과 같은 의문점이 생겼습니다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;ul style=&quot;list-style-type: disc;&quot; data-ke-list-type=&quot;disc&quot;&gt;
&lt;li&gt;&lt;span style=&quot;color: #ef5369;&quot;&gt;Nginx에 SSL 인증서를 적용하면 ELB는 필요가 없는 것 아닌가?&lt;/span&gt;&lt;/li&gt;
&lt;/ul&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;현재 저희 서버에 트래픽이 그렇게 많이 들어오지 않아 아직까진 프리티어 단일 서버로 운영되고 있습니다. 따라서 저희는 이렇게 ELB를 사용하기 보다 ELB를 없애고 Nginx에 SSL 인증서를 적용시키면 HTTPS를 그대로 사용하면서 비용 부담은 줄어들지 않을까 생각을 하였습니다.&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;저희가 Nginx에 SSL을 적용시키기위해 두가지 방식을 생각했는데, 다음과 같습니다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;ul style=&quot;list-style-type: disc;&quot; data-ke-list-type=&quot;disc&quot;&gt;
&lt;li&gt;&lt;span style=&quot;color: #000000;&quot;&gt;&lt;b&gt;Cerbot으로 무료 SSL 인증서 발급&lt;/b&gt;&lt;/span&gt;&lt;/li&gt;
&lt;li&gt;&lt;span style=&quot;color: #000000;&quot;&gt;&lt;b&gt;ACM에 존재하는 SSL 인증서를 Nginx에 적용&lt;/b&gt;&lt;/span&gt;&lt;/li&gt;
&lt;/ul&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;첫번째 방식은 Cerbot을 통해 Let's Encrypt 인증서를 무료로 발급받는 방법이였습니다. 이방식이 굉장히 쉽고 간편한 방식입니다. 하지만 90이라는 짧은 기간 동안만 인증되며, 그 이후에 다시 수동으로 발급하여야 한다는 단점이 존재합니다.&lt;br /&gt;물론 스케줄러를 활용해 자동으로 인증 갱신을 하는 방법도 존재하지만, 아직 저희가 기존에 구매한 ACM SSL 인증서의 보증기간이 1년 남짓 남았기 때문에 Cerbot을 이용하는건 보류하자는 결론이 나왔습니다.&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;두번째 방식은 현재 AWS에서 발급받은 SSL 인증서의 정보를 가져와 Nginx에 적용하는 방식입니다. 사실 ACM의 SSL인증서는 AWS에서 제공하는 ELB나 다른 서비스에 사용할 수 있습니다. 즉 AWS에서 콘솔창으로는 ACM 인증서의 정보가 나와있지않기때문에 Nginx에 적용할 수 없습니다.&lt;br /&gt;하지만, 저희가 구글링을 해본 결과 AWS CLI를 통해 ACM인증서의 정보를 가져올 수 있는 방법을 알게되었습니다. 따라서 저희는 AWS CLI에 로그인을 한후, acm get-certificate 명령을 통해 인증서와 인증서 체인의 정보를 얻을 수 있었습니다.&lt;br /&gt;이렇게 인증서의 정보를 Nginx에 적용을 하고 재시작을 눌렀는데 Private Key 오류가 났습니다. 이 오류에 대해 찾아본 결과 SSL 인증서를 적용해 HTTPS를 구현을 하기 위해선 인증서 + 인증서 체인 + 개인키 이렇게 3개가 필요한데, 우리의 경우 개인키파일이 없기 때문에 일어난 오류였습니다. 정말 당연한 오류였는데, AWS ACM에선 보안상의 이유로 개인키를 직접 사용자에게 제공하지 않고 있었습니다. 따라서 저희는 Nginx에 SSL 인증서를 적용하지 못한다 라고 판단을 하였습니다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;br /&gt;결국, Nginx에 HTTPS를 적용하지않고 로드밸런서를 남겨두기로 결정을 하였습니다. 일단 기존에 구매해둔 SSL 인증서의 인증기간이 남아 있기때문에 Cerbot을 활용하는 방식으로 바꾸기엔 아직 이르다고 생각을 하였고, 정말 혹시 모를 트래픽 증가 때, Scale-Out을 빠르게 적용하기 위해 비용이 조금 들더라도 로드밸런서를 남겨두는게 맞다라는 판단을 하였습니다.&lt;/p&gt;
&lt;p&gt;&lt;figure class=&quot;imageblock alignCenter&quot; data-ke-mobileStyle=&quot;widthOrigin&quot; data-origin-width=&quot;677&quot; data-origin-height=&quot;443&quot;&gt;&lt;span data-url=&quot;https://blog.kakaocdn.net/dn/qlvL9/btsIjdwbCHb/CWjTk9bl9KyGK6Hv5MadU0/img.png&quot; data-phocus=&quot;https://blog.kakaocdn.net/dn/qlvL9/btsIjdwbCHb/CWjTk9bl9KyGK6Hv5MadU0/img.png&quot;&gt;&lt;img src=&quot;https://blog.kakaocdn.net/dn/qlvL9/btsIjdwbCHb/CWjTk9bl9KyGK6Hv5MadU0/img.png&quot; srcset=&quot;https://img1.daumcdn.net/thumb/R1280x0/?scode=mtistory2&amp;fname=https%3A%2F%2Fblog.kakaocdn.net%2Fdn%2FqlvL9%2FbtsIjdwbCHb%2FCWjTk9bl9KyGK6Hv5MadU0%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;677&quot; height=&quot;443&quot; data-origin-width=&quot;677&quot; data-origin-height=&quot;443&quot;/&gt;&lt;/span&gt;&lt;/figure&gt;
&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;따라서, 위의 그림처럼 앞단에 ELB는 그대로 두고 EC2 안에 Nginx를 추가하여 요청이 들어오면 뒷단의 Spring 서버에 전달을 할 수 있게 되었습니다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;blockquote data-ke-style=&quot;style1&quot;&gt;&lt;span style=&quot;color: #000000;&quot;&gt;&lt;b&gt;&lt;span style=&quot;font-family: 'Noto Serif KR';&quot;&gt;Docker를 활용한 무중단 배포&lt;/span&gt;&lt;/b&gt;&lt;/span&gt;&lt;/blockquote&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;이제 Nginx를 도입을 하였으니, 무중단 배포를 구현을 할 차례입니다. 무중단 배포는 Nginx를 기반으로 내부 Port를 바꿔가면서 배포를 적용하여 사용자 입장에서 최소한의 중단 시간으로 사용성을 높일 수 있는 기술입니다.&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;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;&lt;b&gt;Spring 내부 profile을 이용해 서로 다른 port를 설정하는 방식&lt;/b&gt;&lt;/span&gt;&lt;/li&gt;
&lt;li&gt;&lt;span style=&quot;color: #000000;&quot;&gt;&lt;b&gt;Docker를 이용해 서로 다른 port를 설정하는 방식&lt;/b&gt;&lt;/span&gt;&lt;/li&gt;
&lt;/ul&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;전자의 방식은 Spring 내부의 application 파일에 서로 다른 profile을 설정하여 블루/그린의 서로 다른 port(ex. 8080, 8081)를 사용하여 배포할때마다 서로 다른 port로 배포를 하여 무중단 배포를 하는 방식입니다. 이 방식도 굉장히 많이 쓰이는 방식이지만 저희의 경우 application 파일을 깃-서브모듈을 이용해 관리를 하고 있기도하고, Spring을 docker없이 그냥 무중단 배포를 도입한다 했을때, 환경 의존성이 너무 높아진다고 판단하여 docker를 사용하기로 판단을 하였습니다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;docker를 사용하여 무중단 배포를 한다고 가정했을때 다음과 같습니다.&amp;nbsp;&lt;/p&gt;
&lt;p&gt;&lt;figure class=&quot;imageblock alignCenter&quot; data-ke-mobileStyle=&quot;widthOrigin&quot; data-origin-width=&quot;664&quot; data-origin-height=&quot;382&quot;&gt;&lt;span data-url=&quot;https://blog.kakaocdn.net/dn/3Potl/btsIiRGVhgn/Tn35cSpEQOQi3go55Jorck/img.png&quot; data-phocus=&quot;https://blog.kakaocdn.net/dn/3Potl/btsIiRGVhgn/Tn35cSpEQOQi3go55Jorck/img.png&quot;&gt;&lt;img src=&quot;https://blog.kakaocdn.net/dn/3Potl/btsIiRGVhgn/Tn35cSpEQOQi3go55Jorck/img.png&quot; srcset=&quot;https://img1.daumcdn.net/thumb/R1280x0/?scode=mtistory2&amp;fname=https%3A%2F%2Fblog.kakaocdn.net%2Fdn%2F3Potl%2FbtsIiRGVhgn%2FTn35cSpEQOQi3go55Jorck%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;664&quot; height=&quot;382&quot; data-origin-width=&quot;664&quot; data-origin-height=&quot;382&quot;/&gt;&lt;/span&gt;&lt;/figure&gt;
&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;기존에 Blue 컨테이너가 존재할때 배포를 진행했다고 가정 하면, 새로운 Green 컨테이너를 배포합니다. 이때 Nginx는 바뀌는 거 없이 계속해서 기존의 Blue 컨테이너(8080)를 가리키고 있습니다. 이때 Nginx에서 Green 컨테이너에 헬스체크를 하여 Green 컨테이너 내부에서 Spring이 정상적으로 실행이 되고 있는지를 확인합니다. 이후 Green 컨테이너에서 헬스체크가 정상으로 떴다면 Nginx가 가리키는 포트를 Green 컨테이너(8081)로 바꿉니다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;br /&gt;이 과정을 통해 배포시에 서버가 중단되는 시간을 최소화하여 배포를 진행 할 수 있습니다. 이때, Nginx 포트 바꾸는 과정에서 Restart를 해야하기에 0.1~0.5초의 중단 시간은 있지만 이정도면 아주 미세한 시간이기에 무중단 배포라고 불립니다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;blockquote data-ke-style=&quot;style1&quot;&gt;&lt;span style=&quot;font-family: 'Noto Serif KR';&quot;&gt;&lt;b&gt;&lt;span style=&quot;color: #000000;&quot;&gt;무중단 배포 도입 과정&lt;/span&gt;&lt;/b&gt;&lt;br /&gt;&lt;/span&gt;&lt;/blockquote&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;자 이제 Nginx를 어떻게 설정하였고, CI-CD때 무중단 배포 스크립트를 어떻게 작성했는지 알아보도록 하겠습니다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;Nginx의 경우 /etc/nginx 파일 안에 모든 설정파일들이 존재합니다. 여기서 저희가 봐야하는&amp;nbsp; 디렉토리는 다음과 같습니다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;ul style=&quot;list-style-type: disc;&quot; data-ke-list-type=&quot;disc&quot;&gt;
&lt;li&gt;&lt;span style=&quot;color: #000000;&quot;&gt;&lt;b&gt;/etc/nginx/nginx.conf 파일&lt;/b&gt;&lt;/span&gt;&lt;br /&gt;- nginx의 전체설정을 할 수 있는 파일&lt;/li&gt;
&lt;li&gt;&lt;span style=&quot;color: #000000;&quot;&gt;&lt;b&gt;/etc/nginx/sites-available 디렉토리&lt;/b&gt;&lt;/span&gt;&lt;br /&gt;- 사용가능한 모든 설정파일을 관리하는 디렉토리&lt;/li&gt;
&lt;li&gt;&lt;span style=&quot;color: #000000;&quot;&gt;&lt;b&gt;/etc/nginx/sites-enabled 디렉토리&lt;/b&gt;&lt;/span&gt;&lt;br /&gt;- 실제 nginx에 적용하려는 설정파일 관리하는 디렉토리&lt;/li&gt;
&lt;/ul&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;여기서 저희는 sites-available 안에 있는 설정파일을 수정해서 저희에게 맞게 블루그린으로 바꿀려고 합니다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;/etc/nginx/sites-available 디렉토리 안에 default 파일이 있습니다. 이를 삭제하고 저희는 blue, green 파일로 바꾸면됩니다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p&gt;&lt;figure class=&quot;imageblock alignLeft&quot; data-ke-mobileStyle=&quot;widthOrigin&quot; data-origin-width=&quot;743&quot; data-origin-height=&quot;58&quot;&gt;&lt;span data-url=&quot;https://blog.kakaocdn.net/dn/942S2/btsIicdIRwo/SiOFNU2ZsGN0K079a7GcUK/img.png&quot; data-phocus=&quot;https://blog.kakaocdn.net/dn/942S2/btsIicdIRwo/SiOFNU2ZsGN0K079a7GcUK/img.png&quot;&gt;&lt;img src=&quot;https://blog.kakaocdn.net/dn/942S2/btsIicdIRwo/SiOFNU2ZsGN0K079a7GcUK/img.png&quot; srcset=&quot;https://img1.daumcdn.net/thumb/R1280x0/?scode=mtistory2&amp;fname=https%3A%2F%2Fblog.kakaocdn.net%2Fdn%2F942S2%2FbtsIicdIRwo%2FSiOFNU2ZsGN0K079a7GcUK%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;743&quot; height=&quot;58&quot; data-origin-width=&quot;743&quot; data-origin-height=&quot;58&quot;/&gt;&lt;/span&gt;&lt;/figure&gt;
&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;파일은 총 두개가 있으며, blue와 green 파일 내용은 다음과 같습니다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p&gt;&lt;figure class=&quot;imageblock alignLeft&quot; data-ke-mobileStyle=&quot;widthOrigin&quot; data-origin-width=&quot;844&quot; data-origin-height=&quot;587&quot;&gt;&lt;span data-url=&quot;https://blog.kakaocdn.net/dn/ecnYkb/btsIiOKcsj7/7r79QEbY70YxLcL6Z9oJq1/img.png&quot; data-phocus=&quot;https://blog.kakaocdn.net/dn/ecnYkb/btsIiOKcsj7/7r79QEbY70YxLcL6Z9oJq1/img.png&quot;&gt;&lt;img src=&quot;https://blog.kakaocdn.net/dn/ecnYkb/btsIiOKcsj7/7r79QEbY70YxLcL6Z9oJq1/img.png&quot; srcset=&quot;https://img1.daumcdn.net/thumb/R1280x0/?scode=mtistory2&amp;fname=https%3A%2F%2Fblog.kakaocdn.net%2Fdn%2FecnYkb%2FbtsIiOKcsj7%2F7r79QEbY70YxLcL6Z9oJq1%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;844&quot; height=&quot;587&quot; data-origin-width=&quot;844&quot; data-origin-height=&quot;587&quot;/&gt;&lt;/span&gt;&lt;/figure&gt;
&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;blockquote data-ke-style=&quot;style3&quot;&gt;server&amp;nbsp;{ &lt;br /&gt;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;listen&amp;nbsp;80; &lt;br /&gt;&lt;br /&gt;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;location&amp;nbsp;/&amp;nbsp;{ &lt;br /&gt;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;proxy_pass&amp;nbsp;http://127.0.0.1:8080;&amp;nbsp;&lt;span style=&quot;color: #006dd7;&quot;&gt;#blue&lt;/span&gt; &lt;br /&gt;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;proxy_set_header&amp;nbsp;Host&amp;nbsp;$host; &lt;br /&gt;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;proxy_set_header&amp;nbsp;X-Real-IP&amp;nbsp;$remote_addr; &lt;br /&gt;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;proxy_set_header&amp;nbsp;X-Forwarded-For&amp;nbsp;$proxy_add_x_forwarded_for; &lt;br /&gt;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;proxy_set_header&amp;nbsp;X-Forwarded-Proto&amp;nbsp;$scheme; &lt;br /&gt;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;} &lt;br /&gt;}&lt;/blockquote&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;blockquote data-ke-style=&quot;style3&quot;&gt;server&amp;nbsp;{ &lt;br /&gt;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;listen&amp;nbsp;80; &lt;br /&gt;&lt;br /&gt;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;location&amp;nbsp;/&amp;nbsp;{ &lt;br /&gt;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;proxy_pass&amp;nbsp;http://127.0.0.1:8081;&amp;nbsp;&lt;span style=&quot;color: #009a87;&quot;&gt;#green&lt;/span&gt; &lt;br /&gt;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;proxy_set_header&amp;nbsp;Host&amp;nbsp;$host; &lt;br /&gt;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;proxy_set_header&amp;nbsp;X-Real-IP&amp;nbsp;$remote_addr; &lt;br /&gt;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;proxy_set_header&amp;nbsp;X-Forwarded-For&amp;nbsp;$proxy_add_x_forwarded_for; &lt;br /&gt;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;proxy_set_header&amp;nbsp;X-Forwarded-Proto&amp;nbsp;$scheme; &lt;br /&gt;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;} &lt;br /&gt;}&lt;/blockquote&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;이제 이렇게 sites-available 디렉토리에 blue, green 파일을 만들었습니다. 여기에 파일이 있다고해서 바로 적용되는 것은아닙니다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;/etc/nginx/sites-available 디렉토리는 그냥 사용가능한 모든 설정 파일을 관리하는&amp;nbsp; 디렉토리이고, 실제로 설정 파일을 활성화 시키기 위해선 sites-enabled 디렉토리에 해당 파일이 존재해야합니다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;이에 대한 이유는 nginx.conf 파일 내부 코드에 있습니다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;blockquote data-ke-style=&quot;style3&quot;&gt;user&amp;nbsp;www-data; &lt;br /&gt;worker_processes&amp;nbsp;auto; &lt;br /&gt;pid&amp;nbsp;/run/nginx.pid; &lt;br /&gt;&lt;span style=&quot;color: #ee2323;&quot;&gt;include&amp;nbsp;/etc/nginx/modules-enabled/*.conf;&lt;/span&gt; &lt;br /&gt;&lt;br /&gt;&lt;br /&gt;events&amp;nbsp;{ &lt;br /&gt;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;worker_connections&amp;nbsp;768; &lt;br /&gt;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;#&amp;nbsp;multi_accept&amp;nbsp;on; &lt;br /&gt;} &lt;br /&gt;&lt;br /&gt;http&amp;nbsp;{ &lt;br /&gt;&lt;br /&gt;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;## &lt;br /&gt;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;#&amp;nbsp;Basic&amp;nbsp;Settings &lt;br /&gt;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;## &lt;br /&gt;&lt;br /&gt;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;sendfile&amp;nbsp;on; &lt;br /&gt;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;tcp_nopush&amp;nbsp;on; &lt;br /&gt;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;types_hash_max_size&amp;nbsp;2048; &lt;br /&gt;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;#&amp;nbsp;server_tokens&amp;nbsp;off; &lt;br /&gt;&lt;br /&gt;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;#&amp;nbsp;server_names_hash_bucket_size&amp;nbsp;64; &lt;br /&gt;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;#&amp;nbsp;server_name_in_redirect&amp;nbsp;off; &lt;br /&gt;&lt;br /&gt;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;include&amp;nbsp;/etc/nginx/mime.types; &lt;br /&gt;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;default_type&amp;nbsp;application/octet-stream; &lt;br /&gt;&lt;br /&gt;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;## &lt;br /&gt;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;#&amp;nbsp;SSL&amp;nbsp;Settings &lt;br /&gt;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;## &lt;br /&gt;&lt;br /&gt;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;ssl_protocols&amp;nbsp;TLSv1&amp;nbsp;TLSv1.1&amp;nbsp;TLSv1.2&amp;nbsp;TLSv1.3;&amp;nbsp;#&amp;nbsp;Dropping&amp;nbsp;SSLv3,&amp;nbsp;ref:&amp;nbsp;POODLE &lt;br /&gt;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;ssl_prefer_server_ciphers&amp;nbsp;on; &lt;br /&gt;&lt;br /&gt;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;## &lt;br /&gt;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;#&amp;nbsp;Logging&amp;nbsp;Settings &lt;br /&gt;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;## &lt;br /&gt;&lt;br /&gt;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;access_log&amp;nbsp;/var/log/nginx/access.log; &lt;br /&gt;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;error_log&amp;nbsp;/var/log/nginx/error.log; &lt;br /&gt;&lt;br /&gt;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;## &lt;br /&gt;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;#&amp;nbsp;Gzip&amp;nbsp;Settings &lt;br /&gt;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;## &lt;br /&gt;&lt;br /&gt;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;gzip&amp;nbsp;on; &lt;br /&gt;&lt;br /&gt;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;#&amp;nbsp;gzip_vary&amp;nbsp;on; &lt;br /&gt;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;#&amp;nbsp;gzip_proxied&amp;nbsp;any; &lt;br /&gt;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;#&amp;nbsp;gzip_comp_level&amp;nbsp;6; &lt;br /&gt;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;#&amp;nbsp;gzip_buffers&amp;nbsp;16&amp;nbsp;8k; &lt;br /&gt;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;#&amp;nbsp;gzip_http_version&amp;nbsp;1.1; &lt;br /&gt;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;#&amp;nbsp;gzip_types&amp;nbsp;text/plain&amp;nbsp;text/css&amp;nbsp;application/json&amp;nbsp;application/javascript&amp;nbsp;text/xml&amp;nbsp;application/xml&amp;nbsp;application/xml+rss&amp;nbsp;text/javascript; &lt;br /&gt;&lt;br /&gt;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;## &lt;br /&gt;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;#&amp;nbsp;Virtual&amp;nbsp;Host&amp;nbsp;Configs &lt;br /&gt;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;## &lt;br /&gt;&lt;br /&gt;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;include&amp;nbsp;/etc/nginx/conf.d/*.conf; &lt;br /&gt;&amp;nbsp;&amp;nbsp;&amp;nbsp;&lt;span style=&quot;color: #ee2323;&quot;&gt;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;include&amp;nbsp;/etc/nginx/sites-enabled/*;&lt;/span&gt;&lt;/blockquote&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;위의 코드에서 빨간 부분과 같이 nginx.conf 파일에서 sites-enabled 디렉토리 안에 있는 파일들을 가져와 nginx 설정을 하기 때문입니다.&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;따라서, nginx에서 blue, green을 적용하기 위해선 sites-available 디렉토리가 아닌 sites-enabled 디렉토리에 있어야합니다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;이렇게 파일을 계속해서 연결하기 위해서 심볼릭 링크를 사용했으며, 심볼릭 링크를 통해 현재 환경조건에 따라서 sites-available에 있는 파일을 sites-enabled에 포인터 링크를 걸어 관리를 할 수 있게 하였습니다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;즉, blue 컨테이너 일땐 &lt;span style=&quot;color: #333333; text-align: start;&quot;&gt;&lt;span&gt;&amp;nbsp;&lt;/span&gt;&lt;/span&gt;sites-enabled 디렉토리에 blue 환경설정파일이 존재해야하며, green 일땐 green 환경설정파일이 존재해야합니다.&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;심볼릭 링크를 걸어 다음과 같이 환경설정 정보가 sites-enabled에 존재할 수 있도록 하였습니다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p&gt;&lt;figure class=&quot;imageblock alignCenter&quot; data-ke-mobileStyle=&quot;widthOrigin&quot; data-origin-width=&quot;830&quot; data-origin-height=&quot;340&quot;&gt;&lt;span data-url=&quot;https://blog.kakaocdn.net/dn/cwHhdm/btsIkdWvXeA/kV8aUvc7DLVE1ck39jPil0/img.png&quot; data-phocus=&quot;https://blog.kakaocdn.net/dn/cwHhdm/btsIkdWvXeA/kV8aUvc7DLVE1ck39jPil0/img.png&quot;&gt;&lt;img src=&quot;https://blog.kakaocdn.net/dn/cwHhdm/btsIkdWvXeA/kV8aUvc7DLVE1ck39jPil0/img.png&quot; srcset=&quot;https://img1.daumcdn.net/thumb/R1280x0/?scode=mtistory2&amp;fname=https%3A%2F%2Fblog.kakaocdn.net%2Fdn%2FcwHhdm%2FbtsIkdWvXeA%2FkV8aUvc7DLVE1ck39jPil0%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;830&quot; height=&quot;340&quot; data-origin-width=&quot;830&quot; data-origin-height=&quot;340&quot;/&gt;&lt;/span&gt;&lt;/figure&gt;
&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;이렇게 환경설정을 한 후, 무중단 배포 CI-CD 배포 스크립트를 작성했는데 스크립트는 다음과 같습니다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;blockquote data-ke-style=&quot;style3&quot;&gt;name:&amp;nbsp;deploy&amp;nbsp;on&amp;nbsp;prod&amp;nbsp;server &lt;br /&gt;&lt;br /&gt;on: &lt;br /&gt;&amp;nbsp;&amp;nbsp;push: &lt;br /&gt;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;branches:&amp;nbsp;[main] &lt;br /&gt;&lt;br /&gt;jobs: &lt;br /&gt;&amp;nbsp;&amp;nbsp;deploy: &lt;br /&gt;&lt;br /&gt;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;runs-on:&amp;nbsp;ubuntu-latest &lt;br /&gt;&lt;br /&gt;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;steps: &lt;br /&gt;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;-&amp;nbsp;name:&amp;nbsp;체크아웃 &lt;br /&gt;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;uses:&amp;nbsp;actions/checkout@v4 &lt;br /&gt;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;with: &lt;br /&gt;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;submodules:&amp;nbsp;true &lt;br /&gt;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;token:&amp;nbsp;${{&amp;nbsp;secrets.PERSONAL_ACCESS_TOKEN&amp;nbsp;}} &lt;br /&gt;&lt;br /&gt;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;-&amp;nbsp;name:&amp;nbsp;서브모듈&amp;nbsp;업데이트 &lt;br /&gt;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;run:&amp;nbsp;| &lt;br /&gt;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;git&amp;nbsp;submodule&amp;nbsp;update&amp;nbsp;--remote &lt;br /&gt;&lt;br /&gt;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;-&amp;nbsp;name:&amp;nbsp;JDK&amp;nbsp;11&amp;nbsp;설치 &lt;br /&gt;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;uses:&amp;nbsp;actions/setup-java@v4 &lt;br /&gt;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;with: &lt;br /&gt;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;distribution:&amp;nbsp;'corretto' &lt;br /&gt;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;java-version:&amp;nbsp;'11' &lt;br /&gt;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;cache:&amp;nbsp;'gradle' &lt;br /&gt;&lt;br /&gt;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;-&amp;nbsp;name:&amp;nbsp;Gradle에&amp;nbsp;실행&amp;nbsp;권한&amp;nbsp;부여 &lt;br /&gt;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;run:&amp;nbsp;chmod&amp;nbsp;+x&amp;nbsp;gradlew &lt;br /&gt;&lt;br /&gt;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;-&amp;nbsp;name:&amp;nbsp;빌드 &lt;br /&gt;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;run:&amp;nbsp;./gradlew&amp;nbsp;build&amp;nbsp;-x&amp;nbsp;test &lt;br /&gt;&lt;br /&gt;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;-&amp;nbsp;name:&amp;nbsp;DockerHub&amp;nbsp;로그인 &lt;br /&gt;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;uses:&amp;nbsp;docker/login-action@v3 &lt;br /&gt;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;with: &lt;br /&gt;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;username:&amp;nbsp;${{&amp;nbsp;secrets.DOCKERHUB_USERNAME&amp;nbsp;}} &lt;br /&gt;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;password:&amp;nbsp;${{&amp;nbsp;secrets.DOCKERHUB_TOKEN&amp;nbsp;}} &lt;br /&gt;&lt;br /&gt;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;-&amp;nbsp;name:&amp;nbsp;Docker&amp;nbsp;이미지&amp;nbsp;빌드&amp;nbsp;&amp;amp;&amp;nbsp;DockerHub에&amp;nbsp;Push &lt;br /&gt;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;uses:&amp;nbsp;docker/build-push-action@v6 &lt;br /&gt;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;with: &lt;br /&gt;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;context:&amp;nbsp;. &lt;br /&gt;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;file:&amp;nbsp;./docker/Dockerfile-prod &lt;br /&gt;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;push:&amp;nbsp;true &lt;br /&gt;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;tags:&amp;nbsp;${{&amp;nbsp;secrets.DOCKERHUB_USERNAME&amp;nbsp;}}/qtudy-server:prod &lt;br /&gt;&lt;br /&gt;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;-&amp;nbsp;name:&amp;nbsp;EC2&amp;nbsp;서버에&amp;nbsp;배포 &lt;br /&gt;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;uses:&amp;nbsp;appleboy/ssh-action@master &lt;br /&gt;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;with: &lt;br /&gt;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;host:&amp;nbsp;${{&amp;nbsp;secrets.PROD_SERVER_HOST&amp;nbsp;}} &lt;br /&gt;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;username:&amp;nbsp;${{&amp;nbsp;secrets.PROD_SERVER_USERNAME&amp;nbsp;}} &lt;br /&gt;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;key:&amp;nbsp;${{&amp;nbsp;secrets.PROD_SERVER_PEM_KEY&amp;nbsp;}} &lt;br /&gt;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;envs:&amp;nbsp;GITHUB_SHA,&amp;nbsp;DOCKERHUB_USERNAME &lt;br /&gt;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;script:&amp;nbsp;| &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;Nginx&amp;nbsp;설정&amp;nbsp;파일&amp;nbsp;확인 &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;CURRENT_CONF=$(sudo&amp;nbsp;readlink&amp;nbsp;/etc/nginx/sites-enabled/current) &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;echo&amp;nbsp;&quot;Current&amp;nbsp;Nginx&amp;nbsp;configuration:&amp;nbsp;$CURRENT_CONF&quot; &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;if&amp;nbsp;[[&amp;nbsp;&quot;$CURRENT_CONF&quot;&amp;nbsp;==&amp;nbsp;*&quot;blue&quot;&amp;nbsp;]];&amp;nbsp;then &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;NEW_CONF=&quot;/etc/nginx/sites-available/green&quot; &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;OLD_CONTAINER=&quot;blue-container&quot; &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;NEW_CONTAINER=&quot;green-container&quot; &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;NEW_PORT=8081 &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;else &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;NEW_CONF=&quot;/etc/nginx/sites-available/blue&quot; &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;OLD_CONTAINER=&quot;green-container&quot; &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;NEW_CONTAINER=&quot;blue-container&quot; &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;NEW_PORT=8080 &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;fi &lt;br /&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;echo&amp;nbsp;&quot;New&amp;nbsp;Nginx&amp;nbsp;configuration:&amp;nbsp;$NEW_CONF&quot; &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;echo&amp;nbsp;&quot;Old&amp;nbsp;container:&amp;nbsp;$OLD_CONTAINER&quot; &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;echo&amp;nbsp;&quot;New&amp;nbsp;container:&amp;nbsp;$NEW_CONTAINER&quot; &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;echo&amp;nbsp;&quot;New&amp;nbsp;port:&amp;nbsp;$NEW_PORT&quot; &lt;br /&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;Docker&amp;nbsp;이미지&amp;nbsp;가져오기 &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;sudo&amp;nbsp;docker&amp;nbsp;pull&amp;nbsp;${{&amp;nbsp;secrets.DOCKERHUB_USERNAME&amp;nbsp;}}/qtudy-server:prod &lt;br /&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;실행 &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;sudo&amp;nbsp;docker&amp;nbsp;run&amp;nbsp;-d&amp;nbsp;-p&amp;nbsp;$NEW_PORT:8080&amp;nbsp;--name&amp;nbsp;$NEW_CONTAINER&amp;nbsp;${{&amp;nbsp;secrets.DOCKERHUB_USERNAME&amp;nbsp;}}/qtudy-server:prod &lt;br /&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;때까지&amp;nbsp;대기 &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;echo&amp;nbsp;&quot;Waiting&amp;nbsp;for&amp;nbsp;the&amp;nbsp;new&amp;nbsp;container&amp;nbsp;to&amp;nbsp;be&amp;nbsp;ready...&quot; &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;sleep&amp;nbsp;10 &lt;br /&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;새&amp;nbsp;컨테이너가&amp;nbsp;정상적으로&amp;nbsp;실행&amp;nbsp;중인지&amp;nbsp;확인 &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;until&amp;nbsp;[[&amp;nbsp;&quot;$(curl&amp;nbsp;-s&amp;nbsp;-o&amp;nbsp;/dev/null&amp;nbsp;-w&amp;nbsp;''%{http_code}''&amp;nbsp;http://localhost:$NEW_PORT/actuator/health)&quot;&amp;nbsp;==&amp;nbsp;&quot;200&quot;&amp;nbsp;]];&amp;nbsp;do &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;echo&amp;nbsp;&quot;Waiting&amp;nbsp;for&amp;nbsp;the&amp;nbsp;new&amp;nbsp;container&amp;nbsp;to&amp;nbsp;respond&amp;nbsp;with&amp;nbsp;HTTP&amp;nbsp;200...&quot; &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;sleep&amp;nbsp;2 &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;done &lt;br /&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;echo&amp;nbsp;&quot;New&amp;nbsp;container&amp;nbsp;is&amp;nbsp;ready.&amp;nbsp;Switching&amp;nbsp;Nginx&amp;nbsp;configuration.&quot; &lt;br /&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;Nginx&amp;nbsp;설정&amp;nbsp;파일&amp;nbsp;교체&amp;nbsp;및&amp;nbsp;재시작 &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;sudo&amp;nbsp;ln&amp;nbsp;-sf&amp;nbsp;$NEW_CONF&amp;nbsp;/etc/nginx/sites-enabled/current &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;sudo&amp;nbsp;systemctl&amp;nbsp;reload&amp;nbsp;nginx &lt;br /&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;및&amp;nbsp;제거 &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;sudo&amp;nbsp;docker&amp;nbsp;stop&amp;nbsp;$OLD_CONTAINER &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;sudo&amp;nbsp;docker&amp;nbsp;rm&amp;nbsp;$OLD_CONTAINER &lt;br /&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;Docker&amp;nbsp;컨테이너&amp;nbsp;확인 &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;sudo&amp;nbsp;docker&amp;nbsp;ps &lt;br /&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;Docker&amp;nbsp;이미지&amp;nbsp;정리 &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;sudo&amp;nbsp;docker&amp;nbsp;image&amp;nbsp;prune&amp;nbsp;-f&lt;/blockquote&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;위의 배포 스크립트를 적용한 결과 nginx를 활용해 docker 기반 무중단 배포를 손쉽게 적용시킬 수 있었습니다.&lt;br /&gt;docker를 사용해 환경에 제약받지않고 무중단 배포를 적용할 수 있었고, 또한 컨테이너를 기반으로 blue, green을 나눴기때문에 nginx 환경설정도 그렇게 많은 시간을 들이지 않고 개발이 가능했었습니다.&lt;br /&gt;&lt;br /&gt;이렇게 다양한 방식을 고려하고 무중단 배포를 도입해보며 다양한 지식을 얻을 수 있었고 평소에 깊게 파보지못했던 nginx를 깊게 공부할 수 있는 정말 좋은 기회였습니다.&amp;nbsp;&lt;/p&gt;</description>
      <category>네트워크</category>
      <category>docker</category>
      <category>elb</category>
      <category>nginx</category>
      <category>네트워크</category>
      <category>무중단배포</category>
      <author>tioon</author>
      <guid isPermaLink="true">https://tioon.tistory.com/199</guid>
      <comments>https://tioon.tistory.com/199#entry199comment</comments>
      <pubDate>Tue, 2 Jul 2024 02:27:16 +0900</pubDate>
    </item>
    <item>
      <title>NCP 활용 프로젝트 소개 -  flipit</title>
      <link>https://tioon.tistory.com/198</link>
      <description>&lt;blockquote data-ke-style=&quot;style1&quot;&gt;&lt;span style=&quot;font-family: 'Noto Serif KR';&quot;&gt;프로젝트 소개&lt;/span&gt;&lt;/blockquote&gt;
&lt;p style=&quot;color: #333333; text-align: start;&quot; data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p style=&quot;color: #333333; text-align: start;&quot; data-ke-size=&quot;size16&quot;&gt;&lt;b&gt;  플리빗은 세상과 SNS로 대화하는 현세대의 소통 방법을 개선하고자 하는 Q&amp;amp;A 플랫폼입니다.&lt;br /&gt;궁금증을 질문하고 답변을 통해 자신을 표현하여, 소통 과정에서 느끼는 니즈를 충족시키고 어려움을 해결합니다.&lt;/b&gt;&lt;/p&gt;
&lt;p style=&quot;color: #333333; text-align: start;&quot; data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p&gt;&lt;figure class=&quot;imageblock alignCenter&quot; data-ke-mobileStyle=&quot;widthOrigin&quot; data-origin-width=&quot;1280&quot; data-origin-height=&quot;720&quot;&gt;&lt;span data-url=&quot;https://blog.kakaocdn.net/dn/bsEsf4/btsH8G6teAk/idfA1v7uWmIABEtKmYfwgk/img.png&quot; data-phocus=&quot;https://blog.kakaocdn.net/dn/bsEsf4/btsH8G6teAk/idfA1v7uWmIABEtKmYfwgk/img.png&quot;&gt;&lt;img src=&quot;https://blog.kakaocdn.net/dn/bsEsf4/btsH8G6teAk/idfA1v7uWmIABEtKmYfwgk/img.png&quot; srcset=&quot;https://img1.daumcdn.net/thumb/R1280x0/?scode=mtistory2&amp;fname=https%3A%2F%2Fblog.kakaocdn.net%2Fdn%2FbsEsf4%2FbtsH8G6teAk%2FidfA1v7uWmIABEtKmYfwgk%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;1280&quot; height=&quot;720&quot; data-origin-width=&quot;1280&quot; data-origin-height=&quot;720&quot;/&gt;&lt;/span&gt;&lt;/figure&gt;
&lt;/p&gt;
&lt;p style=&quot;color: #333333; text-align: start;&quot; data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p style=&quot;color: #333333; text-align: start;&quot; data-ke-size=&quot;size16&quot;&gt;저희 프로젝트의 서비스 소개글은 아니니 이 부분은 빠르게 스킵하도록 하겠습니다!!&lt;/p&gt;
&lt;p style=&quot;color: #333333; text-align: start;&quot; data-ke-size=&quot;size16&quot;&gt;더 자세한 내용을 보시고 싶으시면 아래 링크에 들어와주세요!&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;a href=&quot;https://github.com/Team-baebae&quot; target=&quot;_blank&quot; rel=&quot;noopener&amp;nbsp;noreferrer&quot;&gt;https://github.com/Team-baebae&lt;/a&gt;&lt;/p&gt;
&lt;p style=&quot;color: #333333; text-align: start;&quot; data-ke-size=&quot;size16&quot;&gt;&lt;a href=&quot;https://www.flipit.co.kr/&quot; target=&quot;_blank&quot; rel=&quot;noopener&amp;nbsp;noreferrer&quot;&gt;https://www.flipit.co.kr/&lt;/a&gt;&lt;/p&gt;
&lt;p style=&quot;color: #333333; text-align: start;&quot; data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p style=&quot;color: #333333; text-align: start;&quot; data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p style=&quot;color: #333333; text-align: start;&quot; data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;blockquote data-ke-style=&quot;style1&quot;&gt;&lt;span style=&quot;font-family: 'Noto Serif KR';&quot;&gt;프로젝트 설계&lt;/span&gt;&lt;/blockquote&gt;
&lt;p style=&quot;color: #333333; text-align: start;&quot; data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p style=&quot;color: #333333; text-align: start;&quot; data-ke-size=&quot;size16&quot;&gt;이번 프로젝트를 설계하면서 정말 많은 고민을 했습니다. 어떻게 하면 좀 더 안정적이고 확장성 있는 설계를 할 수 있는지 정말 많은 자료를 찾아보고 네이버 클라우드 공식문서도 많이 봤던거 같아요.&lt;/p&gt;
&lt;p style=&quot;color: #333333; text-align: start;&quot; data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p style=&quot;color: #333333; text-align: start;&quot; data-ke-size=&quot;size16&quot;&gt;기본적으로 Q&amp;amp;A 플랫폼을 기반으로하기 때문에 실시간 알림 기능도 들어가야 했고, 사용자가 몰렸을때를 대비한 안정성부분도 생각했어야 했습니다. 또한, 개발기간이 그렇게 길지 않았기에 러닝커브를 최소화한 방향으로 설계를 시작하였습니다.&lt;br /&gt;따라서, 저희 백엔드 팀에서 설계를 진행하며 생각을 했을때, 서버 요구사항은 다음과 같았습니다.&lt;/p&gt;
&lt;p style=&quot;color: #333333; text-align: start;&quot; data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;ul style=&quot;list-style-type: disc;&quot; data-ke-list-type=&quot;disc&quot;&gt;
&lt;li&gt;&lt;span style=&quot;color: #000000;&quot;&gt;&lt;b&gt;트래픽이 몰릴때 안정적인 확장 가능해야함&lt;/b&gt;&lt;/span&gt;&lt;/li&gt;
&lt;li&gt;&lt;span style=&quot;color: #000000;&quot;&gt;&lt;b&gt;외부에 서버 정보 노출을 최소화 하여 보안 강화&lt;/b&gt;&lt;/span&gt;&lt;/li&gt;
&lt;li&gt;&lt;span style=&quot;color: #000000;&quot;&gt;&lt;b&gt;사용자에게 빠르고 정확한 실시간 알림 전송&lt;/b&gt;&lt;/span&gt;&lt;/li&gt;
&lt;li&gt;&lt;span style=&quot;color: #000000;&quot;&gt;&lt;b&gt;배포 다운타임 최소화로 사용자 편의성 증가&lt;/b&gt;&lt;/span&gt;&lt;/li&gt;
&lt;li&gt;&lt;span style=&quot;color: #000000;&quot;&gt;&lt;b&gt;개발 러닝커브 최소화한 방법&lt;/b&gt;&lt;/span&gt;&lt;/li&gt;
&lt;/ul&gt;
&lt;p style=&quot;color: #333333; text-align: start;&quot; data-ke-size=&quot;size16&quot;&gt;이런 요구사항들을 충족시키기위해 다양한 아키텍처를 생각하면서 설계를 시작했습니다.&lt;br /&gt;&lt;br /&gt;&lt;/p&gt;
&lt;p style=&quot;color: #333333; text-align: start;&quot; data-ke-size=&quot;size16&quot;&gt;우선, 안정적인 확장을 적용하기위해 AutoScaling과 Kubernates등의 기술들을 고민을 하였고,&lt;br /&gt;실시간 알림 전송 기능을 적용하기위해 Message Queue, FireBase 등을 고민을 하였으며,&lt;br /&gt;다운타임을 최소화한 무중단 배포를 적용하기위해 Jenkins, Github Action, Source Deploy등의 다양한 기술들을 고민을 하였습니다.&lt;br /&gt;이외에도, 보안을 강화하기 위해 VPC 및 SSL인증 부분도 많은 고민을 하였습니다.&lt;/p&gt;
&lt;p style=&quot;color: #333333; text-align: start;&quot; data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p style=&quot;color: #333333; text-align: start;&quot; data-ke-size=&quot;size16&quot;&gt;프로젝트 설계는 블로그 및 네이버 클라우드 공식 문서 등 다양한 자료를 참고하여 진행었으며,&lt;br /&gt;이러한 결과로 나온 아키텍처는 아래에서 사진과 함께 자세히 설명드리도록 하겠습니다.&lt;/p&gt;
&lt;p style=&quot;color: #333333; text-align: start;&quot; data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p style=&quot;color: #333333; text-align: start;&quot; data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p style=&quot;color: #333333; text-align: start;&quot; data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p style=&quot;color: #333333; text-align: start;&quot; data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p style=&quot;color: #333333; text-align: start;&quot; data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p style=&quot;color: #333333; text-align: start;&quot; data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p style=&quot;color: #333333; text-align: start;&quot; data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;blockquote data-ke-style=&quot;style1&quot;&gt;&lt;span style=&quot;font-family: 'Noto Serif KR';&quot;&gt;클라우드 아키텍처 및 설명&lt;/span&gt;&lt;/blockquote&gt;
&lt;p style=&quot;color: #333333; text-align: start;&quot; data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p style=&quot;color: #333333; text-align: start;&quot; data-ke-size=&quot;size16&quot;&gt;이번에 Green Developers 프로그램을 참여하면서 정말 많은 Ncloud의 서비스들을 활용하였습니다.&lt;br /&gt;우선 먼저 저희 프로젝트의 아키텍처에 대해서 보여드리고 하나씩 설명을 드리겠습니다.&lt;/p&gt;
&lt;p&gt;&lt;figure class=&quot;imageblock alignCenter&quot; data-ke-mobileStyle=&quot;widthOrigin&quot; data-filename=&quot;blob&quot; data-origin-width=&quot;1280&quot; data-origin-height=&quot;901&quot;&gt;&lt;span data-url=&quot;https://blog.kakaocdn.net/dn/pA77n/btsIus7OqQf/FNBmST5EoQXNotkdkUJrZ0/img.png&quot; data-phocus=&quot;https://blog.kakaocdn.net/dn/pA77n/btsIus7OqQf/FNBmST5EoQXNotkdkUJrZ0/img.png&quot; data-alt=&quot;FLipit 서비스 아키텍처&quot;&gt;&lt;img src=&quot;https://blog.kakaocdn.net/dn/pA77n/btsIus7OqQf/FNBmST5EoQXNotkdkUJrZ0/img.png&quot; srcset=&quot;https://img1.daumcdn.net/thumb/R1280x0/?scode=mtistory2&amp;fname=https%3A%2F%2Fblog.kakaocdn.net%2Fdn%2FpA77n%2FbtsIus7OqQf%2FFNBmST5EoQXNotkdkUJrZ0%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;921&quot; height=&quot;648&quot; data-filename=&quot;blob&quot; data-origin-width=&quot;1280&quot; data-origin-height=&quot;901&quot;/&gt;&lt;/span&gt;&lt;figcaption&gt;FLipit 서비스 아키텍처&lt;/figcaption&gt;
&lt;/figure&gt;
&lt;/p&gt;
&lt;p style=&quot;color: #333333; text-align: start;&quot; data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p style=&quot;color: #333333; text-align: start;&quot; data-ke-size=&quot;size16&quot;&gt;저희의 아키텍처는 위와 같습니다.&lt;br /&gt;아키텍처 설계때,&amp;nbsp; 시스템의 안정성과 확장성을 우선적으로 중점을 뒀고,&lt;br /&gt;시스템의 안정성과 확장성 만큼 중요한게 빠른 기간내에 개발이 가능할 정도의 적정 수준의 러닝커브였기 때문에 이 부분도 놓치지 않으려고 노력하며 설계를 했습니다.&lt;br /&gt;&lt;br /&gt;&lt;/p&gt;
&lt;p style=&quot;color: #333333; text-align: start;&quot; data-ke-size=&quot;size16&quot;&gt;이 아키텍처 정보를 기반으로 VPC부터 차례대로 자세한 설명을 하도록 하겠습니다.&lt;/p&gt;
&lt;p style=&quot;color: #333333; text-align: start;&quot; data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p style=&quot;color: #333333; text-align: start;&quot; data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p style=&quot;color: #333333; text-align: start;&quot; data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p style=&quot;color: #333333; text-align: start;&quot; data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;h4 style=&quot;color: #000000; text-align: start;&quot; data-ke-size=&quot;size20&quot;&gt;&lt;span style=&quot;color: #000000;&quot;&gt;&lt;b&gt;VPC&lt;/b&gt;&lt;/span&gt;&lt;/h4&gt;
&lt;blockquote style=&quot;color: #666666; text-align: left;&quot; data-ke-style=&quot;style2&quot;&gt;VPC(Virtual Private Cloud)는 퍼블릭 클라우드 환경에서 사용할 수 있는 고객 전용 사설 네트워크입니다. 다른 네트워크와 논리적으로 분리되어 있어 IT 인프라를 안전하게 구축하고 간편하게 관리할 수 있습니다. 또한 기존의 데이터 센터 네트워크와 유사하게 구현할 수 있습니다. - Ncloud 공식문서 -&amp;nbsp;&lt;/blockquote&gt;
&lt;p style=&quot;color: #333333; text-align: start;&quot; data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p&gt;&lt;figure class=&quot;imageblock alignCenter&quot; data-ke-mobileStyle=&quot;widthOrigin&quot; data-origin-width=&quot;624&quot; data-origin-height=&quot;714&quot;&gt;&lt;span data-url=&quot;https://blog.kakaocdn.net/dn/Dwksh/btsIuvpWbYi/nfqhBvpj4ZnUW2Z8K3NWkk/img.png&quot; data-phocus=&quot;https://blog.kakaocdn.net/dn/Dwksh/btsIuvpWbYi/nfqhBvpj4ZnUW2Z8K3NWkk/img.png&quot;&gt;&lt;img src=&quot;https://blog.kakaocdn.net/dn/Dwksh/btsIuvpWbYi/nfqhBvpj4ZnUW2Z8K3NWkk/img.png&quot; srcset=&quot;https://img1.daumcdn.net/thumb/R1280x0/?scode=mtistory2&amp;fname=https%3A%2F%2Fblog.kakaocdn.net%2Fdn%2FDwksh%2FbtsIuvpWbYi%2FnfqhBvpj4ZnUW2Z8K3NWkk%2Fimg.png&quot; onerror=&quot;this.onerror=null; this.src='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png'; this.srcset='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png';&quot; loading=&quot;lazy&quot; width=&quot;500&quot; height=&quot;572&quot; data-origin-width=&quot;624&quot; data-origin-height=&quot;714&quot;/&gt;&lt;/span&gt;&lt;/figure&gt;
&lt;/p&gt;
&lt;p style=&quot;color: #333333; text-align: start;&quot; data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p style=&quot;color: #333333; text-align: start;&quot; data-ke-size=&quot;size16&quot;&gt;VPC는 쉽게 말해,&amp;nbsp; 클라우드 환경에서 public과 private subnet으로 나눠 가상으로 사설 네트워크 처럼 환경을 구축할 수 있는 서비스를 뜻합니다.&lt;br /&gt;VPC를 적용해 외부로의 요청은 Internet GateWay통해 Public Subnet에 존재하는 Application에만 들어올 수 있고, 그 외의 Private Subnet에 존재하는 Application에는 접근을 하지 못하게 할 수 있습니다.&lt;/p&gt;
&lt;p style=&quot;color: #333333; text-align: start;&quot; data-ke-size=&quot;size16&quot;&gt;여기서 다음과 같은 의문점이 들 수도 있습니다.&lt;/p&gt;
&lt;p style=&quot;color: #333333; text-align: start;&quot; data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p style=&quot;color: #333333; text-align: start;&quot; data-ke-size=&quot;size18&quot;&gt;&lt;span style=&quot;color: #ee2323;&quot;&gt;&lt;b&gt;&quot;그냥 네이버에서 기본적으로 제공되는 ACG(Access Control Group)를 사용하면 해결되는거 아닌가??&quot;&lt;/b&gt;&lt;/span&gt;&lt;/p&gt;
&lt;p style=&quot;color: #333333; text-align: start;&quot; data-ke-size=&quot;size18&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p style=&quot;color: #333333; text-align: start;&quot; data-ke-size=&quot;size16&quot;&gt;물론 ACG를 이용해서 어느정도 안전한 보안환경을 구축할 수 있습니다.&lt;/p&gt;
&lt;p style=&quot;color: #333333; text-align: start;&quot; data-ke-size=&quot;size16&quot;&gt;다만, 그냥 공개된 공간에서 ACG만을 의존해 보안을 구축하게된다면 다음과 같은 문제점이 발생할 수 있습니다.&lt;/p&gt;
&lt;p style=&quot;color: #333333; text-align: start;&quot; data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;ol style=&quot;list-style-type: decimal;&quot; data-ke-list-type=&quot;decimal&quot;&gt;
&lt;li&gt;&lt;span style=&quot;color: #000000;&quot;&gt;&lt;b&gt;외부에 모든 서버의 공인 IP가 노출되며, 외부에서 직접적으로 접근이 가능해짐.&lt;/b&gt;&lt;/span&gt;&lt;/li&gt;
&lt;li&gt;&lt;span style=&quot;color: #000000;&quot;&gt;&lt;b&gt;외부에서 노출된 공인 IP기반으로 DDOS 공격 및 포트스캐닝 공격이 가능해짐.&lt;/b&gt;&lt;/span&gt;&lt;/li&gt;
&lt;li&gt;&lt;span style=&quot;color: #000000;&quot;&gt;&lt;b&gt;클라우드 규모가 커질 수록, 공인 IP 관리 비용이 늘어남.&lt;/b&gt;&lt;/span&gt;&lt;/li&gt;
&lt;li&gt;&lt;span style=&quot;color: #000000;&quot;&gt;&lt;b&gt;복잡해지는 ACG 규칙으로 관리가 어려워짐.&lt;/b&gt;&lt;/span&gt;&lt;/li&gt;
&lt;/ol&gt;
&lt;p style=&quot;color: #333333; text-align: start;&quot; data-ke-size=&quot;size16&quot;&gt;이렇게 ACG만으로 보안을 구축하는건 보안 관점에서 매우 위험한 일이고, 비효율적입니다.&lt;br /&gt;따라서, 저희 백엔드팀은 Ncloud의 VPC 서비스를 이용해서&amp;nbsp; 기본적인 네트워크 환경을 구축했습니다.&lt;/p&gt;
&lt;p style=&quot;color: #333333; text-align: start;&quot; data-ke-size=&quot;size16&quot;&gt;VPC의 세부 정보는 다음과 같습니다.&lt;/p&gt;
&lt;p style=&quot;color: #333333; text-align: start;&quot; data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;ul style=&quot;list-style-type: disc;&quot; data-ke-list-type=&quot;disc&quot;&gt;
&lt;li&gt;&lt;b&gt;Public Subnet&lt;/b&gt;&amp;nbsp;
&lt;ul style=&quot;list-style-type: disc;&quot; data-ke-list-type=&quot;disc&quot;&gt;
&lt;li&gt;Load Balancer&lt;/li&gt;
&lt;li&gt;Bastion Host Server&lt;/li&gt;
&lt;li&gt;NAT GateWay&lt;/li&gt;
&lt;/ul&gt;
&lt;/li&gt;
&lt;li&gt;&lt;b&gt;Private Subnet&lt;/b&gt;
&lt;ul style=&quot;list-style-type: disc;&quot; data-ke-list-type=&quot;disc&quot;&gt;
&lt;li&gt;AutoScaling Group&lt;/li&gt;
&lt;li&gt;Server&lt;/li&gt;
&lt;li&gt;MySql DB&lt;/li&gt;
&lt;li&gt;Redis DB&lt;/li&gt;
&lt;/ul&gt;
&lt;/li&gt;
&lt;/ul&gt;
&lt;p style=&quot;color: #333333; text-align: start;&quot; data-ke-size=&quot;size16&quot;&gt;위 처럼 기본적으로 보안이 중요한 실제 운영 서버와 DB들은 Private Subnet에 두어 외부로 노출이 되지 않게끔 설계를 하였으며, Load Balancer를 Public Subnet에 두어, 앞단에서 Internet GateWay를 통해 들어오는 요청들을 받아, 각 Server에게 트래픽을 분산시킬 수 있도록 적용하였습니다.&lt;/p&gt;
&lt;p style=&quot;color: #333333; text-align: start;&quot; data-ke-size=&quot;size16&quot;&gt;&lt;br /&gt;또한, 테스트서버 겸 Bastion Host 역할을 하는 Server를&amp;nbsp; Public Subnet에 두었습니다. 이 Server는 실제 운영하는 서버가 아닌, 운영 서버 배포전에&lt;span&gt;&amp;nbsp;&lt;/span&gt;&lt;span style=&quot;color: #333333; text-align: start;&quot;&gt;문제가 없는지&lt;span&gt;&amp;nbsp;&lt;/span&gt;&lt;/span&gt;&lt;span&gt;&amp;nbsp;&lt;/span&gt;백엔드에서 QA를 진행하는 서버입니다.&lt;br /&gt;또한 추가적으로,&amp;nbsp; Private Subnet은 외부접속으로부터 막혀있어, ssh 접속이 되지않기때문에 로컬에서 접속이 어렵습니다.&lt;br /&gt;이때 이 Bastion Host가 점프 서버의 역할을 하여 로컬에서 ssh 접속을 할 수있게끔 하는 역할을 하도록 하였습니다.&lt;/p&gt;
&lt;p style=&quot;color: #333333; text-align: start;&quot; data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p style=&quot;color: #333333; text-align: start;&quot; data-ke-size=&quot;size16&quot;&gt;이런식으로 저희 서버에선 VPC가 구축되어 있으며, NAT GateWay 부분은 아래에서 따로 설명을 드리겠습니다.&lt;/p&gt;
&lt;p style=&quot;color: #333333; text-align: start;&quot; data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p style=&quot;color: #333333; text-align: start;&quot; data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p style=&quot;color: #333333; text-align: start;&quot; data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p style=&quot;color: #333333; text-align: start;&quot; data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;h4 style=&quot;color: #000000; text-align: start;&quot; data-ke-size=&quot;size20&quot;&gt;&lt;span style=&quot;color: #000000;&quot;&gt;&lt;b&gt;&lt;span style=&quot;font-family: AppleSDGothicNeo-Regular, 'Malgun Gothic', '맑은 고딕', dotum, 돋움, sans-serif;&quot;&gt;Load Balancer&lt;/span&gt;&lt;/b&gt;&lt;/span&gt;&lt;/h4&gt;
&lt;blockquote style=&quot;color: #666666; text-align: left;&quot; data-ke-style=&quot;style2&quot;&gt;Load Balancer는 수신 트래픽을 다수의 서버로 분산시키는 서비스입니다. 등록된 멤버 서버로 수신 트래픽을 분산시켜 가용성을 높이고 시스템 가동률을 조절하는 역할을 수행합니다. 이로 인해 워크로드의 가용성을 향상시켜 예기치 못한 서버의 장애 또는 예정된 변경 작업 등에 대하여 중단 없이 대응할 수 있도록 지원합니다. - Ncloud 공식문서 -&amp;nbsp;&lt;/blockquote&gt;
&lt;p style=&quot;color: #333333; text-align: start;&quot; data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p style=&quot;color: #333333; text-align: start;&quot; data-ke-size=&quot;size16&quot;&gt;Load Balancer는, 서버로 들어오는 트래픽을 분산시켜 안정성과 확장성을 보장시켜주는 서비스입니다.&lt;/p&gt;
&lt;p style=&quot;color: #333333; text-align: start;&quot; data-ke-size=&quot;size16&quot;&gt;Ncloud에서 제공하는 Load Balancer의 종류는 여러개가 있습니다. Network Load Balancer, Network Proxy Load Balancer, Application Load Balancer, Inline Load Balancer가 있는데요.&lt;br /&gt;저희는 여기서 Application Load Balancer를 사용했는데, 이유는 다음과 같습니다.&lt;/p&gt;
&lt;p style=&quot;color: #333333; text-align: start;&quot; data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;ol style=&quot;list-style-type: decimal;&quot; data-ke-list-type=&quot;decimal&quot;&gt;
&lt;li&gt;&lt;span style=&quot;color: #000000;&quot;&gt;&lt;b&gt;웹 기반 서버이다 보니 80포트(HTTP), 443포트(HTTPS)만 사용함&lt;/b&gt;&lt;/span&gt;&lt;/li&gt;
&lt;li&gt;&lt;span style=&quot;color: #000000;&quot;&gt;&lt;b&gt;SSL을 Load Balancer에 적용시켜야함.&lt;/b&gt;&lt;/span&gt;&lt;/li&gt;
&lt;li&gt;&lt;span style=&quot;color: #000000;&quot;&gt;&lt;b&gt;추후에 서버가 복잡해질 시, URL정보를 기반으로 트래픽을 나눌 가능성 존재&lt;/b&gt;&lt;/span&gt;&lt;/li&gt;
&lt;/ol&gt;
&lt;p style=&quot;color: #333333; text-align: start;&quot; data-ke-size=&quot;size16&quot;&gt;위와 같은 이유로 Application Load Balancer를 적용하였습니다.&lt;br /&gt;좀 더 자세히 설명을 하자면, 저희 서버는 웹 기반의 서버입니다. 즉, OSI 7 계층 기준으로 7계층(Application)의 정보를 활용하는 서버입니다. 따라서 들어오는 트래픽들은 거의 HTTP, HTTPS 기반의 트래픽일 거고, 그 아래의 Transport , Network Layer를 기반으로 트래픽을 나누는 상황은 나오지 않기에 Application Load Balancer외에 다른 Load Balancer는 저희에게 필요하지 않았습니다.&lt;br /&gt;또한, 저희는 DNS와 Certificate Manager를 통해 Load Balancer에 추가적인 설정을 해야하는데, 여기서 SSL 인증서를 추가하기 위해선 7계층에서 활동하는 Application Load Balancer가 필요했습니다.&lt;/p&gt;
&lt;p style=&quot;color: #333333; text-align: start;&quot; data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p style=&quot;color: #333333; text-align: start;&quot; data-ke-size=&quot;size16&quot;&gt;따라서, 저희는 이러한 생각을 바탕으로 Application Load Balancer를 활용하였으며, Public Subnet에 두어, 외부로부터 트래픽을 받을 수 있도록 설정하였습니다.&lt;/p&gt;
&lt;p style=&quot;color: #333333; text-align: start;&quot; data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p style=&quot;color: #333333; text-align: start;&quot; data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p style=&quot;color: #333333; text-align: start;&quot; data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p style=&quot;color: #333333; text-align: start;&quot; data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;h4 style=&quot;color: #000000; text-align: start;&quot; data-ke-size=&quot;size20&quot;&gt;&lt;span style=&quot;color: #000000;&quot;&gt;&lt;b&gt;DNS &amp;amp; Certificate Manager&lt;/b&gt;&lt;/span&gt;&lt;/h4&gt;
&lt;blockquote style=&quot;color: #666666; text-align: left;&quot; data-ke-style=&quot;style2&quot;&gt;Global DNS는 인터넷에서 사용하는 도메인 이름을 실제 접속 주소로 식별할 수 있도록 도와주는 서비스입니다. 개인이 직접 운영하기 어려운 DNS(Domain Name System) 서버를 클라우드 형태로 제공하여, 사용자가 서비스 운영에 필요한 도메인을 쉽고 편리하게 관리할 수 있습니다. - Ncloud 공식문서 -&lt;br /&gt;&lt;br /&gt;&lt;/blockquote&gt;
&lt;blockquote style=&quot;color: #666666; text-align: left;&quot; data-ke-style=&quot;style2&quot;&gt;Certificate Manager는 연계 서비스(Load Balancer, CDN+ 등)에서 사용할 인증서를 발급, 등록하고 관리할 수 있는 서비스입니다. - Ncloud 공식문서 -&lt;/blockquote&gt;
&lt;p style=&quot;color: #333333; text-align: start;&quot; data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p style=&quot;color: #333333; text-align: start;&quot; data-ke-size=&quot;size16&quot;&gt;저희는 앞서 말씀드렸듯이, 사용자의 정보를 통신을 통해 주고받기 때문에 HTTPS 통신이 필수적이였으며, 또한 실제 사용자분들의 접근성을 향상시키기 위해 도메인을 적용시켜야 했습니다.&lt;br /&gt;이에, 이걸 적용시키기 위해 다양한 서비스들을 비교한 결과 Ncloud에서 제공하는 Global DNS와 Certificate Manager를 적용을 하였습니다.&lt;br /&gt;이유는 다음과 같습니다.&lt;/p&gt;
&lt;p style=&quot;color: #333333; text-align: start;&quot; data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;ol style=&quot;list-style-type: decimal;&quot; data-ke-list-type=&quot;decimal&quot;&gt;
&lt;li&gt;&lt;span style=&quot;color: #000000;&quot;&gt;&lt;b&gt;같은 Ncloud 서비스 이다보니 개발 편의성 증가&lt;/b&gt;&lt;/span&gt;&lt;/li&gt;
&lt;li&gt;&lt;span style=&quot;color: #000000;&quot;&gt;&lt;b&gt;가비아의 경우 네임서버에서 DNSSEC이 적용되지않아 보안취약점 존재&lt;/b&gt;&lt;/span&gt;&lt;/li&gt;
&lt;/ol&gt;
&lt;p style=&quot;color: #333333; text-align: start;&quot; data-ke-size=&quot;size16&quot;&gt;위와 같은 이유로 Ncloud의 서비스를활용하였으며, 각각 적용하여 Load Balancer에 적용을 하였습니다.&lt;/p&gt;
&lt;p style=&quot;color: #333333; text-align: start;&quot; data-ke-size=&quot;size16&quot;&gt;저도 이번에 개발을 하다 처음알게된 사실인데 가비아에서 기본적으로 제공하는 자체 네임서버의 경우 다음과 같이 DNSSEC을 적용하지않는다 라는 정보가 나와있습니다.&amp;nbsp;&lt;/p&gt;
&lt;p&gt;&lt;figure class=&quot;imageblock alignCenter&quot; data-ke-mobileStyle=&quot;widthOrigin&quot; data-origin-width=&quot;1336&quot; data-origin-height=&quot;65&quot;&gt;&lt;span data-url=&quot;https://blog.kakaocdn.net/dn/bzbuH7/btsIvxG3oct/rZumJGUtRa4eAMKRw9OhF0/img.png&quot; data-phocus=&quot;https://blog.kakaocdn.net/dn/bzbuH7/btsIvxG3oct/rZumJGUtRa4eAMKRw9OhF0/img.png&quot;&gt;&lt;img src=&quot;https://blog.kakaocdn.net/dn/bzbuH7/btsIvxG3oct/rZumJGUtRa4eAMKRw9OhF0/img.png&quot; srcset=&quot;https://img1.daumcdn.net/thumb/R1280x0/?scode=mtistory2&amp;fname=https%3A%2F%2Fblog.kakaocdn.net%2Fdn%2FbzbuH7%2FbtsIvxG3oct%2FrZumJGUtRa4eAMKRw9OhF0%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;1336&quot; height=&quot;65&quot; data-origin-width=&quot;1336&quot; data-origin-height=&quot;65&quot;/&gt;&lt;/span&gt;&lt;/figure&gt;
&lt;/p&gt;
&lt;p style=&quot;color: #333333; text-align: start;&quot; data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p style=&quot;color: #333333; text-align: start;&quot; data-ke-size=&quot;size16&quot;&gt;DNSSEC이란 DNS를 더 안전하게 만들어주는 기술입니다. 즉, 브라우저에서 웹사이트의 도메인 이름을 입력하면 DNS가 이 도메인 이름을 숫자로 된 IP 주소로 바꾸게 되는데, 해커들이 이 과정에서 끼어들어 이상한 IP 주소를 주입할 수 있습니다.이렇게 되면, 우리가 의도하지 않은 위험한 웹사이트에 접속이 될 수도 있습니다.&lt;br /&gt;이러한 공격방법을 DNS 스푸핑 공격 이라고하는데 DNSSEC은 DNS 데이터에 디지털 서명을 추가하여 공개키/개인키 구조로 검증을 할 수 있게 합니다.&lt;/p&gt;
&lt;p style=&quot;color: #333333; text-align: start;&quot; data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p style=&quot;color: #333333; text-align: start;&quot; data-ke-size=&quot;size16&quot;&gt;저희의 서버의 경우 10~20대 초반의 연령대가 주 타켓입니다. 따라서 이러한 DNS 스푸핑 공격으로 인해, 불건전한 사이트 등에 접속을 하게 된다면 굉장히 치명적일 것이라 생각을 하였습니다. 따라서, 이러한 공격을 막기위해 가비아에서 구매한 도메인에 NCP의 DNS를 적용시켰고, 이로인해 이러한 보안 취약점을 개선시킬 수 있었습니다.&lt;br /&gt;&lt;br /&gt;&lt;/p&gt;
&lt;p style=&quot;color: #333333; text-align: start;&quot; data-ke-size=&quot;size16&quot;&gt;여기서 추가적으로, 다음과 같은 의문점이 들 수도 있습니다.&lt;/p&gt;
&lt;p style=&quot;color: #333333; text-align: start;&quot; data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p style=&quot;color: #333333; text-align: start;&quot; data-ke-size=&quot;size18&quot;&gt;&lt;span style=&quot;color: #ee2323;&quot;&gt;&lt;b&gt;&quot;Server에는 적용 안하고 왜 Load Balancer에만 적용을 시키는가?&quot;&lt;/b&gt;&lt;/span&gt;&lt;/p&gt;
&lt;p style=&quot;color: #333333; text-align: start;&quot; data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p style=&quot;color: #333333; text-align: start;&quot; data-ke-size=&quot;size16&quot;&gt;저희는 앞에서 VPC를 적용하여 외부 트래픽은 Load Balancer가 받고, 뒷단의 Private 서버들에게 트래픽을 분산시켜 전달하는 구조입니다.&lt;br /&gt;여기서, Load Balancer는 외부와 직접 통신을 하기때문에, 통신의 내용이 외부 인터넷에 노출되는 상황입니다.&lt;br /&gt;따라서, 악의적인 사용자가 있다면 중간에서 패킷을 탈취해서 내부 내용을 볼 수도 있고, 직접 패킷의 내용을 수정할수도 있습니다. 이를 막기 위해 SSL 인증을 적용시켜 인증키로 패킷 내부 내용을 암호화 하여 외부에서 제 3자가 패킷의 내부 내용을 볼 수 없도록 하는 것입니다.&lt;/p&gt;
&lt;p style=&quot;color: #333333; text-align: start;&quot; data-ke-size=&quot;size16&quot;&gt;&lt;br /&gt;하지만 Load Balancer가 Server에게 트래픽을 분산시킬때의 상황은 반대로 외부 인터넷이 아닌 내부 사설 인터넷망에서 통신이 이루어 집니다. 즉, 이 패킷들은 외부에 노출될 위험이 없다는 뜻입니다. 따라서 내부에서 일어나는 통신들은 암호화가 전혀 필요없기 때문에 내부 Server는 SSL 적용을 하지 않는 것 입니다. 또한 추가적으로, 내부에선 외부 사용자가 직접 접속하는게 아니므로, 도메인 적용도 할 필요가 없습니다.&lt;/p&gt;
&lt;p style=&quot;color: #333333; text-align: start;&quot; data-ke-size=&quot;size16&quot;&gt;&lt;br /&gt;따라서, 저희는 앞단의 Application Load Balancer에게만 SSL 적용 및 도메인 적용을 하였습니다.&lt;/p&gt;
&lt;p style=&quot;color: #333333; text-align: start;&quot; data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p style=&quot;color: #333333; text-align: start;&quot; data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p style=&quot;color: #333333; text-align: start;&quot; data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p style=&quot;color: #333333; text-align: start;&quot; data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;h4 style=&quot;color: #000000; text-align: start;&quot; data-ke-size=&quot;size20&quot;&gt;&lt;span style=&quot;color: #000000;&quot;&gt;&lt;b&gt;AutoScaling&lt;/b&gt;&lt;/span&gt;&lt;/h4&gt;
&lt;blockquote style=&quot;color: #666666; text-align: left;&quot; data-ke-style=&quot;style2&quot;&gt;Auto Scaling은 모니터링 결과나 사용자 설정에 따라서 가상 서버 수를 늘리거나 줄여 수요 변화에 탄력적으로 대응할 수 있도록 돕는 서비스입니다. - Ncloud 공식문서 -&lt;/blockquote&gt;
&lt;p style=&quot;color: #333333; text-align: start;&quot; data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p style=&quot;color: #333333; text-align: start;&quot; data-ke-size=&quot;size16&quot;&gt;저희 서비스를 기획하면서, 서버쪽에서 해결해야할 과제가 있었습니다. Q&amp;amp;A기반의 서비스이다 보니 새학기 시즌에 사용자들이 몰릴 가능성이 있다 라는 것이였습니다.&lt;br /&gt;물론 새학기 시즌 전에 직접 Load Balancer에 추가 Server를 붙여 Scale-Out를 하는것도 괜찮은 방법입니다.&lt;br /&gt;다만, 이렇게 했을때마다 이럴때마다 업데이트 뿐만 아니라, 테스트도 진행해야 했기 때문에 시간 및 인력이 낭비된다 라고 생각을 했습니다. 또한 추가적으로 이렇게 Scale-Out을 진행 했지만, 예상만큼 사용자가 들어오지 않는다면, 그만큼 서버 비용도 낭비되는 것이라고 판단을 했습니다.&lt;br /&gt;&lt;br /&gt;이에, 저희 백엔드 팀은 이 문제를 해결하기위해 서버의 성능을 트래픽 증감에 따라 탄력적이고 안정적으로 운영할 순 없을까??라는 생각을 바탕으로 다양한 자료를 참고하였습니다.&lt;br /&gt;여기서 대안책은 AutoScaling과 Kubernates 였습니다. 이 두가지 대안책을 가지고 백엔드 팀에서 회의를 해본 결과, Kubernates를 적용하여 컨테이너로 나눠 개발하기엔&amp;nbsp; 우리의 비즈니스 로직이 그렇게 크지도 않고, 또한 1달 반동안 개발하기에 러닝커브가 너무 높다 라는 결론이 나왔습니다.&lt;br /&gt;따라서, Kubernates를 적용하기보단 AutoScaling을 통해서 서버의 안정성을 높였습니다.&lt;/p&gt;
&lt;p style=&quot;color: #333333; text-align: start;&quot; data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p style=&quot;color: #333333; text-align: start;&quot; data-ke-size=&quot;size16&quot;&gt;AutoScaling을 적용하기 위해서 기존에 생성했던 Server를 기반으로 Server Image를 생성을 하고, 이 Image 파일을 기반으로 Source Deploy를 실행시키기 위한 init Script를 작성하여 Launch Configuration을 생성하였습니다.&lt;/p&gt;
&lt;p style=&quot;color: #333333; text-align: start;&quot; data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p style=&quot;color: #333333; text-align: start;&quot; data-ke-size=&quot;size16&quot;&gt;따러서 저희는 이 Launch Configuration을 기반으로 AutoScaling Group을 생성하여 쉽게 Server를 똑같은 환경으로 생성할 수 있었습니다.&lt;/p&gt;
&lt;p style=&quot;color: #333333; text-align: start;&quot; data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p style=&quot;color: #333333; text-align: start;&quot; data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p style=&quot;color: #333333; text-align: start;&quot; data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p style=&quot;color: #333333; text-align: start;&quot; data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;h4 style=&quot;color: #000000; text-align: start;&quot; data-ke-size=&quot;size20&quot;&gt;&lt;span style=&quot;color: #000000;&quot;&gt;&lt;b&gt;Object Storage&lt;/b&gt;&lt;/span&gt;&lt;/h4&gt;
&lt;blockquote style=&quot;color: #666666; text-align: left;&quot; data-ke-style=&quot;style2&quot;&gt;Object Storage는 사용자가 언제 어디서나 원하는 데이터를 저장하고 탐색할 수 있도록 파일 저장 공간을 제공하는 서비스입니다. - Ncloud 공식문서 -&lt;/blockquote&gt;
&lt;p style=&quot;color: #333333; text-align: start;&quot; data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p style=&quot;color: #333333; text-align: start;&quot; data-ke-size=&quot;size16&quot;&gt;저희 서비스의 비즈니스 로직상 사용자들의 피드에 존재하는 사진들을 저장하는 저장소가 필요했습니다.&lt;br /&gt;따라서 파일 저장소인 Object Storage를 사진파일 저장소로 활용하였습니다.&lt;br /&gt;AWS S3와 코드가 호환이 되기 때문에 별 어려움 없이 Spring 서버를 통해 개발을 할 수 있었습니다.&lt;/p&gt;
&lt;p style=&quot;color: #333333; text-align: start;&quot; data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p style=&quot;color: #333333; text-align: start;&quot; data-ke-size=&quot;size16&quot;&gt;추가적으로, CI-CD를 진행할때 중간 빌드파일 저장소로 Object Storage를 활용했는데, 이때 AWS CLI와 호환된다고 공식문서에 나와있기 때문에 Github Action에서 AWS CLI를 통해&amp;nbsp; Object Storage에 빌드 파일을 저장했습니다.&lt;br /&gt;다만, 이부분에서 AWS CLI Credentials에서 오류가 일어나 트러블 슈팅을 하는데에 애를 먹었던 부분이 있었습니다.&lt;br /&gt;이부분은 아래에서 자세히 설명드리겠습니다.&lt;/p&gt;
&lt;p style=&quot;color: #333333; text-align: start;&quot; data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p style=&quot;color: #333333; text-align: start;&quot; data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p style=&quot;color: #333333; text-align: start;&quot; data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p style=&quot;color: #333333; text-align: start;&quot; data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;h4 style=&quot;color: #000000; text-align: start;&quot; data-ke-size=&quot;size20&quot;&gt;&lt;span style=&quot;color: #000000;&quot;&gt;&lt;b&gt;Source Deploy&lt;/b&gt;&lt;/span&gt;&lt;/h4&gt;
&lt;blockquote style=&quot;color: #666666; text-align: left;&quot; data-ke-style=&quot;style2&quot;&gt;SourceDeploy는 새로 작성되거나 업데이트된 소스들을 자동으로 서버에 배포하고 적용해 주는 배포 자동화 서비스입니다. SourceDeploy를 사용하여 미리 설정된 사용자 기반 명령어들을 통해 소스 배포, 실행 및 검증을 자동화할 수 있고 배포 중 서비스 중단 시간을 최소화할 수 있습니다. 또한, SourceDeploy는 배포 실행 관리자를 통해 배포 실행을 제어할 수 있어, 필수적으로 필요한 배포만 적용하여 서비스의 품질을 보장할 수 있습니다. - Ncloud 공식문서 -&lt;/blockquote&gt;
&lt;p&gt;&lt;figure class=&quot;imageblock alignCenter&quot; data-ke-mobileStyle=&quot;widthOrigin&quot; data-origin-width=&quot;819&quot; data-origin-height=&quot;794&quot;&gt;&lt;span data-url=&quot;https://blog.kakaocdn.net/dn/bXUC0l/btsIvm6NpXF/a0NmQjdjFilkYfKjFo56c0/img.png&quot; data-phocus=&quot;https://blog.kakaocdn.net/dn/bXUC0l/btsIvm6NpXF/a0NmQjdjFilkYfKjFo56c0/img.png&quot;&gt;&lt;img src=&quot;https://blog.kakaocdn.net/dn/bXUC0l/btsIvm6NpXF/a0NmQjdjFilkYfKjFo56c0/img.png&quot; srcset=&quot;https://img1.daumcdn.net/thumb/R1280x0/?scode=mtistory2&amp;fname=https%3A%2F%2Fblog.kakaocdn.net%2Fdn%2FbXUC0l%2FbtsIvm6NpXF%2Fa0NmQjdjFilkYfKjFo56c0%2Fimg.png&quot; onerror=&quot;this.onerror=null; this.src='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png'; this.srcset='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png';&quot; loading=&quot;lazy&quot; width=&quot;500&quot; height=&quot;485&quot; data-origin-width=&quot;819&quot; data-origin-height=&quot;794&quot;/&gt;&lt;/span&gt;&lt;/figure&gt;
&lt;/p&gt;
&lt;p style=&quot;color: #333333; text-align: start;&quot; data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p style=&quot;color: #333333; text-align: start;&quot; data-ke-size=&quot;size16&quot;&gt;Source Deploy는 저희 서비스에서 정말 필수적인 서비스였습니다. 서버의 안정성과 확장성 생각하여 AutoScaling을 도입을 하였는데, AutoScaling의 빌드 시간이 평균 3~5분정도로 매우 오래걸렸습니다.&lt;br /&gt;따라서, 실제 운영 환경에서 서버 업데이트시마다 5분정도의 다운 타임이 있었고, 이 시간동안 사용자는 저희의 서비스에 접속을 하지 못하는 문제가 발생하였습니다. 물론 여기서 배포 이후에 버그가 발생하면 그만큼 다운타임은 늘어날 것이고, 롤백도 어려워 실제 운영 상황에서 AutoScaling을 그냥 사용하는 것은 위험하다고 판단을 하였습니다.&lt;/p&gt;
&lt;p style=&quot;color: #333333; text-align: start;&quot; data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p style=&quot;color: #333333; text-align: start;&quot; data-ke-size=&quot;size16&quot;&gt;따라서, AutoScaling에 무중단 배포를 적용시키기로 결정을 하였고, 그에 대한 자료를 찾던중 Source Deploy로 AutoScaling Group 무중단 배포가 가능하다는 정보를 얻었습니다.&lt;br /&gt;Source Deploy없이 무중단 배포를 구현하기 위해선, LoadBalancer에 Ncloud API를 통해 새로 배포된 서버의 HealthCheck가 정상적으로 작동된 것을 확인한 이후에, 직접 Target Group을 바꾸는 방식으로 구현을 해야하는데, 이과정이 굉장히 안정성이 떨어진다고 생각을 했습니다. 또한, 기존에 트래픽이 늘어나 Scale-Out이 진행된 상황에서 무중단 배포를 진행한다고 하면 굉장히 예외상황이 많다고 생각을 했습니다.&lt;/p&gt;
&lt;p style=&quot;color: #333333; text-align: start;&quot; data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p style=&quot;color: #333333; text-align: start;&quot; data-ke-size=&quot;size16&quot;&gt;Source Deploy를 활용하기 위해, Github Action을 활용하였습니다. CI 단계에서 성공적으로 빌드파일을 Object Storage 에 저장을 하게 되면, 이 이후에 Github Action에서 쉘 스크립트를 통해 Source Deploy에 API 호출을 통해 원격 실행을 하게 됩니다.&lt;br /&gt;이때, API 호출 할때 필수적으로 API 키를 통한 Naver Signiture방식 서명이 필요합니다. 이것을 구현하기위해 쉘 스크립트를 작성하였으며, HTTP 요청방식 +&amp;nbsp; API_URL + 현재 TimeStamp + nCloud Access Key 정보를 조합한 이후, Secret Key로 SHA256방식 암호화를 하여 API 호출을 하였습니다.&amp;nbsp;&lt;/p&gt;
&lt;p style=&quot;color: #333333; text-align: start;&quot; data-ke-size=&quot;size16&quot;&gt;&lt;br /&gt;따라서, Source Deploy를 이용해 AutoScaling Group 자체를 블루/그린으로 배포하는 방식으로 구현하였으며, 서버의 다운 타임을 1~2초 이내로 성공적으로 줄일 수 있었고, 서버의 안정성과 확장성을 확보 할 수 있었습니다.&lt;/p&gt;
&lt;p style=&quot;color: #333333; text-align: start;&quot; data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p style=&quot;color: #333333; text-align: start;&quot; data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p style=&quot;color: #333333; text-align: start;&quot; data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p style=&quot;color: #333333; text-align: start;&quot; data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;h4 style=&quot;color: #000000; text-align: start;&quot; data-ke-size=&quot;size20&quot;&gt;&lt;span style=&quot;color: #000000;&quot;&gt;&lt;b&gt;NAT GateWay&lt;/b&gt;&lt;/span&gt;&lt;/h4&gt;
&lt;blockquote style=&quot;color: #666666; text-align: left;&quot; data-ke-style=&quot;style2&quot;&gt;NAT Gateway는 클라우드 플랫폼 내부에 있는 여러 대의 비공인 IP를 가진 고객의 서버가 공인 IP 주소를 가진 외부 서버와 통신할 수 있도록 연결하는 NAT 서비스입니다. 비공인 IP 주소를 가진 다수의 서버가 하나의 NAT Gateway를 공유하거나, 여러 개의 NAT Gateway를 만들고 서버 그룹별로 각각의 NAT Gateway를 사용할 수도 있습니다. 고객이 사전에 등록한 외부의 공인 IP 주소를 가진 호스트에만 접속할 수 있도록 설정해 보안을 강화할 수 있습니다. 또한 Auto Scaling 서비스와 연결하여 새로 증설되는 서버가 자동으로 NAT Gateway를 이용하도록 설정할 수 있습니다.&lt;br /&gt;- Ncloud 공식문서 -&lt;/blockquote&gt;
&lt;p style=&quot;color: #333333; text-align: start;&quot; data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p style=&quot;color: #333333; text-align: start;&quot; data-ke-size=&quot;size16&quot;&gt;저희 시스템 아키텍처를 보시면 VPC에 Internet Gateway 뿐만 아니라, NAT Gateway도 추가 되어 있습니다. 저희의 아키텍처 구조상 Private Subnet에 존재하는 서버는 내부 사설 IP만 가지기 때문에 외부와 연결될 수가 없습니다. 따라서 이 서버가 공인 IP를 가지고 라우팅이 외부와 연결이 되어있어야 외부와 통신이 가능해집니다.&lt;br /&gt;저희는 Private Subnet에 존재하는 서의 통신을 가능하게 하기위해 NAT Gateway에 공인 IP를 추가하고, Private Subnet안의 라우팅 테이블에 연결 하였으며, 이를 이용해 서버에서 카카오, Naver Clova 등의 서비스에 HTTP기반 요청을 보낼 수 있었습니다.&lt;/p&gt;
&lt;p style=&quot;color: #333333; text-align: start;&quot; data-ke-size=&quot;size16&quot;&gt;그러면 여기서 두가지의 의문점이 들 수도 있습니다.&lt;/p&gt;
&lt;p style=&quot;color: #333333; text-align: start;&quot; data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p style=&quot;color: #333333; text-align: start;&quot; data-ke-size=&quot;size18&quot;&gt;&lt;span style=&quot;color: #ee2323;&quot;&gt;&lt;b&gt;&quot;이미 Internet Gateway가 존재하는데 이걸 활용해서 통신을 하면 되는거 아닌가?&quot;&lt;/b&gt;&lt;/span&gt;&lt;/p&gt;
&lt;p style=&quot;color: #333333; text-align: start;&quot; data-ke-size=&quot;size18&quot;&gt;&lt;span style=&quot;color: #ee2323;&quot;&gt;&lt;b&gt;&quot;NAT Gateway를 이용해 Private Subnet을 연결하면 결국 외부로 서버 자원을 노출시켜 보안취약점이 생기는거 아닌가?&quot;&lt;/b&gt;&lt;/span&gt;&lt;/p&gt;
&lt;p style=&quot;color: #333333; text-align: start;&quot; data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p style=&quot;color: #333333; text-align: start;&quot; data-ke-size=&quot;size16&quot;&gt;우선 첫번째의 대답을 하자면, 현재 Internet Gateway는 로드밸런서와 연결되어 있고, 그 로드밸런서가 내부 서버와 연결되어있습니다. 즉, Private Subnet에 존재하는 내부 서버들이 Internet Gateway를 통해 연결되려면, 로드밸런서를 거쳐야만 통신을 할 수 있는데, 서버 -&amp;gt; 로드밸런서 -&amp;gt; Internet Gateway 이런과정이 불가능하고, 로드밸런서에서 그런 기능도 제공하고 있지 않습니다. 따라서 Public Subnet에서 직접 Internet Gateway와 연결되어 있는 상황이 아니라면, Internet Gateway를 통해&amp;nbsp; 통신을 하는건 불가능합니다.&lt;/p&gt;
&lt;p style=&quot;color: #333333; text-align: start;&quot; data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p style=&quot;color: #333333; text-align: start;&quot; data-ke-size=&quot;size16&quot;&gt;두번째의 대답을 하자면, NAT Gateway 경우는 내부의 요청을 하나로 모아서 외부로 보내는 역할은 하지만, 외부의 요청을 내부로 들여보내는 역할은 하지 않습니다. 즉, 내부 -&amp;gt; 외부는 가능하지만, 외부 -&amp;gt; 내부는 불가능합니다.&lt;br /&gt;이에 대한 예시를 들어보면, 각 집마다 존재하는 공유기를 예시로 들 수 있습니다. 저희가 집에서 보통 하나의 공인 IP를 할당받아 공유기를 사용하고, 여기서 여러개의 사설 IP로 나뉘어져서 스마트폰, 컴퓨터 등에 할당을 합니다. 여기서 저희가 컴퓨터를 할때, Naver 검색, 카카오톡 등의 외부로 다양한 요청을 보낼 순 있지만, 반대로 외부에선 저희 컴퓨터로 직접적인 요청을 불가능합니다. 이와 똑같은 원리로 NAT Gateway도 외부에서 내부로 직접적인 요청은 불가능합니다.&lt;br /&gt;(물론 포트포워딩을 이용하면 가능하긴 합니다 ㅎㅎ)&lt;/p&gt;
&lt;p style=&quot;color: #333333; text-align: start;&quot; data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p style=&quot;color: #333333; text-align: start;&quot; data-ke-size=&quot;size16&quot;&gt;따라서, 저희는 NAT Gateway를 활용해서 Private Subnet에 존재하는 내부 서버들의 보안을 지키면서 외부와 통신을 할 수 있게 아키텍처를 구성하였습니다.&amp;nbsp;&lt;/p&gt;
&lt;p style=&quot;color: #333333; text-align: start;&quot; data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p style=&quot;color: #333333; text-align: start;&quot; data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p style=&quot;color: #333333; text-align: start;&quot; data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p style=&quot;color: #333333; text-align: start;&quot; data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;h4 style=&quot;color: #000000; text-align: start;&quot; data-ke-size=&quot;size20&quot;&gt;&lt;span style=&quot;color: #000000;&quot;&gt;&lt;b&gt;DB&lt;/b&gt;&lt;/span&gt;&lt;/h4&gt;
&lt;blockquote style=&quot;color: #666666; text-align: left;&quot; data-ke-style=&quot;style2&quot;&gt;MySQL 데이터베이스를 손쉽게 구축하고, 네이버의 최적화 설정을 통해 안정적으로 운영하며, 장애 발생 시 자동으로 복구합니다. -Ncloud 공식문서-&lt;/blockquote&gt;
&lt;p style=&quot;color: #333333; text-align: start;&quot; data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p style=&quot;color: #333333; text-align: start;&quot; data-ke-size=&quot;size16&quot;&gt;저희는 Ncloud에서 총 2가지의 DB를 사용합니다. 하나는 Mysql, 나머지 하나는 Redis입니다.&lt;br /&gt;우선 Mysql은 저희 서버의 메인 DB를 구축하기위해 사용을 하였습니다. 따라서, 저희의 ERD를 기반으로 관계형 데이터베이스 구축을 Ncloud의 Mysql을 활용하여 구축하였습니다. 데이터 자동 백업 및 성능 최적화가 되어있어서 손쉽게 고가용성과 성능 안정성을 보장할 수 있었습니다.&lt;/p&gt;
&lt;p style=&quot;color: #333333; text-align: start;&quot; data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p style=&quot;color: #333333; text-align: start;&quot; data-ke-size=&quot;size16&quot;&gt;Redis는 저희 서버의 Token값 캐싱에 활용하였습니다. 저희 서버는 로그인용&amp;nbsp; JWT Token과 푸시 메세지 발급용 FCM Token을 사용합니다. 즉, 2가지의 토큰을 관리하면서 DB에 있는 Tokne값을 조회하는 경우가 발생합니다. 사용자가 얼마 없을땐 그렇게 부하가 크진 않겠지만, 아무리 Ncloud의 Mysql이 성능최적화가 잘되어있다해도, 관계형 DB의 특성상, 사용자가 많아질 경우 조회시에 부하가 크다고 생각을 하였습니다.&lt;/p&gt;
&lt;p style=&quot;color: #333333; text-align: start;&quot; data-ke-size=&quot;size16&quot;&gt;&lt;br /&gt;따라서, 이런 토큰값의 경우 Redis에 이중 저장을 하여 토큰값 조회가 일어날때 1차적으로 먼저 Redis를 조회하고 존재하지 않을 시에만 Mysql DB를 조회하는 방식으로 수정하여 성능을 최적화 하기 위해 Redis를 활용하였습니다.&lt;/p&gt;
&lt;p style=&quot;color: #333333; text-align: start;&quot; data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p style=&quot;color: #333333; text-align: start;&quot; data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p style=&quot;color: #333333; text-align: start;&quot; data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p style=&quot;color: #333333; text-align: start;&quot; data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p style=&quot;color: #333333; text-align: start;&quot; data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;h4 style=&quot;color: #000000; text-align: start;&quot; data-ke-size=&quot;size20&quot;&gt;&lt;span style=&quot;color: #000000;&quot;&gt;&lt;b&gt;Naver Clova Studio&lt;/b&gt;&lt;/span&gt;&lt;/h4&gt;
&lt;blockquote style=&quot;color: #666666; text-align: left;&quot; data-ke-style=&quot;style2&quot;&gt;CLOVA Studio는 초대규모(Hyperscale) AI 기술인 HyperCLOVA 언어 모델을 활용하여 사용자가 입력한 내용에 따라 AI 기술을 통해 생성된 문구를 출력하는 네이버 클라우드 플랫폼의 서비스입니다. &amp;nbsp;-Ncloud 공식문서-&lt;/blockquote&gt;
&lt;p style=&quot;color: #333333; text-align: start;&quot; data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p style=&quot;color: #333333; text-align: start;&quot; data-ke-size=&quot;size16&quot;&gt;저희가 기획단계에서 고민을 했던 부분이 있습니다. 상대방에게 질문을 할때 어떤 질문을 해야할지 모르는 사용자가 있을 것이며, 이러한 사용자들에게 편의성을 제공할 수 없을까? 라는 고민을 했습니다.&lt;br /&gt;이러한 상황에서 AI기술을 바탕으로 다양한 예시 질문들을 생성해주면 사용자 편의성이 증가할 것이라 생각을 했고, 이에 알맞은 AI 기술들을 조사한 결과 Naver Clova Studio를 활용해서 다양한 예시 질문들을 생성해주자 라는 결론이 나왔습니다.&lt;/p&gt;
&lt;p style=&quot;color: #333333; text-align: start;&quot; data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p style=&quot;color: #333333; text-align: start;&quot; data-ke-size=&quot;size16&quot;&gt;Naver Clova Studio의 장점은 다음과 같습니다.&amp;nbsp;&lt;/p&gt;
&lt;p style=&quot;color: #333333; text-align: start;&quot; data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;ol style=&quot;list-style-type: decimal;&quot; data-ke-list-type=&quot;decimal&quot;&gt;
&lt;li&gt;&lt;span style=&quot;color: #000000;&quot;&gt;&lt;b&gt;한글에 특화된 AI 모델&lt;/b&gt;&lt;/span&gt;&lt;/li&gt;
&lt;li&gt;&lt;span style=&quot;color: #000000;&quot;&gt;&lt;b&gt;간편하게 API처럼 호출 가능&lt;/b&gt;&lt;/span&gt;&lt;/li&gt;
&lt;li&gt;&lt;span style=&quot;color: #000000;&quot;&gt;&lt;b&gt;프롬프트 기법 활용으로 서비스에 맞게 쉽게 빠르게 튜닝 가능&lt;/b&gt;&lt;/span&gt;&lt;/li&gt;
&lt;/ol&gt;
&lt;p style=&quot;color: #333333; text-align: start;&quot; data-ke-size=&quot;size16&quot;&gt;우선 다양한 AI 모델이 있지만 Naver Clova Studio가 가장 한글에 특화된 AI 모델이라고 생각을 했습니다.&lt;br /&gt;또한, 다양한 AI모델을 기반으로 테스트를 해본결과 가장 한글 텍스트를 자연스럽게 생성하는 모델이었습니다.&lt;/p&gt;
&lt;p style=&quot;color: #333333; text-align: start;&quot; data-ke-size=&quot;size16&quot;&gt;그리고 API 호출을 통해서 간편하게 사용할 수 있기에 러닝커브가 적다 라는 장점이 저희에게 매우 크게 다가왔습니다.&lt;br /&gt;플레이그라운드를 통해 테스트를 하며 프롬프트를 작성하였고,&amp;nbsp; 이를 기반으로 테스트 앱을 생성하여 코드를 복붙만 하면 바로 API호출을 할 수 있기 때문에 간편하게 개발할 수 있다는 점이 정말 편리했습니다.&lt;br /&gt;마지막으로, 프롬프트로 직접 예시를 만들 수 있어 파인튜닝 없이 튜닝 효과를 낼 수 있다 라는점이 가장 큰 장점으로 느껴졌습니다.&lt;/p&gt;
&lt;p style=&quot;color: #333333; text-align: start;&quot; data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p style=&quot;color: #333333; text-align: start;&quot; data-ke-size=&quot;size16&quot;&gt;따라서, 다양한 프롬프트를 통해 손쉽게 다양한 질문을 생성할 수 있었습니다.&lt;/p&gt;
&lt;p style=&quot;color: #333333; text-align: start;&quot; data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p style=&quot;color: #333333; text-align: start;&quot; data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p style=&quot;color: #333333; text-align: start;&quot; data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p style=&quot;color: #333333; text-align: start;&quot; data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p style=&quot;color: #333333; text-align: start;&quot; data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p style=&quot;color: #333333; text-align: start;&quot; data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p style=&quot;color: #333333; text-align: start;&quot; data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p style=&quot;color: #333333; text-align: start;&quot; data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p style=&quot;color: #333333; text-align: start;&quot; data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p style=&quot;color: #333333; text-align: start;&quot; data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p style=&quot;color: #333333; text-align: start;&quot; data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p style=&quot;color: #333333; text-align: start;&quot; data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p style=&quot;color: #333333; text-align: start;&quot; data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p style=&quot;color: #333333; text-align: start;&quot; data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p style=&quot;color: #333333; text-align: start;&quot; data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;blockquote data-ke-style=&quot;style1&quot;&gt;&lt;span style=&quot;font-family: 'Noto Serif KR';&quot;&gt;Ncloud 버그 해결기&lt;/span&gt;&lt;/blockquote&gt;
&lt;p style=&quot;color: #333333; text-align: start;&quot; data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p style=&quot;color: #333333; text-align: start;&quot; data-ke-size=&quot;size16&quot;&gt;Ncloud를 이용해 개발을 하면서 마주한 버그들이 있었습니다.&amp;nbsp;&lt;/p&gt;
&lt;p style=&quot;color: #333333; text-align: start;&quot; data-ke-size=&quot;size16&quot;&gt;관련 레퍼런스가 부족하다보니, 직접 계속 테스트 해보면서 버그를 해결했습니다.&lt;br /&gt;이러한 부분이 혹시나 저와 같은 문제를 겪고있는 다른 Ncloud 사용자분들에게 도움이 되거나, 추후 Ncloud에 개선방향에 도움이 될까 싶어 공유합니다..!!&lt;/p&gt;
&lt;h4 style=&quot;color: #000000; text-align: start;&quot; data-ke-size=&quot;size20&quot;&gt;&lt;span style=&quot;color: #ee2323;&quot;&gt;&lt;b&gt;&amp;lt;이 부분은 Ncloud에서 발생한 문제가 아니라, 제가 이해도가 부족해서 발생한 문제 일 수도 있습니다. 참고 부탁드립니다.&amp;gt;&lt;/b&gt;&lt;/span&gt;&lt;/h4&gt;
&lt;p style=&quot;color: #333333; text-align: start;&quot; data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p style=&quot;color: #333333; text-align: start;&quot; data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p style=&quot;color: #333333; text-align: start;&quot; data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;h4 style=&quot;color: #000000; text-align: start;&quot; data-ke-size=&quot;size20&quot;&gt;&lt;span style=&quot;background-color: #ffffff; color: #000000;&quot;&gt;&lt;b&gt;Object Storage의 AWS CLI 연동&lt;/b&gt;&lt;/span&gt;&lt;/h4&gt;
&lt;p style=&quot;color: #333333; text-align: start;&quot; data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p style=&quot;color: #333333; text-align: start;&quot; data-ke-size=&quot;size16&quot;&gt;네이버의 공식문서를 보면 아래처럼 Object Storage를 AWS S3 CLI를 통해서 제어를 할 수 있다고 나와있습니다.&lt;/p&gt;
&lt;p&gt;&lt;figure class=&quot;imageblock alignCenter&quot; data-ke-mobileStyle=&quot;widthOrigin&quot; data-origin-width=&quot;892&quot; data-origin-height=&quot;846&quot;&gt;&lt;span data-url=&quot;https://blog.kakaocdn.net/dn/xuhvw/btsItKgYfvR/TzXC5lsfzw9TysHnTP53KK/img.png&quot; data-phocus=&quot;https://blog.kakaocdn.net/dn/xuhvw/btsItKgYfvR/TzXC5lsfzw9TysHnTP53KK/img.png&quot;&gt;&lt;img src=&quot;https://blog.kakaocdn.net/dn/xuhvw/btsItKgYfvR/TzXC5lsfzw9TysHnTP53KK/img.png&quot; srcset=&quot;https://img1.daumcdn.net/thumb/R1280x0/?scode=mtistory2&amp;fname=https%3A%2F%2Fblog.kakaocdn.net%2Fdn%2Fxuhvw%2FbtsItKgYfvR%2FTzXC5lsfzw9TysHnTP53KK%2Fimg.png&quot; onerror=&quot;this.onerror=null; this.src='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png'; this.srcset='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png';&quot; loading=&quot;lazy&quot; width=&quot;600&quot; height=&quot;569&quot; data-origin-width=&quot;892&quot; data-origin-height=&quot;846&quot;/&gt;&lt;/span&gt;&lt;/figure&gt;
&lt;/p&gt;
&lt;p style=&quot;color: #333333; text-align: start;&quot; data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p style=&quot;color: #333333; text-align: start;&quot; data-ke-size=&quot;size16&quot;&gt;따라서, 아래 처럼 AWS CLI를 통해 Naver API Key를 기반으로 인증을 손쉽게 할 수 있으며, Object Storage를 제어할 수 있습니다.&lt;/p&gt;
&lt;p style=&quot;color: #333333; text-align: start;&quot; data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p&gt;&lt;figure class=&quot;imageblock alignCenter&quot; data-ke-mobileStyle=&quot;widthOrigin&quot; data-origin-width=&quot;1016&quot; data-origin-height=&quot;911&quot;&gt;&lt;span data-url=&quot;https://blog.kakaocdn.net/dn/bVj3tE/btsIvkVtZd4/osbtOoCKYvUnWAkUobInKk/img.png&quot; data-phocus=&quot;https://blog.kakaocdn.net/dn/bVj3tE/btsIvkVtZd4/osbtOoCKYvUnWAkUobInKk/img.png&quot;&gt;&lt;img src=&quot;https://blog.kakaocdn.net/dn/bVj3tE/btsIvkVtZd4/osbtOoCKYvUnWAkUobInKk/img.png&quot; srcset=&quot;https://img1.daumcdn.net/thumb/R1280x0/?scode=mtistory2&amp;fname=https%3A%2F%2Fblog.kakaocdn.net%2Fdn%2FbVj3tE%2FbtsIvkVtZd4%2FosbtOoCKYvUnWAkUobInKk%2Fimg.png&quot; onerror=&quot;this.onerror=null; this.src='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png'; this.srcset='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png';&quot; loading=&quot;lazy&quot; width=&quot;600&quot; height=&quot;538&quot; data-origin-width=&quot;1016&quot; data-origin-height=&quot;911&quot;/&gt;&lt;/span&gt;&lt;/figure&gt;
&lt;/p&gt;
&lt;p style=&quot;color: #333333; text-align: start;&quot; data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p style=&quot;color: #333333; text-align: start;&quot; data-ke-size=&quot;size16&quot;&gt;이 공식문서의 정보를 바탕으로 저희도 쉽게 AWS CLI에 인증을 할 수 있었습니다.&lt;/p&gt;
&lt;p style=&quot;color: #333333; text-align: start;&quot; data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p&gt;&lt;figure class=&quot;imageblock alignCenter&quot; data-ke-mobileStyle=&quot;widthOrigin&quot; data-origin-width=&quot;890&quot; data-origin-height=&quot;245&quot;&gt;&lt;span data-url=&quot;https://blog.kakaocdn.net/dn/bzFrIt/btsIvRL2jtv/8abVceGkzCXSUKFDfo7eK1/img.png&quot; data-phocus=&quot;https://blog.kakaocdn.net/dn/bzFrIt/btsIvRL2jtv/8abVceGkzCXSUKFDfo7eK1/img.png&quot;&gt;&lt;img src=&quot;https://blog.kakaocdn.net/dn/bzFrIt/btsIvRL2jtv/8abVceGkzCXSUKFDfo7eK1/img.png&quot; srcset=&quot;https://img1.daumcdn.net/thumb/R1280x0/?scode=mtistory2&amp;fname=https%3A%2F%2Fblog.kakaocdn.net%2Fdn%2FbzFrIt%2FbtsIvRL2jtv%2F8abVceGkzCXSUKFDfo7eK1%2Fimg.png&quot; onerror=&quot;this.onerror=null; this.src='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png'; this.srcset='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png';&quot; loading=&quot;lazy&quot; width=&quot;600&quot; height=&quot;165&quot; data-origin-width=&quot;890&quot; data-origin-height=&quot;245&quot;/&gt;&lt;/span&gt;&lt;/figure&gt;
&lt;/p&gt;
&lt;p style=&quot;color: #333333; text-align: start;&quot; data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p style=&quot;color: #333333; text-align: start;&quot; data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p style=&quot;color: #333333; text-align: start;&quot; data-ke-size=&quot;size16&quot;&gt;다만, 저희 백엔드 팀에서 Object Storage는 제어는 CI가 정상적으로 수행되고 빌드파일을 저장할 때 필요했습니다.&lt;/p&gt;
&lt;p style=&quot;color: #333333; text-align: start;&quot; data-ke-size=&quot;size16&quot;&gt;따라서, 깃허브 액션으로 직접 AWS CLI를 활용했고,&amp;nbsp; 보통은 AWS CLI에서 Configure를 수행하기 위해 많은 개발자들이 aws-actions를 주로 사용하기 때문에 저희 팀도 아래의 코드처럼 aws-action의 aws credentials@v4를 사용했습니다.&amp;nbsp;&lt;/p&gt;
&lt;p style=&quot;color: #333333; text-align: start;&quot; data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p&gt;&lt;figure class=&quot;imageblock alignCenter&quot; data-ke-mobileStyle=&quot;widthOrigin&quot; data-origin-width=&quot;584&quot; data-origin-height=&quot;169&quot;&gt;&lt;span data-url=&quot;https://blog.kakaocdn.net/dn/4NINr/btsIuuLjID7/DenRvQWbF4rrkns1Eg3Vl0/img.png&quot; data-phocus=&quot;https://blog.kakaocdn.net/dn/4NINr/btsIuuLjID7/DenRvQWbF4rrkns1Eg3Vl0/img.png&quot;&gt;&lt;img src=&quot;https://blog.kakaocdn.net/dn/4NINr/btsIuuLjID7/DenRvQWbF4rrkns1Eg3Vl0/img.png&quot; srcset=&quot;https://img1.daumcdn.net/thumb/R1280x0/?scode=mtistory2&amp;fname=https%3A%2F%2Fblog.kakaocdn.net%2Fdn%2F4NINr%2FbtsIuuLjID7%2FDenRvQWbF4rrkns1Eg3Vl0%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;584&quot; height=&quot;169&quot; data-origin-width=&quot;584&quot; data-origin-height=&quot;169&quot;/&gt;&lt;/span&gt;&lt;/figure&gt;
&lt;/p&gt;
&lt;p style=&quot;color: #333333; text-align: start;&quot; data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p style=&quot;color: #333333; text-align: start;&quot; data-ke-size=&quot;size16&quot;&gt;과거에 AWS S3를 깃허브액션에서 제어했을땐, 당연히 정상적으로 되었고, 로컬에서 테스트 해본 결과 aws configure이 네이버 Object Storage에도 정상적으로 작동을 했기 때문에 당연히 작동될 것이라고 생각을 하였습니다.&lt;br /&gt;하지만 저희 백엔드 팀에서의 예상과 다르게 계속해서 token 인증 실패가 발생하였고, 저희의 코드 문제다 라고 생각을 해 계속 코드의 문법을 바꿔보면서 진행을 했지만, 계속해서 실패를 하였습니다.&lt;/p&gt;
&lt;p style=&quot;color: #333333; text-align: start;&quot; data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p&gt;&lt;figure class=&quot;imageblock alignCenter&quot; data-ke-mobileStyle=&quot;widthOrigin&quot; data-origin-width=&quot;1003&quot; data-origin-height=&quot;280&quot;&gt;&lt;span data-url=&quot;https://blog.kakaocdn.net/dn/AbLlW/btsItHEFm5S/RbGYW3ZMytEUyIp8LMFYM0/img.png&quot; data-phocus=&quot;https://blog.kakaocdn.net/dn/AbLlW/btsItHEFm5S/RbGYW3ZMytEUyIp8LMFYM0/img.png&quot;&gt;&lt;img src=&quot;https://blog.kakaocdn.net/dn/AbLlW/btsItHEFm5S/RbGYW3ZMytEUyIp8LMFYM0/img.png&quot; srcset=&quot;https://img1.daumcdn.net/thumb/R1280x0/?scode=mtistory2&amp;fname=https%3A%2F%2Fblog.kakaocdn.net%2Fdn%2FAbLlW%2FbtsItHEFm5S%2FRbGYW3ZMytEUyIp8LMFYM0%2Fimg.png&quot; onerror=&quot;this.onerror=null; this.src='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png'; this.srcset='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png';&quot; loading=&quot;lazy&quot; width=&quot;600&quot; height=&quot;167&quot; data-origin-width=&quot;1003&quot; data-origin-height=&quot;280&quot;/&gt;&lt;/span&gt;&lt;/figure&gt;
&lt;/p&gt;
&lt;p style=&quot;color: #333333; text-align: start;&quot; data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p style=&quot;color: #333333; text-align: start;&quot; data-ke-size=&quot;size16&quot;&gt;이처럼 계속해서 Invalid Token오류가 났으며, 50회 정도의 테스트의 결과 aws-action을 이용하는건 불가능하다 라고 판단을 하였습니다. 따라서, 아래의 코드처럼 aws-actions를 활용하는것이 아닌 직접 환경변수를 설정함으로써, 해결을 하였습니다.&lt;br /&gt;저두 이번에 처음알았지만 aws configure를 하지않고 그냥 환경변수를 설정하는것 만으로도 AWS CLI를 인증할 수 있었고, 이에 따라 Object Storage 직접 AWS CLI를 통해 제어를 할 수 있었습니다.&amp;nbsp;&lt;/p&gt;
&lt;p style=&quot;color: #333333; text-align: start;&quot; data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p&gt;&lt;figure class=&quot;imageblock alignCenter&quot; data-ke-mobileStyle=&quot;widthOrigin&quot; data-origin-width=&quot;1246&quot; data-origin-height=&quot;218&quot;&gt;&lt;span data-url=&quot;https://blog.kakaocdn.net/dn/ZNrFh/btsIuOirTLY/kXdCNzYkAX3sjGIPUZpBAK/img.png&quot; data-phocus=&quot;https://blog.kakaocdn.net/dn/ZNrFh/btsIuOirTLY/kXdCNzYkAX3sjGIPUZpBAK/img.png&quot;&gt;&lt;img src=&quot;https://blog.kakaocdn.net/dn/ZNrFh/btsIuOirTLY/kXdCNzYkAX3sjGIPUZpBAK/img.png&quot; srcset=&quot;https://img1.daumcdn.net/thumb/R1280x0/?scode=mtistory2&amp;fname=https%3A%2F%2Fblog.kakaocdn.net%2Fdn%2FZNrFh%2FbtsIuOirTLY%2FkXdCNzYkAX3sjGIPUZpBAK%2Fimg.png&quot; onerror=&quot;this.onerror=null; this.src='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png'; this.srcset='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png';&quot; loading=&quot;lazy&quot; width=&quot;600&quot; height=&quot;105&quot; data-origin-width=&quot;1246&quot; data-origin-height=&quot;218&quot;/&gt;&lt;/span&gt;&lt;/figure&gt;
&lt;/p&gt;
&lt;p style=&quot;color: #333333; text-align: start;&quot; data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p style=&quot;color: #333333; text-align: start;&quot; data-ke-size=&quot;size16&quot;&gt;아직 정확한 원인은 모르겠지만 aws-action에서만 invalid token 오류가 나는걸로 보아 추측컨데, 현재 aws-actions/configure-aws-credential에서 자체적으로 토큰 형식(길이,구조)을 기반으로 1차 검증을 진행하고 있는데, 현재 Naver API Key 구조와 AWS IAM Key구조가 달라서 생기는 문제가 아닐까 생각을 하였습니다.&lt;br /&gt;이 부분은 정말 나중에 시간이 지나고 나면 다시 원인을 찾아보고 해결해보고 싶은 문제입니다.&lt;/p&gt;
&lt;p style=&quot;color: #333333; text-align: start;&quot; data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p style=&quot;color: #333333; text-align: start;&quot; data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p style=&quot;color: #333333; text-align: start;&quot; data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p style=&quot;color: #333333; text-align: start;&quot; data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p style=&quot;color: #333333; text-align: start;&quot; data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p style=&quot;color: #333333; text-align: start;&quot; data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;h4 style=&quot;color: #000000; text-align: start;&quot; data-ke-size=&quot;size20&quot;&gt;&lt;span style=&quot;color: #000000;&quot;&gt;&lt;b&gt;Source Deploy 배포시 빌드파일 저장문제&lt;/b&gt;&lt;/span&gt;&lt;/h4&gt;
&lt;p style=&quot;color: #333333; text-align: start;&quot; data-ke-size=&quot;size16&quot;&gt;Source Deploy를 이용해 AutoScaling 배포를 할시 아래사진처럼 배포 전, 배포, 배포 후로 나눠서 빌드 명령어를 커스텀하여 실행할 수 있습니다.&lt;/p&gt;
&lt;p style=&quot;color: #333333; text-align: start;&quot; data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p&gt;&lt;figure class=&quot;imageblock alignCenter&quot; data-ke-mobileStyle=&quot;widthOrigin&quot; data-origin-width=&quot;1293&quot; data-origin-height=&quot;1087&quot;&gt;&lt;span data-url=&quot;https://blog.kakaocdn.net/dn/kWlNC/btsIvkVtZfq/p6aYgHzQttldR9TuVvQjt1/img.png&quot; data-phocus=&quot;https://blog.kakaocdn.net/dn/kWlNC/btsIvkVtZfq/p6aYgHzQttldR9TuVvQjt1/img.png&quot;&gt;&lt;img src=&quot;https://blog.kakaocdn.net/dn/kWlNC/btsIvkVtZfq/p6aYgHzQttldR9TuVvQjt1/img.png&quot; srcset=&quot;https://img1.daumcdn.net/thumb/R1280x0/?scode=mtistory2&amp;fname=https%3A%2F%2Fblog.kakaocdn.net%2Fdn%2FkWlNC%2FbtsIvkVtZfq%2Fp6aYgHzQttldR9TuVvQjt1%2Fimg.png&quot; onerror=&quot;this.onerror=null; this.src='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png'; this.srcset='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png';&quot; loading=&quot;lazy&quot; width=&quot;600&quot; height=&quot;504&quot; data-origin-width=&quot;1293&quot; data-origin-height=&quot;1087&quot;/&gt;&lt;/span&gt;&lt;/figure&gt;
&lt;/p&gt;
&lt;p style=&quot;color: #333333; text-align: start;&quot; data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p style=&quot;color: #333333; text-align: start;&quot; data-ke-size=&quot;size16&quot;&gt;여기서 저희는 스프링 기반의 서버를 구축하였기 때문에, Object Storage에서 .zip파일을 기반으로 스프링 빌드파일인 jar 파일을 서버로 가져옵니다. 이 이후에 jar 파일을 백그라운드로 실행하기만 하면 되었습니다.&lt;br /&gt;다만, Source Deploy를 그냥 실행해본결과 아래 사진처럼 랜덤한 파일 이름이 생성이되고, 이 공간에 jar파일이 자동으로 저장이 되는것을 확인했습니다.&amp;nbsp;&lt;/p&gt;
&lt;p style=&quot;color: #333333; text-align: start;&quot; data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p&gt;&lt;figure class=&quot;imageblock alignCenter&quot; data-ke-mobileStyle=&quot;widthOrigin&quot; data-origin-width=&quot;1140&quot; data-origin-height=&quot;169&quot;&gt;&lt;span data-url=&quot;https://blog.kakaocdn.net/dn/bldNKU/btsIuOv2j9N/83ZY2ILvDGCrcT6PkIlyx0/img.png&quot; data-phocus=&quot;https://blog.kakaocdn.net/dn/bldNKU/btsIuOv2j9N/83ZY2ILvDGCrcT6PkIlyx0/img.png&quot;&gt;&lt;img src=&quot;https://blog.kakaocdn.net/dn/bldNKU/btsIuOv2j9N/83ZY2ILvDGCrcT6PkIlyx0/img.png&quot; srcset=&quot;https://img1.daumcdn.net/thumb/R1280x0/?scode=mtistory2&amp;fname=https%3A%2F%2Fblog.kakaocdn.net%2Fdn%2FbldNKU%2FbtsIuOv2j9N%2F83ZY2ILvDGCrcT6PkIlyx0%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;1140&quot; height=&quot;169&quot; data-origin-width=&quot;1140&quot; data-origin-height=&quot;169&quot;/&gt;&lt;/span&gt;&lt;/figure&gt;
&lt;/p&gt;
&lt;p style=&quot;color: #333333; text-align: start;&quot; data-ke-size=&quot;size16&quot;&gt;위처럼 자동으로 jar파일이 생성이 되는것을 확인할 수 있었는데, 중간에 '174074' 처럼 파일이름이 랜덤하게 바뀌면서 저장이 되었습니다.&lt;br /&gt;이렇게 될경우 저희가 배포스크립트를 미리 작성할 수가 없기때문에 저희가 원하는 위치에 jar 파일을 저장하고 싶었습니다.&lt;/p&gt;
&lt;p style=&quot;color: #333333; text-align: start;&quot; data-ke-size=&quot;size16&quot;&gt;따라서, '파일 배포' 라는 것을 커스텀해서 저희가 원하는 위치에 jar 파일을 저장하려고 시도를 하였습니다.&lt;br /&gt;하지만, 저희가 못찾은 것일 수도 있지만 이거와 관련된 자료가 없었습니다. 관련 블로그도 찾아봤고, Ncloud 공식문서도 찾아봤지만 이것을 어떻게 활용하는지 정확한 예시가 없었고, 설명도 확인할 수 없었습니다.&lt;br /&gt;따라서, 저희가 직접 테스트를 해보며 '파일 배포'를 커스텀하려고 했지만 아래처럼 계속 실패하였습니다.&lt;br /&gt;&lt;br /&gt;&lt;/p&gt;
&lt;p&gt;&lt;figure class=&quot;imageblock alignCenter&quot; data-ke-mobileStyle=&quot;widthOrigin&quot; data-origin-width=&quot;1153&quot; data-origin-height=&quot;236&quot;&gt;&lt;span data-url=&quot;https://blog.kakaocdn.net/dn/5ixkl/btsIt6Yd5lU/pY9FQ3ZA0cJbQ6JjmxEkMK/img.png&quot; data-phocus=&quot;https://blog.kakaocdn.net/dn/5ixkl/btsIt6Yd5lU/pY9FQ3ZA0cJbQ6JjmxEkMK/img.png&quot; data-alt=&quot;test-package 배포 경로 수정&quot;&gt;&lt;img src=&quot;https://blog.kakaocdn.net/dn/5ixkl/btsIt6Yd5lU/pY9FQ3ZA0cJbQ6JjmxEkMK/img.png&quot; srcset=&quot;https://img1.daumcdn.net/thumb/R1280x0/?scode=mtistory2&amp;fname=https%3A%2F%2Fblog.kakaocdn.net%2Fdn%2F5ixkl%2FbtsIt6Yd5lU%2FpY9FQ3ZA0cJbQ6JjmxEkMK%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;1153&quot; height=&quot;236&quot; data-origin-width=&quot;1153&quot; data-origin-height=&quot;236&quot;/&gt;&lt;/span&gt;&lt;figcaption&gt;test-package 배포 경로 수정&lt;/figcaption&gt;
&lt;/figure&gt;
&lt;/p&gt;
&lt;p style=&quot;color: #333333; text-align: start;&quot; data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p&gt;&lt;figure class=&quot;imageblock alignCenter&quot; data-ke-mobileStyle=&quot;widthOrigin&quot; data-origin-width=&quot;1191&quot; data-origin-height=&quot;317&quot;&gt;&lt;span data-url=&quot;https://blog.kakaocdn.net/dn/dC2wwS/btsIuuxKESB/nQl1lCX8zm9DzI4csewExk/img.png&quot; data-phocus=&quot;https://blog.kakaocdn.net/dn/dC2wwS/btsIuuxKESB/nQl1lCX8zm9DzI4csewExk/img.png&quot;&gt;&lt;img src=&quot;https://blog.kakaocdn.net/dn/dC2wwS/btsIuuxKESB/nQl1lCX8zm9DzI4csewExk/img.png&quot; srcset=&quot;https://img1.daumcdn.net/thumb/R1280x0/?scode=mtistory2&amp;fname=https%3A%2F%2Fblog.kakaocdn.net%2Fdn%2FdC2wwS%2FbtsIuuxKESB%2FnQl1lCX8zm9DzI4csewExk%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;1191&quot; height=&quot;317&quot; data-origin-width=&quot;1191&quot; data-origin-height=&quot;317&quot;/&gt;&lt;/span&gt;&lt;/figure&gt;
&lt;/p&gt;
&lt;p style=&quot;color: #333333; text-align: start;&quot; data-ke-size=&quot;size16&quot;&gt;위처럼 다양한 소스 파일 경로를 입력하면서 테스트를 해봤지만 계속해서 찾을수 없다라는 오류가 떴습니다.&lt;br /&gt;계속해서 시도를 해봤지만 계속 실패를하였고, AutoScaling에 한번 배포할때마다 5분정도 시간이 소요되었기 때문에 계속해서 테스트를 진행을 할 수 없는 상황이었습니다.&lt;br /&gt;따라서 '파일 배포'를 커스텀하는것은 포기하고, 배포 후 실행에서 직접 파일명을 find 명령으로 찾는 방법으로 개발을 하였습니다.&lt;/p&gt;
&lt;p&gt;&lt;figure class=&quot;imageblock alignCenter&quot; data-ke-mobileStyle=&quot;widthOrigin&quot; data-origin-width=&quot;1235&quot; data-origin-height=&quot;220&quot;&gt;&lt;span data-url=&quot;https://blog.kakaocdn.net/dn/bkcW5s/btsIt98s2Sm/3udNhKleCFBkDZ4HnGzLu1/img.png&quot; data-phocus=&quot;https://blog.kakaocdn.net/dn/bkcW5s/btsIt98s2Sm/3udNhKleCFBkDZ4HnGzLu1/img.png&quot; data-alt=&quot;find 명령으로 파일 실행&quot;&gt;&lt;img src=&quot;https://blog.kakaocdn.net/dn/bkcW5s/btsIt98s2Sm/3udNhKleCFBkDZ4HnGzLu1/img.png&quot; srcset=&quot;https://img1.daumcdn.net/thumb/R1280x0/?scode=mtistory2&amp;fname=https%3A%2F%2Fblog.kakaocdn.net%2Fdn%2FbkcW5s%2FbtsIt98s2Sm%2F3udNhKleCFBkDZ4HnGzLu1%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;1235&quot; height=&quot;220&quot; data-origin-width=&quot;1235&quot; data-origin-height=&quot;220&quot;/&gt;&lt;/span&gt;&lt;figcaption&gt;find 명령으로 파일 실행&lt;/figcaption&gt;
&lt;/figure&gt;
&lt;/p&gt;
&lt;p style=&quot;color: #333333; text-align: start;&quot; data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p style=&quot;color: #333333; text-align: start;&quot; data-ke-size=&quot;size16&quot;&gt;이런식으로 Source Deploy를 성공적으로 적용시켰지만, 이 방식이 완벽한 방법은 아니다 라고 생각을 합니다.&lt;br /&gt;따라서, 추후에 기회가 된다면, '파일 배포'를 커스텀하는 방식을 다시 시도해보려 합니다.&lt;br /&gt;좀 더 관련 자료나 예시가 있었으면 좋았을 텐데... 라는 아쉬움은 남아있습니다.&amp;nbsp;&lt;/p&gt;
&lt;p style=&quot;color: #333333; text-align: start;&quot; data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p style=&quot;color: #333333; text-align: start;&quot; data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p style=&quot;color: #333333; text-align: start;&quot; data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p style=&quot;color: #333333; text-align: start;&quot; data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p style=&quot;color: #333333; text-align: start;&quot; data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;h4 style=&quot;color: #000000; text-align: start;&quot; data-ke-size=&quot;size20&quot;&gt;&lt;span style=&quot;color: #000000;&quot;&gt;&lt;b&gt;Naver Signiture 서명 방식&lt;/b&gt;&lt;/span&gt;&lt;/h4&gt;
&lt;p style=&quot;color: #333333; text-align: start;&quot; data-ke-size=&quot;size16&quot;&gt;현재 Ncloud의 서비스들을 API 호출을 통해서제어를 하기 위해선 API 호출할때 각종 인증 정보를 보내야합니다.&lt;/p&gt;
&lt;p style=&quot;color: #333333; text-align: start;&quot; data-ke-size=&quot;size16&quot;&gt;저희는 깃허브 액션으로 SourceDeploy에게 신호를 보내기 위해 API 호출이 필요했습니다.&lt;/p&gt;
&lt;p style=&quot;color: #333333; text-align: start;&quot; data-ke-size=&quot;size16&quot;&gt;아래 페이지에서 각 서비스의 API에 대한 정보 및 방법에 대한 가이드 정보가 있습니다.&lt;/p&gt;
&lt;p style=&quot;color: #333333; text-align: start;&quot; data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p style=&quot;color: #333333; text-align: start;&quot; data-ke-size=&quot;size16&quot;&gt;&lt;a href=&quot;https://api.ncloud-docs.com/docs/ko/home&quot;&gt;https://api.ncloud-docs.com/docs/ko/home&lt;/a&gt;&lt;/p&gt;
&lt;figure id=&quot;og_1720623644372&quot; style=&quot;color: #333333; text-align: start;&quot; contenteditable=&quot;false&quot; data-og-image=&quot;&quot; data-og-url=&quot;https://api.ncloud-docs.com/docs/ko/home&quot; data-og-source-url=&quot;https://api.ncloud-docs.com/docs/ko/home&quot; data-og-host=&quot;api.ncloud-docs.com&quot; data-og-description=&quot;&quot; data-og-title=&quot;HOME&quot; data-og-type=&quot;article&quot; data-ke-align=&quot;alignCenter&quot; data-ke-type=&quot;opengraph&quot;&gt;&lt;a style=&quot;color: #000000;&quot; href=&quot;https://api.ncloud-docs.com/docs/ko/home&quot; data-source-url=&quot;https://api.ncloud-docs.com/docs/ko/home&quot;&gt;
&lt;div style=&quot;background-image: url();&quot;&gt;&amp;nbsp;&lt;/div&gt;
&lt;div&gt;
&lt;p style=&quot;color: #000000;&quot; data-ke-size=&quot;size16&quot;&gt;HOME&lt;/p&gt;
&lt;p style=&quot;color: #909090;&quot; data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p style=&quot;color: #909090;&quot; data-ke-size=&quot;size16&quot;&gt;api.ncloud-docs.com&lt;/p&gt;
&lt;/div&gt;
&lt;/a&gt;&lt;/figure&gt;
&lt;p style=&quot;color: #333333; text-align: start;&quot; data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p style=&quot;color: #333333; text-align: start;&quot; data-ke-size=&quot;size16&quot;&gt;이렇게 API 호출할 때, 필요한 Header 정보는 다음과 같습니다.&lt;/p&gt;
&lt;p style=&quot;color: #333333; text-align: start;&quot; data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;ul style=&quot;list-style-type: disc;&quot; data-ke-list-type=&quot;disc&quot;&gt;
&lt;li&gt;현재 timestamp 값&lt;/li&gt;
&lt;li&gt;Naver Access Key&lt;/li&gt;
&lt;li&gt;Naver Signiture V2 서명 값&lt;/li&gt;
&lt;/ul&gt;
&lt;p style=&quot;color: #333333; text-align: start;&quot; data-ke-size=&quot;size16&quot;&gt;여기서 Naver Signiture V2 서명값을 다음과 같습니다&lt;/p&gt;
&lt;blockquote style=&quot;background-color: #fcfcfc; color: #666666; text-align: left;&quot; data-ke-style=&quot;style3&quot;&gt;HTTP 메서드&amp;nbsp; + Api URL 정보 + 현재 timestamp 값 + Naver Access Key&lt;/blockquote&gt;
&lt;p style=&quot;color: #333333; text-align: start;&quot; data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p style=&quot;color: #333333; text-align: start;&quot; data-ke-size=&quot;size16&quot;&gt;이 정보들을 다 합한 값을 Naver Secrets Key를 바탕으로 SHA256 방식으로 암호화를 한 값이 서명값입니다.&lt;/p&gt;
&lt;p style=&quot;color: #333333; text-align: start;&quot; data-ke-size=&quot;size16&quot;&gt;이 서명값을 기반으로 API 호출때 인증이 되기 때문에 매우 중요합니다.&lt;/p&gt;
&lt;p style=&quot;color: #333333; text-align: start;&quot; data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p style=&quot;color: #333333; text-align: start;&quot; data-ke-size=&quot;size16&quot;&gt;이 과정이 꽤 복잡한 과정이기 때문에 쉘 명령어로 해결할 수 없었기에, 저희는 쉘 스크립트 파일을 만들어서 요청을 할 수 있게 하였습니다. 아래가 저희의 쉘 스크립트 파일입니다.&lt;/p&gt;
&lt;p style=&quot;color: #333333; text-align: start;&quot; data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;blockquote style=&quot;background-color: #fcfcfc; color: #666666; text-align: left;&quot; data-ke-style=&quot;style3&quot;&gt;#!/bin/bash&lt;br /&gt;&lt;br /&gt;#&amp;nbsp;Function&amp;nbsp;to&amp;nbsp;sign&amp;nbsp;the&amp;nbsp;request&lt;br /&gt;function&amp;nbsp;Sign_Request()&amp;nbsp;{&lt;br /&gt;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;local&amp;nbsp;api_server=$1&lt;br /&gt;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;local&amp;nbsp;api_url=$2&lt;br /&gt;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;local&amp;nbsp;apicall_method=$3&lt;br /&gt;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;local&amp;nbsp;ncloud_accesskey=$4&lt;br /&gt;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;local&amp;nbsp;ncloud_secretkey=$5&lt;br /&gt;&lt;br /&gt;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;local&amp;nbsp;unixtimestamp=$(echo&amp;nbsp;$(($(date&amp;nbsp;+%s%N)/1000000)))&lt;br /&gt;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;local&amp;nbsp;message=&quot;${apicall_method}&amp;nbsp;${api_url}\n${unixtimestamp}\n${ncloud_accesskey}&quot;&lt;br /&gt;&lt;br /&gt;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;local&amp;nbsp;signature=$(echo&amp;nbsp;-n&amp;nbsp;-e&amp;nbsp;&quot;$message&quot;|iconv&amp;nbsp;-t&amp;nbsp;utf8&amp;nbsp;|openssl&amp;nbsp;dgst&amp;nbsp;-sha256&amp;nbsp;-hmac&amp;nbsp;&quot;$ncloud_secretkey&quot;&amp;nbsp;-binary|openssl&amp;nbsp;enc&amp;nbsp;-base64)&lt;br /&gt;&lt;br /&gt;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;echo&amp;nbsp;-e&amp;nbsp;&quot;x-ncp-apigw-timestamp:$unixtimestamp\nx-ncp-iam-access-key:$ncloud_accesskey\nx-ncp-apigw-signature-v2:$signature&quot;&lt;br /&gt;}&lt;br /&gt;&lt;br /&gt;#&amp;nbsp;Sample&amp;nbsp;API&amp;nbsp;server&amp;nbsp;and&amp;nbsp;URL&lt;br /&gt;api_server = &amp;lt;API URL 정보&amp;gt;&lt;br /&gt;api_url = &amp;lt;API 세부 URI 정보&amp;gt;&lt;br /&gt;apicall_method = &amp;lt;HTTP 메서드&amp;gt;&lt;br /&gt;ncloud_accesskey = &amp;lt;Access Key&amp;gt;&lt;br /&gt;ncloud_secretkey = &amp;lt;Secret Key&amp;gt;&lt;br /&gt;api_full_url=&quot;${api_server}${api_url}&quot;&lt;br /&gt;&lt;br /&gt;#&amp;nbsp;Get&amp;nbsp;the&amp;nbsp;headers&amp;nbsp;from&amp;nbsp;the&amp;nbsp;Sign_Request&amp;nbsp;function&lt;br /&gt;headers=$(Sign_Request&amp;nbsp;&quot;$api_server&quot;&amp;nbsp;&quot;$api_url&quot;&amp;nbsp;&quot;$apicall_method&quot;&amp;nbsp;&quot;$ncloud_accesskey&quot;&amp;nbsp;&quot;$ncloud_secretkey&quot;)&lt;br /&gt;&lt;br /&gt;#&amp;nbsp;Prepare&amp;nbsp;header&amp;nbsp;arguments&amp;nbsp;for&amp;nbsp;curl&lt;br /&gt;header_args=()&lt;br /&gt;while&amp;nbsp;IFS=&amp;nbsp;read&amp;nbsp;-r&amp;nbsp;line;&amp;nbsp;do&lt;br /&gt;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;header_args+=(&quot;-H&quot;&amp;nbsp;&quot;$line&quot;)&lt;br /&gt;done&amp;nbsp;&amp;lt;&amp;lt;&amp;lt;&amp;nbsp;&quot;$headers&quot;&lt;br /&gt;&lt;br /&gt;#&amp;nbsp;Make&amp;nbsp;the&amp;nbsp;API&amp;nbsp;request&lt;br /&gt;curl&amp;nbsp;-X&amp;nbsp;&quot;$apicall_method&quot;&amp;nbsp;&quot;$api_full_url&quot;&amp;nbsp;&quot;${header_args[@]}&quot;&lt;/blockquote&gt;
&lt;p style=&quot;color: #333333; text-align: start;&quot; data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p style=&quot;color: #333333; text-align: start;&quot; data-ke-size=&quot;size16&quot;&gt;이렇게 로컬에서 쉘스크립트를 통해 직접 테스트를 하여, 성공적으로 작동하는 것을 확인하였고, 이 이후에 바로 깃허브 액션에 적용시켜 해결을 하려 했습니다.&lt;/p&gt;
&lt;p style=&quot;color: #333333; text-align: start;&quot; data-ke-size=&quot;size16&quot;&gt;이 파일을 깃허브 액션에 생성하기 위해서 깃허브액션 Secrets 에 key:value 형식으로 이 파일의 내용을 저장하였습니다.&lt;br /&gt;이렇게 성공적으로 되나 싶었지만.... 아래와 같은 오류가 발생하였습니다.&lt;/p&gt;
&lt;p style=&quot;color: #333333; text-align: start;&quot; data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p&gt;&lt;figure class=&quot;imageblock alignCenter&quot; data-ke-mobileStyle=&quot;widthOrigin&quot; data-origin-width=&quot;629&quot; data-origin-height=&quot;98&quot;&gt;&lt;span data-url=&quot;https://blog.kakaocdn.net/dn/esCIBH/btsIvl09r0V/kl3wWjw38Kk3gA7hnaJ5U1/img.png&quot; data-phocus=&quot;https://blog.kakaocdn.net/dn/esCIBH/btsIvl09r0V/kl3wWjw38Kk3gA7hnaJ5U1/img.png&quot; data-alt=&quot;'&quot;&gt;&lt;img src=&quot;https://blog.kakaocdn.net/dn/esCIBH/btsIvl09r0V/kl3wWjw38Kk3gA7hnaJ5U1/img.png&quot; srcset=&quot;https://img1.daumcdn.net/thumb/R1280x0/?scode=mtistory2&amp;fname=https%3A%2F%2Fblog.kakaocdn.net%2Fdn%2FesCIBH%2FbtsIvl09r0V%2Fkl3wWjw38Kk3gA7hnaJ5U1%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;629&quot; height=&quot;98&quot; data-origin-width=&quot;629&quot; data-origin-height=&quot;98&quot;/&gt;&lt;/span&gt;&lt;figcaption&gt;'&lt;/figcaption&gt;
&lt;/figure&gt;
&lt;/p&gt;
&lt;p style=&quot;color: #333333; text-align: start;&quot; data-ke-size=&quot;size16&quot;&gt;정말 처음보는 오류라서, 계속 헤맸습니다. 분명히 값들도 다 정확히 매핑 시켜놨고, 로컬에선 잘돌아 가는데 깃허브 액션에서만 오류가 발생하였습니다.&amp;nbsp;&lt;/p&gt;
&lt;p style=&quot;color: #333333; text-align: start;&quot; data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p style=&quot;color: #333333; text-align: start;&quot; data-ke-size=&quot;size16&quot;&gt;결국 계속해서 디버깅을 해본결과 아래 처럼 특정 값들이 빈값으로 들어온다 라는것을 확인했습니다.&lt;/p&gt;
&lt;p&gt;&lt;figure class=&quot;imageblock alignCenter&quot; data-ke-mobileStyle=&quot;widthOrigin&quot; data-origin-width=&quot;527&quot; data-origin-height=&quot;178&quot;&gt;&lt;span data-url=&quot;https://blog.kakaocdn.net/dn/kETRn/btsIuuxKETD/UdJ1pkDlY5rtNcXnJ0a0OK/img.png&quot; data-phocus=&quot;https://blog.kakaocdn.net/dn/kETRn/btsIuuxKETD/UdJ1pkDlY5rtNcXnJ0a0OK/img.png&quot;&gt;&lt;img src=&quot;https://blog.kakaocdn.net/dn/kETRn/btsIuuxKETD/UdJ1pkDlY5rtNcXnJ0a0OK/img.png&quot; srcset=&quot;https://img1.daumcdn.net/thumb/R1280x0/?scode=mtistory2&amp;fname=https%3A%2F%2Fblog.kakaocdn.net%2Fdn%2FkETRn%2FbtsIuuxKETD%2FUdJ1pkDlY5rtNcXnJ0a0OK%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;527&quot; height=&quot;178&quot; data-origin-width=&quot;527&quot; data-origin-height=&quot;178&quot;/&gt;&lt;/span&gt;&lt;/figure&gt;
&lt;/p&gt;
&lt;p style=&quot;color: #333333; text-align: start;&quot; data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p style=&quot;color: #333333; text-align: start;&quot; data-ke-size=&quot;size16&quot;&gt;위와 같은 현상이 일어난 이유는 저희의 쉘 스크립트 코드에 $라는 문자가 들어갔기 때문입니다. 저희 쉘스크립트 코드에서 환경변수로 설정을 하려고 일부로 $를 사용해서 코드를 작성을 한건데, 이게 깃허브액션에서도 $가 환경변수로 작동해서 쉘스크립트 코드 안의 환경변수가 아닌, 깃허브액션 Sercerts 값을 참조했고, 참조한 결과 해당 값이 존재하지않아 빈값을 주입하고 있었던 버그가 있었습니다.&lt;br /&gt;&lt;br /&gt;&lt;/p&gt;
&lt;p style=&quot;color: #333333; text-align: start;&quot; data-ke-size=&quot;size16&quot;&gt;이에 저희는 파일내용 자체를 깃허브 액션 Secrets를 사용하는게 아니라, 내부 변수들만 Secrets으로 넣어두고 스프링 서버 내에 쉘스크립트 파일을 넣었습니다. 따라서, 깃허브 액션에서는 이 코드를 git pull 받아 쉘스크립트 파일로 가서 설정된 환경 변수만 주입하면 되었습니다.&lt;/p&gt;
&lt;p style=&quot;color: #333333; text-align: start;&quot; data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p&gt;&lt;figure class=&quot;imageblock alignCenter&quot; data-ke-mobileStyle=&quot;widthOrigin&quot; data-origin-width=&quot;308&quot; data-origin-height=&quot;217&quot;&gt;&lt;span data-url=&quot;https://blog.kakaocdn.net/dn/cNSfn1/btsIvjPO0Lb/XbnOaRzQXKoUlkIepkozLk/img.png&quot; data-phocus=&quot;https://blog.kakaocdn.net/dn/cNSfn1/btsIvjPO0Lb/XbnOaRzQXKoUlkIepkozLk/img.png&quot; data-alt=&quot;완성된 쉘스크립트 코드&quot;&gt;&lt;img src=&quot;https://blog.kakaocdn.net/dn/cNSfn1/btsIvjPO0Lb/XbnOaRzQXKoUlkIepkozLk/img.png&quot; srcset=&quot;https://img1.daumcdn.net/thumb/R1280x0/?scode=mtistory2&amp;fname=https%3A%2F%2Fblog.kakaocdn.net%2Fdn%2FcNSfn1%2FbtsIvjPO0Lb%2FXbnOaRzQXKoUlkIepkozLk%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;217&quot; data-origin-width=&quot;308&quot; data-origin-height=&quot;217&quot;/&gt;&lt;/span&gt;&lt;figcaption&gt;완성된 쉘스크립트 코드&lt;/figcaption&gt;
&lt;/figure&gt;
&lt;/p&gt;
&lt;p style=&quot;color: #333333; text-align: start;&quot; data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p style=&quot;color: #333333; text-align: start;&quot; data-ke-size=&quot;size16&quot;&gt;&lt;span style=&quot;color: #333333;&quot;&gt;이렇게 API 호출과 관련된 버그를 해결하였습니다.&lt;br /&gt;처음엔 깃허브액션에서 사용하기 위해 Naver CLI 없이 사용하는 간단한 방법을 찾다가, 오히려 돌아온 느낌이었으며,&lt;br /&gt;개인적으로, API호출과 관련된 action 코드가 있었으면 좋았을거 같다는 아쉬움이 있었습니다.&lt;/span&gt;&lt;/p&gt;
&lt;h4 style=&quot;color: #000000; text-align: start;&quot; data-ke-size=&quot;size20&quot;&gt;&amp;nbsp;&lt;/h4&gt;
&lt;p style=&quot;color: #333333; text-align: start;&quot; data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p style=&quot;color: #333333; text-align: start;&quot; data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;h4 style=&quot;color: #000000; text-align: start;&quot; data-ke-size=&quot;size20&quot;&gt;&lt;span style=&quot;color: #000000;&quot;&gt;&lt;b&gt;Private Subnet SSH 접속&lt;/b&gt;&lt;/span&gt;&lt;/h4&gt;
&lt;p style=&quot;color: #333333; text-align: start;&quot; data-ke-size=&quot;size16&quot;&gt;이 부분은 사실 버그라기보다는 VPC내에 Private Subnet안의 서버에 SSH 접속을 하기위해서 어떤 방법을 사용해야하나 라는 저희의 고민이 담긴 문제였습니다. 기본적으로 Private Subnet 안에 있는 서버들은 외부로 노출되지않기 때문에 저희 로컬 에서도 원격으로 SSH 접속을 할 수 없었습니다. 따라서 기존의 SSH 접속방법으로는 접속이 불가능하고 다른 방법으로 접속을 해야 합니다. 저희가 생각했던 방법은 크게 3가지였습니다.&lt;/p&gt;
&lt;p style=&quot;color: #333333; text-align: start;&quot; data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;ol style=&quot;list-style-type: decimal;&quot; data-ke-list-type=&quot;decimal&quot;&gt;
&lt;li&gt;&lt;span style=&quot;color: #000000;&quot;&gt;&lt;b&gt;Public Subnet에 Bastion Host를 두어, 경유를 거쳐 SSH 접속을 하는 방법&lt;/b&gt;&lt;/span&gt;&lt;/li&gt;
&lt;li&gt;&lt;span style=&quot;color: #000000;&quot;&gt;&lt;b&gt;VPC에 VPN Gateway추가 및 터널링을 통한 접속&lt;/b&gt;&lt;/span&gt;&lt;/li&gt;
&lt;li&gt;&lt;span style=&quot;color: #000000;&quot;&gt;&lt;b&gt;NAT Gateway에 포트포워딩을 통한 SSH 접속&lt;/b&gt;&lt;/span&gt;&lt;/li&gt;
&lt;/ol&gt;
&lt;p style=&quot;color: #333333; text-align: start;&quot; data-ke-size=&quot;size16&quot;&gt;위의 방식처럼 3가지의 방법을 생각을 했는데, 제일 비용부담이 적은것은 3번째 방식이였습니다. NAT Gateway의 포트를 내부 서버 22번 포트로 포워딩하여 내부로 SSH 접속을 할 수 있게 하면 추가 요금도 없어서 부담이 제일 적을 것이라 생각을 하였습니다.&lt;br /&gt;다만, 이러한 방식의 경우 외부에 특정 포트가 노출이 되는것이기 때문에 보안 취약점이 생긴다는 단점이 있었습니다. 관련 자료를 찾아보니 Ncloud에서도 이러한 문제점 때문인지 NAT Gateway를 포트포워딩하는 기능은 제공하고 있지않았기에, 이 방식은 적용할 수 없었습니다.&lt;/p&gt;
&lt;p style=&quot;color: #333333; text-align: start;&quot; data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p style=&quot;color: #333333; text-align: start;&quot; data-ke-size=&quot;size16&quot;&gt;이게 1,2번 두가지 방식이 있는데, 두 가지 방식 다 비용 부담이 컸었습니다. 첫번째 방식은 Server의 기본요금 자체가 커, 시간당 최소 120원 이상의 가격이 들었고, 두번째 방식은 VPN Gateway를 뚫는거 자체가 시간당 138원이 들었습니다.&amp;nbsp;&lt;br /&gt;이미 운영 서버와 DB에 가격부담을 느끼고 있던터라, 여기에 적지않은 요금을 투입하는거 자체가 저희 팀에 큰 부담으로 느껴졌고, 또한, 단지 SSH접속만을 위해서 VPN을 사용한다는거 자체가 조금 말이 안된다 라고 생각을 했습니다.&amp;nbsp;&lt;/p&gt;
&lt;p style=&quot;color: #333333; text-align: start;&quot; data-ke-size=&quot;size16&quot;&gt;이에 저희는 평상시엔 네이버 웹페이지에 있는 서버 접속 콘솔을 이용하기로 하고, 긴급한 버그 해결이라던지, 아니면 대규모 수정을 해야할 부분이 있다 라고 할때만 따로 Test Server를 생성하여 Bastion Host 역할을 할 수 있게 하자 라는 결론이 나왔습니다.&lt;/p&gt;
&lt;p style=&quot;color: #333333; text-align: start;&quot; data-ke-size=&quot;size16&quot;&gt;이 부분은 결국 근본적으로 해결을 하지못한 상황입니다. 혹시나 다른 해결책이 있으시면 알려주세요 ㅎㅎ&lt;/p&gt;
&lt;p style=&quot;color: #333333; text-align: start;&quot; data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p style=&quot;color: #333333; text-align: start;&quot; data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p style=&quot;color: #333333; text-align: start;&quot; data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p style=&quot;color: #333333; text-align: start;&quot; data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p style=&quot;color: #333333; text-align: start;&quot; data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p style=&quot;color: #333333; text-align: start;&quot; data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p style=&quot;color: #333333; text-align: start;&quot; data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p style=&quot;color: #333333; text-align: start;&quot; data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p style=&quot;color: #333333; text-align: start;&quot; data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;blockquote data-ke-style=&quot;style1&quot;&gt;&lt;span style=&quot;font-family: 'Noto Serif KR';&quot;&gt;Ncloud 사용중 만족했던 점 &amp;amp; 아쉬웠던 점&lt;/span&gt;&lt;/blockquote&gt;
&lt;p style=&quot;color: #333333; text-align: start;&quot; data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p style=&quot;color: #333333; text-align: start;&quot; data-ke-size=&quot;size16&quot;&gt;우선 Ncloud를 사용하면서 만족했던 점은 다음과 같습니다.&lt;/p&gt;
&lt;p style=&quot;color: #333333; text-align: start;&quot; data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;ul style=&quot;list-style-type: disc;&quot; data-ke-list-type=&quot;disc&quot;&gt;
&lt;li&gt;&lt;span style=&quot;color: #000000;&quot;&gt;&lt;b&gt;한글로 되어있는 공식문서&lt;/b&gt;&lt;/span&gt;&lt;/li&gt;
&lt;li&gt;&lt;span style=&quot;color: #000000;&quot;&gt;&lt;b&gt;높은 AI 편의성&lt;/b&gt;&lt;/span&gt;&lt;/li&gt;
&lt;/ul&gt;
&lt;p style=&quot;color: #333333; text-align: start;&quot; data-ke-size=&quot;size16&quot;&gt;Ncloud를 활용하면서 정말 편했던 점은 공식문서가 한글로 되어있다는 점이었습니다. 따라서 공식문서를 기반으로 쉽게 개발환경을 세팅할 수 있었습니다.&lt;br /&gt;또한, 추가적으로 Naver Clova Studio에서 플레이그라운드로 AI를 사용할떄 정말 많은 편리함을 느꼈습니다.&lt;br /&gt;타 AI기술과 다르게 직접 웹을 통해 튜닝하고, 테스트를 해 볼 수 있다는 점이 정말 편했습니다.&lt;br /&gt;이를 기반으로 저희 백엔드 팀에서 AI 지식이 많이 없음에도 불구하고 빠르게 개발이 가능했습니다.&lt;/p&gt;
&lt;p style=&quot;color: #333333; text-align: start;&quot; data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p style=&quot;color: #333333; text-align: start;&quot; data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p style=&quot;color: #333333; text-align: start;&quot; data-ke-size=&quot;size16&quot;&gt;반대로 Ncloud를 사용하면서 아쉬웠던 점은 다음과 같습니다.&lt;/p&gt;
&lt;p style=&quot;color: #333333; text-align: start;&quot; data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;ul style=&quot;list-style-type: disc;&quot; data-ke-list-type=&quot;disc&quot;&gt;
&lt;li&gt;&lt;span style=&quot;color: #000000;&quot;&gt;&lt;b&gt;관련 레퍼런스 부족&lt;/b&gt;&lt;/span&gt;&lt;/li&gt;
&lt;li&gt;&lt;span style=&quot;color: #000000;&quot;&gt;&lt;b&gt;Server 및 DB의 과도한 최저성능 및 요금&lt;/b&gt;&lt;/span&gt;&lt;/li&gt;
&lt;/ul&gt;
&lt;p style=&quot;color: #333333; text-align: start;&quot; data-ke-size=&quot;size16&quot;&gt;Ncloud를 사용하면서 정말 많이 느꼈던게, AWS Cloud에 비해 블로그같은 레퍼런스가 찾기가 매우 어려웠습니다. 물론 사용자 차이로 인해 당연히 자료가 부족한게 당연하겠지만... 한번 버그가 일어나면 공식문서 외에는 참고할 자료가 없었습니다. Object Storage나, Source Deploy같은 서비스의 경우는 AWS와 비슷하겠지.. 하면서 S3 및 Code Deploy 관련 자료를 찾아보면서 해결했어야 했습니다. 따라서 이러한 참고 자료를 좀더 다양하게 존재했으면 좋겠다... 라는 아쉬움이 있었습니다.&lt;/p&gt;
&lt;p style=&quot;color: #333333; text-align: start;&quot; data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p style=&quot;color: #333333; text-align: start;&quot; data-ke-size=&quot;size16&quot;&gt;또한, 아래의 자료가 현재 NaverCloud의 Server 및 DB의 요금페이지 입니다.&amp;nbsp;&amp;nbsp;&lt;/p&gt;
&lt;p style=&quot;color: #333333; text-align: start;&quot; data-ke-size=&quot;size16&quot;&gt;&lt;a href=&quot;https://www.ncloud.com/product/compute/server&quot;&gt;https://www.ncloud.com/product/compute/server&lt;/a&gt;&lt;/p&gt;
&lt;p style=&quot;color: #333333; text-align: start;&quot; data-ke-size=&quot;size16&quot;&gt;&lt;a href=&quot;https://www.ncloud.com/product/database/cloudDbMysql&quot;&gt;https://www.ncloud.com/product/database/cloudDbMysql&lt;/a&gt;&lt;/p&gt;
&lt;p style=&quot;color: #333333; text-align: start;&quot; data-ke-size=&quot;size16&quot;&gt;위의 사이트에서 보면 아시겠지만 VPC 기준으로 Server의 기본 요금은 119원, MySQL DB의 기본 요금은 256원 입니다.&lt;br /&gt;아직 학생의 입장이라, 서버와 DB의 최저 성능이 너무 높게 느껴졌고, 그로인한 요금에 대한 부담이 크게 다가왔습니다.&lt;br /&gt;특히 VPC 경우, 직접 Private Subnet에 ssh 접속을 하지 못하다 보니 Bastion Host용 서버가 필요한데, 이 접속용 서버로 vCPU 2, 메모리 8GB, 디스크 50GB가 너무 오버스펙이라는 생각이 들었습니다..ㅠㅠ&lt;br /&gt;따라서, VPC 용도로 프리티어 같은 낮은 성능의 Server를 1개 정도 생성할 수 있도록 하면 괜찮지 않았을까.... 라는 아쉬움이 있었습니다.&lt;/p&gt;
&lt;p style=&quot;color: #333333; text-align: start;&quot; data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p style=&quot;color: #333333; text-align: start;&quot; data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p style=&quot;color: #333333; text-align: start;&quot; data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p style=&quot;color: #333333; text-align: start;&quot; data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p style=&quot;color: #333333; text-align: start;&quot; data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p style=&quot;color: #333333; text-align: start;&quot; data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;blockquote data-ke-style=&quot;style1&quot;&gt;&lt;span style=&quot;font-family: 'Noto Serif KR';&quot;&gt;Green Developers 프로그램 참여 소감&lt;/span&gt;&lt;/blockquote&gt;
&lt;p style=&quot;color: #333333; text-align: start;&quot; data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p style=&quot;color: #333333; text-align: start;&quot; data-ke-size=&quot;size16&quot;&gt;이번에 동아리에서 우연히 네이버의 Green Developers 프로그램에 참여하게 되었는데, 클라우드에 관심 있는 개발자에겐 정말 너무 좋은 기회다 라고 생각합니다.&lt;/p&gt;
&lt;p style=&quot;color: #333333; text-align: start;&quot; data-ke-size=&quot;size16&quot;&gt;&lt;br /&gt;사실 처음에는 그냥 Server와 DB만 사용할 목적으로 신청을 했지만, 홈페이지를 보니 정말 다양한 Ncloud의 서비스가 있었습니다. 따라서, 이번 기회에 다양한 클라우드 제품들을 써볼까 싶어 Ncloud의 모든 서비스들을 분석하였고, 또한 제한된 크레딧 내에서 어떻게 하면 효율적인 비용으로 아키텍처를 구성할 수 있을까 고민을 많이 하면서 개인적으로 성장을 많이 했다고 생각합니다.&lt;/p&gt;
&lt;p style=&quot;color: #333333; text-align: start;&quot; data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p style=&quot;color: #333333; text-align: start;&quot; data-ke-size=&quot;size16&quot;&gt;특히 관련 레퍼런스가 적다보니 조금은 힘들었지만 AI 및 관련자료 없이 혼자서 고민을 하는 시간이 많았어서 더욱 더 많이 배우고 기억할 수 있었던 값진 기회였던거 같습니다.&lt;br /&gt;이번에 이렇게 좋은 기회를 주셔서 너무 감사드리고, 이번 경험을 바탕으로 다음에 다른 Developers 프로그램이 있다면 참여하여 학습을 해보고 싶습니다!!&lt;/p&gt;</description>
      <category>클라우드</category>
      <author>tioon</author>
      <guid isPermaLink="true">https://tioon.tistory.com/198</guid>
      <comments>https://tioon.tistory.com/198#entry198comment</comments>
      <pubDate>Sun, 23 Jun 2024 00:46:12 +0900</pubDate>
    </item>
    <item>
      <title>FCM을 통한 실시간 푸시 메세지 구현 (Feat. FCM 토큰)</title>
      <link>https://tioon.tistory.com/197</link>
      <description>&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;이번에 진행한 프로젝트에서 실시간 푸시 메세지가 꼭 필요한 기능이였습니다. &lt;br /&gt;따라서 푸시메세지를 구현하기 위해 FCM을 사용했으며, 이를 사용할때 마주했던 문제들과 이를 해결했던 방법들을 공유해보고자 합니다.&lt;br /&gt;&lt;br /&gt;우선, 실시간 푸시 메세지를 구현할 수 있는 방법은 다양한데, 대표적인 기술들을 알아 보도록 하겠습니다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;blockquote data-ke-style=&quot;style1&quot;&gt;&lt;span style=&quot;font-family: 'Noto Serif KR';&quot;&gt;&lt;b&gt;실시간 푸시 메세지 구현 기술 종류&lt;/b&gt;&lt;br /&gt;&lt;br /&gt;&lt;/span&gt;&lt;/blockquote&gt;
&lt;ul style=&quot;list-style-type: disc;&quot; data-ke-list-type=&quot;disc&quot;&gt;
&lt;li&gt;&lt;b&gt;WebSocket&lt;/b&gt;
&lt;ul style=&quot;list-style-type: disc;&quot; data-ke-list-type=&quot;disc&quot;&gt;
&lt;li&gt;클라이언트와 서버간 지속적인 연결을 통해 양방향 통신을 할 수 있음.&lt;/li&gt;
&lt;li&gt;이 지속적인 연결을 통해 실시간 푸시메세지를 구현할 수 있음.&lt;/li&gt;
&lt;li&gt;하지만, 웹소켓 구현이 복잡하며 실시간 푸시메세지 구현을 위해 WebSocket을 쓰는게 알맞지 않음&lt;/li&gt;
&lt;/ul&gt;
&lt;/li&gt;
&lt;li&gt;&lt;b&gt;&amp;nbsp;Message Queue(Pub/Sub)&lt;/b&gt;
&lt;ul style=&quot;list-style-type: disc;&quot; data-ke-list-type=&quot;disc&quot;&gt;
&lt;li&gt;Redis, Kafka 등의 메세지 큐를 통해 메세지 게시 및 구독을 할 수 있음.&lt;/li&gt;
&lt;li&gt;이 메세지를 기반으로 실시간 푸시메세지를 구현할 수 있음.&lt;/li&gt;
&lt;li&gt;서버를 따로 두어야 하며 이로인해 구현이 복잡해지므로, 실시간 푸시 메세지구현을 위해 Message Queue를 쓰는게 알맞지 않음&lt;/li&gt;
&lt;/ul&gt;
&lt;/li&gt;
&lt;li&gt;&lt;b&gt;Firebase Cloud Messaging (FCM)&lt;/b&gt;
&lt;ul style=&quot;list-style-type: disc;&quot; data-ke-list-type=&quot;disc&quot;&gt;
&lt;li&gt;Google의 클라우드 메세징 서비스로, 쉽게 실시간 푸시메세지를 구현할 수 있음&lt;/li&gt;
&lt;li&gt;FCM 토큰이라는 것으로 사용자를 식별할 수 있으며, 이 토큰만 가지고 있으면 손쉽게 푸시메세지를 발핼 할 수 있음.&lt;/li&gt;
&lt;li&gt;FCM 서버는 Google이 관리하고 있으므로, 구현이 복잡하지않음.&lt;/li&gt;
&lt;/ul&gt;
&lt;/li&gt;
&lt;/ul&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;이렇게 대표적으로 3가지의 구현 방식이 있었지만 FCM방식이 제일 알맞은 방법이라고 생각하여 FCM을 통해 푸시메세지를 구현하였습니다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;개발기간이 1달 정도밖에 안남았기 때문에 구현방식이 최대한 간단해야했으며, 높은 안정성을 위해 별도의 서버 구축없이 진행하는 방식이 맞다고 생각을 했기 때문에 FCM을 통해 구현을 하기로 하였습니다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;blockquote data-ke-style=&quot;style1&quot;&gt;&lt;b&gt;&lt;span style=&quot;font-family: 'Noto Serif KR';&quot;&gt;FCM 푸시메세지 발행 과정&lt;/span&gt;&lt;/b&gt;&lt;/blockquote&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;우선 FCM구현 과정을 설명하기 이전에, FCM이 어떤원리로 실시간 푸시메세지를 발급할 수 있는지를 먼저 알아 보도록 하겠습니다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p&gt;&lt;figure class=&quot;imageblock alignCenter&quot; data-ke-mobileStyle=&quot;widthOrigin&quot; data-origin-width=&quot;668&quot; data-origin-height=&quot;424&quot;&gt;&lt;span data-url=&quot;https://blog.kakaocdn.net/dn/nLvEx/btsIajB5I8C/WNmN0pLWNSdNzA0wuyhFj1/img.png&quot; data-phocus=&quot;https://blog.kakaocdn.net/dn/nLvEx/btsIajB5I8C/WNmN0pLWNSdNzA0wuyhFj1/img.png&quot;&gt;&lt;img src=&quot;https://blog.kakaocdn.net/dn/nLvEx/btsIajB5I8C/WNmN0pLWNSdNzA0wuyhFj1/img.png&quot; srcset=&quot;https://img1.daumcdn.net/thumb/R1280x0/?scode=mtistory2&amp;fname=https%3A%2F%2Fblog.kakaocdn.net%2Fdn%2FnLvEx%2FbtsIajB5I8C%2FWNmN0pLWNSdNzA0wuyhFj1%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;668&quot; height=&quot;424&quot; data-origin-width=&quot;668&quot; data-origin-height=&quot;424&quot;/&gt;&lt;/span&gt;&lt;/figure&gt;
&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;FCM을 구현하기 위해선 프론트, 백엔드 둘 다 구현을 해야하는 부분이 있기 때문에 서로 소통을 통해 이 구조에 대해 명확히 하는게 중요합니다. 우선 서로 구현해야하는 부분은 다음과 같습니다.&lt;/p&gt;
&lt;ul style=&quot;list-style-type: disc;&quot; data-ke-list-type=&quot;disc&quot;&gt;
&lt;li&gt;&lt;b&gt;프론트&lt;/b&gt;
&lt;ul style=&quot;list-style-type: disc;&quot; data-ke-list-type=&quot;disc&quot;&gt;
&lt;li&gt;Service Worker의 MessageListener 구현&lt;/li&gt;
&lt;li&gt;FCM 토큰 발급 및 저장 로직 구현&lt;/li&gt;
&lt;/ul&gt;
&lt;/li&gt;
&lt;li&gt;&lt;b&gt;백엔드&lt;/b&gt;
&lt;ul style=&quot;list-style-type: disc;&quot; data-ke-list-type=&quot;disc&quot;&gt;
&lt;li&gt;FCM 토큰 저장 API 구현&lt;/li&gt;
&lt;li&gt;FCM 토큰 관리 로직 구현&lt;/li&gt;
&lt;li&gt;푸시메세지 발급 요청 구현&lt;/li&gt;
&lt;/ul&gt;
&lt;/li&gt;
&lt;/ul&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;여기서 프론트와 백엔드는 서로 같은 Firebase의 정보를가지고 있어야하며 이 정보를 기반으로 서로 FCM 토큰을 발급 받고 이 FCM 토큰을 기반으로 실시간 푸시메세지를 전송 및 수신 하여야합니다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;이때, Service Worker는 푸시메세지를 수신하기 위해선 무조건 필요합니다. ServiceWorker가 백그라운드로 켜져있어야 사용자가 해당 앱을 사용하지 않을때에도 실시간으로 알림을 받을 수 있기 때문입니다.&lt;br /&gt;&lt;br /&gt;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;구체적인 전체 과정은 다음과 같습니다.&lt;/p&gt;
&lt;ol style=&quot;list-style-type: decimal;&quot; data-ke-list-type=&quot;decimal&quot;&gt;
&lt;li&gt;&lt;b&gt;FCM 토큰 요청 및 획득&lt;/b&gt;&lt;br /&gt;- 프론트에서 Firebase 측에 FCM 토큰을 발급 요청을 합니다. 이때 발급요청이 성공적으로 이루어지면, FCM 토큰을 획득할 수 있고, 이 토큰이 해당 사용자의 고유한 FCM 토큰입니다.&lt;br /&gt;&lt;br /&gt;&lt;/li&gt;
&lt;li&gt;&lt;b&gt;서버에 FCM 토큰 저장&lt;/b&gt;&lt;br /&gt;- 발급받은 FCM 토큰으로 백엔드 서버에 FCM 토큰을 저장해야 합니다. 이때, 백엔드 측에선 FCM 토큰을 저장하는 API를 개발하여야하며, 이는 해당 프로젝트의 비즈니스 요구사항에 따라 API 구현 방식이 달라질 수 있습니다.&lt;br /&gt;프론트에서는 발급받은 FCM 토큰을 해당 API를 이용해 백엔드 서버에 저장하여야합니다.&lt;br /&gt;&lt;br /&gt;&lt;/li&gt;
&lt;li&gt;&lt;b&gt;FCM 토큰으로 메세지 전송요청&lt;/b&gt;&lt;br /&gt;- 백엔드에선 해당 FCM 토큰을 저장하고 있다가 푸시메세지를 발급해야할때, 해당 FCM 토큰으로 Firebase에 푸시메세지 발급 요청을 합니다. 이때, 해당 FCM 토큰이 정상적인 토큰일 시에 메세지 발급이 요청되며, 그외의 경우는 ErrorCode를 반환합니다.&lt;br /&gt;&lt;br /&gt;&lt;/li&gt;
&lt;li&gt;&lt;b&gt;메세지 전송&lt;/b&gt;&lt;br /&gt;- 백엔드에서 푸시 메세지 발급이 정상적으로 완료 되면, Firebase에서 FCM 토큰의 정보를 바탕으로 해당 사용자에게 푸시 메세지를 발급합니다. 이때 해당 사용자의 Service Worker에게 전송하게 됩니다.&lt;br /&gt;이때, ServiceWorker가 정상적으로 백그라운드에서 실행되고 있다면, 푸시메세지를 수신하게 됩니다.&lt;br /&gt;&lt;br /&gt;&lt;/li&gt;
&lt;li&gt;&lt;b&gt;리스너를 통해 메세지 수신&lt;/b&gt;&lt;br /&gt;- Service Worker를 통해 정상적으로 푸시 메세지를 발급받은 후, 사용자에게 정상적으로 정보를 출력하게 됩니다.&lt;/li&gt;
&lt;/ol&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;blockquote data-ke-style=&quot;style1&quot;&gt;&lt;b&gt;&lt;span style=&quot;font-family: 'Noto Serif KR';&quot;&gt;구글 파이어베이스 구현&lt;/span&gt;&lt;/b&gt;&lt;/blockquote&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;우선 먼저 아래링크의 구글 파이어베이스에서 직접 FCM 등록을 해야합니다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;a href=&quot;https://firebase.google.com/?hl=ko&quot; target=&quot;_blank&quot; rel=&quot;noopener&amp;nbsp;noreferrer&quot;&gt;https://firebase.google.com/?hl=ko&lt;/a&gt;&lt;/p&gt;
&lt;figure id=&quot;og_1719118334676&quot; contenteditable=&quot;false&quot; data-ke-type=&quot;opengraph&quot; data-ke-align=&quot;alignCenter&quot; data-og-type=&quot;website&quot; data-og-title=&quot;Firebase | Google's Mobile and Web App Development Platform&quot; data-og-description=&quot;개발자가 사용자가 좋아할 만한 앱과 게임을 빌드하도록 지원하는 Google의 모바일 및 웹 앱 개발 플랫폼인 Firebase에 대해 알아보세요.&quot; data-og-host=&quot;firebase.google.com&quot; data-og-source-url=&quot;https://firebase.google.com/?hl=ko&quot; data-og-url=&quot;https://firebase.google.com/?hl=ko&quot; data-og-image=&quot;https://scrap.kakaocdn.net/dn/gFtG2/hyWoKmsPFC/sYP6EiHYtHYL7zUcikkEE1/img.png?width=1600&amp;amp;height=800&amp;amp;face=0_0_1600_800,https://scrap.kakaocdn.net/dn/CaFRN/hyWoOCn0gI/JRhZFuAmDIXBJ9CcE6v2a1/img.png?width=1600&amp;amp;height=800&amp;amp;face=0_0_1600_800&quot;&gt;&lt;a href=&quot;https://firebase.google.com/?hl=ko&quot; target=&quot;_blank&quot; rel=&quot;noopener&quot; data-source-url=&quot;https://firebase.google.com/?hl=ko&quot;&gt;
&lt;div class=&quot;og-image&quot; style=&quot;background-image: url('https://scrap.kakaocdn.net/dn/gFtG2/hyWoKmsPFC/sYP6EiHYtHYL7zUcikkEE1/img.png?width=1600&amp;amp;height=800&amp;amp;face=0_0_1600_800,https://scrap.kakaocdn.net/dn/CaFRN/hyWoOCn0gI/JRhZFuAmDIXBJ9CcE6v2a1/img.png?width=1600&amp;amp;height=800&amp;amp;face=0_0_1600_800');&quot;&gt;&amp;nbsp;&lt;/div&gt;
&lt;div class=&quot;og-text&quot;&gt;
&lt;p class=&quot;og-title&quot; data-ke-size=&quot;size16&quot;&gt;Firebase | Google's Mobile and Web App Development Platform&lt;/p&gt;
&lt;p class=&quot;og-desc&quot; data-ke-size=&quot;size16&quot;&gt;개발자가 사용자가 좋아할 만한 앱과 게임을 빌드하도록 지원하는 Google의 모바일 및 웹 앱 개발 플랫폼인 Firebase에 대해 알아보세요.&lt;/p&gt;
&lt;p class=&quot;og-host&quot; data-ke-size=&quot;size16&quot;&gt;firebase.google.com&lt;/p&gt;
&lt;/div&gt;
&lt;/a&gt;&lt;/figure&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;여기서 FCM 등록을 하게되면, '서비스 계정'이라는게 하나 나오게 되며, 이것을 이용해 백엔드 서버에서 FCM 환경설정을 할 수 있고, 이를 이용해 FCM 푸시메세지를 전송할 수 있기 때문에 이 서비스 계정을 JSON파일로 저장한 이후에 백엔드 서버에 등록을 해주시면 됩니다.&lt;br /&gt;&lt;br /&gt;저희의 경우 스프링서버이므로, 아래와 같이 resources 디렉토리 내부에 서비스계정 JSON 정보를 등록하였습니다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;이때 무조건 resources 파일 내에 json 파일을 두어야 서버에서 인식을 할 수 있다고 정보를 본 것 같습니다.&lt;br /&gt;(이건 확실치 않습니다...ㅠㅠ)&lt;/p&gt;
&lt;p&gt;&lt;figure class=&quot;imageblock alignCenter&quot; data-ke-mobileStyle=&quot;widthOrigin&quot; data-origin-width=&quot;606&quot; data-origin-height=&quot;225&quot;&gt;&lt;span data-url=&quot;https://blog.kakaocdn.net/dn/o1JLQ/btsH84TxSV6/SiyJyUTI8XM5kr5JVKArX0/img.png&quot; data-phocus=&quot;https://blog.kakaocdn.net/dn/o1JLQ/btsH84TxSV6/SiyJyUTI8XM5kr5JVKArX0/img.png&quot;&gt;&lt;img src=&quot;https://blog.kakaocdn.net/dn/o1JLQ/btsH84TxSV6/SiyJyUTI8XM5kr5JVKArX0/img.png&quot; srcset=&quot;https://img1.daumcdn.net/thumb/R1280x0/?scode=mtistory2&amp;fname=https%3A%2F%2Fblog.kakaocdn.net%2Fdn%2Fo1JLQ%2FbtsH84TxSV6%2FSiyJyUTI8XM5kr5JVKArX0%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;606&quot; height=&quot;225&quot; data-origin-width=&quot;606&quot; data-origin-height=&quot;225&quot;/&gt;&lt;/span&gt;&lt;/figure&gt;
&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;이렇게 json파일을 두고, 서버에서 이 정보를 가지고 FCM 환경설정을 할 것입니다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p&gt;&lt;figure class=&quot;imageblock alignCenter&quot; data-ke-mobileStyle=&quot;widthOrigin&quot; data-origin-width=&quot;331&quot; data-origin-height=&quot;252&quot;&gt;&lt;span data-url=&quot;https://blog.kakaocdn.net/dn/dwLJFQ/btsH9Hi5H2c/9I0kiQ6VYwF5ShRZefWUTK/img.png&quot; data-phocus=&quot;https://blog.kakaocdn.net/dn/dwLJFQ/btsH9Hi5H2c/9I0kiQ6VYwF5ShRZefWUTK/img.png&quot;&gt;&lt;img src=&quot;https://blog.kakaocdn.net/dn/dwLJFQ/btsH9Hi5H2c/9I0kiQ6VYwF5ShRZefWUTK/img.png&quot; srcset=&quot;https://img1.daumcdn.net/thumb/R1280x0/?scode=mtistory2&amp;fname=https%3A%2F%2Fblog.kakaocdn.net%2Fdn%2FdwLJFQ%2FbtsH9Hi5H2c%2F9I0kiQ6VYwF5ShRZefWUTK%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;331&quot; height=&quot;252&quot; data-origin-width=&quot;331&quot; data-origin-height=&quot;252&quot;/&gt;&lt;/span&gt;&lt;/figure&gt;
&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;저희의 경우는 global이라는 패키지로 전체 서버 환경을 설정하고 있기때문에 global 패키지 내에 firebase를 두어서 파이어베이스 기능을 구현하였습니다. 물론, FirebaseMessagingService와 FirebaseNotificationService는 따로 도메인으로 빼도 되었지만, 개발 기간이 매우 짧아 이것에 대한 고민은 충분히 하지 못하였으며, 따로 도메인으로 빼기엔 한곳에서 Firebase를 관리하는게 좀 더 편리하다는 점 때문에 이렇게 global에 한곳에 두었습니다.&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;이제 아래는 FirebaseInitializer 코드입니다.&lt;/p&gt;
&lt;pre class=&quot;crystal&quot; style=&quot;background-color: #2b2b2b; color: #a9b7c6;&quot;&gt;&lt;code&gt;import com.google.auth.oauth2.GoogleCredentials;
import com.google.firebase.FirebaseApp;
import com.google.firebase.FirebaseOptions;
import org.springframework.beans.factory.annotation.Value;
import org.springframework.context.annotation.Configuration;
import org.springframework.core.io.ClassPathResource;

import java.io.FileInputStream;
import java.io.IOException;
import java.io.InputStream;

import javax.annotation.PostConstruct;

@Configuration
public class FirebaseInitializer {

    @Value(&quot;${firebase.service-account-file}&quot;)
    private String serviceAccountFile;

    @Value(&quot;${firebase.database-url}&quot;)
    private String databaseUrl;

    @PostConstruct
    public void initialize() {
        try {
            InputStream serviceAccount =
                    new ClassPathResource(serviceAccountFile).getInputStream();

            FirebaseOptions options = new FirebaseOptions.Builder()
                    .setCredentials(GoogleCredentials.fromStream(serviceAccount))
                    .setDatabaseUrl(databaseUrl)
                    .build();

            if (FirebaseApp.getApps().isEmpty()) {
                System.out.println(&quot;파이어베이스 초기화&quot;);
                FirebaseApp.initializeApp(options);
            }
        } catch (IOException e) {
            e.printStackTrace();
        }
    }
}&lt;/code&gt;&lt;/pre&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;생각보다 이 파이어베이스 정보를 기반으로 초기화 하는 코드에 대한 예시가 구글링에 없어서 좀 시간이 걸렸던 기억이 있습니다.&lt;br /&gt;제가 못찾았을 수도 있지만, 구글링으로 찾은 코드들은 다 제 스프링에서 오류가 났었습니다. 또한, 이 코드들이 컴파일 오류가 나는것이 아닌, FCM 실행시 런타임 오류가 났기 때문에 디버깅과정에서 오랜 시간이 걸렸습니다...ㅋㅋㅋ&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;아무튼, 이렇게 스프링에서 파이어베이스를 초기화 하였으며, service-account-file엔 해당 json 파일의 풀네임을 입력하였으며, database-url은 Google 에서 얻은 파이어베이스의 Database URL의 정보를 입력하였습니다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;이제 이 다음으로,이 정보를 기반으로 FCM토큰을 이용해 푸시메세지를 발행하는 코드를 구현해야합니다.&lt;br /&gt;저 같은 경우는 스프링에서 Service라는 것을 이용해 구현하였습니다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;pre class=&quot;reasonml&quot; style=&quot;background-color: #2b2b2b; color: #a9b7c6;&quot;&gt;&lt;code&gt;@Service
@RequiredArgsConstructor
@Slf4j
public class FirebaseMessagingService {
    private final FcmTokenRepository fcmTokenRepository;

    /**
     * Firebase를 통해 푸시 알림을 전송합니다.
     *
     * @param token   대상 디바이스의 FCM 토큰
     * @param title   알림 제목
     * @param body    알림 내용
     * @return        전송된 메시지의 ID
     * @throws FirebaseMessagingException FCM 전송 중 오류 발생 시
     */
    public String sendNotification(String token, String title, String body) {
        Notification notification = Notification.builder()
                .setTitle(title)
                .setBody(body)
                .build();

        Message message = Message.builder()
                .setToken(token)
                .setNotification(notification)
                .build();

        try {
            return FirebaseMessaging.getInstance().send(message);
        } catch (FirebaseMessagingException e) {
            if (e.getMessagingErrorCode().equals(MessagingErrorCode.INVALID_ARGUMENT)) {
                // 토큰이 유효하지 않은 경우, 오류 코드를 반환
                return e.getMessagingErrorCode().toString();
            } else if (e.getMessagingErrorCode().equals(MessagingErrorCode.UNREGISTERED)) {
                // 재발급된 이전 토큰인 경우, 오류 코드를 반환
                return e.getMessagingErrorCode().toString();
            }
            else { // 그 외, 오류는 런타임 예외로 처리
                throw new RuntimeException(e);
            }
        }
    }
}&lt;/code&gt;&lt;/pre&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;이 코드를 보시면 FirebaseMessaging.getInstance().send(message); 라는것을 이용해 푸시메세지를 보내는 것을 확인하실 수 있습니다.근데 지금 보면 try-catch문으로 에러처리하고 있는 부분이 있습니다. 이부분은 아래에서 자세히 기술하도록 하겠습니다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;blockquote data-ke-style=&quot;style1&quot;&gt;&lt;span style=&quot;font-family: 'Noto Serif KR';&quot;&gt;FCM 토큰 관리방법&lt;/span&gt;&lt;/blockquote&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;지금 까지 코드량을 보면 거의 50줄도 안되기 때문에 굉장히 구현이 쉽습니다. 저 또한, 이 기술을 구현하는데 그렇게 많은 시간이 들지 않았으며, 실제 동작테스트도 그렇게 많은 시간이 걸리지 않았습니다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;다만,실제 동작을 테스트하는 과정에서 비즈니스 로직 상의 버그를 만났으며 이를 해결하기위해 가설을 세우고 하나씩 해결해 나가는 과정을 거쳤습니다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;우선 저희가 생각한 사용자가 FCM 토큰을 사용할 때 일어날 수 있는 비즈니스 로직 오류는 다음과 같습니다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;blockquote data-ke-style=&quot;style2&quot;&gt;한 사용자가 한 계정으로 여러 기기에서 동시에 사용&lt;/blockquote&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;이 경우는 한사용자가 폰, 태블릿, 노트북 등의 기기에서 한 계정을 동시에 사용하는 경우입니다. 우리 주변에서 예를들면 카톡을 여러 기기에서 동시에 사용하고 있는 경우입니다.&amp;nbsp;&lt;br /&gt;저희는 한 사용자에게 한 개의 FCM 토큰을 가질 수 있게 하였기때문에 새로운 기기에서 로그인을 하게되면 해당 기기에서만 푸시메세지가 발행이 되고, 그 외의 기기에서는 푸시메세지가 전송이 안되는 문제가 발생했습니다.&lt;br /&gt;따라서, 이를 해결하기 위해서는 한 사용자 계정에 각각의 기기마다 다르게 FCM 토큰이 저장이 되어야 했으며, 이걸 위해선 ERD 자체를 수정해 FCM 토큰이라는 테이블을 따로 분리하여 한사용자마다 여러개의 FCM 토큰을 가질 수 있게하였습니다.&lt;/p&gt;
&lt;p&gt;&lt;figure class=&quot;imageblock alignCenter&quot; data-ke-mobileStyle=&quot;widthOrigin&quot; data-origin-width=&quot;573&quot; data-origin-height=&quot;324&quot;&gt;&lt;span data-url=&quot;https://blog.kakaocdn.net/dn/cwtlTj/btsIaHbMpZ9/9KKvp1cuRC0eJJkkAEfook/img.png&quot; data-phocus=&quot;https://blog.kakaocdn.net/dn/cwtlTj/btsIaHbMpZ9/9KKvp1cuRC0eJJkkAEfook/img.png&quot;&gt;&lt;img src=&quot;https://blog.kakaocdn.net/dn/cwtlTj/btsIaHbMpZ9/9KKvp1cuRC0eJJkkAEfook/img.png&quot; srcset=&quot;https://img1.daumcdn.net/thumb/R1280x0/?scode=mtistory2&amp;fname=https%3A%2F%2Fblog.kakaocdn.net%2Fdn%2FcwtlTj%2FbtsIaHbMpZ9%2F9KKvp1cuRC0eJJkkAEfook%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;573&quot; height=&quot;324&quot; data-origin-width=&quot;573&quot; data-origin-height=&quot;324&quot;/&gt;&lt;/span&gt;&lt;/figure&gt;
&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;br /&gt;따라서, 이를 이용해 한 사용자가 여러기기에서 동시에 사용을 함에도 모든 기기에서 푸시메세지를 받을 수 있도록 구현 하였습니다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;blockquote data-ke-style=&quot;style2&quot;&gt;&lt;b&gt;&lt;span style=&quot;color: #000000;&quot;&gt;&amp;nbsp;한 기기에서 여러 계정 사용&lt;/span&gt;&lt;/b&gt;&lt;/blockquote&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;이 경우는 전자의 상황과 반대로 한 기기에서 여러 계정을 사용하는 경우입니다. 우리 주변에서 예를 들면 내 계정을 사용하다가 갑자기 친구계정으로 로그인을 하거나, 서브 계정으로 로그인하는 경우입니다.&lt;br /&gt;이 경우엔, 해당 기기의 브라우저안에 FCM토큰의 정보가 남아 있을 수 있으며 이 정보가 남아 있을 경우, 해당 기기에 새로 로그인한 사용자의 푸시메세지 뿐만아니라, 전에 로그인된 사용자의 푸시메세지 까지 발행될 수 있기때문에 비즈니스상 오류가 발생할 수 있었습니다.&lt;br /&gt;따라서, 이를 해결하기위해 로그아웃시에 FCM 토큰을 추가로 서버로 넘기게하여, 로그아웃시 해당 계정의 FCM 토큰을 삭제하는 로직을 추가하였습니다.&lt;br /&gt;이를 통해, 한 기기안에서 중복으로 푸시메세지가 발행되는 문제를 해결하였으며,정상적으로 현재 로그인된 사용자의 FCM 푸시메세지만 받을 수 있게 하였습니다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;blockquote data-ke-style=&quot;style2&quot;&gt;&lt;b&gt;&lt;span style=&quot;color: #000000;&quot;&gt;한 사용자가 오랫동안 사용하지않음&lt;/span&gt;&lt;/b&gt;&lt;/blockquote&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;이 경우는 비즈니스상의 심각한 오류는 아니지만, FCM 토큰이 테이블에 쌓이게 되면 이후에 FCM 토큰 조회시에 성능이 저하 될 것이며, 실시간 푸시메세지 발행에 있어 성능 장애가 생길 수 있을 것이라 판단하였습니다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;즉, 오랫동안 사용하지 않는 사람들의 FCM 토큰을 계속 저장하게 되면 성능 저하가 발생하기 때문에 이 사용자들의 FCM 토큰은 주기적으로 체크 후 삭제하는게 맞다는 판단을 하였습니다.&lt;br /&gt;따라서, FCM 토큰에 신선도 라는 로직을 추가하였고, 최종 사용 시각이 2달이 지난 사용자는 자동으로 삭제할 수 있게 스케줄러를 설정하여 구현하였습니다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;blockquote data-ke-style=&quot;style2&quot;&gt;&lt;span style=&quot;color: #000000;&quot;&gt;&lt;b&gt;이외의 예기치 않은 FCM 토큰 오류&lt;/b&gt;&lt;/span&gt;&lt;/blockquote&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;이외에 예기치 않은 FCM 토큰 오류가 있었고 내용은 다음과 같습니다.&lt;/p&gt;
&lt;ul style=&quot;list-style-type: disc;&quot; data-ke-list-type=&quot;disc&quot;&gt;
&lt;li&gt;사용중에 예기치않게 프론트에서 FCM 토큰이 재발급되는 경우&lt;/li&gt;
&lt;li&gt;의도치않게 서버에 이상한 FCM 토큰이 저장되는 경우&lt;/li&gt;
&lt;/ul&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;테스트 도중 이렇게 의도치않게 FCM 토큰이 변경 및 수정되는 경우가 있었으며 이렇게 잘못된 FCM 토큰이 서버에[ 저장이 될 경우, 푸시메세지 발행시에 FirebaseMessagingException이 발생하였습니다. 이것을 해결하기 위해선 직접 Try-Catch를 통해 Exception처리를 하여 해당 토큰을 삭제하도록 하였습니다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;이때 FirebaseMessagingException의 종류는 다음과 같습니다.&lt;/p&gt;
&lt;p&gt;&lt;figure class=&quot;imageblock alignCenter&quot; data-ke-mobileStyle=&quot;widthOrigin&quot; data-origin-width=&quot;700&quot; data-origin-height=&quot;584&quot;&gt;&lt;span data-url=&quot;https://blog.kakaocdn.net/dn/b8t7wA/btsH86KyEvq/SVnVNPeQHcvXUdwjIYDZQ1/img.png&quot; data-phocus=&quot;https://blog.kakaocdn.net/dn/b8t7wA/btsH86KyEvq/SVnVNPeQHcvXUdwjIYDZQ1/img.png&quot;&gt;&lt;img src=&quot;https://blog.kakaocdn.net/dn/b8t7wA/btsH86KyEvq/SVnVNPeQHcvXUdwjIYDZQ1/img.png&quot; srcset=&quot;https://img1.daumcdn.net/thumb/R1280x0/?scode=mtistory2&amp;fname=https%3A%2F%2Fblog.kakaocdn.net%2Fdn%2Fb8t7wA%2FbtsH86KyEvq%2FSVnVNPeQHcvXUdwjIYDZQ1%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;700&quot; height=&quot;584&quot; data-origin-width=&quot;700&quot; data-origin-height=&quot;584&quot;/&gt;&lt;/span&gt;&lt;/figure&gt;
&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;많은 Exception이 있지만 여기서 저희가 주로 판단해야하는 상황은 INVALID_ARGUMENT, UNREGISTERED 이렇게 두가지의 경우가 있었습니다.&lt;/p&gt;
&lt;ul style=&quot;list-style-type: disc;&quot; data-ke-list-type=&quot;disc&quot;&gt;
&lt;li&gt;&lt;b&gt;INVALID_ARGUMENT&lt;/b&gt;&lt;br /&gt;- 아예 유효하지 않은 토큰의 경우일때 오류 코드를 반환하는 것으로, 아예 잘못된 토큰일 경우 발생합니다.&lt;br /&gt;FCM 메세지를 송신할 때, 이 메세지가 떴다면 해당 토큰을 그냥 DB에서 삭제하는 방식으로 구현을 하였습니다.&lt;br /&gt;&lt;br /&gt;&lt;/li&gt;
&lt;li&gt;&lt;b&gt;UNREGISTERED&lt;/b&gt;&lt;br /&gt;- 이전엔 유효하였으나, 재발급되어서 새로운 토큰으로 바뀐 경우입니다. 따라서 아예 잘못된 토큰은 아니고, 토큰이 만료된 경우입니다.&lt;br /&gt;FCM 메세지를 송신할 때, 이 메세지가 떴다면 해당 토큰을 그냥 DB에서 삭제하는 방식으로 구현을 하였습니다. \&lt;/li&gt;
&lt;/ul&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;이렇게 저희의 프로젝트에서 FCM토큰을 구현하고, 저희의 비즈니스 로직에 맞게 변환시켜 실시간 푸시메세지를 구현하였습니다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;이번에 FCM을 이용해보면서 푸시메세지 구현이 굉장히 손쉽게 구현될 수 있다는 것을 알았으나, 저희의 프로젝트는 대량의 푸시메세지가 필요하지않았기에 FCM으로 구현이 가능했으며, FCM의 경우 한번에 발급할 수 있는 메세지의 최대치가 정해져있어 동시에 10000건 등의 대량 메세지 발급에는 부적합하다는 결론이 나왔습니다.&lt;br /&gt;&lt;br /&gt;따라서, 저희의 프로젝트가 더 업데이트 되거나, 사용자가 많아질 경우 FCM이 아닌 MessageQueue를 도입하는 방식 등을 고민해봐야할 것 같습니다 ㅎㅎ&lt;br /&gt;(이건 정말 나중에 하는걸로....ㅎㅎ)&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;</description>
      <category>클라우드</category>
      <category>FCM</category>
      <category>firebase</category>
      <category>구글</category>
      <category>백엔드</category>
      <category>서버</category>
      <category>실시간</category>
      <category>푸시메세지</category>
      <author>tioon</author>
      <guid isPermaLink="true">https://tioon.tistory.com/197</guid>
      <comments>https://tioon.tistory.com/197#entry197comment</comments>
      <pubDate>Sun, 23 Jun 2024 00:39:12 +0900</pubDate>
    </item>
    <item>
      <title>네이버 Green Developers 프로젝트 후기 -  flipit</title>
      <link>https://tioon.tistory.com/195</link>
      <description>&lt;blockquote data-ke-style=&quot;style1&quot;&gt;&lt;b&gt;&lt;span style=&quot;font-family: 'Noto Serif KR';&quot;&gt;프로젝트 소개&lt;/span&gt;&lt;/b&gt;&lt;/blockquote&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;b&gt;  플리빗은 세상과 SNS로 대화하는 현세대의 소통 방법을 개선하고자 하는 Q&amp;amp;A 플랫폼입니다.&lt;br /&gt;궁금증을 질문하고 답변을 통해 자신을 표현하여, 소통 과정에서 느끼는 니즈를 충족시키고 어려움을 해결합니다.&lt;/b&gt;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p&gt;&lt;figure class=&quot;imageblock alignCenter&quot; data-ke-mobileStyle=&quot;widthOrigin&quot; data-origin-width=&quot;1280&quot; data-origin-height=&quot;720&quot;&gt;&lt;span data-url=&quot;https://blog.kakaocdn.net/dn/byZHSH/btsHh8JUeI3/0EWhnvZ7jsSVk8FhqrENk1/img.png&quot; data-phocus=&quot;https://blog.kakaocdn.net/dn/byZHSH/btsHh8JUeI3/0EWhnvZ7jsSVk8FhqrENk1/img.png&quot;&gt;&lt;img src=&quot;https://blog.kakaocdn.net/dn/byZHSH/btsHh8JUeI3/0EWhnvZ7jsSVk8FhqrENk1/img.png&quot; srcset=&quot;https://img1.daumcdn.net/thumb/R1280x0/?scode=mtistory2&amp;fname=https%3A%2F%2Fblog.kakaocdn.net%2Fdn%2FbyZHSH%2FbtsHh8JUeI3%2F0EWhnvZ7jsSVk8FhqrENk1%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;1280&quot; height=&quot;720&quot; data-origin-width=&quot;1280&quot; data-origin-height=&quot;720&quot;/&gt;&lt;/span&gt;&lt;/figure&gt;
&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;저희 프로젝트의 서비스 소개글은 아니니 이 부분은 빠르게 스킵하도록 하겠습니다!!&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;a href=&quot;https://github.com/Team-baebae/29th_Semi_README/tree/main/TeamC_Flipit&quot; target=&quot;_blank&quot; rel=&quot;noopener&amp;nbsp;noreferrer&quot;&gt;https://github.com/Team-baebae/29th_Semi_README/tree/main/TeamC_Flipit&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;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;blockquote data-ke-style=&quot;style1&quot;&gt;&lt;span style=&quot;font-family: 'Noto Serif KR';&quot;&gt;&lt;b&gt;프로젝트 설계&lt;/b&gt;&lt;br /&gt;&lt;/span&gt;&lt;/blockquote&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;이번 프로젝트를 설계하면서 정말 많은 고민을 했습니다. 어떻게 하면 좀 더 안정적이고 확장성 있는 설계를 할 수 있는지 정말 많은 자료를 찾아보고 네이버 클라우드 공식문서도 많이 봤던거 같아요.&lt;/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;기본적으로 Q&amp;amp;A 플랫폼을 기반으로하기 때문에 실시간 알림 기능도 들어가야 했고, 사용자가 몰렸을때를 대비한 안정성부분도 생각했어야 했습니다. 또한, 개발기간이 그렇게 길지 않았기에 러닝커브를 최소화한 방향으로 설계를 시작하였습니다.&lt;br /&gt;따라서, 저희 백엔드 팀에서 설계를 진행하며 생각을 했을때, 서버 요구사항은 다음과 같았습니다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;ul style=&quot;list-style-type: disc;&quot; data-ke-list-type=&quot;disc&quot;&gt;
&lt;li&gt;&lt;span style=&quot;color: #000000;&quot;&gt;&lt;b&gt;트래픽이 몰릴때 안정적인 확장 가능해야함&lt;/b&gt;&lt;/span&gt;&lt;/li&gt;
&lt;li&gt;&lt;span style=&quot;color: #000000;&quot;&gt;&lt;b&gt;외부에 서버 정보 노출을 최소화 하여 보안 강화&lt;/b&gt;&lt;/span&gt;&lt;/li&gt;
&lt;li&gt;&lt;span style=&quot;color: #000000;&quot;&gt;&lt;b&gt;사용자에게 빠르고 정확한 실시간 알림 전송&lt;/b&gt;&lt;/span&gt;&lt;/li&gt;
&lt;li&gt;&lt;span style=&quot;color: #000000;&quot;&gt;&lt;b&gt;배포 다운타임 최소화로 사용자 편의성 증가&lt;/b&gt;&lt;/span&gt;&lt;/li&gt;
&lt;li&gt;&lt;span style=&quot;color: #000000;&quot;&gt;&lt;b&gt;개발 러닝커브 최소화한 방법&lt;/b&gt;&lt;/span&gt;&lt;/li&gt;
&lt;/ul&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;이런 요구사항들을 충족시키기위해 다양한 아키텍처를 생각하면서 설계를 시작했습니다.&lt;br /&gt;&lt;br /&gt;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;우선, 안정적인 확장을 적용하기위해 AutoScaling과 Kubernates등의 기술들을 고민을 하였고,&lt;br /&gt;실시간 알림 전송 기능을 적용하기위해 Message Queue, FireBase 등을 고민을 하였으며,&lt;br /&gt;다운타임을 최소화한 무중단 배포를 적용하기위해 Jenkins, Github Action, Source Deploy등의 다양한 기술들을 고민을 하였습니다.&lt;br /&gt;이외에도, 보안을 강화하기 위해 VPC 및 SSL인증 부분도 많은 고민을 하였습니다.&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;br /&gt;이러한 결과로 나온 아키텍처는 아래에서 사진과 함께 자세히 설명드리도록 하겠습니다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;blockquote data-ke-style=&quot;style1&quot;&gt;&lt;span style=&quot;color: #000000;&quot;&gt;&lt;b&gt;&lt;span style=&quot;font-family: 'Noto Serif KR';&quot;&gt;클라우드 아키텍처 및 설명&lt;/span&gt;&lt;/b&gt;&lt;/span&gt;&lt;/blockquote&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;이번에 Green Developers 프로그램을 참여하면서 정말 많은 Ncloud의 서비스들을 활용하였습니다.&lt;br /&gt;우선 먼저 저희 프로젝트의 아키텍처에 대해서 보여드리고 하나씩 설명을 드리겠습니다.&lt;/p&gt;
&lt;p&gt;&lt;figure class=&quot;imageblock alignCenter&quot; data-ke-mobileStyle=&quot;widthOrigin&quot; data-filename=&quot;blob&quot; data-origin-width=&quot;1280&quot; data-origin-height=&quot;901&quot;&gt;&lt;span data-url=&quot;https://blog.kakaocdn.net/dn/ZfsyV/btsHk0Q2rkh/56YvGvxXyRPAQJ7OMrLPH0/img.png&quot; data-phocus=&quot;https://blog.kakaocdn.net/dn/ZfsyV/btsHk0Q2rkh/56YvGvxXyRPAQJ7OMrLPH0/img.png&quot; data-alt=&quot;FLipit 서비스 아키텍처&quot;&gt;&lt;img src=&quot;https://blog.kakaocdn.net/dn/ZfsyV/btsHk0Q2rkh/56YvGvxXyRPAQJ7OMrLPH0/img.png&quot; srcset=&quot;https://img1.daumcdn.net/thumb/R1280x0/?scode=mtistory2&amp;fname=https%3A%2F%2Fblog.kakaocdn.net%2Fdn%2FZfsyV%2FbtsHk0Q2rkh%2F56YvGvxXyRPAQJ7OMrLPH0%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;921&quot; height=&quot;648&quot; data-filename=&quot;blob&quot; data-origin-width=&quot;1280&quot; data-origin-height=&quot;901&quot;/&gt;&lt;/span&gt;&lt;figcaption&gt;FLipit 서비스 아키텍처&lt;/figcaption&gt;
&lt;/figure&gt;
&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;저희의 아키텍처는 위와 같습니다. &lt;br /&gt;아키텍처 설계때,&amp;nbsp; 시스템의 안정성과 확장성을 우선적으로 중점을 뒀고, &lt;br /&gt;시스템의 안정성과 확장성 만큼 중요한게 빠른 기간내에 개발이 가능할 정도의 적정 수준의 러닝커브였기 때문에 이 부분도 놓치지 않으려고 노력하며 설계를 했습니다.&lt;br /&gt;&lt;br /&gt;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;이 아키텍처 정보를 기반으로 VPC부터 차례대로 자세한 설명을 하도록 하겠습니다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;h4 data-ke-size=&quot;size20&quot;&gt;&lt;span style=&quot;color: #000000;&quot;&gt;&lt;b&gt;VPC&lt;/b&gt;&lt;/span&gt;&lt;/h4&gt;
&lt;blockquote data-ke-style=&quot;style2&quot;&gt;VPC(Virtual Private Cloud)는 퍼블릭 클라우드 환경에서 사용할 수 있는 고객 전용 사설 네트워크입니다. 다른 네트워크와 논리적으로 분리되어 있어 IT 인프라를 안전하게 구축하고 간편하게 관리할 수 있습니다. 또한 기존의 데이터 센터 네트워크와 유사하게 구현할 수 있습니다. - Ncloud 공식문서 -&amp;nbsp;&lt;/blockquote&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p&gt;&lt;figure class=&quot;imageblock alignCenter&quot; data-ke-mobileStyle=&quot;widthOrigin&quot; data-origin-width=&quot;624&quot; data-origin-height=&quot;714&quot;&gt;&lt;span data-url=&quot;https://blog.kakaocdn.net/dn/nreiz/btsG35frntl/aftmokZ70aVxmz9ISoDL60/img.png&quot; data-phocus=&quot;https://blog.kakaocdn.net/dn/nreiz/btsG35frntl/aftmokZ70aVxmz9ISoDL60/img.png&quot;&gt;&lt;img src=&quot;https://blog.kakaocdn.net/dn/nreiz/btsG35frntl/aftmokZ70aVxmz9ISoDL60/img.png&quot; srcset=&quot;https://img1.daumcdn.net/thumb/R1280x0/?scode=mtistory2&amp;fname=https%3A%2F%2Fblog.kakaocdn.net%2Fdn%2Fnreiz%2FbtsG35frntl%2FaftmokZ70aVxmz9ISoDL60%2Fimg.png&quot; onerror=&quot;this.onerror=null; this.src='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png'; this.srcset='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png';&quot; loading=&quot;lazy&quot; width=&quot;500&quot; height=&quot;572&quot; data-origin-width=&quot;624&quot; data-origin-height=&quot;714&quot;/&gt;&lt;/span&gt;&lt;/figure&gt;
&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;VPC는 쉽게 말해,&amp;nbsp; 클라우드 환경에서 public과 private subnet으로 나눠 가상으로 사설 네트워크 처럼 환경을 구축할 수 있는 서비스를 뜻합니다.&lt;br /&gt;VPC를 적용해 외부로의 요청은 Internet GateWay통해 Public Subnet에 존재하는 Application에만 들어올 수 있고, 그 외의 Private Subnet에 존재하는 Application에는 접근을 하지 못하게 할 수 있습니다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;여기서 다음과 같은 의문점이 들 수도 있습니다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size18&quot;&gt;&lt;span style=&quot;color: #ee2323;&quot;&gt;&lt;b&gt;&quot;그냥 네이버에서 기본적으로 제공되는 ACG(Access Control Group)를 사용하면 해결되는거 아닌가??&quot;&lt;/b&gt;&lt;/span&gt;&lt;/p&gt;
&lt;p data-ke-size=&quot;size18&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;물론 ACG를 이용해서 어느정도 안전한 보안환경을 구축할 수 있습니다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;다만, 그냥 공개된 공간에서 ACG만을 의존해 보안을 구축하게된다면 다음과 같은 문제점이 발생할 수 있습니다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;ol style=&quot;list-style-type: decimal;&quot; data-ke-list-type=&quot;decimal&quot;&gt;
&lt;li&gt;&lt;span style=&quot;color: #000000;&quot;&gt;&lt;b&gt;외부에 모든 서버의 공인 IP가 노출되며, 외부에서 직접적으로 접근이 가능해짐.&lt;/b&gt;&lt;/span&gt;&lt;/li&gt;
&lt;li&gt;&lt;span style=&quot;color: #000000;&quot;&gt;&lt;b&gt;외부에서 노출된 공인 IP기반으로 DDOS 공격 및 포트스캐닝 공격이 가능해짐.&lt;/b&gt;&lt;/span&gt;&lt;/li&gt;
&lt;li&gt;&lt;span style=&quot;color: #000000;&quot;&gt;&lt;b&gt;클라우드 규모가 커질 수록, 공인 IP 관리 비용이 늘어남.&lt;/b&gt;&lt;/span&gt;&lt;/li&gt;
&lt;li&gt;&lt;span style=&quot;color: #000000;&quot;&gt;&lt;b&gt;복잡해지는 ACG 규칙으로 관리가 어려워짐.&lt;/b&gt;&lt;/span&gt;&lt;/li&gt;
&lt;/ol&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;이렇게 ACG만으로 보안을 구축하는건 보안 관점에서 매우 위험한 일이고, 비효율적입니다.&lt;br /&gt;따라서, 저희 백엔드팀은 Ncloud의 VPC 서비스를 이용해서&amp;nbsp; 기본적인 네트워크 환경을 구축했습니다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;VPC의 세부 정보는 다음과 같습니다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;ul style=&quot;list-style-type: disc;&quot; data-ke-list-type=&quot;disc&quot;&gt;
&lt;li&gt;&lt;b&gt;Public Subnet&lt;/b&gt;&amp;nbsp;
&lt;ul style=&quot;list-style-type: disc;&quot; data-ke-list-type=&quot;disc&quot;&gt;
&lt;li&gt;Load Balancer&lt;/li&gt;
&lt;li&gt;Bastion Host Server&lt;/li&gt;
&lt;li&gt;NAT GateWay&lt;/li&gt;
&lt;/ul&gt;
&lt;/li&gt;
&lt;li&gt;&lt;b&gt;Private Subnet&lt;/b&gt;
&lt;ul style=&quot;list-style-type: disc;&quot; data-ke-list-type=&quot;disc&quot;&gt;
&lt;li&gt;AutoScaling Group&lt;/li&gt;
&lt;li&gt;Server&lt;/li&gt;
&lt;li&gt;MySql DB&lt;/li&gt;
&lt;li&gt;Redis DB&lt;/li&gt;
&lt;/ul&gt;
&lt;/li&gt;
&lt;/ul&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;위 처럼 기본적으로 보안이 중요한 실제 운영 서버와 DB들은 Private Subnet에 두어 외부로 노출이 되지 않게끔 설계를 하였으며, Load Balancer를 Public Subnet에 두어, 앞단에서 Internet GateWay를 통해 들어오는 요청들을 받아, 각 Server에게 트래픽을 분산시킬 수 있도록 적용하였습니다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;br /&gt;또한, 테스트서버 겸 Bastion Host 역할을 하는 Server를&amp;nbsp; Public Subnet에 두었습니다. 이 Server는 실제 운영하는 서버가 아닌, 운영 서버 배포전에 &lt;span style=&quot;color: #333333; text-align: start;&quot;&gt;문제가 없는지&lt;span&gt;&amp;nbsp;&lt;/span&gt;&lt;/span&gt; 백엔드에서 QA를 진행하는 서버입니다.&lt;br /&gt;또한 추가적으로,&amp;nbsp; Private Subnet은 외부접속으로부터 막혀있어, ssh 접속이 되지않기때문에 로컬에서 접속이 어렵습니다.&lt;br /&gt;이때 이 Bastion Host가 점프 서버의 역할을 하여 로컬에서 ssh 접속을 할 수있게끔 하는 역할을 하도록 하였습니다.&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;이런식으로 저희 서버에선 VPC가 구축되어 있으며, NAT GateWay 부분은 아래에서 따로 설명을 드리겠습니다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;h4 data-ke-size=&quot;size20&quot;&gt;&lt;span style=&quot;color: #000000;&quot;&gt;&lt;b&gt;&lt;span style=&quot;font-family: AppleSDGothicNeo-Regular, 'Malgun Gothic', '맑은 고딕', dotum, 돋움, sans-serif;&quot;&gt;Load Balancer&lt;/span&gt;&lt;/b&gt;&lt;/span&gt;&lt;/h4&gt;
&lt;blockquote data-ke-style=&quot;style2&quot;&gt;Load Balancer는 수신 트래픽을 다수의 서버로 분산시키는 서비스입니다. 등록된 멤버 서버로 수신 트래픽을 분산시켜 가용성을 높이고 시스템 가동률을 조절하는 역할을 수행합니다. 이로 인해 워크로드의 가용성을 향상시켜 예기치 못한 서버의 장애 또는 예정된 변경 작업 등에 대하여 중단 없이 대응할 수 있도록 지원합니다. - Ncloud 공식문서 -&amp;nbsp;&lt;/blockquote&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;Load Balancer는, 서버로 들어오는 트래픽을 분산시켜 안정성과 확장성을 보장시켜주는 서비스입니다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;Ncloud에서 제공하는 Load Balancer의 종류는 여러개가 있습니다. Network Load Balancer, Network Proxy Load Balancer, Application Load Balancer, Inline Load Balancer가 있는데요.&lt;br /&gt;저희는 여기서 Application Load Balancer를 사용했는데, 이유는 다음과 같습니다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;ol style=&quot;list-style-type: decimal;&quot; data-ke-list-type=&quot;decimal&quot;&gt;
&lt;li&gt;&lt;span style=&quot;color: #000000;&quot;&gt;&lt;b&gt;웹 기반 서버이다 보니 80포트(HTTP), 443포트(HTTPS)만 사용함&lt;/b&gt;&lt;/span&gt;&lt;/li&gt;
&lt;li&gt;&lt;span style=&quot;color: #000000;&quot;&gt;&lt;b&gt;SSL을 Load Balancer에 적용시켜야함.&lt;/b&gt;&lt;/span&gt;&lt;/li&gt;
&lt;li&gt;&lt;span style=&quot;color: #000000;&quot;&gt;&lt;b&gt;추후에 서버가 복잡해질 시, URL정보를 기반으로 트래픽을 나눌 가능성 존재&lt;/b&gt;&lt;/span&gt;&lt;/li&gt;
&lt;/ol&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;위와 같은 이유로 Application Load Balancer를 적용하였습니다.&lt;br /&gt;좀 더 자세히 설명을 하자면, 저희 서버는 웹 기반의 서버입니다. 즉, OSI 7 계층 기준으로 7계층(Application)의 정보를 활용하는 서버입니다. 따라서 들어오는 트래픽들은 거의 HTTP, HTTPS 기반의 트래픽일 거고, 그 아래의 Transport , Network Layer를 기반으로 트래픽을 나누는 상황은 나오지 않기에 Application Load Balancer외에 다른 Load Balancer는 저희에게 필요하지 않았습니다.&lt;br /&gt;또한, 저희는 DNS와 Certificate Manager를 통해 Load Balancer에 추가적인 설정을 해야하는데, 여기서 SSL 인증서를 추가하기 위해선 7계층에서 활동하는 Application Load Balancer가 필요했습니다.&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;따라서, 저희는 이러한 생각을 바탕으로 Application Load Balancer를 활용하였으며, Public Subnet에 두어, 외부로부터 트래픽을 받을 수 있도록 설정하였습니다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;h4 data-ke-size=&quot;size20&quot;&gt;&lt;span style=&quot;color: #000000;&quot;&gt;&lt;b&gt;DNS &amp;amp; Certificate Manager&lt;/b&gt;&lt;/span&gt;&lt;/h4&gt;
&lt;blockquote data-ke-style=&quot;style2&quot;&gt;Global DNS는 인터넷에서 사용하는 도메인 이름을 실제 접속 주소로 식별할 수 있도록 도와주는 서비스입니다. 개인이 직접 운영하기 어려운 DNS(Domain Name System) 서버를 클라우드 형태로 제공하여, 사용자가 서비스 운영에 필요한 도메인을 쉽고 편리하게 관리할 수 있습니다. - Ncloud 공식문서 -&lt;br /&gt;&lt;br /&gt;&lt;/blockquote&gt;
&lt;blockquote data-ke-style=&quot;style2&quot;&gt;Certificate Manager는 연계 서비스(Load Balancer, CDN+ 등)에서 사용할 인증서를 발급, 등록하고 관리할 수 있는 서비스입니다. - Ncloud 공식문서 -&lt;/blockquote&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;저희는 앞서 말씀드렸듯이, 사용자의 정보를 통신을 통해 주고받기 때문에 HTTPS 통신이 필수적이였으며, 또한 실제 사용자분들의 접근성을 향상시키기 위해 도메인을 적용시켜야 했습니다.&lt;br /&gt;이에, 이걸 적용시키기 위해 다양한 서비스들을 비교한 결과 Ncloud에서 제공하는 Global DNS와 Certificate Manager를 적용을 하였습니다.&lt;br /&gt;이유는 다음과 같습니다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;ol style=&quot;list-style-type: decimal;&quot; data-ke-list-type=&quot;decimal&quot;&gt;
&lt;li&gt;&lt;span style=&quot;color: #000000;&quot;&gt;&lt;b&gt;같은 Ncloud 서비스 이다보니 개발 편의성 증가&lt;/b&gt;&lt;/span&gt;&lt;/li&gt;
&lt;li&gt;&lt;span style=&quot;color: #000000;&quot;&gt;&lt;b&gt;가비아의 경우 네임서버에서 DNSSEC이 적용되지않아 보안취약점 존재&lt;/b&gt;&lt;/span&gt;&lt;/li&gt;
&lt;/ol&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;위와 같은 이유로 Ncloud의 서비스를활용하였으며, 각각 적용하여 Load Balancer에 적용을 하였습니다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;저도 이번에 개발을 하다 처음알게된 사실인데 가비아에서 기본적으로 제공하는 자체 네임서버의 경우 다음과 같이 DNSSEC을 적용하지않는다 라는 정보가 나와있습니다.&amp;nbsp;&lt;/p&gt;
&lt;p&gt;&lt;figure class=&quot;imageblock alignCenter&quot; data-ke-mobileStyle=&quot;widthOrigin&quot; data-origin-width=&quot;1336&quot; data-origin-height=&quot;65&quot;&gt;&lt;span data-url=&quot;https://blog.kakaocdn.net/dn/c8dCCq/btsH91VZQ8a/vgXIN1gIYMYSy8MXnlWfI0/img.png&quot; data-phocus=&quot;https://blog.kakaocdn.net/dn/c8dCCq/btsH91VZQ8a/vgXIN1gIYMYSy8MXnlWfI0/img.png&quot;&gt;&lt;img src=&quot;https://blog.kakaocdn.net/dn/c8dCCq/btsH91VZQ8a/vgXIN1gIYMYSy8MXnlWfI0/img.png&quot; srcset=&quot;https://img1.daumcdn.net/thumb/R1280x0/?scode=mtistory2&amp;fname=https%3A%2F%2Fblog.kakaocdn.net%2Fdn%2Fc8dCCq%2FbtsH91VZQ8a%2FvgXIN1gIYMYSy8MXnlWfI0%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;1336&quot; height=&quot;65&quot; data-origin-width=&quot;1336&quot; data-origin-height=&quot;65&quot;/&gt;&lt;/span&gt;&lt;/figure&gt;
&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;DNSSEC이란 DNS를 더 안전하게 만들어주는 기술입니다. 즉, 브라우저에서 웹사이트의 도메인 이름을 입력하면 DNS가 이 도메인 이름을 숫자로 된 IP 주소로 바꾸게 되는데, 해커들이 이 과정에서 끼어들어 이상한 IP 주소를 주입할 수 있습니다.이렇게 되면, 우리가 의도하지 않은 위험한 웹사이트에 접속이 될 수도 있습니다. &lt;br /&gt;이러한 공격방법을 DNS 스푸핑 공격 이라고하는데 DNSSEC은 DNS 데이터에 디지털 서명을 추가하여 공개키/개인키 구조로 검증을 할 수 있게 합니다.&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;저희의 서버의 경우 10~20대 초반의 연령대가 주 타켓입니다. 따라서 이러한 DNS 스푸핑 공격으로 인해, 불건전한 사이트 등에 접속을 하게 된다면 굉장히 치명적일 것이라 생각을 하였습니다. 따라서, 이러한 공격을 막기위해 가비아에서 구매한 도메인에 NCP의 DNS를 적용시켰고, 이로인해 이러한 보안 취약점을 개선시킬 수 있었습니다.&lt;br /&gt;&lt;br /&gt;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;여기서 추가적으로, 다음과 같은 의문점이 들 수도 있습니다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size18&quot;&gt;&lt;span style=&quot;color: #ee2323;&quot;&gt;&lt;b&gt;&quot;Server에는 적용 안하고 왜 Load Balancer에만 적용을 시키는가?&quot;&lt;/b&gt;&lt;/span&gt;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;저희는 앞에서 VPC를 적용하여 외부 트래픽은 Load Balancer가 받고, 뒷단의 Private 서버들에게 트래픽을 분산시켜 전달하는 구조입니다.&lt;br /&gt;여기서, Load Balancer는 외부와 직접 통신을 하기때문에, 통신의 내용이 외부 인터넷에 노출되는 상황입니다.&lt;br /&gt;따라서, 악의적인 사용자가 있다면 중간에서 패킷을 탈취해서 내부 내용을 볼 수도 있고, 직접 패킷의 내용을 수정할수도 있습니다. 이를 막기 위해 SSL 인증을 적용시켜 인증키로 패킷 내부 내용을 암호화 하여 외부에서 제 3자가 패킷의 내부 내용을 볼 수 없도록 하는 것입니다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;br /&gt;하지만 Load Balancer가 Server에게 트래픽을 분산시킬때의 상황은 반대로 외부 인터넷이 아닌 내부 사설 인터넷망에서 통신이 이루어 집니다. 즉, 이 패킷들은 외부에 노출될 위험이 없다는 뜻입니다. 따라서 내부에서 일어나는 통신들은 암호화가 전혀 필요없기 때문에 내부 Server는 SSL 적용을 하지 않는 것 입니다. 또한 추가적으로, 내부에선 외부 사용자가 직접 접속하는게 아니므로, 도메인 적용도 할 필요가 없습니다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;br /&gt;따라서, 저희는 앞단의 Application Load Balancer에게만 SSL 적용 및 도메인 적용을 하였습니다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;h4 data-ke-size=&quot;size20&quot;&gt;&lt;span style=&quot;color: #000000;&quot;&gt;&lt;b&gt;AutoScaling&lt;/b&gt;&lt;/span&gt;&lt;/h4&gt;
&lt;blockquote data-ke-style=&quot;style2&quot;&gt;Auto Scaling은 모니터링 결과나 사용자 설정에 따라서 가상 서버 수를 늘리거나 줄여 수요 변화에 탄력적으로 대응할 수 있도록 돕는 서비스입니다. - Ncloud 공식문서 -&lt;/blockquote&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;저희 서비스를 기획하면서, 서버쪽에서 해결해야할 과제가 있었습니다. Q&amp;amp;A기반의 서비스이다 보니 새학기 시즌에 사용자들이 몰릴 가능성이 있다 라는 것이였습니다.&lt;br /&gt;물론 새학기 시즌 전에 직접 Load Balancer에 추가 Server를 붙여 Scale-Out를 하는것도 괜찮은 방법입니다.&lt;br /&gt;다만, 이렇게 했을때마다 이럴때마다 업데이트 뿐만 아니라, 테스트도 진행해야 했기 때문에 시간 및 인력이 낭비된다 라고 생각을 했습니다. 또한 추가적으로 이렇게 Scale-Out을 진행 했지만, 예상만큼 사용자가 들어오지 않는다면, 그만큼 서버 비용도 낭비되는 것이라고 판단을 했습니다.&lt;br /&gt;&lt;br /&gt;이에, 저희 백엔드 팀은 이 문제를 해결하기위해 서버의 성능을 트래픽 증감에 따라 탄력적이고 안정적으로 운영할 순 없을까??라는 생각을 바탕으로 다양한 자료를 참고하였습니다.&lt;br /&gt;여기서 대안책은 AutoScaling과 Kubernates 였습니다. 이 두가지 대안책을 가지고 백엔드 팀에서 회의를 해본 결과, Kubernates를 적용하여 컨테이너로 나눠 개발하기엔&amp;nbsp; 우리의 비즈니스 로직이 그렇게 크지도 않고, 또한 1달 반동안 개발하기에 러닝커브가 너무 높다 라는 결론이 나왔습니다.&lt;br /&gt;따라서, Kubernates를 적용하기보단 AutoScaling을 통해서 서버의 안정성을 높였습니다.&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;AutoScaling을 적용하기 위해서 기존에 생성했던 Server를 기반으로 Server Image를 생성을 하고, 이 Image 파일을 기반으로 Source Deploy를 실행시키기 위한 init Script를 작성하여 Launch Configuration을 생성하였습니다.&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;따러서 저희는 이 Launch Configuration을 기반으로 AutoScaling Group을 생성하여 쉽게 Server를 똑같은 환경으로 생성할 수 있었습니다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;h4 data-ke-size=&quot;size20&quot;&gt;&lt;span style=&quot;color: #000000;&quot;&gt;&lt;b&gt;Object Storage&lt;/b&gt;&lt;/span&gt;&lt;/h4&gt;
&lt;blockquote data-ke-style=&quot;style2&quot;&gt;Object Storage는 사용자가 언제 어디서나 원하는 데이터를 저장하고 탐색할 수 있도록 파일 저장 공간을 제공하는 서비스입니다. - Ncloud 공식문서 -&lt;/blockquote&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;저희 서비스의 비즈니스 로직상 사용자들의 피드에 존재하는 사진들을 저장하는 저장소가 필요했습니다.&lt;br /&gt;따라서 파일 저장소인 Object Storage를 사진파일 저장소로 활용하였습니다.&lt;br /&gt;AWS S3와 코드가 호환이 되기 때문에 별 어려움 없이 Spring 서버를 통해 개발을 할 수 있었습니다.&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;추가적으로, CI-CD를 진행할때 중간 빌드파일 저장소로 Object Storage를 활용했는데, 이때 AWS CLI와 호환된다고 공식문서에 나와있기 때문에 Github Action에서 AWS CLI를 통해&amp;nbsp; Object Storage에 빌드 파일을 저장했습니다.&lt;br /&gt;다만, 이부분에서 AWS CLI Credentials에서 오류가 일어나 트러블 슈팅을 하는데에 애를 먹었던 부분이 있었습니다.&lt;br /&gt;이부분은 아래에서 자세히 설명드리겠습니다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;h4 data-ke-size=&quot;size20&quot;&gt;&lt;span style=&quot;color: #000000;&quot;&gt;&lt;b&gt;Source Deploy&lt;/b&gt;&lt;/span&gt;&lt;/h4&gt;
&lt;blockquote data-ke-style=&quot;style2&quot;&gt;SourceDeploy는 새로 작성되거나 업데이트된 소스들을 자동으로 서버에 배포하고 적용해 주는 배포 자동화 서비스입니다. SourceDeploy를 사용하여 미리 설정된 사용자 기반 명령어들을 통해 소스 배포, 실행 및 검증을 자동화할 수 있고 배포 중 서비스 중단 시간을 최소화할 수 있습니다. 또한, SourceDeploy는 배포 실행 관리자를 통해 배포 실행을 제어할 수 있어, 필수적으로 필요한 배포만 적용하여 서비스의 품질을 보장할 수 있습니다. - Ncloud 공식문서 -&lt;/blockquote&gt;
&lt;p&gt;&lt;figure class=&quot;imageblock alignCenter&quot; data-ke-mobileStyle=&quot;widthOrigin&quot; data-origin-width=&quot;819&quot; data-origin-height=&quot;794&quot;&gt;&lt;span data-url=&quot;https://blog.kakaocdn.net/dn/Odo2O/btsG5SeVk2a/KatYleQ3ej17sD8mTlRUd1/img.png&quot; data-phocus=&quot;https://blog.kakaocdn.net/dn/Odo2O/btsG5SeVk2a/KatYleQ3ej17sD8mTlRUd1/img.png&quot;&gt;&lt;img src=&quot;https://blog.kakaocdn.net/dn/Odo2O/btsG5SeVk2a/KatYleQ3ej17sD8mTlRUd1/img.png&quot; srcset=&quot;https://img1.daumcdn.net/thumb/R1280x0/?scode=mtistory2&amp;fname=https%3A%2F%2Fblog.kakaocdn.net%2Fdn%2FOdo2O%2FbtsG5SeVk2a%2FKatYleQ3ej17sD8mTlRUd1%2Fimg.png&quot; onerror=&quot;this.onerror=null; this.src='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png'; this.srcset='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png';&quot; loading=&quot;lazy&quot; width=&quot;500&quot; height=&quot;485&quot; data-origin-width=&quot;819&quot; data-origin-height=&quot;794&quot;/&gt;&lt;/span&gt;&lt;/figure&gt;
&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;Source Deploy는 저희 서비스에서 정말 필수적인 서비스였습니다. 서버의 안정성과 확장성 생각하여 AutoScaling을 도입을 하였는데, AutoScaling의 빌드 시간이 평균 3~5분정도로 매우 오래걸렸습니다.&lt;br /&gt;따라서, 실제 운영 환경에서 서버 업데이트시마다 5분정도의 다운 타임이 있었고, 이 시간동안 사용자는 저희의 서비스에 접속을 하지 못하는 문제가 발생하였습니다. 물론 여기서 배포 이후에 버그가 발생하면 그만큼 다운타임은 늘어날 것이고, 롤백도 어려워 실제 운영 상황에서 AutoScaling을 그냥 사용하는 것은 위험하다고 판단을 하였습니다.&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;따라서, AutoScaling에 무중단 배포를 적용시키기로 결정을 하였고, 그에 대한 자료를 찾던중 Source Deploy로 AutoScaling Group 무중단 배포가 가능하다는 정보를 얻었습니다.&lt;br /&gt;Source Deploy없이 무중단 배포를 구현하기 위해선, LoadBalancer에 Ncloud API를 통해 새로 배포된 서버의 HealthCheck가 정상적으로 작동된 것을 확인한 이후에, 직접 Target Group을 바꾸는 방식으로 구현을 해야하는데, 이과정이 굉장히 안정성이 떨어진다고 생각을 했습니다. 또한, 기존에 트래픽이 늘어나 Scale-Out이 진행된 상황에서 무중단 배포를 진행한다고 하면 굉장히 예외상황이 많다고 생각을 했습니다.&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;Source Deploy를 활용하기 위해, Github Action을 활용하였습니다. CI 단계에서 성공적으로 빌드파일을 Object Storage 에 저장을 하게 되면, 이 이후에 Github Action에서 쉘 스크립트를 통해 Source Deploy에 API 호출을 통해 원격 실행을 하게 됩니다.&lt;br /&gt;이때, API 호출 할때 필수적으로 API 키를 통한 Naver Signiture방식 서명이 필요합니다. 이것을 구현하기위해 쉘 스크립트를 작성하였으며, HTTP 요청방식 +&amp;nbsp; API_URL + 현재 TimeStamp + nCloud Access Key 정보를 조합한 이후, Secret Key로 SHA256방식 암호화를 하여 API 호출을 하였습니다.&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;br /&gt;따라서, Source Deploy를 이용해 AutoScaling Group 자체를 블루/그린으로 배포하는 방식으로 구현하였으며, 서버의 다운 타임을 1~2초 이내로 성공적으로 줄일 수 있었고, 서버의 안정성과 확장성을 확보 할 수 있었습니다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;h4 data-ke-size=&quot;size20&quot;&gt;&lt;span style=&quot;color: #000000;&quot;&gt;&lt;b&gt;NAT GateWay&lt;/b&gt;&lt;/span&gt;&lt;/h4&gt;
&lt;blockquote data-ke-style=&quot;style2&quot;&gt;NAT Gateway는 클라우드 플랫폼 내부에 있는 여러 대의 비공인 IP를 가진 고객의 서버가 공인 IP 주소를 가진 외부 서버와 통신할 수 있도록 연결하는 NAT 서비스입니다. 비공인 IP 주소를 가진 다수의 서버가 하나의 NAT Gateway를 공유하거나, 여러 개의 NAT Gateway를 만들고 서버 그룹별로 각각의 NAT Gateway를 사용할 수도 있습니다. 고객이 사전에 등록한 외부의 공인 IP 주소를 가진 호스트에만 접속할 수 있도록 설정해 보안을 강화할 수 있습니다. 또한 Auto Scaling 서비스와 연결하여 새로 증설되는 서버가 자동으로 NAT Gateway를 이용하도록 설정할 수 있습니다. &lt;br /&gt;- Ncloud 공식문서 -&lt;/blockquote&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;저희 시스템 아키텍처를 보시면 VPC에 Internet Gateway 뿐만 아니라, NAT Gateway도 추가 되어 있습니다. 저희의 아키텍처 구조상 Private Subnet에 존재하는 서버는 내부 사설 IP만 가지기 때문에 외부와 연결될 수가 없습니다. 따라서 이 서버가 공인 IP를 가지고 라우팅이 외부와 연결이 되어있어야 외부와 통신이 가능해집니다.&lt;br /&gt;저희는 Private Subnet에 존재하는 서의 통신을 가능하게 하기위해 NAT Gateway에 공인 IP를 추가하고, Private Subnet안의 라우팅 테이블에 연결 하였으며, 이를 이용해 서버에서 카카오, Naver Clova 등의 서비스에 HTTP기반 요청을 보낼 수 있었습니다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;그러면 여기서 두가지의 의문점이 들 수도 있습니다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size18&quot;&gt;&lt;span style=&quot;color: #ee2323;&quot;&gt;&lt;b&gt;&quot;이미 Internet Gateway가 존재하는데 이걸 활용해서 통신을 하면 되는거 아닌가?&quot;&lt;/b&gt;&lt;/span&gt;&lt;/p&gt;
&lt;p data-ke-size=&quot;size18&quot;&gt;&lt;span style=&quot;color: #ee2323;&quot;&gt;&lt;b&gt;&quot;NAT Gateway를 이용해 Private Subnet을 연결하면 결국 외부로 서버 자원을 노출시켜 보안취약점이 생기는거 아닌가?&quot;&lt;/b&gt;&lt;/span&gt;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;우선 첫번째의 대답을 하자면, 현재 Internet Gateway는 로드밸런서와 연결되어 있고, 그 로드밸런서가 내부 서버와 연결되어있습니다. 즉, Private Subnet에 존재하는 내부 서버들이 Internet Gateway를 통해 연결되려면, 로드밸런서를 거쳐야만 통신을 할 수 있는데, 서버 -&amp;gt; 로드밸런서 -&amp;gt; Internet Gateway 이런과정이 불가능하고, 로드밸런서에서 그런 기능도 제공하고 있지 않습니다. 따라서 Public Subnet에서 직접 Internet Gateway와 연결되어 있는 상황이 아니라면, Internet Gateway를 통해&amp;nbsp; 통신을 하는건 불가능합니다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;두번째의 대답을 하자면, NAT Gateway 경우는 내부의 요청을 하나로 모아서 외부로 보내는 역할은 하지만, 외부의 요청을 내부로 들여보내는 역할은 하지 않습니다. 즉, 내부 -&amp;gt; 외부는 가능하지만, 외부 -&amp;gt; 내부는 불가능합니다.&lt;br /&gt;이에 대한 예시를 들어보면, 각 집마다 존재하는 공유기를 예시로 들 수 있습니다. 저희가 집에서 보통 하나의 공인 IP를 할당받아 공유기를 사용하고, 여기서 여러개의 사설 IP로 나뉘어져서 스마트폰, 컴퓨터 등에 할당을 합니다. 여기서 저희가 컴퓨터를 할때, Naver 검색, 카카오톡 등의 외부로 다양한 요청을 보낼 순 있지만, 반대로 외부에선 저희 컴퓨터로 직접적인 요청을 불가능합니다. 이와 똑같은 원리로 NAT Gateway도 외부에서 내부로 직접적인 요청은 불가능합니다.&lt;br /&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;따라서, 저희는 NAT Gateway를 활용해서 Private Subnet에 존재하는 내부 서버들의 보안을 지키면서 외부와 통신을 할 수 있게 아키텍처를 구성하였습니다.&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;h4 data-ke-size=&quot;size20&quot;&gt;&lt;span style=&quot;color: #000000;&quot;&gt;&lt;b&gt;DB&lt;/b&gt;&lt;/span&gt;&lt;/h4&gt;
&lt;blockquote data-ke-style=&quot;style2&quot;&gt;MySQL 데이터베이스를 손쉽게 구축하고, 네이버의 최적화 설정을 통해 안정적으로 운영하며, 장애 발생 시 자동으로 복구합니다. -Ncloud 공식문서-&lt;/blockquote&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;저희는 Ncloud에서 총 2가지의 DB를 사용합니다. 하나는 Mysql, 나머지 하나는 Redis입니다.&lt;br /&gt;우선 Mysql은 저희 서버의 메인 DB를 구축하기위해 사용을 하였습니다. 따라서, 저희의 ERD를 기반으로 관계형 데이터베이스 구축을 Ncloud의 Mysql을 활용하여 구축하였습니다. 데이터 자동 백업 및 성능 최적화가 되어있어서 손쉽게 고가용성과 성능 안정성을 보장할 수 있었습니다.&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;Redis는 저희 서버의 Token값 캐싱에 활용하였습니다. 저희 서버는 로그인용&amp;nbsp; JWT Token과 푸시 메세지 발급용 FCM Token을 사용합니다. 즉, 2가지의 토큰을 관리하면서 DB에 있는 Tokne값을 조회하는 경우가 발생합니다. 사용자가 얼마 없을땐 그렇게 부하가 크진 않겠지만, 아무리 Ncloud의 Mysql이 성능최적화가 잘되어있다해도, 관계형 DB의 특성상, 사용자가 많아질 경우 조회시에 부하가 크다고 생각을 하였습니다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;br /&gt;따라서, 이런 토큰값의 경우 Redis에 이중 저장을 하여 토큰값 조회가 일어날때 1차적으로 먼저 Redis를 조회하고 존재하지 않을 시에만 Mysql DB를 조회하는 방식으로 수정하여 성능을 최적화 하기 위해 Redis를 활용하였습니다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;h4 data-ke-size=&quot;size20&quot;&gt;&lt;span style=&quot;color: #000000;&quot;&gt;&lt;b&gt;Naver Clova Studio&lt;/b&gt;&lt;/span&gt;&lt;/h4&gt;
&lt;blockquote data-ke-style=&quot;style2&quot;&gt;CLOVA Studio는 초대규모(Hyperscale) AI 기술인 HyperCLOVA 언어 모델을 활용하여 사용자가 입력한 내용에 따라 AI 기술을 통해 생성된 문구를 출력하는 네이버 클라우드 플랫폼의 서비스입니다. &amp;nbsp;-Ncloud 공식문서-&lt;/blockquote&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;저희가 기획단계에서 고민을 했던 부분이 있습니다. 상대방에게 질문을 할때 어떤 질문을 해야할지 모르는 사용자가 있을 것이며, 이러한 사용자들에게 편의성을 제공할 수 없을까? 라는 고민을 했습니다.&lt;br /&gt;이러한 상황에서 AI기술을 바탕으로 다양한 예시 질문들을 생성해주면 사용자 편의성이 증가할 것이라 생각을 했고, 이에 알맞은 AI 기술들을 조사한 결과 Naver Clova Studio를 활용해서 다양한 예시 질문들을 생성해주자 라는 결론이 나왔습니다.&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;Naver Clova Studio의 장점은 다음과 같습니다.&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;ol style=&quot;list-style-type: decimal;&quot; data-ke-list-type=&quot;decimal&quot;&gt;
&lt;li&gt;&lt;span style=&quot;color: #000000;&quot;&gt;&lt;b&gt;한글에 특화된 AI 모델&lt;/b&gt;&lt;/span&gt;&lt;/li&gt;
&lt;li&gt;&lt;span style=&quot;color: #000000;&quot;&gt;&lt;b&gt;간편하게 API처럼 호출 가능&lt;/b&gt;&lt;/span&gt;&lt;/li&gt;
&lt;li&gt;&lt;span style=&quot;color: #000000;&quot;&gt;&lt;b&gt;프롬프트 기법 활용으로 서비스에 맞게 쉽게 빠르게 튜닝 가능&lt;/b&gt;&lt;/span&gt;&lt;/li&gt;
&lt;/ol&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;우선 다양한 AI 모델이 있지만 Naver Clova Studio가 가장 한글에 특화된 AI 모델이라고 생각을 했습니다. &lt;br /&gt;또한, 다양한 AI모델을 기반으로 테스트를 해본결과 가장 한글 텍스트를 자연스럽게 생성하는 모델이었습니다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;그리고 API 호출을 통해서 간편하게 사용할 수 있기에 러닝커브가 적다 라는 장점이 저희에게 매우 크게 다가왔습니다.&lt;br /&gt;플레이그라운드를 통해 테스트를 하며 프롬프트를 작성하였고,&amp;nbsp; 이를 기반으로 테스트 앱을 생성하여 코드를 복붙만 하면 바로 API호출을 할 수 있기 때문에 간편하게 개발할 수 있다는 점이 정말 편리했습니다.&lt;br /&gt;마지막으로, 프롬프트로 직접 예시를 만들 수 있어 파인튜닝 없이 튜닝 효과를 낼 수 있다 라는점이 가장 큰 장점으로 느껴졌습니다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;따라서, 다양한 프롬프트를 통해 손쉽게 다양한 질문을 생성할 수 있었습니다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;blockquote data-ke-style=&quot;style1&quot;&gt;&lt;b&gt;Ncloud 버그 해결기&lt;/b&gt;&lt;/blockquote&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;Ncloud를 이용해 개발을 하면서 마주한 버그들이 있었습니다.&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;관련 레퍼런스가 부족하다보니, 직접 계속 테스트 해보면서 버그를 해결했습니다.&lt;br /&gt;이러한 부분이 혹시나 저와 같은 문제를 겪고있는 다른 Ncloud 사용자분들에게 도움이 되거나, 추후 Ncloud에 개선방향에 도움이 될까 싶어 공유합니다..!!&lt;/p&gt;
&lt;h4 data-ke-size=&quot;size20&quot;&gt;&lt;span style=&quot;color: #ee2323;&quot;&gt;&lt;b&gt;&amp;lt;이 부분은 Ncloud에서 발생한 문제가 아니라, 제가 이해도가 부족해서 발생한 문제 일 수도 있습니다. 참고 부탁드립니다.&amp;gt;&lt;/b&gt;&lt;/span&gt;&lt;/h4&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;h4 data-ke-size=&quot;size20&quot;&gt;&lt;span style=&quot;background-color: #ffffff; color: #000000;&quot;&gt;&lt;b&gt;Object Storage의 AWS CLI 연동&lt;/b&gt;&lt;/span&gt;&lt;/h4&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;네이버의 공식문서를 보면 아래처럼 Object Storage를 AWS S3 CLI를 통해서 제어를 할 수 있다고 나와있습니다.&lt;/p&gt;
&lt;p&gt;&lt;figure class=&quot;imageblock alignCenter&quot; data-ke-mobileStyle=&quot;widthOrigin&quot; data-origin-width=&quot;892&quot; data-origin-height=&quot;846&quot;&gt;&lt;span data-url=&quot;https://blog.kakaocdn.net/dn/chyBwJ/btsG6KHnAtb/ySS0PX7eCwYnJ1Nv0kojd0/img.png&quot; data-phocus=&quot;https://blog.kakaocdn.net/dn/chyBwJ/btsG6KHnAtb/ySS0PX7eCwYnJ1Nv0kojd0/img.png&quot;&gt;&lt;img src=&quot;https://blog.kakaocdn.net/dn/chyBwJ/btsG6KHnAtb/ySS0PX7eCwYnJ1Nv0kojd0/img.png&quot; srcset=&quot;https://img1.daumcdn.net/thumb/R1280x0/?scode=mtistory2&amp;fname=https%3A%2F%2Fblog.kakaocdn.net%2Fdn%2FchyBwJ%2FbtsG6KHnAtb%2FySS0PX7eCwYnJ1Nv0kojd0%2Fimg.png&quot; onerror=&quot;this.onerror=null; this.src='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png'; this.srcset='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png';&quot; loading=&quot;lazy&quot; width=&quot;600&quot; height=&quot;569&quot; data-origin-width=&quot;892&quot; data-origin-height=&quot;846&quot;/&gt;&lt;/span&gt;&lt;/figure&gt;
&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;따라서, 아래 처럼 AWS CLI를 통해 Naver API Key를 기반으로 인증을 손쉽게 할 수 있으며, Object Storage를 제어할 수 있습니다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p&gt;&lt;figure class=&quot;imageblock alignCenter&quot; data-ke-mobileStyle=&quot;widthOrigin&quot; data-origin-width=&quot;1016&quot; data-origin-height=&quot;911&quot;&gt;&lt;span data-url=&quot;https://blog.kakaocdn.net/dn/bQ4VQB/btsG5r9Nk6D/FD2iBdw2UWZByIxbTV21k1/img.png&quot; data-phocus=&quot;https://blog.kakaocdn.net/dn/bQ4VQB/btsG5r9Nk6D/FD2iBdw2UWZByIxbTV21k1/img.png&quot;&gt;&lt;img src=&quot;https://blog.kakaocdn.net/dn/bQ4VQB/btsG5r9Nk6D/FD2iBdw2UWZByIxbTV21k1/img.png&quot; srcset=&quot;https://img1.daumcdn.net/thumb/R1280x0/?scode=mtistory2&amp;fname=https%3A%2F%2Fblog.kakaocdn.net%2Fdn%2FbQ4VQB%2FbtsG5r9Nk6D%2FFD2iBdw2UWZByIxbTV21k1%2Fimg.png&quot; onerror=&quot;this.onerror=null; this.src='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png'; this.srcset='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png';&quot; loading=&quot;lazy&quot; width=&quot;600&quot; height=&quot;538&quot; data-origin-width=&quot;1016&quot; data-origin-height=&quot;911&quot;/&gt;&lt;/span&gt;&lt;/figure&gt;
&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;이 공식문서의 정보를 바탕으로 저희도 쉽게 AWS CLI에 인증을 할 수 있었습니다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p&gt;&lt;figure class=&quot;imageblock alignCenter&quot; data-ke-mobileStyle=&quot;widthOrigin&quot; data-origin-width=&quot;890&quot; data-origin-height=&quot;245&quot;&gt;&lt;span data-url=&quot;https://blog.kakaocdn.net/dn/NZEKf/btsG3yIQ6sY/Moi6w5FqWj4LZcs8lAS9MK/img.png&quot; data-phocus=&quot;https://blog.kakaocdn.net/dn/NZEKf/btsG3yIQ6sY/Moi6w5FqWj4LZcs8lAS9MK/img.png&quot;&gt;&lt;img src=&quot;https://blog.kakaocdn.net/dn/NZEKf/btsG3yIQ6sY/Moi6w5FqWj4LZcs8lAS9MK/img.png&quot; srcset=&quot;https://img1.daumcdn.net/thumb/R1280x0/?scode=mtistory2&amp;fname=https%3A%2F%2Fblog.kakaocdn.net%2Fdn%2FNZEKf%2FbtsG3yIQ6sY%2FMoi6w5FqWj4LZcs8lAS9MK%2Fimg.png&quot; onerror=&quot;this.onerror=null; this.src='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png'; this.srcset='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png';&quot; loading=&quot;lazy&quot; width=&quot;600&quot; height=&quot;165&quot; data-origin-width=&quot;890&quot; data-origin-height=&quot;245&quot;/&gt;&lt;/span&gt;&lt;/figure&gt;
&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;다만, 저희 백엔드 팀에서 Object Storage는 제어는 CI가 정상적으로 수행되고 빌드파일을 저장할 때 필요했습니다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;따라서, 깃허브 액션으로 직접 AWS CLI를 활용했고,&amp;nbsp; 보통은 AWS CLI에서 Configure를 수행하기 위해 많은 개발자들이 aws-actions를 주로 사용하기 때문에 저희 팀도 아래의 코드처럼 aws-action의 aws credentials@v4를 사용했습니다.&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p&gt;&lt;figure class=&quot;imageblock alignCenter&quot; data-ke-mobileStyle=&quot;widthOrigin&quot; data-origin-width=&quot;584&quot; data-origin-height=&quot;169&quot;&gt;&lt;span data-url=&quot;https://blog.kakaocdn.net/dn/brfYJE/btsG4hGFpBp/AlttjzsqRpH8oYzct0Ri60/img.png&quot; data-phocus=&quot;https://blog.kakaocdn.net/dn/brfYJE/btsG4hGFpBp/AlttjzsqRpH8oYzct0Ri60/img.png&quot;&gt;&lt;img src=&quot;https://blog.kakaocdn.net/dn/brfYJE/btsG4hGFpBp/AlttjzsqRpH8oYzct0Ri60/img.png&quot; srcset=&quot;https://img1.daumcdn.net/thumb/R1280x0/?scode=mtistory2&amp;fname=https%3A%2F%2Fblog.kakaocdn.net%2Fdn%2FbrfYJE%2FbtsG4hGFpBp%2FAlttjzsqRpH8oYzct0Ri60%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;584&quot; height=&quot;169&quot; data-origin-width=&quot;584&quot; data-origin-height=&quot;169&quot;/&gt;&lt;/span&gt;&lt;/figure&gt;
&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;과거에 AWS S3를 깃허브액션에서 제어했을땐, 당연히 정상적으로 되었고, 로컬에서 테스트 해본 결과 aws configure이 네이버 Object Storage에도 정상적으로 작동을 했기 때문에 당연히 작동될 것이라고 생각을 하였습니다.&lt;br /&gt;하지만 저희 백엔드 팀에서의 예상과 다르게 계속해서 token 인증 실패가 발생하였고, 저희의 코드 문제다 라고 생각을 해 계속 코드의 문법을 바꿔보면서 진행을 했지만, 계속해서 실패를 하였습니다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p&gt;&lt;figure class=&quot;imageblock alignCenter&quot; data-ke-mobileStyle=&quot;widthOrigin&quot; data-origin-width=&quot;1003&quot; data-origin-height=&quot;280&quot;&gt;&lt;span data-url=&quot;https://blog.kakaocdn.net/dn/tk6xO/btsG4iyN9ZE/6ONELkFMXkfD4d8fW3JeZK/img.png&quot; data-phocus=&quot;https://blog.kakaocdn.net/dn/tk6xO/btsG4iyN9ZE/6ONELkFMXkfD4d8fW3JeZK/img.png&quot;&gt;&lt;img src=&quot;https://blog.kakaocdn.net/dn/tk6xO/btsG4iyN9ZE/6ONELkFMXkfD4d8fW3JeZK/img.png&quot; srcset=&quot;https://img1.daumcdn.net/thumb/R1280x0/?scode=mtistory2&amp;fname=https%3A%2F%2Fblog.kakaocdn.net%2Fdn%2Ftk6xO%2FbtsG4iyN9ZE%2F6ONELkFMXkfD4d8fW3JeZK%2Fimg.png&quot; onerror=&quot;this.onerror=null; this.src='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png'; this.srcset='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png';&quot; loading=&quot;lazy&quot; width=&quot;600&quot; height=&quot;167&quot; data-origin-width=&quot;1003&quot; data-origin-height=&quot;280&quot;/&gt;&lt;/span&gt;&lt;/figure&gt;
&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;이처럼 계속해서 Invalid Token오류가 났으며, 50회 정도의 테스트의 결과 aws-action을 이용하는건 불가능하다 라고 판단을 하였습니다. 따라서, 아래의 코드처럼 aws-actions를 활용하는것이 아닌 직접 환경변수를 설정함으로써, 해결을 하였습니다. &lt;br /&gt;저두 이번에 처음알았지만 aws configure를 하지않고 그냥 환경변수를 설정하는것 만으로도 AWS CLI를 인증할 수 있었고, 이에 따라 Object Storage 직접 AWS CLI를 통해 제어를 할 수 있었습니다.&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p&gt;&lt;figure class=&quot;imageblock alignCenter&quot; data-ke-mobileStyle=&quot;widthOrigin&quot; data-origin-width=&quot;1246&quot; data-origin-height=&quot;218&quot;&gt;&lt;span data-url=&quot;https://blog.kakaocdn.net/dn/dOvZ2e/btsG3fJyEYs/M3FLKs6AWTMK7ZDic89lmK/img.png&quot; data-phocus=&quot;https://blog.kakaocdn.net/dn/dOvZ2e/btsG3fJyEYs/M3FLKs6AWTMK7ZDic89lmK/img.png&quot;&gt;&lt;img src=&quot;https://blog.kakaocdn.net/dn/dOvZ2e/btsG3fJyEYs/M3FLKs6AWTMK7ZDic89lmK/img.png&quot; srcset=&quot;https://img1.daumcdn.net/thumb/R1280x0/?scode=mtistory2&amp;fname=https%3A%2F%2Fblog.kakaocdn.net%2Fdn%2FdOvZ2e%2FbtsG3fJyEYs%2FM3FLKs6AWTMK7ZDic89lmK%2Fimg.png&quot; onerror=&quot;this.onerror=null; this.src='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png'; this.srcset='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png';&quot; loading=&quot;lazy&quot; width=&quot;600&quot; height=&quot;105&quot; data-origin-width=&quot;1246&quot; data-origin-height=&quot;218&quot;/&gt;&lt;/span&gt;&lt;/figure&gt;
&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;아직 정확한 원인은 모르겠지만 aws-action에서만 invalid token 오류가 나는걸로 보아 추측컨데, 현재 aws-actions/configure-aws-credential에서 자체적으로 토큰 형식(길이,구조)을 기반으로 1차 검증을 진행하고 있는데, 현재 Naver API Key 구조와 AWS IAM Key구조가 달라서 생기는 문제가 아닐까 생각을 하였습니다.&lt;br /&gt;이 부분은 정말 나중에 시간이 지나고 나면 다시 원인을 찾아보고 해결해보고 싶은 문제입니다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;h4 data-ke-size=&quot;size20&quot;&gt;&lt;span style=&quot;color: #000000;&quot;&gt;&lt;b&gt;Source Deploy 배포시 빌드파일 저장문제&lt;/b&gt;&lt;/span&gt;&lt;/h4&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;Source Deploy를 이용해 AutoScaling 배포를 할시 아래사진처럼 배포 전, 배포, 배포 후로 나눠서 빌드 명령어를 커스텀하여 실행할 수 있습니다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p&gt;&lt;figure class=&quot;imageblock alignCenter&quot; data-ke-mobileStyle=&quot;widthOrigin&quot; data-origin-width=&quot;1293&quot; data-origin-height=&quot;1087&quot;&gt;&lt;span data-url=&quot;https://blog.kakaocdn.net/dn/nXHDY/btsG3sPtLgS/0cPUERKziI2ymupEDxMN80/img.png&quot; data-phocus=&quot;https://blog.kakaocdn.net/dn/nXHDY/btsG3sPtLgS/0cPUERKziI2ymupEDxMN80/img.png&quot;&gt;&lt;img src=&quot;https://blog.kakaocdn.net/dn/nXHDY/btsG3sPtLgS/0cPUERKziI2ymupEDxMN80/img.png&quot; srcset=&quot;https://img1.daumcdn.net/thumb/R1280x0/?scode=mtistory2&amp;fname=https%3A%2F%2Fblog.kakaocdn.net%2Fdn%2FnXHDY%2FbtsG3sPtLgS%2F0cPUERKziI2ymupEDxMN80%2Fimg.png&quot; onerror=&quot;this.onerror=null; this.src='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png'; this.srcset='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png';&quot; loading=&quot;lazy&quot; width=&quot;600&quot; height=&quot;504&quot; data-origin-width=&quot;1293&quot; data-origin-height=&quot;1087&quot;/&gt;&lt;/span&gt;&lt;/figure&gt;
&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;여기서 저희는 스프링 기반의 서버를 구축하였기 때문에, Object Storage에서 .zip파일을 기반으로 스프링 빌드파일인 jar 파일을 서버로 가져옵니다. 이 이후에 jar 파일을 백그라운드로 실행하기만 하면 되었습니다.&lt;br /&gt;다만, Source Deploy를 그냥 실행해본결과 아래 사진처럼 랜덤한 파일 이름이 생성이되고, 이 공간에 jar파일이 자동으로 저장이 되는것을 확인했습니다.&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p&gt;&lt;figure class=&quot;imageblock alignCenter&quot; data-ke-mobileStyle=&quot;widthOrigin&quot; data-origin-width=&quot;1140&quot; data-origin-height=&quot;169&quot;&gt;&lt;span data-url=&quot;https://blog.kakaocdn.net/dn/cMKYhw/btsG3N6VBNQ/N5uSSOgD7VujwKNYbwVKiK/img.png&quot; data-phocus=&quot;https://blog.kakaocdn.net/dn/cMKYhw/btsG3N6VBNQ/N5uSSOgD7VujwKNYbwVKiK/img.png&quot;&gt;&lt;img src=&quot;https://blog.kakaocdn.net/dn/cMKYhw/btsG3N6VBNQ/N5uSSOgD7VujwKNYbwVKiK/img.png&quot; srcset=&quot;https://img1.daumcdn.net/thumb/R1280x0/?scode=mtistory2&amp;fname=https%3A%2F%2Fblog.kakaocdn.net%2Fdn%2FcMKYhw%2FbtsG3N6VBNQ%2FN5uSSOgD7VujwKNYbwVKiK%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;1140&quot; height=&quot;169&quot; data-origin-width=&quot;1140&quot; data-origin-height=&quot;169&quot;/&gt;&lt;/span&gt;&lt;/figure&gt;
&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;위처럼 자동으로 jar파일이 생성이 되는것을 확인할 수 있었는데, 중간에 '174074' 처럼 파일이름이 랜덤하게 바뀌면서 저장이 되었습니다.&lt;br /&gt;이렇게 될경우 저희가 배포스크립트를 미리 작성할 수가 없기때문에 저희가 원하는 위치에 jar 파일을 저장하고 싶었습니다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;따라서, '파일 배포' 라는 것을 커스텀해서 저희가 원하는 위치에 jar 파일을 저장하려고 시도를 하였습니다.&lt;br /&gt;하지만, 저희가 못찾은 것일 수도 있지만 이거와 관련된 자료가 없었습니다. 관련 블로그도 찾아봤고, Ncloud 공식문서도 찾아봤지만 이것을 어떻게 활용하는지 정확한 예시가 없었고, 설명도 확인할 수 없었습니다.&lt;br /&gt;따라서, 저희가 직접 테스트를 해보며 '파일 배포'를 커스텀하려고 했지만 아래처럼 계속 실패하였습니다.&lt;br /&gt;&lt;br /&gt;&lt;/p&gt;
&lt;p&gt;&lt;figure class=&quot;imageblock alignCenter&quot; data-ke-mobileStyle=&quot;widthOrigin&quot; data-origin-width=&quot;1153&quot; data-origin-height=&quot;236&quot;&gt;&lt;span data-url=&quot;https://blog.kakaocdn.net/dn/oudnA/btsG47KvZea/D60dzNdMqJPQXzbxEFpxDk/img.png&quot; data-phocus=&quot;https://blog.kakaocdn.net/dn/oudnA/btsG47KvZea/D60dzNdMqJPQXzbxEFpxDk/img.png&quot; data-alt=&quot;test-package 배포 경로 수정&quot;&gt;&lt;img src=&quot;https://blog.kakaocdn.net/dn/oudnA/btsG47KvZea/D60dzNdMqJPQXzbxEFpxDk/img.png&quot; srcset=&quot;https://img1.daumcdn.net/thumb/R1280x0/?scode=mtistory2&amp;fname=https%3A%2F%2Fblog.kakaocdn.net%2Fdn%2FoudnA%2FbtsG47KvZea%2FD60dzNdMqJPQXzbxEFpxDk%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;1153&quot; height=&quot;236&quot; data-origin-width=&quot;1153&quot; data-origin-height=&quot;236&quot;/&gt;&lt;/span&gt;&lt;figcaption&gt;test-package 배포 경로 수정&lt;/figcaption&gt;
&lt;/figure&gt;
&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p&gt;&lt;figure class=&quot;imageblock alignCenter&quot; data-ke-mobileStyle=&quot;widthOrigin&quot; data-origin-width=&quot;1191&quot; data-origin-height=&quot;317&quot;&gt;&lt;span data-url=&quot;https://blog.kakaocdn.net/dn/Uh02j/btsG6ePqkYr/4fKyP1OrDRja38jABP6w7k/img.png&quot; data-phocus=&quot;https://blog.kakaocdn.net/dn/Uh02j/btsG6ePqkYr/4fKyP1OrDRja38jABP6w7k/img.png&quot;&gt;&lt;img src=&quot;https://blog.kakaocdn.net/dn/Uh02j/btsG6ePqkYr/4fKyP1OrDRja38jABP6w7k/img.png&quot; srcset=&quot;https://img1.daumcdn.net/thumb/R1280x0/?scode=mtistory2&amp;fname=https%3A%2F%2Fblog.kakaocdn.net%2Fdn%2FUh02j%2FbtsG6ePqkYr%2F4fKyP1OrDRja38jABP6w7k%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;1191&quot; height=&quot;317&quot; data-origin-width=&quot;1191&quot; data-origin-height=&quot;317&quot;/&gt;&lt;/span&gt;&lt;/figure&gt;
&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;위처럼 다양한 소스 파일 경로를 입력하면서 테스트를 해봤지만 계속해서 찾을수 없다라는 오류가 떴습니다.&lt;br /&gt;계속해서 시도를 해봤지만 계속 실패를하였고, AutoScaling에 한번 배포할때마다 5분정도 시간이 소요되었기 때문에 계속해서 테스트를 진행을 할 수 없는 상황이었습니다.&lt;br /&gt;따라서 '파일 배포'를 커스텀하는것은 포기하고, 배포 후 실행에서 직접 파일명을 find 명령으로 찾는 방법으로 개발을 하였습니다.&lt;/p&gt;
&lt;p&gt;&lt;figure class=&quot;imageblock alignCenter&quot; data-ke-mobileStyle=&quot;widthOrigin&quot; data-origin-width=&quot;1235&quot; data-origin-height=&quot;220&quot;&gt;&lt;span data-url=&quot;https://blog.kakaocdn.net/dn/J16vl/btsG35flSld/HvRdTEeZPFJ6gCAKEL5QSk/img.png&quot; data-phocus=&quot;https://blog.kakaocdn.net/dn/J16vl/btsG35flSld/HvRdTEeZPFJ6gCAKEL5QSk/img.png&quot; data-alt=&quot;find 명령으로 파일 실행&quot;&gt;&lt;img src=&quot;https://blog.kakaocdn.net/dn/J16vl/btsG35flSld/HvRdTEeZPFJ6gCAKEL5QSk/img.png&quot; srcset=&quot;https://img1.daumcdn.net/thumb/R1280x0/?scode=mtistory2&amp;fname=https%3A%2F%2Fblog.kakaocdn.net%2Fdn%2FJ16vl%2FbtsG35flSld%2FHvRdTEeZPFJ6gCAKEL5QSk%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;1235&quot; height=&quot;220&quot; data-origin-width=&quot;1235&quot; data-origin-height=&quot;220&quot;/&gt;&lt;/span&gt;&lt;figcaption&gt;find 명령으로 파일 실행&lt;/figcaption&gt;
&lt;/figure&gt;
&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;이런식으로 Source Deploy를 성공적으로 적용시켰지만, 이 방식이 완벽한 방법은 아니다 라고 생각을 합니다.&lt;br /&gt;따라서, 추후에 기회가 된다면, '파일 배포'를 커스텀하는 방식을 다시 시도해보려 합니다.&lt;br /&gt;좀 더 관련 자료나 예시가 있었으면 좋았을 텐데... 라는 아쉬움은 남아있습니다.&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;h4 style=&quot;color: #000000; text-align: start;&quot; data-ke-size=&quot;size20&quot;&gt;&lt;span style=&quot;color: #000000;&quot;&gt;&lt;b&gt;Naver Signiture 서명 방식&lt;/b&gt;&lt;/span&gt;&lt;/h4&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;현재 Ncloud의 서비스들을 API 호출을 통해서제어를 하기 위해선 API 호출할때 각종 인증 정보를 보내야합니다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;저희는 깃허브 액션으로 SourceDeploy에게 신호를 보내기 위해 API 호출이 필요했습니다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;아래 페이지에서 각 서비스의 API에 대한 정보 및 방법에 대한 가이드 정보가 있습니다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;a href=&quot;https://api.ncloud-docs.com/docs/ko/home&quot; target=&quot;_blank&quot; rel=&quot;noopener&amp;nbsp;noreferrer&quot;&gt;https://api.ncloud-docs.com/docs/ko/home&lt;/a&gt;&lt;/p&gt;
&lt;figure id=&quot;og_1714546442084&quot; contenteditable=&quot;false&quot; data-ke-type=&quot;opengraph&quot; data-ke-align=&quot;alignCenter&quot; data-og-type=&quot;article&quot; data-og-title=&quot;HOME&quot; data-og-description=&quot; &quot; data-og-host=&quot;api.ncloud-docs.com&quot; data-og-source-url=&quot;https://api.ncloud-docs.com/docs/ko/home&quot; data-og-url=&quot;https://api.ncloud-docs.com/docs/ko/home&quot; data-og-image=&quot;&quot;&gt;&lt;a href=&quot;https://api.ncloud-docs.com/docs/ko/home&quot; target=&quot;_blank&quot; rel=&quot;noopener&quot; data-source-url=&quot;https://api.ncloud-docs.com/docs/ko/home&quot;&gt;
&lt;div class=&quot;og-image&quot; style=&quot;background-image: url();&quot;&gt;&amp;nbsp;&lt;/div&gt;
&lt;div class=&quot;og-text&quot;&gt;
&lt;p class=&quot;og-title&quot; data-ke-size=&quot;size16&quot;&gt;HOME&lt;/p&gt;
&lt;p class=&quot;og-desc&quot; data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p class=&quot;og-host&quot; data-ke-size=&quot;size16&quot;&gt;api.ncloud-docs.com&lt;/p&gt;
&lt;/div&gt;
&lt;/a&gt;&lt;/figure&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;이렇게 API 호출할 때, 필요한 Header 정보는 다음과 같습니다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;ul style=&quot;list-style-type: disc;&quot; data-ke-list-type=&quot;disc&quot;&gt;
&lt;li&gt;현재 timestamp 값&lt;/li&gt;
&lt;li&gt;Naver Access Key&lt;/li&gt;
&lt;li&gt;Naver Signiture V2 서명 값&lt;/li&gt;
&lt;/ul&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;여기서 Naver Signiture V2 서명값을 다음과 같습니다&lt;/p&gt;
&lt;blockquote data-ke-style=&quot;style3&quot;&gt;HTTP 메서드&amp;nbsp; + Api URL 정보 + 현재 timestamp 값 + Naver Access Key&lt;/blockquote&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;이 정보들을 다 합한 값을 Naver Secrets Key를 바탕으로 SHA256 방식으로 암호화를 한 값이 서명값입니다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;이 서명값을 기반으로 API 호출때 인증이 되기 때문에 매우 중요합니다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;이 과정이 꽤 복잡한 과정이기 때문에 쉘 명령어로 해결할 수 없었기에, 저희는 쉘 스크립트 파일을 만들어서 요청을 할 수 있게 하였습니다. 아래가 저희의 쉘 스크립트 파일입니다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;blockquote data-ke-style=&quot;style3&quot;&gt;#!/bin/bash &lt;br /&gt;&lt;br /&gt;#&amp;nbsp;Function&amp;nbsp;to&amp;nbsp;sign&amp;nbsp;the&amp;nbsp;request &lt;br /&gt;function&amp;nbsp;Sign_Request()&amp;nbsp;{ &lt;br /&gt;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;local&amp;nbsp;api_server=$1 &lt;br /&gt;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;local&amp;nbsp;api_url=$2 &lt;br /&gt;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;local&amp;nbsp;apicall_method=$3 &lt;br /&gt;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;local&amp;nbsp;ncloud_accesskey=$4 &lt;br /&gt;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;local&amp;nbsp;ncloud_secretkey=$5 &lt;br /&gt;&lt;br /&gt;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;local&amp;nbsp;unixtimestamp=$(echo&amp;nbsp;$(($(date&amp;nbsp;+%s%N)/1000000))) &lt;br /&gt;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;local&amp;nbsp;message=&quot;${apicall_method}&amp;nbsp;${api_url}\n${unixtimestamp}\n${ncloud_accesskey}&quot; &lt;br /&gt;&lt;br /&gt;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;local&amp;nbsp;signature=$(echo&amp;nbsp;-n&amp;nbsp;-e&amp;nbsp;&quot;$message&quot;|iconv&amp;nbsp;-t&amp;nbsp;utf8&amp;nbsp;|openssl&amp;nbsp;dgst&amp;nbsp;-sha256&amp;nbsp;-hmac&amp;nbsp;&quot;$ncloud_secretkey&quot;&amp;nbsp;-binary|openssl&amp;nbsp;enc&amp;nbsp;-base64) &lt;br /&gt;&lt;br /&gt;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;echo&amp;nbsp;-e&amp;nbsp;&quot;x-ncp-apigw-timestamp:$unixtimestamp\nx-ncp-iam-access-key:$ncloud_accesskey\nx-ncp-apigw-signature-v2:$signature&quot; &lt;br /&gt;} &lt;br /&gt;&lt;br /&gt;#&amp;nbsp;Sample&amp;nbsp;API&amp;nbsp;server&amp;nbsp;and&amp;nbsp;URL &lt;br /&gt;api_server = &amp;lt;API URL 정보&amp;gt;&lt;br /&gt;api_url = &amp;lt;API 세부 URI 정보&amp;gt;&lt;br /&gt;apicall_method = &amp;lt;HTTP 메서드&amp;gt;&lt;br /&gt;ncloud_accesskey = &amp;lt;Access Key&amp;gt;&lt;br /&gt;ncloud_secretkey = &amp;lt;Secret Key&amp;gt;&lt;br /&gt;api_full_url=&quot;${api_server}${api_url}&quot; &lt;br /&gt;&lt;br /&gt;#&amp;nbsp;Get&amp;nbsp;the&amp;nbsp;headers&amp;nbsp;from&amp;nbsp;the&amp;nbsp;Sign_Request&amp;nbsp;function &lt;br /&gt;headers=$(Sign_Request&amp;nbsp;&quot;$api_server&quot;&amp;nbsp;&quot;$api_url&quot;&amp;nbsp;&quot;$apicall_method&quot;&amp;nbsp;&quot;$ncloud_accesskey&quot;&amp;nbsp;&quot;$ncloud_secretkey&quot;) &lt;br /&gt;&lt;br /&gt;#&amp;nbsp;Prepare&amp;nbsp;header&amp;nbsp;arguments&amp;nbsp;for&amp;nbsp;curl &lt;br /&gt;header_args=() &lt;br /&gt;while&amp;nbsp;IFS=&amp;nbsp;read&amp;nbsp;-r&amp;nbsp;line;&amp;nbsp;do &lt;br /&gt;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;header_args+=(&quot;-H&quot;&amp;nbsp;&quot;$line&quot;) &lt;br /&gt;done&amp;nbsp;&amp;lt;&amp;lt;&amp;lt;&amp;nbsp;&quot;$headers&quot; &lt;br /&gt;&lt;br /&gt;#&amp;nbsp;Make&amp;nbsp;the&amp;nbsp;API&amp;nbsp;request &lt;br /&gt;curl&amp;nbsp;-X&amp;nbsp;&quot;$apicall_method&quot;&amp;nbsp;&quot;$api_full_url&quot;&amp;nbsp;&quot;${header_args[@]}&quot;&lt;/blockquote&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;이렇게 로컬에서 쉘스크립트를 통해 직접 테스트를 하여, 성공적으로 작동하는 것을 확인하였고, 이 이후에 바로 깃허브 액션에 적용시켜 해결을 하려 했습니다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;이 파일을 깃허브 액션에 생성하기 위해서 깃허브액션 Secrets 에 key:value 형식으로 이 파일의 내용을 저장하였습니다.&lt;br /&gt;이렇게 성공적으로 되나 싶었지만.... 아래와 같은 오류가 발생하였습니다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p&gt;&lt;figure class=&quot;imageblock alignCenter&quot; data-ke-mobileStyle=&quot;widthOrigin&quot; data-origin-width=&quot;629&quot; data-origin-height=&quot;98&quot;&gt;&lt;span data-url=&quot;https://blog.kakaocdn.net/dn/b5nDy2/btsG5VbFKss/YUS1AyYPwNJoYJNCKXSGxk/img.png&quot; data-phocus=&quot;https://blog.kakaocdn.net/dn/b5nDy2/btsG5VbFKss/YUS1AyYPwNJoYJNCKXSGxk/img.png&quot; data-alt=&quot;'&quot;&gt;&lt;img src=&quot;https://blog.kakaocdn.net/dn/b5nDy2/btsG5VbFKss/YUS1AyYPwNJoYJNCKXSGxk/img.png&quot; srcset=&quot;https://img1.daumcdn.net/thumb/R1280x0/?scode=mtistory2&amp;fname=https%3A%2F%2Fblog.kakaocdn.net%2Fdn%2Fb5nDy2%2FbtsG5VbFKss%2FYUS1AyYPwNJoYJNCKXSGxk%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;629&quot; height=&quot;98&quot; data-origin-width=&quot;629&quot; data-origin-height=&quot;98&quot;/&gt;&lt;/span&gt;&lt;figcaption&gt;'&lt;/figcaption&gt;
&lt;/figure&gt;
&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;정말 처음보는 오류라서, 계속 헤맸습니다. 분명히 값들도 다 정확히 매핑 시켜놨고, 로컬에선 잘돌아 가는데 깃허브 액션에서만 오류가 발생하였습니다.&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;결국 계속해서 디버깅을 해본결과 아래 처럼 특정 값들이 빈값으로 들어온다 라는것을 확인했습니다.&lt;/p&gt;
&lt;p&gt;&lt;figure class=&quot;imageblock alignCenter&quot; data-ke-mobileStyle=&quot;widthOrigin&quot; data-origin-width=&quot;527&quot; data-origin-height=&quot;178&quot;&gt;&lt;span data-url=&quot;https://blog.kakaocdn.net/dn/cMBqbf/btsG47w6GiZ/S62oDDOC9XO0Kf3nweOeq0/img.png&quot; data-phocus=&quot;https://blog.kakaocdn.net/dn/cMBqbf/btsG47w6GiZ/S62oDDOC9XO0Kf3nweOeq0/img.png&quot;&gt;&lt;img src=&quot;https://blog.kakaocdn.net/dn/cMBqbf/btsG47w6GiZ/S62oDDOC9XO0Kf3nweOeq0/img.png&quot; srcset=&quot;https://img1.daumcdn.net/thumb/R1280x0/?scode=mtistory2&amp;fname=https%3A%2F%2Fblog.kakaocdn.net%2Fdn%2FcMBqbf%2FbtsG47w6GiZ%2FS62oDDOC9XO0Kf3nweOeq0%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;527&quot; height=&quot;178&quot; data-origin-width=&quot;527&quot; data-origin-height=&quot;178&quot;/&gt;&lt;/span&gt;&lt;/figure&gt;
&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;위와 같은 현상이 일어난 이유는 저희의 쉘 스크립트 코드에 $라는 문자가 들어갔기 때문입니다. 저희 쉘스크립트 코드에서 환경변수로 설정을 하려고 일부로 $를 사용해서 코드를 작성을 한건데, 이게 깃허브액션에서도 $가 환경변수로 작동해서 쉘스크립트 코드 안의 환경변수가 아닌, 깃허브액션 Sercerts 값을 참조했고, 참조한 결과 해당 값이 존재하지않아 빈값을 주입하고 있었던 버그가 있었습니다.&lt;br /&gt;&lt;br /&gt;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;이에 저희는 파일내용 자체를 깃허브 액션 Secrets를 사용하는게 아니라, 내부 변수들만 Secrets으로 넣어두고 스프링 서버 내에 쉘스크립트 파일을 넣었습니다. 따라서, 깃허브 액션에서는 이 코드를 git pull 받아 쉘스크립트 파일로 가서 설정된 환경 변수만 주입하면 되었습니다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p&gt;&lt;figure class=&quot;imageblock alignCenter&quot; data-ke-mobileStyle=&quot;widthOrigin&quot; data-origin-width=&quot;308&quot; data-origin-height=&quot;217&quot;&gt;&lt;span data-url=&quot;https://blog.kakaocdn.net/dn/cYwNUw/btsG54Gp7pB/0PNOgPbs9NBfWw8NFeFEsk/img.png&quot; data-phocus=&quot;https://blog.kakaocdn.net/dn/cYwNUw/btsG54Gp7pB/0PNOgPbs9NBfWw8NFeFEsk/img.png&quot; data-alt=&quot;완성된 쉘스크립트 코드&quot;&gt;&lt;img src=&quot;https://blog.kakaocdn.net/dn/cYwNUw/btsG54Gp7pB/0PNOgPbs9NBfWw8NFeFEsk/img.png&quot; srcset=&quot;https://img1.daumcdn.net/thumb/R1280x0/?scode=mtistory2&amp;fname=https%3A%2F%2Fblog.kakaocdn.net%2Fdn%2FcYwNUw%2FbtsG54Gp7pB%2F0PNOgPbs9NBfWw8NFeFEsk%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;217&quot; data-origin-width=&quot;308&quot; data-origin-height=&quot;217&quot;/&gt;&lt;/span&gt;&lt;figcaption&gt;완성된 쉘스크립트 코드&lt;/figcaption&gt;
&lt;/figure&gt;
&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;span style=&quot;color: #333333;&quot;&gt;이렇게 API 호출과 관련된 버그를 해결하였습니다.&lt;br /&gt;처음엔 깃허브액션에서 사용하기 위해 Naver CLI 없이 사용하는 간단한 방법을 찾다가, 오히려 돌아온 느낌이었으며,&lt;br /&gt;개인적으로, API호출과 관련된 action 코드가 있었으면 좋았을거 같다는 아쉬움이 있었습니다.&lt;/span&gt;&lt;/p&gt;
&lt;h4 data-ke-size=&quot;size20&quot;&gt;&amp;nbsp;&lt;/h4&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;h4 data-ke-size=&quot;size20&quot;&gt;&lt;span style=&quot;color: #000000;&quot;&gt;&lt;b&gt;Private Subnet SSH 접속&lt;/b&gt;&lt;/span&gt;&lt;/h4&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;이 부분은 사실 버그라기보다는 VPC내에 Private Subnet안의 서버에 SSH 접속을 하기위해서 어떤 방법을 사용해야하나 라는 저희의 고민이 담긴 문제였습니다. 기본적으로 Private Subnet 안에 있는 서버들은 외부로 노출되지않기 때문에 저희 로컬 에서도 원격으로 SSH 접속을 할 수 없었습니다. 따라서 기존의 SSH 접속방법으로는 접속이 불가능하고 다른 방법으로 접속을 해야 합니다. 저희가 생각했던 방법은 크게 3가지였습니다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;ol style=&quot;list-style-type: decimal;&quot; data-ke-list-type=&quot;decimal&quot;&gt;
&lt;li&gt;&lt;span style=&quot;color: #000000;&quot;&gt;&lt;b&gt;Public Subnet에 Bastion Host를 두어, 경유를 거쳐 SSH 접속을 하는 방법&lt;/b&gt;&lt;/span&gt;&lt;/li&gt;
&lt;li&gt;&lt;span style=&quot;color: #000000;&quot;&gt;&lt;b&gt;VPC에 VPN Gateway추가 및 터널링을 통한 접속&lt;/b&gt;&lt;/span&gt;&lt;/li&gt;
&lt;li&gt;&lt;span style=&quot;color: #000000;&quot;&gt;&lt;b&gt;NAT Gateway에 포트포워딩을 통한 SSH 접속&lt;/b&gt;&lt;/span&gt;&lt;/li&gt;
&lt;/ol&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;위의 방식처럼 3가지의 방법을 생각을 했는데, 제일 비용부담이 적은것은 3번째 방식이였습니다. NAT Gateway의 포트를 내부 서버 22번 포트로 포워딩하여 내부로 SSH 접속을 할 수 있게 하면 추가 요금도 없어서 부담이 제일 적을 것이라 생각을 하였습니다.&lt;br /&gt;다만, 이러한 방식의 경우 외부에 특정 포트가 노출이 되는것이기 때문에 보안 취약점이 생긴다는 단점이 있었습니다. 관련 자료를 찾아보니 Ncloud에서도 이러한 문제점 때문인지 NAT Gateway를 포트포워딩하는 기능은 제공하고 있지않았기에, 이 방식은 적용할 수 없었습니다.&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;이게 1,2번 두가지 방식이 있는데, 두 가지 방식 다 비용 부담이 컸었습니다. 첫번째 방식은 Server의 기본요금 자체가 커, 시간당 최소 120원 이상의 가격이 들었고, 두번째 방식은 VPN Gateway를 뚫는거 자체가 시간당 138원이 들었습니다.&amp;nbsp;&lt;br /&gt;이미 운영 서버와 DB에 가격부담을 느끼고 있던터라, 여기에 적지않은 요금을 투입하는거 자체가 저희 팀에 큰 부담으로 느껴졌고, 또한, 단지 SSH접속만을 위해서 VPN을 사용한다는거 자체가 조금 말이 안된다 라고 생각을 했습니다.&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;이에 저희는 평상시엔 네이버 웹페이지에 있는 서버 접속 콘솔을 이용하기로 하고, 긴급한 버그 해결이라던지, 아니면 대규모 수정을 해야할 부분이 있다 라고 할때만 따로 Test Server를 생성하여 Bastion Host 역할을 할 수 있게 하자 라는 결론이 나왔습니다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;이 부분은 결국 근본적으로 해결을 하지못한 상황입니다. 혹시나 다른 해결책이 있으시면 알려주세요 ㅎㅎ&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;blockquote data-ke-style=&quot;style1&quot;&gt;&lt;b&gt;&lt;span style=&quot;font-family: 'Noto Serif KR';&quot;&gt;Ncloud 사용중 만족했던 점 &amp;amp; 아쉬웠던 점&lt;/span&gt;&lt;/b&gt;&lt;/blockquote&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;우선 Ncloud를 사용하면서 만족했던 점은 다음과 같습니다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;ul style=&quot;list-style-type: disc;&quot; data-ke-list-type=&quot;disc&quot;&gt;
&lt;li&gt;&lt;span style=&quot;color: #000000;&quot;&gt;&lt;b&gt;한글로 되어있는 공식문서&lt;/b&gt;&lt;/span&gt;&lt;/li&gt;
&lt;li&gt;&lt;span style=&quot;color: #000000;&quot;&gt;&lt;b&gt;높은 AI 편의성&lt;/b&gt;&lt;/span&gt;&lt;/li&gt;
&lt;/ul&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;Ncloud를 활용하면서 정말 편했던 점은 공식문서가 한글로 되어있다는 점이었습니다. 따라서 공식문서를 기반으로 쉽게 개발환경을 세팅할 수 있었습니다.&lt;br /&gt;또한, 추가적으로 Naver Clova Studio에서 플레이그라운드로 AI를 사용할떄 정말 많은 편리함을 느꼈습니다.&lt;br /&gt;타 AI기술과 다르게 직접 웹을 통해 튜닝하고, 테스트를 해 볼 수 있다는 점이 정말 편했습니다.&lt;br /&gt;이를 기반으로 저희 백엔드 팀에서 AI 지식이 많이 없음에도 불구하고 빠르게 개발이 가능했습니다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;반대로 Ncloud를 사용하면서 아쉬웠던 점은 다음과 같습니다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;ul style=&quot;list-style-type: disc;&quot; data-ke-list-type=&quot;disc&quot;&gt;
&lt;li&gt;&lt;span style=&quot;color: #000000;&quot;&gt;&lt;b&gt;관련 레퍼런스 부족&lt;/b&gt;&lt;/span&gt;&lt;/li&gt;
&lt;li&gt;&lt;span style=&quot;color: #000000;&quot;&gt;&lt;b&gt;Server 및 DB의 과도한 최저성능 및 요금&lt;/b&gt;&lt;/span&gt;&lt;/li&gt;
&lt;/ul&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;Ncloud를 사용하면서 정말 많이 느꼈던게, AWS Cloud에 비해 블로그같은 레퍼런스가 찾기가 매우 어려웠습니다. 물론 사용자 차이로 인해 당연히 자료가 부족한게 당연하겠지만... 한번 버그가 일어나면 공식문서 외에는 참고할 자료가 없었습니다. Object Storage나, Source Deploy같은 서비스의 경우는 AWS와 비슷하겠지.. 하면서 S3 및 Code Deploy 관련 자료를 찾아보면서 해결했어야 했습니다. 따라서 이러한 참고 자료를 좀더 다양하게 존재했으면 좋겠다... 라는 아쉬움이 있었습니다.&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;또한, 아래의 자료가 현재 NaverCloud의 Server 및 DB의 요금페이지 입니다.&amp;nbsp;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;a href=&quot;https://www.ncloud.com/product/compute/server&quot; target=&quot;_blank&quot; rel=&quot;noopener&amp;nbsp;noreferrer&quot;&gt;https://www.ncloud.com/product/compute/server&lt;/a&gt;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;a href=&quot;https://www.ncloud.com/product/database/cloudDbMysql&quot; target=&quot;_blank&quot; rel=&quot;noopener&amp;nbsp;noreferrer&quot;&gt;https://www.ncloud.com/product/database/cloudDbMysql&lt;/a&gt;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;위의 사이트에서 보면 아시겠지만 VPC 기준으로 Server의 기본 요금은 119원, MySQL DB의 기본 요금은 256원 입니다.&lt;br /&gt;아직 학생의 입장이라, 서버와 DB의 최저 성능이 너무 높게 느껴졌고, 그로인한 요금에 대한 부담이 크게 다가왔습니다. &lt;br /&gt;특히 VPC 경우, 직접 Private Subnet에 ssh 접속을 하지 못하다 보니 Bastion Host용 서버가 필요한데, 이 접속용 서버로 vCPU 2, 메모리 8GB, 디스크 50GB가 너무 오버스펙이라는 생각이 들었습니다..ㅠㅠ&lt;br /&gt;따라서, VPC 용도로 프리티어 같은 낮은 성능의 Server를 1개 정도 생성할 수 있도록 하면 괜찮지 않았을까.... 라는 아쉬움이 있었습니다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;blockquote data-ke-style=&quot;style1&quot;&gt;&lt;b&gt;&lt;span style=&quot;font-family: 'Noto Serif KR';&quot;&gt;Green Developers 프로그램 참여 소감&lt;/span&gt;&lt;/b&gt;&lt;/blockquote&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;이번에 동아리에서 우연히 네이버의 Green Developers 프로그램에 참여하게 되었는데, 클라우드에 관심 있는 개발자에겐 정말 너무 좋은 기회다 라고 생각합니다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;br /&gt;사실 처음에는 그냥 Server와 DB만 사용할 목적으로 신청을 했지만, 홈페이지를 보니 정말 다양한 Ncloud의 서비스가 있었습니다. 따라서, 이번 기회에 다양한 클라우드 제품들을 써볼까 싶어 Ncloud의 모든 서비스들을 분석하였고, 또한 제한된 크레딧 내에서 어떻게 하면 효율적인 비용으로 아키텍처를 구성할 수 있을까 고민을 많이 하면서 개인적으로 성장을 많이 했다고 생각합니다.&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;특히 관련 레퍼런스가 적다보니 조금은 힘들었지만 AI 및 관련자료 없이 혼자서 고민을 하는 시간이 많았어서 더욱 더 많이 배우고 기억할 수 있었던 값진 기회였던거 같습니다.&lt;br /&gt;이번에 이렇게 좋은 기회를 주셔서 너무 감사드리고, 이번 경험을 바탕으로 다음에 다른 Developers 프로그램이 있다면 참여하여 학습을 해보고 싶습니다!!&lt;/p&gt;</description>
      <category>클라우드</category>
      <author>tioon</author>
      <guid isPermaLink="true">https://tioon.tistory.com/195</guid>
      <comments>https://tioon.tistory.com/195#entry195comment</comments>
      <pubDate>Wed, 1 May 2024 03:13:17 +0900</pubDate>
    </item>
    <item>
      <title>스프링 WebSocket 및 Redis로 채팅 서버 구현기</title>
      <link>https://tioon.tistory.com/194</link>
      <description>&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;이번에 미니 사이드 프로젝트로, 실시간 채팅기능을 구현하게 되었는데 이 과정을 한번 정리를 해보려합니다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;우선 저희의 요구사항은 다음과 같았습니다.&lt;/p&gt;
&lt;ol style=&quot;list-style-type: decimal;&quot; data-ke-list-type=&quot;decimal&quot;&gt;
&lt;li&gt;사용자간의 실시간 채팅이 구현되어야함.&lt;/li&gt;
&lt;li&gt;방이름을 기반으로 채팅방이 구분되어야함.&lt;/li&gt;
&lt;li&gt;전에 했던 채팅 기능이 저장이 되어야함.&lt;/li&gt;
&lt;li&gt;scale-out시에 채팅기능에 문제가 생기지 않아야함.&lt;/li&gt;
&lt;/ol&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;크게 이렇게 4가지가 있었는데요. 이 요구사항을 지키기위해서 갖은 삽질과 버그가 있었습니다.&lt;br /&gt;이 미니 사이드 프로젝트는 Spring, 타임리프, Redis, mongoDB로 구현되어있으며, 중간에 발생한 문제점으로 인해 Kafka로 마이그레이션을 시도했습니다만.... 시간이 부족해 실패했습니다 이과정에 대해선 아래에서 자세히 설명드리겠습니다.&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;일단 먼저 스프링 WebSokcet과 MessageQueue의 전체적인 설명 먼저 해보겠습니다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;blockquote data-ke-style=&quot;style1&quot;&gt;&lt;b&gt;&lt;span style=&quot;font-family: 'Noto Serif KR';&quot;&gt;&amp;nbsp;스프링 WebSocket 실시간 통신&lt;/span&gt;&lt;/b&gt;&lt;/blockquote&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;스프링 WebSocket으로 실시간 채팅을 구현하기 전에, 아래의 사진으로 스프링 WebSocket의 기본적인 구조에 대해 알아보고 가도록하겠습니다.&lt;/p&gt;
&lt;p&gt;&lt;figure class=&quot;imageblock alignCenter&quot; data-ke-mobileStyle=&quot;widthOrigin&quot; data-origin-width=&quot;1334&quot; data-origin-height=&quot;671&quot;&gt;&lt;span data-url=&quot;https://blog.kakaocdn.net/dn/bmVCfm/btsGMBGcOni/Sq8CjQ7rQKTuEdSIQrSQJk/img.png&quot; data-phocus=&quot;https://blog.kakaocdn.net/dn/bmVCfm/btsGMBGcOni/Sq8CjQ7rQKTuEdSIQrSQJk/img.png&quot;&gt;&lt;img src=&quot;https://blog.kakaocdn.net/dn/bmVCfm/btsGMBGcOni/Sq8CjQ7rQKTuEdSIQrSQJk/img.png&quot; srcset=&quot;https://img1.daumcdn.net/thumb/R1280x0/?scode=mtistory2&amp;fname=https%3A%2F%2Fblog.kakaocdn.net%2Fdn%2FbmVCfm%2FbtsGMBGcOni%2FSq8CjQ7rQKTuEdSIQrSQJk%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;1334&quot; height=&quot;671&quot; data-origin-width=&quot;1334&quot; data-origin-height=&quot;671&quot;/&gt;&lt;/span&gt;&lt;/figure&gt;
&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;스프링 웹소켓의 구성은 위와 같습니다.&lt;/p&gt;
&lt;ul style=&quot;list-style-type: disc;&quot; data-ke-list-type=&quot;disc&quot;&gt;
&lt;li&gt;Message Handler&lt;/li&gt;
&lt;li&gt;Simple Broker&lt;/li&gt;
&lt;li&gt;Broker Channel&lt;/li&gt;
&lt;/ul&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;이렇게 3개를 통해 웹소켓의 메세지들을 관리하고, 전달을 할 수 있습니다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;기본적으로 스프링 WebSocket은 ws-stomp 프로토콜 위에서 동작하며, 사용자가 /app으로 요청을 보냈을시엔, Message Handler를 통해 서버에서 추가 가공을 한후, BrokerChannel을 거쳐 다시 SimpleBroker에게 전달되는 형식입니다.&lt;br /&gt;또한, /topic으로 요청을 보냈을 시엔, 서버에서 추가 가공없이 해당 메세지를 구독자들에게 전달하는 방식입니다.&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;따라서 채팅기능을 스프링 WebSocket으로 구현한다 라고 가정을 하면, 사용자들은 스프링 웹소켓에 /topic/{구독정보}으로 구독을 합니다.&amp;nbsp; 구독을 하게 되면 해당 topic으로 메세지가 들어오게될시 SimpleBroker로부터 메세지를 실시간으로 받게 될 수 있습니다.&lt;br /&gt;또한 해당 /app 이나 /topic 으로 메세지를 보내 구독중인 다른 사용자들에게 메세지를 전달을 할 수도 있습니다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;이런식으로 채팅서버를 간단하게 구현이 가능합니다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;스프링 웹소켓의 내부엔 더 복잡한 기능이 있지만, 웹소켓 설명글이 아니니, 웹소켓의 동작과정은 여기서 생략하고 다음으로 넘어가겠습니다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;blockquote data-ke-style=&quot;style1&quot;&gt;&lt;b&gt;&lt;span style=&quot;font-family: 'Noto Serif KR';&quot;&gt;스프링 WebSocket으로만 채팅 구현시 문제점&lt;/span&gt;&lt;/b&gt;&lt;/blockquote&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;위에 설명한 그림처럼 채팅서버를 스프링 WebSocket으로 만 충분히 구현이 가능하지않나...? 라고 생각할 수 있습니다.&lt;br /&gt;물론, 서버가 1대만 존재할 경우에는 스프링 WebSocket으로만 구현하는것은 문제가 되지않습니다.&lt;br /&gt;하지만, 스프링 Web Socket에 대해 좀 더 깊숙히 생각을 해보면 몇가지의 문제점을 발견할 수 있습니다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;ul style=&quot;list-style-type: disc;&quot; data-ke-list-type=&quot;disc&quot;&gt;
&lt;li&gt;서버 재시작 시에 채팅 데이터들은...?&lt;/li&gt;
&lt;li&gt;서버가 Scale-Out되면 어떻게 되지...?&lt;/li&gt;
&lt;/ul&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;일단 첫번째로, 스프링 WebSocket은 따로 db에 저장되거나, 백업이 되는 기술이 아닙니다. 즉 memory의 세션방식을 통해 구현되는 기술이기 때문에 서버를 재시작 한다거나, 서버가 갑자기 먹통이 되는경우에 채팅 기록에 문제가 생긴다는 이슈가 있습니다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;두번쨰로, 스프링 WebSocket은 기본적으로 Spring 내부에 존재하는 기술입니다. 즉, Spring 서버가 여러대에 존재한다면, 스프링 WebSocket도 여러개가 존재하는것이죠.... 이렇게 된다면 여러 서버간 채팅이 공유되지않아&amp;nbsp; 다른 서버로 접속한 클라이언트는 채팅 기록을 받지 못하는 문제점이 생깁니다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;그렇다면.... 이 문제점들을 해결하기 위해 어떻게 해야할까요??&lt;br /&gt;일단 첫번째로는 DB 혹은 다른 저장소를 이용해 채팅방의 데이터가 계속 유지되도록 처리가 필요하다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;두번쨰로는, 여러 스프링 WebSocket들 끼리 구독할 수 있게 이들을 통합시킬 수 있는 시스템이 필요하다. 입니다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;이제 아래에서 이 문제점을 어떻게 해결을 할 수 있는지 알아보도록 하겠습니다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;blockquote data-ke-style=&quot;style1&quot;&gt;&lt;b&gt;&lt;span style=&quot;font-family: 'Noto Serif KR';&quot;&gt;스프링 WebSocket의 문제점을 해결할 솔루션&lt;/span&gt;&lt;/b&gt;&lt;/blockquote&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;위에서 기술했던 스프링 WebSocket의 문제점을 해결하기 위해선 다른 기술을 도입해야합니다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;우선 첫번째 문제였던 데이터 유지처리같은경우는 별도의 DB를 두어서 저장할 수 있게하고,&lt;br /&gt;두번째 문제였던 스프링 WebSocket들의 통합 문제는 Message Queue를 활용해 Pub/Sub 모델로 통합 시켜야합니다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;우선 DB는 SQL vs NOSQL로 나눌 수 있으나, 채팅의 경우 복잡한 데이터구조가 아니고, 많은 데이터의 이동시에 빠른속도가 중요하기에 Read/Write 시에 적은 오버헤드를 가진 DB가 적합합니다. 따라서, 이를 기반으로 적합한 DB를 고르자면 현재 몽고DB가 제일 적합한 DB인거 같습니다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p style=&quot;text-align: left;&quot; data-ke-size=&quot;size16&quot;&gt;이제 Message Queue를 선택할 차례입니다. 우선 많은 Message Queue가 있겠지만, 여기서는 제일 유명한 Kafka와 Redis만을 비교해 선택하도록 하겠습니다. 다음 표는 Kafka와 Redis를 비교한 표입니다.&lt;/p&gt;
&lt;table style=&quot;border-collapse: collapse; width: 69.6512%; height: 431px;&quot; border=&quot;1&quot; data-ke-align=&quot;alignCenter&quot; data-ke-style=&quot;style12&quot;&gt;
&lt;tbody&gt;
&lt;tr&gt;
&lt;td style=&quot;width: 33.3333%; text-align: center;&quot;&gt;&amp;nbsp;&lt;/td&gt;
&lt;td style=&quot;width: 33.3333%; text-align: center;&quot;&gt;Kafka&lt;/td&gt;
&lt;td style=&quot;width: 33.3333%; text-align: center;&quot;&gt;Redis&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td style=&quot;width: 33.3333%; text-align: center;&quot;&gt;브로커 종류&lt;/td&gt;
&lt;td style=&quot;width: 33.3333%; text-align: center;&quot;&gt;Event 기반 Broker&lt;/td&gt;
&lt;td style=&quot;width: 33.3333%; text-align: center;&quot;&gt;Message 브로커&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td style=&quot;width: 33.3333%; text-align: center;&quot;&gt;속도&lt;/td&gt;
&lt;td style=&quot;width: 33.3333%; text-align: center;&quot;&gt;비교적 느림&lt;/td&gt;
&lt;td style=&quot;width: 33.3333%; text-align: center;&quot;&gt;비교적 빠름&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td style=&quot;width: 33.3333%; text-align: center;&quot;&gt;구분 방식&lt;/td&gt;
&lt;td style=&quot;width: 33.3333%; text-align: center;&quot;&gt;topic으로 구분&lt;/td&gt;
&lt;td style=&quot;width: 33.3333%; text-align: center;&quot;&gt;Channel로 구분&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td style=&quot;width: 33.3333%; text-align: center;&quot;&gt;데이터 저장&lt;/td&gt;
&lt;td style=&quot;width: 33.3333%; text-align: center;&quot;&gt;기본적으로 데이터가 저장 이 됨&lt;/td&gt;
&lt;td style=&quot;width: 33.3333%; text-align: center;&quot;&gt;데이터 저장 x&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td style=&quot;width: 33.3333%; text-align: center;&quot;&gt;scale-out&lt;/td&gt;
&lt;td style=&quot;width: 33.3333%; text-align: center;&quot;&gt;가능&lt;/td&gt;
&lt;td style=&quot;width: 33.3333%; text-align: center;&quot;&gt;가능&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td style=&quot;width: 33.3333%; text-align: center;&quot;&gt;순서 보장&lt;/td&gt;
&lt;td style=&quot;width: 33.3333%; text-align: center;&quot;&gt;O&lt;/td&gt;
&lt;td style=&quot;width: 33.3333%; text-align: center;&quot;&gt;X&lt;/td&gt;
&lt;/tr&gt;
&lt;/tbody&gt;
&lt;/table&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;위처럼 Kafka와 Redis는 서로 다른 특징을 가졌습니다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;각각의 특징에 따라 장단점이 명확한데, 여기서 중요한것이 데이터 저장 부분과 순서보장 부분입니다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;Kafka는 Redis와 비교하여 무겁고 속도가 느리지만, 데이터가 저장이 되고, 순서를 보장한다는 측면에서 메세지 유실 및 순서 보장에 대해 확실히 제어를 할 수 있습니다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;다만, Redis같은 경우는 Kafka와 비교하여 가볍고 속도도 빠르지만, 기본적으로 데이터가 저장이 안되고, 순서를 보장하지 않는다는 측면에서 안정성이 떨어집니다.&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;따라서 개발하려는 서비스의 특징에 따라 MessageBroker를 선택하는 기준이 달라져야 합니다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;카톡, 라인처럼 사용자간의 메세지가 순서가 보장이 되어있어야하고, 혹시나 중간에 있을 데이터 유실을 제어하고 싶다면 Kafka를 쓰는게 맞고,&lt;br /&gt;디스코드, 줌 같은 화상채팅같이, 무엇보다 속도가 매우 중요하고 사용자간의 메세지가 순서가 보장이 되지않아도되고, 중간에 데이터 유실이 있어도, 그렇게 치명적이지 않을경우 Redis를 쓰는게 좋습니다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;blockquote data-ke-style=&quot;style1&quot;&gt;&lt;b&gt;실시간 채팅 아키텍처&lt;/b&gt;&lt;/blockquote&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;이제 위의 기술들이 적용된 전체적인 아키텍처를 보겠습니다.&lt;/p&gt;
&lt;p&gt;&lt;figure class=&quot;imageblock alignCenter&quot; data-ke-mobileStyle=&quot;widthOrigin&quot; data-origin-width=&quot;733&quot; data-origin-height=&quot;514&quot;&gt;&lt;span data-url=&quot;https://blog.kakaocdn.net/dn/tkTdX/btsGNBlvt2Q/7mJIdEhY1Bt2fIXisaIkY1/img.png&quot; data-phocus=&quot;https://blog.kakaocdn.net/dn/tkTdX/btsGNBlvt2Q/7mJIdEhY1Bt2fIXisaIkY1/img.png&quot;&gt;&lt;img src=&quot;https://blog.kakaocdn.net/dn/tkTdX/btsGNBlvt2Q/7mJIdEhY1Bt2fIXisaIkY1/img.png&quot; srcset=&quot;https://img1.daumcdn.net/thumb/R1280x0/?scode=mtistory2&amp;fname=https%3A%2F%2Fblog.kakaocdn.net%2Fdn%2FtkTdX%2FbtsGNBlvt2Q%2F7mJIdEhY1Bt2fIXisaIkY1%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;876&quot; height=&quot;614&quot; data-origin-width=&quot;733&quot; data-origin-height=&quot;514&quot;/&gt;&lt;/span&gt;&lt;/figure&gt;
&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;위처럼 여러개의 WebSocket 서버가 있다라고 할때, 각 서버들이 Pub/Sub 기능을 합니다. 이떄 중간에 Message Queue는 Pub으로 메세지가 들어왔다면 해당 메세지를 가공 및 DB에 저장 후, Sub에게 전달하는 기능을 합니다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;즉, 전체적인 과정은 다음과 같습니다.&lt;/p&gt;
&lt;ol style=&quot;list-style-type: decimal;&quot; data-ke-list-type=&quot;decimal&quot;&gt;
&lt;li&gt;사용자가 WebSocket서버로 채팅 전송&lt;/li&gt;
&lt;li&gt;WebSocket서버에서 MessageQueue로 Pub 전송&lt;/li&gt;
&lt;li&gt;MessageQueue에서 메세지 가공이 필요할 경우 메세지 정보를 가공&lt;/li&gt;
&lt;li&gt;DB에 메세지 저장&lt;/li&gt;
&lt;li&gt;MessageQueue에서 다른 WebSocket 서버로 Sub 전송&lt;/li&gt;
&lt;li&gt;해당 WebSocket서버에서 사용자에게 채팅 데이터 전송&lt;/li&gt;
&lt;/ol&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;전체적인 과정은 위와 같으며, 중간에 MessageQueue가 중간 매개체 역할을 하여 WebSocket 서버들을 실시간으로 관리할 수있는 구조입니다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;이때 중간에 MessageQueue가 있기때문에 당연히 WebSocket서버가 scale-out되어 더 많아지는 상황 이라고 해도 문제가 생기지 않습니다. 유연한 서버 구조를 가져갈 수 있습니다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;또한, 몽고DB를 연결함으로 실시간 채팅때 전달되는 채팅데이터들을 안정적으로 저장할 수 있고, 데이터 유실시에도 백업이나 롤백등의 기능을 활용하여 안정적으로 데이터 복구를 할 수 있습니다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;blockquote data-ke-style=&quot;style1&quot;&gt;&lt;b&gt;미니 사이드 프로젝트에서 발생한 문제점&lt;/b&gt;&lt;/blockquote&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;우선 결론부터 말하자면, 초기에 잘못된 생각으로 설계를 잘못한거 같습니다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;제가 개발한 프로젝트는 실시간 채팅을 통한 감정분석이었습니다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;이때, 저는 실시간 채팅의 경우엔 속도가 매우 중요하다 라는 생각을 하였고, 이 생각 때문에 Kafka보단 Redis가 더 좋은 선택지라는 생각을 하였습니다.&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;그래서, 스프링 WebSocket 서버와 Redis를 이용해 채팅을 구현했고, 테스트시에도 정상적으로 작동을 하였습니다.&lt;br /&gt;하지만, 프로젝트가 거의 끝나갈때쯤에 Redis의 경우 메세지들의 순서가 보장되지 않는다 라는 개념을 알게 되었고, 이를 기반으로 사용자가 많아졌을때, 네트워크 상황에 따라 메세지들의 순서가 서로 꼬일 수 있다 라는것을 알았습니다.&lt;br /&gt;이를 해결하기위해선, Redis 대신 Kafka를 도입했어야 하나, 프로젝트 마감기한이 얼마 남지않아 도입을 하진 못했습니다.ㅜㅜ&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;또한, 감정분석 부분에서도 문제가 생겼습니다. 이는 제가 생각했던 경우는 실시간 채팅이 되면서, 그 채팅데이터를 기반으로 감정분석이 바로 진행되고, 그 사람의 상위 감정 top3를 바로 보여주자! 였습니다.&lt;br /&gt;이 프로젝트에서는 허깅페이스의 go_emotion 이라는 감정분석 API를 활용했고, 채팅데이터가 들어오게되면 해당 채팅 데이터를 기반으로 API 호출을 통해 감정 분석 결과값을 가져와 프론트에 출력하도록 개발을 하였습니다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;물론 이 API가 느린 API는 아니여서 문제가 되지않을 거라 생각을 했지만, 테스트시에 API 서버의 상황에 따라 응답속도가 차이나는 현상을 발견했습니다. 따라서 이문제는 API호출을 동기처리하는게 아닌, 비동기 처리로 해결을 하거나,&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;DB에 따로 저장 후, 감정분석은 시간 텀을 둬 분석을 해야겠다 라는 개선점을 생각하였습니다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;blockquote data-ke-style=&quot;style1&quot;&gt;&lt;b&gt;결론&lt;/b&gt;&lt;br /&gt;&lt;br /&gt;&lt;/blockquote&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;비록 이번 프로젝트를 초기에 설정한 목표점까지 달성하진 못했지만, 많은 점을 배울 수 있었습니다.&lt;br /&gt;이번 프로젝트를 통해 기존 개발할때 느끼지 못했던 문제점들을 다양하게 느꼇던 소중한 기회였던것 같습니다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;특히 많이 배울 수 있었던 건 다음과 같습니다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;ul style=&quot;list-style-type: disc;&quot; data-ke-list-type=&quot;disc&quot;&gt;
&lt;li&gt;Message Queue 작동원리에 대한 이해&lt;/li&gt;
&lt;li&gt;실시간 데이터 처리시 유의점&lt;/li&gt;
&lt;li&gt;테스트시에 최대한 실사용 환경과 비슷하게 테스트 환경 구성&lt;/li&gt;
&lt;li&gt;API 활용시 비동기 처리&lt;/li&gt;
&lt;/ul&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;비록 이번 미니 프로젝트땐 완성도 있는 프로젝트 개발은 실패하였지만,&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;나중에 진짜로 이런 실시간 데이터 처리 기술이 들어간 프로젝트를 개발하게 된다면 이번에 느꼇던 점들을 설계때 최대한 고려하여 개발을 진행하고자합니다.&lt;/p&gt;</description>
      <category>자바/스프링</category>
      <category>Kafka</category>
      <category>Redis</category>
      <category>메세지큐</category>
      <category>스프링</category>
      <category>웹소켓</category>
      <category>채팅</category>
      <author>tioon</author>
      <guid isPermaLink="true">https://tioon.tistory.com/194</guid>
      <comments>https://tioon.tistory.com/194#entry194comment</comments>
      <pubDate>Mon, 22 Apr 2024 02:21:56 +0900</pubDate>
    </item>
    <item>
      <title>[Kafka] 카프카 Producer, Consumer</title>
      <link>https://tioon.tistory.com/193</link>
      <description>&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;아파치 카프카에서 Producer와 Consumer의 내부 동작원리 및 특징에 대해서 알아보도록 하겠습니다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;blockquote data-ke-size=&quot;size16&quot; data-ke-style=&quot;style1&quot;&gt;&lt;b&gt;&lt;span style=&quot;font-family: 'Noto Serif KR';&quot;&gt;아파치 카프카의 전체구조&lt;/span&gt;&lt;/b&gt;&lt;/blockquote&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;우선 카프카의 전체 구조를 다시한번 보고 가겠습니다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;카프카의 전체 구조는 다음과 같습니다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p&gt;&lt;figure class=&quot;imageblock alignCenter&quot; data-ke-mobileStyle=&quot;widthOrigin&quot; data-origin-width=&quot;1182&quot; data-origin-height=&quot;438&quot;&gt;&lt;span data-url=&quot;https://blog.kakaocdn.net/dn/dl3Qbk/btsGdpF3gfQ/1ZwO5EEvovCMWtCJUKOWn1/img.png&quot; data-phocus=&quot;https://blog.kakaocdn.net/dn/dl3Qbk/btsGdpF3gfQ/1ZwO5EEvovCMWtCJUKOWn1/img.png&quot;&gt;&lt;img src=&quot;https://blog.kakaocdn.net/dn/dl3Qbk/btsGdpF3gfQ/1ZwO5EEvovCMWtCJUKOWn1/img.png&quot; srcset=&quot;https://img1.daumcdn.net/thumb/R1280x0/?scode=mtistory2&amp;fname=https%3A%2F%2Fblog.kakaocdn.net%2Fdn%2Fdl3Qbk%2FbtsGdpF3gfQ%2F1ZwO5EEvovCMWtCJUKOWn1%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;1182&quot; height=&quot;438&quot; data-origin-width=&quot;1182&quot; data-origin-height=&quot;438&quot;/&gt;&lt;/span&gt;&lt;/figure&gt;
&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;Producer에서 Key-Value 형태로 메세지를 send하면, 브로커의 한 토픽을 기준으로 key로 파티션을 나누어 브로커에 저장이 됩니다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;(여기서 메세지 send를 할때, key값은 partition을 나누는 것이지, topic을 나누는것이 아닙니다!!)&lt;br /&gt;(key가 아니라 partition으로도 나눌 수 있습니다. 다만, Key를 활용하는 방식이 유연성이 높습니다.)&lt;br /&gt;&lt;br /&gt;해당 브로커에선 Queue 형태로 데이터들을 저장하며, 이를 기반으로 카프카가 동작을 하게 됩니다.&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;이제 해당 Consumer 그룹에 존재하는 consumer들이 데이터를 받아올 수&amp;nbsp; 있습니다.&lt;br /&gt;이때, (Consumer의 수 &amp;lt; 파티션의 수) 일시, 여러 파티션에서 데이터를 받는 consumer가 생깁니다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;이렇게 카프카의 전체 구조를 한번 쭉 봤고 이제 세부적으로 어떻게 작동되는지 알아보도록 하겠습니다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;blockquote data-ke-style=&quot;style1&quot;&gt;&lt;b&gt;Producer의 내부 구조&lt;/b&gt;&lt;/blockquote&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;카프카에서 Producer의 내부 구조는 다음과 같습니다.&lt;/p&gt;
&lt;p&gt;&lt;figure class=&quot;imageblock alignCenter&quot; data-ke-mobileStyle=&quot;widthOrigin&quot; data-origin-width=&quot;1722&quot; data-origin-height=&quot;600&quot;&gt;&lt;span data-url=&quot;https://blog.kakaocdn.net/dn/bkFIqa/btsGcO0oGK9/7UekrG6drRGp0RvzFWlyx0/img.png&quot; data-phocus=&quot;https://blog.kakaocdn.net/dn/bkFIqa/btsGcO0oGK9/7UekrG6drRGp0RvzFWlyx0/img.png&quot;&gt;&lt;img src=&quot;https://blog.kakaocdn.net/dn/bkFIqa/btsGcO0oGK9/7UekrG6drRGp0RvzFWlyx0/img.png&quot; srcset=&quot;https://img1.daumcdn.net/thumb/R1280x0/?scode=mtistory2&amp;fname=https%3A%2F%2Fblog.kakaocdn.net%2Fdn%2FbkFIqa%2FbtsGcO0oGK9%2F7UekrG6drRGp0RvzFWlyx0%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;1722&quot; height=&quot;600&quot; data-origin-width=&quot;1722&quot; data-origin-height=&quot;600&quot;/&gt;&lt;/span&gt;&lt;/figure&gt;
&lt;/p&gt;
&lt;ol style=&quot;list-style-type: decimal;&quot; data-ke-list-type=&quot;decimal&quot;&gt;
&lt;li&gt;Record 생성&lt;br /&gt;
&lt;ul style=&quot;list-style-type: disc;&quot; data-ke-list-type=&quot;disc&quot;&gt;
&lt;li&gt;Broker에 저장할 Record를 생성합니다.&lt;/li&gt;
&lt;li&gt;이때, topic과 value는 필수이며, Partition 및 Key는 선택사항입니다.&lt;/li&gt;
&lt;li&gt;topic = 토픽을 나누기 위한 값&lt;br /&gt;Partition = 파티션을 사용자가 직접 지정하는 값&lt;br /&gt;Key = 파티션을 Kafka가 지정할 수 있게 하는 값&lt;/li&gt;
&lt;/ul&gt;
&lt;/li&gt;
&lt;li&gt;Serializer와 Partitioner&lt;br /&gt;
&lt;ul style=&quot;list-style-type: disc;&quot; data-ke-list-type=&quot;disc&quot;&gt;
&lt;li&gt;Serializer를 통해 직렬화하여 Byte 배열로 만듭니다.&lt;/li&gt;
&lt;li&gt;Partitioner를 통해 해당 Record가 어느 Partition으로 갈지 정합니다.&lt;/li&gt;
&lt;li&gt;만약 여기서 partition을 지정했다면, 아무 동작없이 해당 partition으로 전달.&lt;br /&gt;key를 지정했다면, key를 기반으로 partition 선택하여 전달.&lt;br /&gt;아무것도 지정하지 않았다면, round robin 방식으로 동작.&lt;/li&gt;
&lt;/ul&gt;
&lt;/li&gt;
&lt;li&gt;Buffer&lt;br /&gt;
&lt;ul style=&quot;list-style-type: disc;&quot; data-ke-list-type=&quot;disc&quot;&gt;
&lt;li&gt;Buffer 내부에서 여러 Record들을 Batch로 모읍니다.&lt;/li&gt;
&lt;li&gt;이렇게 Record들을 한번에 모아 Broker에 한번에 전달합니다.&lt;/li&gt;
&lt;li&gt;Batch로 메세지 저장과 Sender로 메세지 보내는 과정은 독립적입니다.&lt;br /&gt;(즉, Batch에 저장되는 수와 상관없이 Send 할 수 있음)&lt;/li&gt;
&lt;/ul&gt;
&lt;/li&gt;
&lt;li&gt;Sender
&lt;ul style=&quot;list-style-type: disc;&quot; data-ke-list-type=&quot;disc&quot;&gt;
&lt;li&gt;Broker로 Batch들을 전송합니다.&lt;/li&gt;
&lt;li&gt;전송과정 실패 시, 재시도 동작 진행합니다.&lt;/li&gt;
&lt;li&gt;지정된 횟수만큼 재시도 실패시, 최종실패합니다.&lt;/li&gt;
&lt;/ul&gt;
&lt;/li&gt;
&lt;/ol&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;즉, 우리가 Record를 생성하고 Producer를 통해 Broker내부의 토픽에 저장을 할때 위와 같은 과정을 거쳐 저장이 됩니다.&lt;br /&gt;먼저, 해당 Record를 Serializer를 해 직렬화한 후, Partitioner를 통해 어느 파티션에 넣을지 정합니다.&lt;br /&gt;그 이후에 Buffer안의 Batch에 메세지들이 쌓이게되고, 이와 독립적인 Sender가 Batch안에 레코드 데이터가 있다면 꺼내서 Broker에 전달해 저장을 하게 됩니다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;blockquote data-ke-style=&quot;style1&quot;&gt;&lt;b&gt;Consumer의 특징&lt;/b&gt;&lt;/blockquote&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;카프카에서 Consumer의 구조는 다음과 같습니다.&lt;/p&gt;
&lt;p&gt;&lt;figure class=&quot;imageblock alignCenter&quot; data-ke-mobileStyle=&quot;widthOrigin&quot; data-origin-width=&quot;1215&quot; data-origin-height=&quot;634&quot;&gt;&lt;span data-url=&quot;https://blog.kakaocdn.net/dn/bka3mo/btsGd9iy7cX/4rUuGTSwSMj9rHJkC68dv1/img.png&quot; data-phocus=&quot;https://blog.kakaocdn.net/dn/bka3mo/btsGd9iy7cX/4rUuGTSwSMj9rHJkC68dv1/img.png&quot;&gt;&lt;img src=&quot;https://blog.kakaocdn.net/dn/bka3mo/btsGd9iy7cX/4rUuGTSwSMj9rHJkC68dv1/img.png&quot; srcset=&quot;https://img1.daumcdn.net/thumb/R1280x0/?scode=mtistory2&amp;fname=https%3A%2F%2Fblog.kakaocdn.net%2Fdn%2Fbka3mo%2FbtsGd9iy7cX%2F4rUuGTSwSMj9rHJkC68dv1%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;1215&quot; height=&quot;634&quot; data-origin-width=&quot;1215&quot; data-origin-height=&quot;634&quot;/&gt;&lt;/span&gt;&lt;/figure&gt;
&lt;/p&gt;
&lt;ul style=&quot;list-style-type: disc;&quot; data-ke-list-type=&quot;disc&quot;&gt;
&lt;li&gt;&lt;b&gt;Consumer Group&lt;/b&gt;
&lt;ul style=&quot;list-style-type: disc;&quot; data-ke-list-type=&quot;disc&quot;&gt;
&lt;li&gt;Consumer들의 묶음이라고 생각하면 되며, 반드시 한 topic의 파티션은 그 Consumer Group과 1:N 매칭이 되어야합니다.&lt;/li&gt;
&lt;li&gt;즉, 파티션의 개수 &amp;gt;= consumer의 개수&lt;/li&gt;
&lt;li&gt;예시로는 다음과 같습니다.&lt;br /&gt;Partition3 : Consumer2&amp;nbsp; -&amp;gt; Consumer 중에 하나는 2개의 파티션을 소비&lt;br /&gt;Partition3 : Consumer3&amp;nbsp; -&amp;gt; Consumer와 Partition 각각 1대1 매칭&lt;br /&gt;Partition3 : Consumer4&amp;nbsp; -&amp;gt; Consumer 1개가 아무것도 하지 않음&lt;/li&gt;
&lt;/ul&gt;
&lt;/li&gt;
&lt;/ul&gt;
&lt;p&gt;&lt;figure class=&quot;imageblock alignCenter&quot; data-ke-mobileStyle=&quot;widthOrigin&quot; data-origin-width=&quot;1215&quot; data-origin-height=&quot;634&quot;&gt;&lt;span data-url=&quot;https://blog.kakaocdn.net/dn/bka3mo/btsGd9iy7cX/4rUuGTSwSMj9rHJkC68dv1/img.png&quot; data-phocus=&quot;https://blog.kakaocdn.net/dn/bka3mo/btsGd9iy7cX/4rUuGTSwSMj9rHJkC68dv1/img.png&quot; data-alt=&quot;Partition3 : Consumer2 상황&quot;&gt;&lt;img src=&quot;https://blog.kakaocdn.net/dn/bka3mo/btsGd9iy7cX/4rUuGTSwSMj9rHJkC68dv1/img.png&quot; srcset=&quot;https://img1.daumcdn.net/thumb/R1280x0/?scode=mtistory2&amp;fname=https%3A%2F%2Fblog.kakaocdn.net%2Fdn%2Fbka3mo%2FbtsGd9iy7cX%2F4rUuGTSwSMj9rHJkC68dv1%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;501&quot; height=&quot;261&quot; data-origin-width=&quot;1215&quot; data-origin-height=&quot;634&quot;/&gt;&lt;/span&gt;&lt;figcaption&gt;Partition3 : Consumer2 상황&lt;/figcaption&gt;
&lt;/figure&gt;
&lt;figure class=&quot;imageblock alignCenter&quot; data-ke-mobileStyle=&quot;widthOrigin&quot; data-origin-width=&quot;517&quot; data-origin-height=&quot;265&quot;&gt;&lt;span data-url=&quot;https://blog.kakaocdn.net/dn/LmFNc/btsGc3XAePJ/PjW1dBSQ7btU8kbfkw9tVk/img.png&quot; data-phocus=&quot;https://blog.kakaocdn.net/dn/LmFNc/btsGc3XAePJ/PjW1dBSQ7btU8kbfkw9tVk/img.png&quot; data-alt=&quot;Partition3 : Consumer3 상황&quot;&gt;&lt;img src=&quot;https://blog.kakaocdn.net/dn/LmFNc/btsGc3XAePJ/PjW1dBSQ7btU8kbfkw9tVk/img.png&quot; srcset=&quot;https://img1.daumcdn.net/thumb/R1280x0/?scode=mtistory2&amp;fname=https%3A%2F%2Fblog.kakaocdn.net%2Fdn%2FLmFNc%2FbtsGc3XAePJ%2FPjW1dBSQ7btU8kbfkw9tVk%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;517&quot; height=&quot;265&quot; data-origin-width=&quot;517&quot; data-origin-height=&quot;265&quot;/&gt;&lt;/span&gt;&lt;figcaption&gt;Partition3 : Consumer3 상황&lt;/figcaption&gt;
&lt;/figure&gt;
&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p&gt;&lt;figure class=&quot;imageblock alignCenter&quot; data-ke-mobileStyle=&quot;widthOrigin&quot; data-origin-width=&quot;540&quot; data-origin-height=&quot;345&quot;&gt;&lt;span data-url=&quot;https://blog.kakaocdn.net/dn/zLj0f/btsGc3pK4hv/FzL62IUesftJz5Clnor8PK/img.png&quot; data-phocus=&quot;https://blog.kakaocdn.net/dn/zLj0f/btsGc3pK4hv/FzL62IUesftJz5Clnor8PK/img.png&quot; data-alt=&quot;Partition3 : Consumer4 상황&quot;&gt;&lt;img src=&quot;https://blog.kakaocdn.net/dn/zLj0f/btsGc3pK4hv/FzL62IUesftJz5Clnor8PK/img.png&quot; srcset=&quot;https://img1.daumcdn.net/thumb/R1280x0/?scode=mtistory2&amp;fname=https%3A%2F%2Fblog.kakaocdn.net%2Fdn%2FzLj0f%2FbtsGc3pK4hv%2FFzL62IUesftJz5Clnor8PK%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;540&quot; height=&quot;345&quot; data-origin-width=&quot;540&quot; data-origin-height=&quot;345&quot;/&gt;&lt;/span&gt;&lt;figcaption&gt;Partition3 : Consumer4 상황&lt;/figcaption&gt;
&lt;/figure&gt;
&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;ul style=&quot;list-style-type: disc;&quot; data-ke-list-type=&quot;disc&quot;&gt;
&lt;li&gt;&lt;b&gt;리밸런싱&lt;/b&gt;
&lt;ul style=&quot;list-style-type: disc;&quot; data-ke-list-type=&quot;disc&quot;&gt;
&lt;li&gt;특정 토픽을 구독하던 Consumer Group에 문제가 발생했을때, 해당 Group 안에서 Partition을 재분배하는 기술&lt;/li&gt;
&lt;li&gt;이 기술을 통해 Consumer Group에 문제가 생겼을때, 자동적으로 해결할 수 있습니다.&lt;/li&gt;
&lt;li&gt;리밸런싱이 일어나는 조건은 다음과 같습니다.&lt;br /&gt;Consumer Group안의 Consumer가 생성 or 삭제된 경우&lt;br /&gt;설정된 시간 내에 poll()요청을 다 끝내지 못한 경우&lt;br /&gt;설정된 시간내에 하트비트 메세지를 보내지 못한경우&lt;/li&gt;
&lt;/ul&gt;
&lt;/li&gt;
&lt;/ul&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;</description>
      <category>클라우드</category>
      <author>tioon</author>
      <guid isPermaLink="true">https://tioon.tistory.com/193</guid>
      <comments>https://tioon.tistory.com/193#entry193comment</comments>
      <pubDate>Sun, 31 Mar 2024 15:59:33 +0900</pubDate>
    </item>
    <item>
      <title>[Kafka] 아파치 카프카 CLI 명령어</title>
      <link>https://tioon.tistory.com/192</link>
      <description>&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;이번장에서는 아파치 카프카 CLI 명령어를 알아보도록 하겠습니다.&lt;br /&gt;&lt;br /&gt;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;직접 아파치 카프카를 도커 컨테이너로 실행하여 CLI 명령어를 실행해볼 것이며, 아파치 카프카를 실행시키기 위해&lt;br /&gt;도커와 아파치 카프카 오픈소스 깃 clone 과정이 필요합니다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;blockquote data-ke-style=&quot;style1&quot;&gt;&lt;span style=&quot;font-family: 'Noto Serif KR';&quot;&gt;&lt;b&gt;&amp;nbsp;도커 설치&lt;/b&gt;&lt;br /&gt;&lt;br /&gt;&lt;/span&gt;&lt;/blockquote&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;로컬 컴퓨터에 도커를 설치해 주시면됩니다..! 이 과정은 인터넷에 너무 많으니 패스하도록 하겠습니다.&lt;br /&gt;저는 윈도우용의 도커를 설치했습니다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;blockquote data-ke-style=&quot;style1&quot;&gt;&lt;b&gt;&lt;span style=&quot;font-family: 'Noto Serif KR';&quot;&gt;아파치 카프카 오픈소스 설치&lt;/span&gt;&lt;/b&gt;&lt;/blockquote&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;이제 도커를 설치했다면, 도커 컨테이너에 올릴 아파치 카프카 오픈소스가 필요합니다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;저는 conductor라는 오픈소스를 깃으로 clone을 해와서 가져올 것이며, 링크는 아래에 있습니다.&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/conduktor/kafka-stack-docker-compose&quot;&gt;conduktor/kafka-stack-docker-compose: docker compose files to create a fully working kafka stack (github.com)&lt;/a&gt;&lt;/p&gt;
&lt;figure id=&quot;og_1711261544259&quot; contenteditable=&quot;false&quot; data-ke-type=&quot;opengraph&quot; data-ke-align=&quot;alignCenter&quot; data-og-type=&quot;object&quot; data-og-title=&quot;GitHub - conduktor/kafka-stack-docker-compose: docker compose files to create a fully working kafka stack&quot; data-og-description=&quot;docker compose files to create a fully working kafka stack - conduktor/kafka-stack-docker-compose&quot; data-og-host=&quot;github.com&quot; data-og-source-url=&quot;https://github.com/conduktor/kafka-stack-docker-compose&quot; data-og-url=&quot;https://github.com/conduktor/kafka-stack-docker-compose&quot; data-og-image=&quot;https://scrap.kakaocdn.net/dn/d31qLT/hyVDys7Ht5/AbP3mmd5MrzPhbhqT3MSf0/img.png?width=1200&amp;amp;height=600&amp;amp;face=0_0_1200_600&quot;&gt;&lt;a href=&quot;https://github.com/conduktor/kafka-stack-docker-compose&quot; target=&quot;_blank&quot; rel=&quot;noopener&quot; data-source-url=&quot;https://github.com/conduktor/kafka-stack-docker-compose&quot;&gt;
&lt;div class=&quot;og-image&quot; style=&quot;background-image: url('https://scrap.kakaocdn.net/dn/d31qLT/hyVDys7Ht5/AbP3mmd5MrzPhbhqT3MSf0/img.png?width=1200&amp;amp;height=600&amp;amp;face=0_0_1200_600');&quot;&gt;&amp;nbsp;&lt;/div&gt;
&lt;div class=&quot;og-text&quot;&gt;
&lt;p class=&quot;og-title&quot; data-ke-size=&quot;size16&quot;&gt;GitHub - conduktor/kafka-stack-docker-compose: docker compose files to create a fully working kafka stack&lt;/p&gt;
&lt;p class=&quot;og-desc&quot; data-ke-size=&quot;size16&quot;&gt;docker compose files to create a fully working kafka stack - conduktor/kafka-stack-docker-compose&lt;/p&gt;
&lt;p class=&quot;og-host&quot; data-ke-size=&quot;size16&quot;&gt;github.com&lt;/p&gt;
&lt;/div&gt;
&lt;/a&gt;&lt;/figure&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;이 오픈소스를 통해 저희는 따로 클라우드 사용없이, 쉽게 로컬에서 아파치 카프카를 도커로 실행시킬 수 있습니다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;우선 git clone을 해오겠습니다.&lt;/p&gt;
&lt;blockquote data-ke-style=&quot;style3&quot;&gt;git clone &lt;a href=&quot;https://github.com/conduktor/kafka-stack-docker-compose.git&quot; target=&quot;_blank&quot; rel=&quot;noopener&amp;nbsp;noreferrer&quot;&gt;https://github.com/conduktor/kafka-stack-docker-compose.git&lt;/a&gt;&lt;/blockquote&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;이렇게 git clone을 해오면 다음과 같이 도커 컴포즈 파일들이 생성이 됩니다. (visual studio code 기준)&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p&gt;&lt;figure class=&quot;imageblock alignLeft&quot; data-ke-mobileStyle=&quot;widthOrigin&quot; data-origin-width=&quot;511&quot; data-origin-height=&quot;609&quot;&gt;&lt;span data-url=&quot;https://blog.kakaocdn.net/dn/EwtZ6/btsF4wQSP2K/oBKepumJDUWCfNKDQ0WDP1/img.png&quot; data-phocus=&quot;https://blog.kakaocdn.net/dn/EwtZ6/btsF4wQSP2K/oBKepumJDUWCfNKDQ0WDP1/img.png&quot;&gt;&lt;img src=&quot;https://blog.kakaocdn.net/dn/EwtZ6/btsF4wQSP2K/oBKepumJDUWCfNKDQ0WDP1/img.png&quot; srcset=&quot;https://img1.daumcdn.net/thumb/R1280x0/?scode=mtistory2&amp;fname=https%3A%2F%2Fblog.kakaocdn.net%2Fdn%2FEwtZ6%2FbtsF4wQSP2K%2FoBKepumJDUWCfNKDQ0WDP1%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;511&quot; height=&quot;609&quot; data-origin-width=&quot;511&quot; data-origin-height=&quot;609&quot;/&gt;&lt;/span&gt;&lt;/figure&gt;
&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;여기서 각 파일들은 도커 컴포즈 파일로, yml로 구성되어진 파일들입니다.&amp;nbsp;&lt;br /&gt;각 파일들을 간략하게나마 설명을 드리면 다음과 같습니다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;ul style=&quot;list-style-type: disc;&quot; data-ke-list-type=&quot;disc&quot;&gt;
&lt;li&gt;&lt;b&gt;conduktor.yml&lt;/b&gt;&lt;br /&gt;- Conduktor 플랫폼을 실행하기 위한 Docker Compose 구성 파일입니다.&lt;br /&gt;여기서 conduktor는 kafka 클러스터를 쉽게 관리하고 모니터링 하기위한 gui 도구입니다.&lt;br /&gt;따라서 이 파일을 통해 Conduktor를 실행하여 쉽게 Kafka 클러스터를 관리하고 모니터링할 수 있습니다.&lt;/li&gt;
&lt;li&gt;&lt;b&gt;full-stack.yml&lt;/b&gt;&lt;br /&gt;- Kafka와 관련된 모든 서비스를 실행하는 데 사용되는 Docker Compose 구성 파일입니다.&lt;br /&gt;이 파일에선 Kafka, Zookeeper, Kafka RestProxy등의 서비스가 포함되어 있습니다.&lt;br /&gt;&lt;br /&gt;&lt;/li&gt;
&lt;li&gt;&lt;b&gt;zk-multiple-kafka-multiple-schema-registry.yml&lt;/b&gt;&lt;br /&gt;- 여러 개의 Zookeeper 노드와 Kafka 브로커 및 Schema Registry를 실행하기 위한 Docker Compose 구성 파일입니다.&lt;br /&gt;이 파일을 통해 실배포 환경을 시뮬레이션 및 테스트 할때 활용할 수 있습니다.&lt;/li&gt;
&lt;li&gt;&lt;b&gt;zk-multiple-kafka-multiple.yml&lt;/b&gt;&lt;br /&gt;- 여러 개의 Zookeeper 노드와 Kafka 브로커를 실행하기 위한 Docker Compose 구성 파일입니다.&lt;br /&gt;이 파일을 통해 Kafka 클러스터의 고가용성 및 장애 허용성을 실험할 수 있습니다.&lt;/li&gt;
&lt;/ul&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;저희는 여기서 full-stack.yml 파일을 기반으로 Docker Compose up을 하여 도커 컨테이너에 올리도록 하겠습니다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;이제 docker compose에 올리는 명령어는 다음과 같습니다.&lt;/p&gt;
&lt;blockquote data-ke-style=&quot;style3&quot;&gt;docker compose -f full-stack.yml up&lt;/blockquote&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;이 명령어로 실행을 하고 나면 다음과 같은 log가 뜰겁니다.&amp;nbsp;&lt;br /&gt;아래처럼 started ~~~ 하면서 log가 뜨면 성공입니다.&lt;/p&gt;
&lt;p&gt;&lt;figure class=&quot;imageblock alignCenter&quot; data-ke-mobileStyle=&quot;widthOrigin&quot; data-origin-width=&quot;887&quot; data-origin-height=&quot;119&quot;&gt;&lt;span data-url=&quot;https://blog.kakaocdn.net/dn/bYMsLp/btsF3fa3lEO/XovBNNSk5WKZe6IYzYEgY1/img.png&quot; data-phocus=&quot;https://blog.kakaocdn.net/dn/bYMsLp/btsF3fa3lEO/XovBNNSk5WKZe6IYzYEgY1/img.png&quot;&gt;&lt;img src=&quot;https://blog.kakaocdn.net/dn/bYMsLp/btsF3fa3lEO/XovBNNSk5WKZe6IYzYEgY1/img.png&quot; srcset=&quot;https://img1.daumcdn.net/thumb/R1280x0/?scode=mtistory2&amp;fname=https%3A%2F%2Fblog.kakaocdn.net%2Fdn%2FbYMsLp%2FbtsF3fa3lEO%2FXovBNNSk5WKZe6IYzYEgY1%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;887&quot; height=&quot;119&quot; data-origin-width=&quot;887&quot; data-origin-height=&quot;119&quot;/&gt;&lt;/span&gt;&lt;/figure&gt;
&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;이제 이렇게 컨테이너를 실행 했으니 docker ps 명령어를 통해 docker container로 정보를 확인한 후, 해당 컨테이너 내부로 들어가 줍니다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p&gt;&lt;figure class=&quot;imageblock alignCenter&quot; data-ke-mobileStyle=&quot;widthOrigin&quot; data-origin-width=&quot;2390&quot; data-origin-height=&quot;430&quot;&gt;&lt;span data-url=&quot;https://blog.kakaocdn.net/dn/cAnkZj/btsF1uN4GpD/kzJAUg4PwuzpFsPRpJMWm0/img.png&quot; data-phocus=&quot;https://blog.kakaocdn.net/dn/cAnkZj/btsF1uN4GpD/kzJAUg4PwuzpFsPRpJMWm0/img.png&quot;&gt;&lt;img src=&quot;https://blog.kakaocdn.net/dn/cAnkZj/btsF1uN4GpD/kzJAUg4PwuzpFsPRpJMWm0/img.png&quot; srcset=&quot;https://img1.daumcdn.net/thumb/R1280x0/?scode=mtistory2&amp;fname=https%3A%2F%2Fblog.kakaocdn.net%2Fdn%2FcAnkZj%2FbtsF1uN4GpD%2FkzJAUg4PwuzpFsPRpJMWm0%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;2390&quot; height=&quot;430&quot; data-origin-width=&quot;2390&quot; data-origin-height=&quot;430&quot;/&gt;&lt;/span&gt;&lt;/figure&gt;
&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;docker compose를 정상적으로 up 하셨고, 컨테이너들이 제대로 실행이 완료가 됐다면, 위 사진처럼 컨테이너가 8개가 나오는게 정상입니다.(만약 제대로 안나오신다면 좀 더 기다리시거나, Docker Desktop으로 상태를 보시는 걸 추천드립니다.)&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;이렇게 8개의 컨테이너중 kafka1에 들어가야하기 때문에 아래 명령어를 통해 직접 shell로 들어갑니다.&lt;/p&gt;
&lt;blockquote data-ke-style=&quot;style3&quot;&gt;docker exec-it [컨테이너 이름] bash&lt;/blockquote&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;이렇게 들어온 후, kafka와 관련된 데이터는 bin/파일에 kafka라는 이름의 파일들로 존재하므로, bin파일로 갑니다.&lt;/p&gt;
&lt;p&gt;&lt;figure class=&quot;imageblock alignLeft&quot; data-ke-mobileStyle=&quot;widthOrigin&quot; data-origin-width=&quot;260&quot; data-origin-height=&quot;57&quot;&gt;&lt;span data-url=&quot;https://blog.kakaocdn.net/dn/PUTJO/btsF1rX6i91/nyy9PqKQIPkAaQpEA8O3F0/img.png&quot; data-phocus=&quot;https://blog.kakaocdn.net/dn/PUTJO/btsF1rX6i91/nyy9PqKQIPkAaQpEA8O3F0/img.png&quot;&gt;&lt;img src=&quot;https://blog.kakaocdn.net/dn/PUTJO/btsF1rX6i91/nyy9PqKQIPkAaQpEA8O3F0/img.png&quot; srcset=&quot;https://img1.daumcdn.net/thumb/R1280x0/?scode=mtistory2&amp;fname=https%3A%2F%2Fblog.kakaocdn.net%2Fdn%2FPUTJO%2FbtsF1rX6i91%2Fnyy9PqKQIPkAaQpEA8O3F0%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;260&quot; height=&quot;57&quot; data-origin-width=&quot;260&quot; data-origin-height=&quot;57&quot;/&gt;&lt;/span&gt;&lt;/figure&gt;
&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;이제 여기서 카프카 CLI 명령어를 활용해서 카프카에서 토픽 생성 및 데이터 삽입, 추출을 할 수 있습니다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;blockquote data-ke-style=&quot;style1&quot;&gt;&lt;b&gt;&lt;span style=&quot;font-family: 'Noto Serif KR';&quot;&gt;kafka CLI 명령어&lt;/span&gt;&lt;/b&gt;&lt;/blockquote&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;CLI를 활용해서 Kafka를 제어할 수 있는데, 명령어의 종류는 크게 다음과 같습니다.&lt;/p&gt;
&lt;ul style=&quot;list-style-type: disc;&quot; data-ke-list-type=&quot;disc&quot;&gt;
&lt;li&gt;&lt;b&gt;kafka topic 관리&lt;/b&gt;&lt;/li&gt;
&lt;li&gt;&lt;b&gt;kafka producer 메세지 삽입&lt;/b&gt;&lt;/li&gt;
&lt;li&gt;&lt;b&gt;kafka consumer 메세지 추출&lt;/b&gt;&lt;/li&gt;
&lt;/ul&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;위의 3가지의 명령으로 카프카를 직접 관리를 할 수 있습니다. 이제 명령어들을 하나하나 알아보도록 하겠습니다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;blockquote data-ke-style=&quot;style1&quot;&gt;&lt;b&gt;Kafka Topic 관리 명령어&lt;/b&gt;&lt;/blockquote&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;우선 카프카 topic을 관리할 수 있는 명령어는 kafka-topics 명령어입니다. 이 명렁어를 기반으로 현재 토픽 목록을 조회할 수 있고, 토픽 생성 및 삭제, 정보 수정까지 할 수있습니다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;각각의 명령어는 다음과 같습니다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;우선 먼저 카프카 브로커의 주소를 입력해야하는데, 카프카 브로커 주소는 다음과 같습니다.&lt;/p&gt;
&lt;blockquote data-ke-style=&quot;style3&quot;&gt;&lt;b&gt;[카프카 컨테이너 이름]:[카프카 port 번호]&lt;/b&gt;&lt;/blockquote&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;저 같은 경우는 kafka1:19092였습니다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;ul style=&quot;list-style-type: disc;&quot; data-ke-list-type=&quot;disc&quot;&gt;
&lt;li&gt;&lt;b&gt;카프카 토픽 목록 조회&lt;/b&gt;&lt;/li&gt;
&lt;/ul&gt;
&lt;blockquote data-ke-style=&quot;style3&quot;&gt;kafka-topics --bootstrap-server [카프카 브로커 주소] --list&lt;/blockquote&gt;
&lt;p&gt;&lt;figure class=&quot;imageblock alignLeft&quot; data-ke-mobileStyle=&quot;widthOrigin&quot; data-origin-width=&quot;758&quot; data-origin-height=&quot;212&quot;&gt;&lt;span data-url=&quot;https://blog.kakaocdn.net/dn/dRRerP/btsF1JEepFn/6tFdn6AB0pQA4tWt776b70/img.png&quot; data-phocus=&quot;https://blog.kakaocdn.net/dn/dRRerP/btsF1JEepFn/6tFdn6AB0pQA4tWt776b70/img.png&quot;&gt;&lt;img src=&quot;https://blog.kakaocdn.net/dn/dRRerP/btsF1JEepFn/6tFdn6AB0pQA4tWt776b70/img.png&quot; srcset=&quot;https://img1.daumcdn.net/thumb/R1280x0/?scode=mtistory2&amp;fname=https%3A%2F%2Fblog.kakaocdn.net%2Fdn%2FdRRerP%2FbtsF1JEepFn%2F6tFdn6AB0pQA4tWt776b70%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;758&quot; height=&quot;212&quot; data-origin-width=&quot;758&quot; data-origin-height=&quot;212&quot;/&gt;&lt;/span&gt;&lt;/figure&gt;
&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;해당 명령어를 통해 현재 생성되어있는 카프카 토픽들의 정보 리스트를 볼 수 있습니다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;ul style=&quot;list-style-type: disc;&quot; data-ke-list-type=&quot;disc&quot;&gt;
&lt;li&gt;&lt;b&gt;카프카 토픽 상세 조회&lt;/b&gt;&lt;/li&gt;
&lt;/ul&gt;
&lt;blockquote data-ke-style=&quot;style3&quot;&gt;kafka-topics --bootstrap-server [카프카 브로커 주소] --describe --topic [토픽 이름]&lt;/blockquote&gt;
&lt;p&gt;&lt;figure class=&quot;imageblock alignCenter&quot; data-ke-mobileStyle=&quot;widthOrigin&quot; data-origin-width=&quot;1320&quot; data-origin-height=&quot;85&quot;&gt;&lt;span data-url=&quot;https://blog.kakaocdn.net/dn/b31twh/btsF13JceE1/qfskpMevBBZRTRouCjngi1/img.png&quot; data-phocus=&quot;https://blog.kakaocdn.net/dn/b31twh/btsF13JceE1/qfskpMevBBZRTRouCjngi1/img.png&quot;&gt;&lt;img src=&quot;https://blog.kakaocdn.net/dn/b31twh/btsF13JceE1/qfskpMevBBZRTRouCjngi1/img.png&quot; srcset=&quot;https://img1.daumcdn.net/thumb/R1280x0/?scode=mtistory2&amp;fname=https%3A%2F%2Fblog.kakaocdn.net%2Fdn%2Fb31twh%2FbtsF13JceE1%2FqfskpMevBBZRTRouCjngi1%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;1320&quot; height=&quot;85&quot; data-origin-width=&quot;1320&quot; data-origin-height=&quot;85&quot;/&gt;&lt;/span&gt;&lt;/figure&gt;
&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;해당 명령어를 통해 해당 카프카 토픽의 상세 정보를 볼 수 있습니다.&lt;br /&gt;해당 토픽의 partition 개수, Leader 개수, Replicas 등의 정보를 얻을 수 있습니다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;ul style=&quot;list-style-type: disc;&quot; data-ke-list-type=&quot;disc&quot;&gt;
&lt;li&gt;&lt;b&gt;카프카 토픽 생성&lt;/b&gt;&lt;/li&gt;
&lt;/ul&gt;
&lt;blockquote data-ke-style=&quot;style3&quot;&gt;kafka-topics --bootstrap-server [카프카 브로커 주소] --create --topic [토픽 이름] --partitions [파티션 개수] --replication-factor [replication-factor 개수] --config retention.ms=[토픽 메세지 보관 시간]&lt;/blockquote&gt;
&lt;p&gt;&lt;figure class=&quot;imageblock alignCenter&quot; data-ke-mobileStyle=&quot;widthOrigin&quot; data-origin-width=&quot;1105&quot; data-origin-height=&quot;79&quot;&gt;&lt;span data-url=&quot;https://blog.kakaocdn.net/dn/2UDG6/btsF2LH2af0/hQ9HkAapkYSciuwNQdN3EK/img.png&quot; data-phocus=&quot;https://blog.kakaocdn.net/dn/2UDG6/btsF2LH2af0/hQ9HkAapkYSciuwNQdN3EK/img.png&quot;&gt;&lt;img src=&quot;https://blog.kakaocdn.net/dn/2UDG6/btsF2LH2af0/hQ9HkAapkYSciuwNQdN3EK/img.png&quot; srcset=&quot;https://img1.daumcdn.net/thumb/R1280x0/?scode=mtistory2&amp;fname=https%3A%2F%2Fblog.kakaocdn.net%2Fdn%2F2UDG6%2FbtsF2LH2af0%2FhQ9HkAapkYSciuwNQdN3EK%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;1105&quot; height=&quot;79&quot; data-origin-width=&quot;1105&quot; data-origin-height=&quot;79&quot;/&gt;&lt;/span&gt;&lt;/figure&gt;
&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;해당 명령어를 통해 카프카에 토픽을 새로 생성할 수 있습니다.&lt;br /&gt;생성시 옵션에는 파티션 개수, 복제 개수, 토픽 메세지 보관시간을 옵션으로 줄 수 있습니다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;ul style=&quot;list-style-type: disc;&quot; data-ke-list-type=&quot;disc&quot;&gt;
&lt;li&gt;&lt;b&gt;카프카 토픽 정보 수정&lt;/b&gt;&lt;/li&gt;
&lt;/ul&gt;
&lt;blockquote data-ke-style=&quot;style3&quot;&gt;kafka-configs --bootstrap-server [카프카 브로커 주소] --entity-type topics --entity-name [토픽 이름] --alter --add-config retention.ms=[토픽 메세지 보관 시간]&lt;/blockquote&gt;
&lt;p&gt;&lt;figure class=&quot;imageblock alignCenter&quot; data-ke-mobileStyle=&quot;widthOrigin&quot; data-origin-width=&quot;1100&quot; data-origin-height=&quot;92&quot;&gt;&lt;span data-url=&quot;https://blog.kakaocdn.net/dn/b2Xbar/btsF3n1a95t/w4ypomltvlOo0cP9W7mq41/img.png&quot; data-phocus=&quot;https://blog.kakaocdn.net/dn/b2Xbar/btsF3n1a95t/w4ypomltvlOo0cP9W7mq41/img.png&quot;&gt;&lt;img src=&quot;https://blog.kakaocdn.net/dn/b2Xbar/btsF3n1a95t/w4ypomltvlOo0cP9W7mq41/img.png&quot; srcset=&quot;https://img1.daumcdn.net/thumb/R1280x0/?scode=mtistory2&amp;fname=https%3A%2F%2Fblog.kakaocdn.net%2Fdn%2Fb2Xbar%2FbtsF3n1a95t%2Fw4ypomltvlOo0cP9W7mq41%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;1100&quot; height=&quot;92&quot; data-origin-width=&quot;1100&quot; data-origin-height=&quot;92&quot;/&gt;&lt;/span&gt;&lt;/figure&gt;
&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;해당 명령어를 통해 이미 존재하는 카프카 토픽정보를 수정할 수 있습니다.&lt;br /&gt;수정할 수 있는 정보는 메세지 보관기간, 메세지 보관용량, 메세지 삭제정책 등이 있으나, 파티션 개수 및 복제 개수등은 수정할 수 없습니다.&lt;br /&gt;해당 정보를 수정하려면, 토픽 재생성을 해야합니다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;ul style=&quot;list-style-type: disc;&quot; data-ke-list-type=&quot;disc&quot;&gt;
&lt;li&gt;카프카 토픽 삭제&lt;/li&gt;
&lt;/ul&gt;
&lt;blockquote data-ke-style=&quot;style3&quot;&gt;kafka-topics --bootstrap-server [카프카 브로커 주소] --delete --topic [토픽 이름]&lt;/blockquote&gt;
&lt;p&gt;&lt;figure class=&quot;imageblock alignCenter&quot; data-ke-mobileStyle=&quot;widthOrigin&quot; data-origin-width=&quot;942&quot; data-origin-height=&quot;65&quot;&gt;&lt;span data-url=&quot;https://blog.kakaocdn.net/dn/cJiCTY/btsF1CSOvUc/CUGlJBqnuEKcC8cFxKsbmK/img.png&quot; data-phocus=&quot;https://blog.kakaocdn.net/dn/cJiCTY/btsF1CSOvUc/CUGlJBqnuEKcC8cFxKsbmK/img.png&quot;&gt;&lt;img src=&quot;https://blog.kakaocdn.net/dn/cJiCTY/btsF1CSOvUc/CUGlJBqnuEKcC8cFxKsbmK/img.png&quot; srcset=&quot;https://img1.daumcdn.net/thumb/R1280x0/?scode=mtistory2&amp;fname=https%3A%2F%2Fblog.kakaocdn.net%2Fdn%2FcJiCTY%2FbtsF1CSOvUc%2FCUGlJBqnuEKcC8cFxKsbmK%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;942&quot; height=&quot;65&quot; data-origin-width=&quot;942&quot; data-origin-height=&quot;65&quot;/&gt;&lt;/span&gt;&lt;/figure&gt;
&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;해당 명령어를 통해 카프카 토픽을 삭제할 수 있습니다.&lt;br /&gt;이미 존재하는 카프카 토픽만을 삭제할 수 있습니다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;ul style=&quot;list-style-type: disc;&quot; data-ke-list-type=&quot;disc&quot;&gt;
&lt;li&gt;&lt;b&gt;카프카 Producer 메세지 삽입&lt;/b&gt;&lt;/li&gt;
&lt;/ul&gt;
&lt;blockquote data-ke-style=&quot;style3&quot;&gt;kafka-console-producer --bootstrap-server [카프카 브로커 주소] --topic [토픽 이름] --property &quot;parse.key=true&quot; --property &quot;key.separator=:&quot;&lt;/blockquote&gt;
&lt;p&gt;&lt;figure class=&quot;imageblock alignCenter&quot; data-ke-mobileStyle=&quot;widthOrigin&quot; data-origin-width=&quot;1107&quot; data-origin-height=&quot;279&quot;&gt;&lt;span data-url=&quot;https://blog.kakaocdn.net/dn/WAbe2/btsF1b83dki/1PWGE7zbmlAZLgKNtbfpW1/img.png&quot; data-phocus=&quot;https://blog.kakaocdn.net/dn/WAbe2/btsF1b83dki/1PWGE7zbmlAZLgKNtbfpW1/img.png&quot;&gt;&lt;img src=&quot;https://blog.kakaocdn.net/dn/WAbe2/btsF1b83dki/1PWGE7zbmlAZLgKNtbfpW1/img.png&quot; srcset=&quot;https://img1.daumcdn.net/thumb/R1280x0/?scode=mtistory2&amp;fname=https%3A%2F%2Fblog.kakaocdn.net%2Fdn%2FWAbe2%2FbtsF1b83dki%2F1PWGE7zbmlAZLgKNtbfpW1%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;1107&quot; height=&quot;279&quot; data-origin-width=&quot;1107&quot; data-origin-height=&quot;279&quot;/&gt;&lt;/span&gt;&lt;/figure&gt;
&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;해당 명령어를 통해 존재하는 producer를 활용하여 해당 토픽에 메세지를 넣을 수 있습니다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;'property' 옵션을 통해 추가 옵션을 지정할 수 있으며, 주로 쓰이는 옵션은 parse.key와 key.separator 입니다.&lt;br /&gt;parse.key는 메세지 파싱 유무이며, key.separator는 구분자 설정을 할 수 있습니다.&lt;/p&gt;
&lt;p&gt;&lt;figure class=&quot;imageblock alignCenter&quot; data-ke-mobileStyle=&quot;widthOrigin&quot; data-origin-width=&quot;579&quot; data-origin-height=&quot;361&quot;&gt;&lt;span data-url=&quot;https://blog.kakaocdn.net/dn/blmGz6/btsF2ROYHzT/8ukCzd6pXmJk1r5gsjkH6K/img.png&quot; data-phocus=&quot;https://blog.kakaocdn.net/dn/blmGz6/btsF2ROYHzT/8ukCzd6pXmJk1r5gsjkH6K/img.png&quot;&gt;&lt;img src=&quot;https://blog.kakaocdn.net/dn/blmGz6/btsF2ROYHzT/8ukCzd6pXmJk1r5gsjkH6K/img.png&quot; srcset=&quot;https://img1.daumcdn.net/thumb/R1280x0/?scode=mtistory2&amp;fname=https%3A%2F%2Fblog.kakaocdn.net%2Fdn%2FblmGz6%2FbtsF2ROYHzT%2F8ukCzd6pXmJk1r5gsjkH6K%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;579&quot; height=&quot;361&quot; data-origin-width=&quot;579&quot; data-origin-height=&quot;361&quot;/&gt;&lt;/span&gt;&lt;/figure&gt;
&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;명령어를 반복적으로 입력하여 메세지를 넣을 수 있습니다.&amp;nbsp;&lt;br /&gt;만약 해당 topic에 존재하는 파티션의 개수가 1개가 아닌경우, 라운드 로빈으로 각 파티션에 나뉘어서 들어갑니다.&lt;br /&gt;마지막으론, 빈 문자열을 입력함으로, 메세지 삽입을 중단할 수 있습니다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;ul style=&quot;list-style-type: disc;&quot; data-ke-list-type=&quot;disc&quot;&gt;
&lt;li&gt;&lt;b&gt;카프카 Consumer 메세지 추출&lt;/b&gt;&lt;/li&gt;
&lt;/ul&gt;
&lt;blockquote data-ke-style=&quot;style3&quot;&gt;kafka-console-consumer --bootstrap-server [카프카 브로커 주소] --topic [토픽 이름] --property print.key=true --property key.separator=&quot;-&quot; --group [컨슈머 그룹이름] --from-beginning&lt;/blockquote&gt;
&lt;p&gt;&lt;figure class=&quot;imageblock alignCenter&quot; data-ke-mobileStyle=&quot;widthOrigin&quot; data-origin-width=&quot;1114&quot; data-origin-height=&quot;332&quot;&gt;&lt;span data-url=&quot;https://blog.kakaocdn.net/dn/Da6jc/btsF3cFqqMI/tZaIn90s9FQgdOwgqb74z0/img.png&quot; data-phocus=&quot;https://blog.kakaocdn.net/dn/Da6jc/btsF3cFqqMI/tZaIn90s9FQgdOwgqb74z0/img.png&quot;&gt;&lt;img src=&quot;https://blog.kakaocdn.net/dn/Da6jc/btsF3cFqqMI/tZaIn90s9FQgdOwgqb74z0/img.png&quot; srcset=&quot;https://img1.daumcdn.net/thumb/R1280x0/?scode=mtistory2&amp;fname=https%3A%2F%2Fblog.kakaocdn.net%2Fdn%2FDa6jc%2FbtsF3cFqqMI%2FtZaIn90s9FQgdOwgqb74z0%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;1114&quot; height=&quot;332&quot; data-origin-width=&quot;1114&quot; data-origin-height=&quot;332&quot;/&gt;&lt;/span&gt;&lt;/figure&gt;
&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;해당 명령어를 통해 존재하는 consumer를 활용하여 해당 토픽에 존재하는 메세지를 추출하여 받아올 수 있습니다.&lt;br /&gt;'property' 옵션을 통해 추가 옵션을 지정할 수 있으며, 주로 쓰이는 옵션은 print.key와 key.separator입니다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;추가적으로, --group 옵션을 활용하여 특정 컨슈머 그룹에서 메세지를 가져올 수 있으며&lt;br /&gt;--from-beginning을 활용하여 이전에 이미 읽었던 메세지들도 다 읽어올 수 있습니다.&lt;/p&gt;
&lt;p&gt;&lt;figure class=&quot;imageblock alignCenter&quot; data-ke-mobileStyle=&quot;widthOrigin&quot; data-origin-width=&quot;622&quot; data-origin-height=&quot;292&quot;&gt;&lt;span data-url=&quot;https://blog.kakaocdn.net/dn/cGi2OU/btsF1z2Sufa/yg0I6pKNKAK7HOAC1hnbbk/img.png&quot; data-phocus=&quot;https://blog.kakaocdn.net/dn/cGi2OU/btsF1z2Sufa/yg0I6pKNKAK7HOAC1hnbbk/img.png&quot;&gt;&lt;img src=&quot;https://blog.kakaocdn.net/dn/cGi2OU/btsF1z2Sufa/yg0I6pKNKAK7HOAC1hnbbk/img.png&quot; srcset=&quot;https://img1.daumcdn.net/thumb/R1280x0/?scode=mtistory2&amp;fname=https%3A%2F%2Fblog.kakaocdn.net%2Fdn%2FcGi2OU%2FbtsF1z2Sufa%2Fyg0I6pKNKAK7HOAC1hnbbk%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;622&quot; height=&quot;292&quot; data-origin-width=&quot;622&quot; data-origin-height=&quot;292&quot;/&gt;&lt;/span&gt;&lt;/figure&gt;
&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;ul style=&quot;list-style-type: disc;&quot; data-ke-list-type=&quot;disc&quot;&gt;
&lt;li&gt;&lt;b&gt;카프카 consumer 그룹 보기&lt;/b&gt;&lt;/li&gt;
&lt;/ul&gt;
&lt;blockquote data-ke-style=&quot;style3&quot;&gt;kafka-consumer-groups --bootstrap-server [카프카 브로커 주소] --list&lt;/blockquote&gt;
&lt;p&gt;&lt;figure class=&quot;imageblock alignLeft&quot; data-ke-mobileStyle=&quot;widthOrigin&quot; data-origin-width=&quot;841&quot; data-origin-height=&quot;100&quot;&gt;&lt;span data-url=&quot;https://blog.kakaocdn.net/dn/eRAFQN/btsF2wqPGj1/nUpgc7PYsDyVq44RQDi0TK/img.png&quot; data-phocus=&quot;https://blog.kakaocdn.net/dn/eRAFQN/btsF2wqPGj1/nUpgc7PYsDyVq44RQDi0TK/img.png&quot;&gt;&lt;img src=&quot;https://blog.kakaocdn.net/dn/eRAFQN/btsF2wqPGj1/nUpgc7PYsDyVq44RQDi0TK/img.png&quot; srcset=&quot;https://img1.daumcdn.net/thumb/R1280x0/?scode=mtistory2&amp;fname=https%3A%2F%2Fblog.kakaocdn.net%2Fdn%2FeRAFQN%2FbtsF2wqPGj1%2FnUpgc7PYsDyVq44RQDi0TK%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;841&quot; height=&quot;100&quot; data-origin-width=&quot;841&quot; data-origin-height=&quot;100&quot;/&gt;&lt;/span&gt;&lt;/figure&gt;
&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;해당 명령어를 통해 현재 존재하는 모든 consumer 그룹 리스트를 확인 할 수 있습니다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;ul style=&quot;list-style-type: disc;&quot; data-ke-list-type=&quot;disc&quot;&gt;
&lt;li&gt;&lt;b&gt;카프카 consumer 그룹 상세 보기&lt;/b&gt;&lt;/li&gt;
&lt;/ul&gt;
&lt;blockquote data-ke-style=&quot;style3&quot;&gt;kafka-consumer-groups --bootstrap-server [카프카 브로커 주소] --group [consumer 그룹이름] --describe&lt;/blockquote&gt;
&lt;p&gt;&lt;figure class=&quot;imageblock alignCenter&quot; data-ke-mobileStyle=&quot;widthOrigin&quot; data-origin-width=&quot;1354&quot; data-origin-height=&quot;155&quot;&gt;&lt;span data-url=&quot;https://blog.kakaocdn.net/dn/bo4SSR/btsF10FHi0U/L2pl3q5rivdsKj0qSrUwk0/img.png&quot; data-phocus=&quot;https://blog.kakaocdn.net/dn/bo4SSR/btsF10FHi0U/L2pl3q5rivdsKj0qSrUwk0/img.png&quot;&gt;&lt;img src=&quot;https://blog.kakaocdn.net/dn/bo4SSR/btsF10FHi0U/L2pl3q5rivdsKj0qSrUwk0/img.png&quot; srcset=&quot;https://img1.daumcdn.net/thumb/R1280x0/?scode=mtistory2&amp;fname=https%3A%2F%2Fblog.kakaocdn.net%2Fdn%2Fbo4SSR%2FbtsF10FHi0U%2FL2pl3q5rivdsKj0qSrUwk0%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;1354&quot; height=&quot;155&quot; data-origin-width=&quot;1354&quot; data-origin-height=&quot;155&quot;/&gt;&lt;/span&gt;&lt;/figure&gt;
&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;해당 명령어를 통해 해당 consumer 그룹의 상세정보를 확인할 수 있습니다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;ul style=&quot;list-style-type: disc;&quot; data-ke-list-type=&quot;disc&quot;&gt;
&lt;li&gt;&lt;b&gt;카프카 verifiable-producer 데이터 삽입&lt;/b&gt;&lt;/li&gt;
&lt;/ul&gt;
&lt;blockquote data-ke-style=&quot;style3&quot;&gt;kafka-verifiable-producer --bootstrap-server [카프카 브로커 주소] --max-messages [메세지 개수] --topic verify-test&lt;/blockquote&gt;
&lt;p&gt;&lt;figure class=&quot;imageblock alignCenter&quot; data-ke-mobileStyle=&quot;widthOrigin&quot; data-origin-width=&quot;1349&quot; data-origin-height=&quot;347&quot;&gt;&lt;span data-url=&quot;https://blog.kakaocdn.net/dn/1qi3t/btsF1SVpDcr/pzT52Rd87Z4EPAzh738cHK/img.png&quot; data-phocus=&quot;https://blog.kakaocdn.net/dn/1qi3t/btsF1SVpDcr/pzT52Rd87Z4EPAzh738cHK/img.png&quot;&gt;&lt;img src=&quot;https://blog.kakaocdn.net/dn/1qi3t/btsF1SVpDcr/pzT52Rd87Z4EPAzh738cHK/img.png&quot; srcset=&quot;https://img1.daumcdn.net/thumb/R1280x0/?scode=mtistory2&amp;fname=https%3A%2F%2Fblog.kakaocdn.net%2Fdn%2F1qi3t%2FbtsF1SVpDcr%2FpzT52Rd87Z4EPAzh738cHK%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;1349&quot; height=&quot;347&quot; data-origin-width=&quot;1349&quot; data-origin-height=&quot;347&quot;/&gt;&lt;/span&gt;&lt;/figure&gt;
&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;해당 명령어를 통해 verifiable-producer로 테스트 메세지를 삽입할 수 있습니다.&lt;br /&gt;이는 보통 카프카가 제대로 실행되고 있는지 테스트 용도로 활용이 되며, 임의로 메세지 개수를 정해 메세지가 성적으로 전송되는지 확인할 수 있습니다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;ul style=&quot;list-style-type: disc;&quot; data-ke-list-type=&quot;disc&quot;&gt;
&lt;li&gt;&lt;b&gt;카프카 verifiable-consumer 데이터 추출&lt;/b&gt;&lt;/li&gt;
&lt;/ul&gt;
&lt;blockquote data-ke-style=&quot;style3&quot;&gt;kafka-verifiable-consumer --bootstrap-server [카프카 브로커 주소] --topic verify-test --group-id [그룹 이름]&lt;/blockquote&gt;
&lt;p&gt;&lt;figure class=&quot;imageblock alignCenter&quot; data-ke-mobileStyle=&quot;widthOrigin&quot; data-origin-width=&quot;1122&quot; data-origin-height=&quot;208&quot;&gt;&lt;span data-url=&quot;https://blog.kakaocdn.net/dn/bfRVkY/btsF3f9ZcBI/1xlzbrvioyn2wecDeHanMk/img.png&quot; data-phocus=&quot;https://blog.kakaocdn.net/dn/bfRVkY/btsF3f9ZcBI/1xlzbrvioyn2wecDeHanMk/img.png&quot;&gt;&lt;img src=&quot;https://blog.kakaocdn.net/dn/bfRVkY/btsF3f9ZcBI/1xlzbrvioyn2wecDeHanMk/img.png&quot; srcset=&quot;https://img1.daumcdn.net/thumb/R1280x0/?scode=mtistory2&amp;fname=https%3A%2F%2Fblog.kakaocdn.net%2Fdn%2FbfRVkY%2FbtsF3f9ZcBI%2F1xlzbrvioyn2wecDeHanMk%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;1122&quot; height=&quot;208&quot; data-origin-width=&quot;1122&quot; data-origin-height=&quot;208&quot;/&gt;&lt;/span&gt;&lt;/figure&gt;
&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;해당 명령어를 통해 verifiable-consumer로 테스트 메세지들을 추출할 수 있습니다.&lt;br /&gt;이는 producer와 마찬가지로 카프카가 제대로 실행되고 있는지 테스트 용도로 활용이 됩니다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;group-id 옵션이 필수적이므로, consumer 그룹 id를 필수적으로 입력하셔야 합니다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;</description>
      <category>클라우드</category>
      <category>conduktor</category>
      <category>메세지큐</category>
      <category>빅데이터</category>
      <category>오픈소스</category>
      <category>카프카</category>
      <category>클라우드</category>
      <author>tioon</author>
      <guid isPermaLink="true">https://tioon.tistory.com/192</guid>
      <comments>https://tioon.tistory.com/192#entry192comment</comments>
      <pubDate>Sun, 24 Mar 2024 20:59:01 +0900</pubDate>
    </item>
    <item>
      <title>[Kafka] 아파치 카프카란?</title>
      <link>https://tioon.tistory.com/191</link>
      <description>&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;이번 학기에 아파치 카프카에 대해서 스터디를 하게 되었는데 공부하면서 한번 정리를 해보려합니다!&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;혹시나 틀린 정보가 있다면 말씀해주세요!&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;현재 백엔드에서 빅데이터 처리시에 가장 많이 쓰이고 있는 플랫폼은 카프카인데요.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;시작하기에 앞서, 우선 먼저 카프카를 한마디로 소개를 먼저 하자면 다음과 같습니다.&lt;/p&gt;
&lt;blockquote data-ke-style=&quot;style2&quot;&gt;&lt;span style=&quot;color: #000000;&quot;&gt;&lt;b&gt;실시간 스트리밍 대용량, 대규모 데이터 처리에 특화된 프레임워크&lt;/b&gt;&lt;/span&gt;&lt;/blockquote&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;이를 먼저 기억해주시고,&amp;nbsp; 아래 글을 읽어주세요!&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;blockquote data-ke-style=&quot;style1&quot;&gt;&lt;b&gt;&lt;span style=&quot;font-family: 'Noto Serif KR';&quot;&gt;아파치 카프카(Kafka)의 시작&lt;/span&gt;&lt;/b&gt;&lt;/blockquote&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;과거, 기존의 백엔드 서버에서 데이터를 수집하기위해 직접 데이터를 카테고리화 해서 저장을 하였습니다.&lt;br /&gt;즉, 직접 1대1 매핑을 통해 데이터와 앱을 연결하여 카테고리처럼 저장하는 방식이였습니다.&lt;br /&gt;초기에는 이러한 방식이 운영이 어렵지 않았고, 아키텍처도 복잡하지 않아서 운영이 힘들진 않았습니다.&lt;br /&gt;&lt;br /&gt;하지만, 점점 서버는 발전을 하게 되었고, 그에 따라 서버의 아키텍처도 점점 복잡하게 발전을 했습니다.&lt;br /&gt;이렇게 아키텍처가 점점 복잡해지면서 직접 1대1 매핑하는 방식은 다양한 불편함과 버그가 발생하는 이슈가 있었습니다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;과거의 1대1 매핑 방식의 아키텍처는 다음과 같습니다.&lt;/p&gt;
&lt;p&gt;&lt;figure class=&quot;imageblock alignCenter&quot; data-ke-mobileStyle=&quot;widthOrigin&quot; data-origin-width=&quot;1280&quot; data-origin-height=&quot;468&quot;&gt;&lt;span data-url=&quot;https://blog.kakaocdn.net/dn/SLMAG/btsFQVSOpnY/1rDNsl5OZANe7RUlxG9Fn1/img.png&quot; data-phocus=&quot;https://blog.kakaocdn.net/dn/SLMAG/btsFQVSOpnY/1rDNsl5OZANe7RUlxG9Fn1/img.png&quot;&gt;&lt;img src=&quot;https://blog.kakaocdn.net/dn/SLMAG/btsFQVSOpnY/1rDNsl5OZANe7RUlxG9Fn1/img.png&quot; srcset=&quot;https://img1.daumcdn.net/thumb/R1280x0/?scode=mtistory2&amp;fname=https%3A%2F%2Fblog.kakaocdn.net%2Fdn%2FSLMAG%2FbtsFQVSOpnY%2F1rDNsl5OZANe7RUlxG9Fn1%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;1280&quot; height=&quot;468&quot; data-origin-width=&quot;1280&quot; data-origin-height=&quot;468&quot;/&gt;&lt;/span&gt;&lt;/figure&gt;
&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;지금 얼핏봐도 굉장히 많이 복잡해 보이는 아키텍처입니다.&lt;br /&gt;1대1 매핑 방식이다보니, 하나의 서비스에서 여러개의 연결고리가 생기고, 이에 따라 버전관리의 어려움도 생겼으며,&lt;br /&gt;하나의 서비스에서 버그 발생시, 연결된 모든 곳에서 오류가 발생한다는 어려움도 생겼습니다.&lt;br /&gt;&lt;br /&gt;이를 어떻게 해결하면 좋을까요?&lt;br /&gt;바로 메세지큐 방식으로 중간에 하나의 '중앙 집중 장치'를 둬서 한곳에서 실시간 관리를 하는 것입니다.&lt;br /&gt;이 중앙 집중 장치가 위에서 말한 카프카 입니다.&lt;br /&gt;이를 통해 각각 1대1 매핑하여 데이터 처리를 하는 것이 아닌, 한 곳에 모아 간편하게 처리를 할 수 있게 되었습니다.&lt;br /&gt;카프카가 적용된 아키텍처는 다음과 같습니다.&lt;/p&gt;
&lt;p&gt;&lt;figure class=&quot;imageblock alignCenter&quot; data-ke-mobileStyle=&quot;widthOrigin&quot; data-origin-width=&quot;1280&quot; data-origin-height=&quot;560&quot;&gt;&lt;span data-url=&quot;https://blog.kakaocdn.net/dn/bJVEWG/btsFVtz3pCu/7iDHNmhNa6fyFkxybIhtT1/img.png&quot; data-phocus=&quot;https://blog.kakaocdn.net/dn/bJVEWG/btsFVtz3pCu/7iDHNmhNa6fyFkxybIhtT1/img.png&quot;&gt;&lt;img src=&quot;https://blog.kakaocdn.net/dn/bJVEWG/btsFVtz3pCu/7iDHNmhNa6fyFkxybIhtT1/img.png&quot; srcset=&quot;https://img1.daumcdn.net/thumb/R1280x0/?scode=mtistory2&amp;fname=https%3A%2F%2Fblog.kakaocdn.net%2Fdn%2FbJVEWG%2FbtsFVtz3pCu%2F7iDHNmhNa6fyFkxybIhtT1%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;1280&quot; height=&quot;560&quot; data-origin-width=&quot;1280&quot; data-origin-height=&quot;560&quot;/&gt;&lt;/span&gt;&lt;/figure&gt;
&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;위의 사진을 보면, 모든 서비스는 카프카와 연결되어 있고, 이를 통해 아키텍처가 매우 간편해 진것을 확인할 수 있습니다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;즉, 카프카를 중앙에 배치함으로써, 서비스간의 의존도를 낮추며, 아키텍처를 매우 간편하게 구축할 수 있습니다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;blockquote data-ke-style=&quot;style1&quot;&gt;&lt;b&gt;&lt;span style=&quot;font-family: 'Noto Serif KR';&quot;&gt;카프카의 특징&lt;/span&gt;&lt;/b&gt;&lt;/blockquote&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;카프카의 특징은 다음과 같습니다.&lt;/p&gt;
&lt;ul style=&quot;list-style-type: disc;&quot; data-ke-list-type=&quot;disc&quot;&gt;
&lt;li&gt;&lt;b&gt;높은 데이터 처리량&lt;/b&gt;
&lt;ul style=&quot;list-style-type: disc;&quot; data-ke-list-type=&quot;disc&quot;&gt;
&lt;li&gt;브로커가 데이터를 송수신 할때, 한번에 묶어 전송하기 때문에 네트워크 오버헤드가 적어 속도가 빠름.&lt;/li&gt;
&lt;li&gt;파티션 개수만큼 컨슈머의 개수를 늘려서 병렬처리로 데이터 처리가 빠름.&lt;/li&gt;
&lt;/ul&gt;
&lt;/li&gt;
&lt;li&gt;&lt;b&gt;높은 확장성&lt;/b&gt;
&lt;ul style=&quot;list-style-type: disc;&quot; data-ke-list-type=&quot;disc&quot;&gt;
&lt;li&gt;클러스터 및 브로커가 존재하여, 쉽게 스케일 아웃(Scale-Out)이 가능함.&lt;/li&gt;
&lt;li&gt;스케일 아웃 및 스케일 인 과정에서 무중단 운영을 지원하므로 안정적으로 확장이 가능함.&lt;/li&gt;
&lt;/ul&gt;
&lt;/li&gt;
&lt;li&gt;&lt;b&gt;데이터 영속성&lt;/b&gt;
&lt;ul style=&quot;list-style-type: disc;&quot; data-ke-list-type=&quot;disc&quot;&gt;
&lt;li&gt;다른 메세지 큐와 다르게 메모리에 저장하는게 아닌, 디스크 파일 시스템에 저장함.&lt;/li&gt;
&lt;li&gt;디스크 I/O 성능 향상을 위해 페이지 캐시 기능을 활용함.&lt;/li&gt;
&lt;/ul&gt;
&lt;/li&gt;
&lt;li&gt;&lt;b&gt;고가용성&lt;/b&gt;
&lt;ul style=&quot;list-style-type: disc;&quot; data-ke-list-type=&quot;disc&quot;&gt;
&lt;li&gt;클러스터 복제를 활용해 데이터 유실시에 복구 가능함.&lt;/li&gt;
&lt;li&gt;최소 3개 이상의 클러스터를 구성하여 장애 발생 시에도 안정적으로 데이터 처리 가능.&lt;/li&gt;
&lt;/ul&gt;
&lt;/li&gt;
&lt;/ul&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;카프카의 특징은 위와 같습니다. 이러한 특징을 통해 대규모의 데이터를 처리를 빠르고 안정적이게 할 수 있어,&amp;nbsp;&lt;br /&gt;요새 서버 아키텍처에서 대부분 카프카를 활용하고 있습니다.&lt;br /&gt;&lt;br /&gt;위의 특징을 보면 브로커, 클러스터 등의 카프카 개념들이 나오는데요. &lt;br /&gt;이제 카프카의 기본 구조 및 개념을 한번 알아보겠습니다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;blockquote data-ke-style=&quot;style1&quot;&gt;&lt;b&gt;&lt;span style=&quot;font-family: 'Noto Serif KR';&quot;&gt;카프카의 구조&lt;/span&gt;&lt;/b&gt;&lt;/blockquote&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p&gt;&lt;figure class=&quot;imageblock alignCenter&quot; data-ke-mobileStyle=&quot;widthOrigin&quot; data-origin-width=&quot;674&quot; data-origin-height=&quot;485&quot;&gt;&lt;span data-url=&quot;https://blog.kakaocdn.net/dn/bXUfbr/btsFUBrIQcP/XLfCCwpgc1z8DoYmYokshk/img.png&quot; data-phocus=&quot;https://blog.kakaocdn.net/dn/bXUfbr/btsFUBrIQcP/XLfCCwpgc1z8DoYmYokshk/img.png&quot;&gt;&lt;img src=&quot;https://blog.kakaocdn.net/dn/bXUfbr/btsFUBrIQcP/XLfCCwpgc1z8DoYmYokshk/img.png&quot; srcset=&quot;https://img1.daumcdn.net/thumb/R1280x0/?scode=mtistory2&amp;fname=https%3A%2F%2Fblog.kakaocdn.net%2Fdn%2FbXUfbr%2FbtsFUBrIQcP%2FXLfCCwpgc1z8DoYmYokshk%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;674&quot; height=&quot;485&quot; data-origin-width=&quot;674&quot; data-origin-height=&quot;485&quot;/&gt;&lt;/span&gt;&lt;/figure&gt;
&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;카프카의 구조는 위의 사진과 같습니다. 카프카는 다양한 구성장치들을 포함하고 있으며,&lt;br /&gt;해당 구성장치들을 활용해 대용량 데이터를 처리하고 있습니다.&lt;br /&gt;이제 각 구성장치들을 자세히 알아보도록 하겠습니다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;ul style=&quot;list-style-type: disc;&quot; data-ke-list-type=&quot;disc&quot;&gt;
&lt;li&gt;&lt;b&gt;주키퍼(Zookeeper)&lt;br /&gt;&lt;/b&gt;
&lt;ul style=&quot;list-style-type: disc;&quot; data-ke-list-type=&quot;disc&quot;&gt;
&lt;li&gt;여러 브로커들의 상태를 모니터링하고 조정하는데 사용되는 분산 코디네이션임&lt;/li&gt;
&lt;li&gt;여러 서버를 클러스터로 구성하며, 해당 정보들을 지노드(znode)에 key-value 형태로 저장함&lt;/li&gt;
&lt;li&gt;주키퍼는 한개의 Leader와 나머지의 Follower로 구성되어 데이터 동기화를 진행함.&lt;br /&gt;&lt;br /&gt;(다만, 현재 카프카 측에서는 주키퍼 없이 관리할 수 있는 방향으로 개발이 진행되고 있다고 합니다...!)&lt;br /&gt;&lt;br /&gt;&lt;/li&gt;
&lt;/ul&gt;
&lt;/li&gt;
&lt;li&gt;&lt;b&gt;카프카 브로커(Broker)&lt;/b&gt;&lt;br /&gt;
&lt;ul style=&quot;list-style-type: disc;&quot; data-ke-list-type=&quot;disc&quot;&gt;
&lt;li&gt;여러대의 브로커와 주키퍼가 하나의 클러스터를 구축함.&lt;/li&gt;
&lt;li&gt;Producer로부터 메세지를 받아들이고, Consumer에게 메세지를 전달하는 역할을 함.&lt;/li&gt;
&lt;li&gt;1개의 대표 브로커가 Controller가 되어, 나머지 브로커들의 상태를 확인하고 관리함.&lt;/li&gt;
&lt;li&gt;데이터 복제(Replication)을 통해 각 파티션들을 가용성있게 관리할 수 있음.&lt;br /&gt;(이 경우엔 각 브로커가 동일한 파티션을 가지고 있는 경우임)&lt;/li&gt;
&lt;/ul&gt;
&lt;/li&gt;
&lt;li&gt;&lt;b&gt;토픽(Topic)&lt;/b&gt;
&lt;ul style=&quot;list-style-type: disc;&quot; data-ke-list-type=&quot;disc&quot;&gt;
&lt;li&gt;카프카에서 카테고리처럼 데이터를 구분하기위한 단위&lt;/li&gt;
&lt;li&gt;한 토픽에 여러개의 파티션으로 구성되어 속도를 늘림.&lt;/li&gt;
&lt;li&gt;브로커가 데이터를 Push 및 Pull 하는 논리적 단위 (실제 데이터는 파티션에 저장됨)&lt;/li&gt;
&lt;/ul&gt;
&lt;/li&gt;
&lt;li&gt;&lt;b&gt;파티션(Partition)&lt;/b&gt;&lt;br /&gt;
&lt;ul style=&quot;list-style-type: disc;&quot; data-ke-list-type=&quot;disc&quot;&gt;
&lt;li&gt;실제 데이터가 저장되는 공간임&lt;/li&gt;
&lt;li&gt;각 파티션은 독립적으로 관리되어, 서로 독립적으로 데이터를 가지고 있음.&lt;/li&gt;
&lt;li&gt;한개의 Leader와 나머지의 Follower로 구성하여, Follower들은 Leader의 데이터를 복제 한 후, 읽기 작업을 병렬로 처리가능함&lt;/li&gt;
&lt;li&gt;파티션을 늘려 병렬처리를 함으로써, 처리 속도를 높일 수 있음.&lt;/li&gt;
&lt;/ul&gt;
&lt;/li&gt;
&lt;li&gt;&lt;b&gt;프로듀서(Producer)&lt;/b&gt;
&lt;ul style=&quot;list-style-type: disc;&quot; data-ke-list-type=&quot;disc&quot;&gt;
&lt;li&gt;카프카 브로커로 데이터를 전송하는 역할을 수행함.&lt;/li&gt;
&lt;/ul&gt;
&lt;/li&gt;
&lt;li&gt;&lt;b&gt;컨슈머(Consumer)&lt;/b&gt;
&lt;ul style=&quot;list-style-type: disc;&quot; data-ke-list-type=&quot;disc&quot;&gt;
&lt;li&gt;카프카 브로커로부터 데이터를 전달받는 역할을 수행함.&lt;/li&gt;
&lt;li&gt;컨슈머가 데이터를 받는다 해도, 해당 데이터가 없어지지않음.&lt;/li&gt;
&lt;/ul&gt;
&lt;/li&gt;
&lt;li&gt;&lt;b&gt;세그먼트(Segment)&lt;/b&gt;
&lt;ul style=&quot;list-style-type: disc;&quot; data-ke-list-type=&quot;disc&quot;&gt;
&lt;li&gt;실제 디스크에 저장되는 단위 (기본 값은 1GB)&lt;/li&gt;
&lt;li&gt;Key-Value형태로 저장되며, 이진데이터 형태로 저장됨.&lt;/li&gt;
&lt;li&gt;일정 크기까지 모은 후, 한번에 Disk I/O를 진행함.&lt;/li&gt;
&lt;/ul&gt;
&lt;/li&gt;
&lt;/ul&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;이렇게 카프카는 각 구성요소들이 함께 실행되며 이루어진 프로그램입니다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;이렇게 토픽을 기준으로 파티션을 구분하여, 실제 데이터를 카테고리화 하여 저장한다 라는 것을 알았습니다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;그럼 이제 좀 더 자세하게 카프카만의 특징에 대해서 알아보겠습니다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;blockquote data-ke-size=&quot;size16&quot; data-ke-style=&quot;style1&quot;&gt;&lt;b&gt;&lt;span style=&quot;font-family: 'Noto Serif KR';&quot;&gt;브로커의 데이터 복제&lt;/span&gt;&lt;/b&gt;&lt;/blockquote&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;위에서 카프카는 여러개의 브로커를 가질 수 있다고 하였습니다. 기본적으론, 이 브로커들끼리는 서로 다른 토픽과 파티션을 가지고 있어 독립적인 데이터를 가지고 있습니다.&lt;br /&gt;하지만 카프카에선 데이터의 가용성을 보장하기위해 Replication Factor(복제 팩터)라는 개념을 활용합니다.&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;즉, 여러 개의 브로커가 존재할때, Leader-Follower 모델을 활용하여, 리더 브로커의 내용을 팔로워 브로커에게 복제하여&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;카프카를 안전히 사용할 수 있게 됩니다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p&gt;&lt;figure class=&quot;imageblock alignCenter&quot; data-ke-mobileStyle=&quot;widthOrigin&quot; data-origin-width=&quot;761&quot; data-origin-height=&quot;363&quot;&gt;&lt;span data-url=&quot;https://blog.kakaocdn.net/dn/c2bZ8N/btsFWLt4CTv/kCqS0OzvnSn8bdNaktczXK/img.png&quot; data-phocus=&quot;https://blog.kakaocdn.net/dn/c2bZ8N/btsFWLt4CTv/kCqS0OzvnSn8bdNaktczXK/img.png&quot;&gt;&lt;img src=&quot;https://blog.kakaocdn.net/dn/c2bZ8N/btsFWLt4CTv/kCqS0OzvnSn8bdNaktczXK/img.png&quot; srcset=&quot;https://img1.daumcdn.net/thumb/R1280x0/?scode=mtistory2&amp;fname=https%3A%2F%2Fblog.kakaocdn.net%2Fdn%2Fc2bZ8N%2FbtsFWLt4CTv%2FkCqS0OzvnSn8bdNaktczXK%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;761&quot; height=&quot;363&quot; data-origin-width=&quot;761&quot; data-origin-height=&quot;363&quot;/&gt;&lt;/span&gt;&lt;/figure&gt;
&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;br /&gt;이때, 리더 브로커안에 있는 파티션은 Read-Write 둘다 가능하지만, 팔로워 브로커의 파티션은 Read만 할 수 있다 라는 특징이 있습니다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;이렇게 브로커가 3대일때, Replication Factor = 3 이라면, 브로커들은 각각 다른 데이터를 가지고 있는게 아니라, 동일한 데이터를 가지고 있습니다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;하지만 리더 브로커만 Write를 진행할 수 있고, 나머지 팔로워들은 Read만하여 읽기 작업때에만 병렬적으로 진행 할 수 있기 때문에, 처리 속도가 느려진다 라는 단점이 있습니다.&lt;br /&gt;&lt;br /&gt;따라서, Replication Factor는 현재 카프카 내의 총 브로커 수와 현재 프로젝트의 요구사항에 맞춰 선택해야 합니다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;blockquote data-ke-size=&quot;size16&quot; data-ke-style=&quot;style1&quot;&gt;&lt;b&gt;카프카의 동작 과정&lt;/b&gt;&lt;/blockquote&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;지금까지 배운 카프카의 기본개념을 배웠습니다.&lt;br /&gt;그럼 이제 간단히 카프카의 동작과정에 대해 순차적으로 살펴 보겠습니다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p&gt;&lt;figure class=&quot;imageblock alignCenter&quot; data-ke-mobileStyle=&quot;widthOrigin&quot; data-origin-width=&quot;833&quot; data-origin-height=&quot;571&quot;&gt;&lt;span data-url=&quot;https://blog.kakaocdn.net/dn/coXPg5/btsFScmT8y9/TAXlchopkekIq0xEXouRPk/img.png&quot; data-phocus=&quot;https://blog.kakaocdn.net/dn/coXPg5/btsFScmT8y9/TAXlchopkekIq0xEXouRPk/img.png&quot;&gt;&lt;img src=&quot;https://blog.kakaocdn.net/dn/coXPg5/btsFScmT8y9/TAXlchopkekIq0xEXouRPk/img.png&quot; srcset=&quot;https://img1.daumcdn.net/thumb/R1280x0/?scode=mtistory2&amp;fname=https%3A%2F%2Fblog.kakaocdn.net%2Fdn%2FcoXPg5%2FbtsFScmT8y9%2FTAXlchopkekIq0xEXouRPk%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;833&quot; height=&quot;571&quot; data-origin-width=&quot;833&quot; data-origin-height=&quot;571&quot;/&gt;&lt;/span&gt;&lt;/figure&gt;
&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;ol style=&quot;list-style-type: decimal;&quot; data-ke-list-type=&quot;decimal&quot;&gt;
&lt;li&gt;&lt;b&gt;카프카 클러스터 실행&lt;br /&gt;&lt;/b&gt;- 주키퍼를 통해 카프카 클러스터를 실행 시킵니다.&lt;br /&gt;이때, 각 클러스터의 각 노드(서버)들은 브로커 역할을 합니다.&lt;b&gt;&lt;br /&gt;&lt;/b&gt;&lt;b&gt;&lt;/b&gt;&lt;/li&gt;
&lt;li&gt;&lt;b&gt;토픽 생성&lt;br /&gt;&lt;/b&gt;- 프로듀셔와 컨슈머가서로 공유할 토픽을 생성합니다.&lt;br /&gt;이 토픽은 CLI를 사용해 생성할 수 있습니다.&lt;b&gt;&lt;br /&gt;&lt;/b&gt;&lt;/li&gt;
&lt;li&gt;&lt;b&gt;Producer 데이터 Push&lt;br /&gt;&lt;/b&gt;- 프로듀서가 브로커에게 특정 토픽에 데이터를 Push 합니다.&lt;br /&gt;이때, 해당 토픽 안의 Partition 중 하나에 Push되며, 보통 라운드로빈 알고리즘으로 구현됩니다.&lt;b&gt;&lt;br /&gt;&lt;br /&gt;&lt;/b&gt;&lt;/li&gt;
&lt;li&gt;&lt;b&gt;Consumer 데이터 Pop&lt;br /&gt;&lt;/b&gt;- 컨슈머가 특정 토픽에 데이터를 Pop 합니다.&lt;br /&gt;이때, 자신이 가져온 데이터의 OffSet을 기록하여 중복 소비를 방지합니다.&lt;br /&gt;또한, 데이터를 Pop한다고 Partition의 해당 데이터가 사라지는 것이 아니라, 그대로 남습니다.&lt;/li&gt;
&lt;/ol&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;blockquote data-ke-style=&quot;style1&quot;&gt;&lt;b&gt;&lt;span style=&quot;font-family: 'Noto Serif KR';&quot;&gt;토픽 이름 제약 조건&lt;/span&gt;&lt;/b&gt;&lt;/blockquote&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;토픽은 개발자가 직접 생성하여 여러개의 토픽으로 관리를 할 수 있습니다.&lt;br /&gt;토픽을 기준으로 실제 데이터가 구분되기 때문에 토픽의 이름을 잘 정하는것이 제일 중요합니다.&lt;br /&gt;즉, 카프카에는 토픽 이름 제약 조건이 있는데, 다음과 같습니다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;ul style=&quot;list-style-type: disc;&quot; data-ke-list-type=&quot;disc&quot;&gt;
&lt;li&gt;아무것도 안들어간 토픽 이름은 지원하지 않음&lt;/li&gt;
&lt;li&gt;마침표(.)와 언더바(_)가 동시에 들어가면 안됨.&lt;/li&gt;
&lt;li&gt;토픽이름은 대소문자, 숫자, 마침표(.), 언더바(_), 하이픈(-)만 조합가능함.&lt;/li&gt;
&lt;li&gt;실수를 줄이기 위해 케밥케이스(kebab-case)나 스네이크 케이스(snake_case)를 추천함.&lt;/li&gt;
&lt;/ul&gt;</description>
      <category>클라우드</category>
      <category>메세지큐</category>
      <category>빅데이터</category>
      <category>아파치</category>
      <category>카프카</category>
      <category>클라우드</category>
      <author>tioon</author>
      <guid isPermaLink="true">https://tioon.tistory.com/191</guid>
      <comments>https://tioon.tistory.com/191#entry191comment</comments>
      <pubDate>Tue, 19 Mar 2024 03:10:22 +0900</pubDate>
    </item>
    <item>
      <title>SSH, SCP 명령어 정리</title>
      <link>https://tioon.tistory.com/190</link>
      <description>&lt;p data-ke-size=&quot;size16&quot;&gt;이번 블로그에서는&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;터미널에서 SSH와 SCP를 직접 어떻게 사용하는지, 또한, 깃허브 액션에선 SSH와 SCP를 어떻게 사용하는지에 대해서 알아 보겠습니다.&lt;br /&gt;&lt;br /&gt;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;사실 저희가 서버를 배포를 한다고 하면, SSH와 SCP 둘 다 사용할 일이 많잖아요??&lt;br /&gt;하지만 그럴때마다 까먹어서 구글링 하느라 시간 다쓴 적 있지않나요?&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;br /&gt;저도 서버 배포를 하면서 매번 SSH, SCP를 사용하는데 그때마다 까먹어서 자료 찾는게 귀찮아서 이번에 한번 제대로 공부하고, 까먹을 때마다 보려고 합니다 ㅎㅎ&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;우선 터미널 상에서 SSH와 SCP를 어떻게 사용하고, 옵션은 어떻게 활용하는지에 대해서 정리를 한 후,&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;추가적으로, 깃허브 액션에서는 어떻게 SSH와 SCP를 활용할 수 있는지에 대해서 정리를 해보도록 하겠습니다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;blockquote data-ke-size=&quot;size16&quot; data-ke-style=&quot;style1&quot;&gt;&lt;span style=&quot;font-family: 'Noto Serif KR';&quot;&gt;&lt;b&gt;SSH&lt;/b&gt; &lt;/span&gt;&lt;/blockquote&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;blockquote data-ke-style=&quot;style2&quot;&gt;&lt;span style=&quot;color: #000000;&quot;&gt;&lt;b&gt;SSH 기본 사용법&lt;/b&gt;&lt;/span&gt;&lt;/blockquote&gt;
&lt;blockquote data-ke-style=&quot;style3&quot;&gt;&lt;span style=&quot;background-color: #fcfcfc; color: #666666; text-align: left;&quot;&gt;ssh -i&lt;span&gt;&amp;nbsp;&lt;/span&gt;&lt;/span&gt;&lt;span style=&quot;color: #006dd7;&quot;&gt;[보안키.pem]&lt;/span&gt;&lt;span style=&quot;background-color: #fcfcfc; color: #666666; text-align: left;&quot;&gt;&lt;span&gt;&amp;nbsp;&lt;/span&gt;-p&lt;span&gt;&amp;nbsp;&lt;/span&gt;&lt;/span&gt;&lt;span style=&quot;color: #ee2323;&quot;&gt;&lt;span style=&quot;color: #006dd7;&quot;&gt;[포트번호]&lt;/span&gt; [원격 계정]&lt;/span&gt;&lt;span style=&quot;background-color: #fcfcfc; color: #666666; text-align: left;&quot;&gt;@&lt;/span&gt;&lt;span style=&quot;color: #ee2323;&quot;&gt;[원격지 IP]&lt;/span&gt;&lt;/blockquote&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;위의 명령어에서 빨간색이 ssh 기본 명령어구조입니다. 기본적으로 ssh가 안전하게 다른 서버에 연결을 하는 것이니, 저희는 기본적은 정보를 넣어서 ssh 연결을 시도하게 됩니다.&lt;/p&gt;
&lt;ul style=&quot;list-style-type: disc;&quot; data-ke-list-type=&quot;disc&quot;&gt;
&lt;li&gt;원격 계정&amp;nbsp;&lt;br /&gt;- 접속할 원격 서버의 계정입니다. ubuntu, root 등등 다양한 이름이 있을 수 있습니다.&lt;/li&gt;
&lt;li&gt;원격지 IP&lt;br /&gt;- 접속할 원격 서버의 IP주소입니다. (공인 IP를 적으셔야 해요)&lt;/li&gt;
&lt;/ul&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;이렇게 하면 해당 서버에 SSH 접속을 할 수 있을까요??&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;아쉽게도 위의 기본 명령어만으로는 SSH 접속을 할 수 없습니다.&lt;br /&gt;추가옵션을 설정해야 SSH 접속을 할 수 있어요.&lt;br /&gt;위에서 파랑색이 옵션 입니다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p style=&quot;text-align: left;&quot; data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;SSH엔 다양한 옵션이 있지만, 저희는 진짜 많이 쓰는 2개만 정리해보도록 하겠습니다.&lt;/p&gt;
&lt;table style=&quot;border-collapse: collapse; width: 57.6744%; height: 165px;&quot; border=&quot;1&quot; data-ke-align=&quot;alignLeft&quot;&gt;
&lt;tbody&gt;
&lt;tr&gt;
&lt;td style=&quot;width: 40.3226%; text-align: center;&quot;&gt;옵션&lt;/td&gt;
&lt;td style=&quot;width: 59.6774%; text-align: center;&quot;&gt;설명&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td style=&quot;width: 40.3226%; text-align: center;&quot;&gt;-p&lt;/td&gt;
&lt;td style=&quot;width: 59.6774%; text-align: center;&quot;&gt;SSH 접속 포트 설정하는 옵션&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td style=&quot;width: 40.3226%; text-align: center;&quot;&gt;-i&lt;/td&gt;
&lt;td style=&quot;width: 59.6774%; text-align: center;&quot;&gt;보안키(pem) 설정하는 옵션&lt;/td&gt;
&lt;/tr&gt;
&lt;/tbody&gt;
&lt;/table&gt;
&lt;p style=&quot;text-align: left;&quot; data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p style=&quot;text-align: left;&quot; data-ke-size=&quot;size16&quot;&gt;위 두가지가 ssh접속할때 필수적으로 많이 쓰이는 옵션입니다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;blockquote data-ke-style=&quot;style1&quot;&gt;&lt;b&gt;SCP&lt;/b&gt;&lt;/blockquote&gt;
&lt;blockquote data-ke-style=&quot;style2&quot;&gt;&lt;span style=&quot;color: #000000;&quot;&gt;&lt;b&gt;SCP 기본 사용법&lt;/b&gt;&lt;/span&gt;&lt;/blockquote&gt;
&lt;blockquote data-ke-style=&quot;style3&quot;&gt;scp -i&lt;span style=&quot;color: #006dd7;&quot;&gt; [보안키.pem]&lt;/span&gt; -p &lt;span style=&quot;color: #006dd7;&quot;&gt;[포트번호]&lt;/span&gt;&lt;span style=&quot;color: #ee2323;&quot;&gt; [전송 파일경로] [원격 계정]&lt;/span&gt;@&lt;span style=&quot;color: #ee2323;&quot;&gt;[원격지 IP]:[도착 파일경로]&lt;/span&gt;&lt;/blockquote&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;위의 명령어에서 빨간색이 scp 기본 명령어 구조입니다. SCP는 SSH와는 다르게 원격으로 접속하는 것이 아닌, 파일 전송이기떄문에 전송할 파일의 정보와 도착할 파일 위치정보가 필요합니다.&lt;/p&gt;
&lt;ul style=&quot;list-style-type: disc;&quot; data-ke-list-type=&quot;disc&quot;&gt;
&lt;li&gt;&lt;b&gt;원격 계정&amp;nbsp;&lt;/b&gt;&lt;br /&gt;- 접속할 원격 서버의 계정입니다. ubuntu, root 등등 다양한 이름이 있을 수 있습니다.&lt;/li&gt;
&lt;li&gt;&lt;b&gt;원격지 IP&lt;/b&gt;&lt;br /&gt;- 접속할 원격 서버의 IP주소입니다. (공인 IP를 적으셔야 해요)&lt;/li&gt;
&lt;li&gt;&lt;b&gt;전송 파일 경로&lt;/b&gt;&lt;br /&gt;- 전송할 파일의 경로입니다. 상대주소도 가능하고, 절대 주소도 가능합니다.&lt;/li&gt;
&lt;li&gt;&lt;b&gt;도착 파일 경로&lt;/b&gt;&lt;br /&gt;- 원격지에 도착 경로입니다. 어느 위치에 저장하게 할지 정할 수 있습니다.&lt;/li&gt;
&lt;/ul&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;SSH와 똑같이 -i와 -p 옵션으로 보안키와 포트 번호를 설정하여 안전하게 파일이 전송될 수 있게 합니다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;blockquote data-ke-size=&quot;size16&quot; data-ke-style=&quot;style1&quot;&gt;&lt;span style=&quot;font-family: 'Noto Serif KR';&quot;&gt;&lt;b&gt;SSH-ACTION&lt;/b&gt;&lt;br /&gt;&lt;/span&gt;&lt;/blockquote&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;ssh-action은 깃허브에서 쓸 수 있는 액션 중 하나입니다. ssh를 좀더 쉽게 사용하여 원격 서버에 연결을 쉽게 할 수 있게 합니다.&lt;br /&gt;즉, ssh를 액션을 통해 좀더 사용자가 정해진 양식대로 좀더 쉽게 사용할 수 있는 것 입니다.&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;그럼 이제 ssh-action의 활용법을 알아보겠습니다.&amp;nbsp;&lt;br /&gt;기본적인 전체 코드는 다음과 같으며, 파랑색의 코드들이 주로 많이 쓰이는 옵션입니다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;blockquote data-ke-style=&quot;style3&quot;&gt;-&amp;nbsp;name:&amp;nbsp;SSH&amp;nbsp;Action &lt;br /&gt;&amp;nbsp;&amp;nbsp;uses:&amp;nbsp;appleboy/ssh-action@v2 &lt;br /&gt;&amp;nbsp;&amp;nbsp;with: &lt;br /&gt;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&lt;span style=&quot;color: #006dd7;&quot;&gt;host:&amp;nbsp;${{&amp;nbsp;secrets.SSH_HOST&amp;nbsp;}}&lt;/span&gt; &lt;br /&gt;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&lt;span style=&quot;color: #006dd7;&quot;&gt;username:&amp;nbsp;${{&amp;nbsp;secrets.SSH_USERNAME&amp;nbsp;}}&lt;/span&gt; &lt;br /&gt;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;password:&amp;nbsp;${{&amp;nbsp;secrets.SSH_PASSWORD&amp;nbsp;}} &lt;br /&gt;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&lt;span style=&quot;color: #006dd7;&quot;&gt;port:&amp;nbsp;${{&amp;nbsp;secrets.SSH_PORT&amp;nbsp;}}&lt;/span&gt; &lt;br /&gt;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&lt;span style=&quot;color: #006dd7;&quot;&gt;script:&amp;nbsp;| &lt;/span&gt;&lt;br /&gt;&lt;span style=&quot;color: #006dd7;&quot;&gt;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;echo&amp;nbsp;&quot;Hello,&amp;nbsp;SSH!&quot;&lt;/span&gt; &lt;br /&gt;&amp;nbsp;&amp;nbsp;&amp;nbsp;&lt;span style=&quot;color: #006dd7;&quot;&gt;&amp;nbsp;privateKey:&amp;nbsp;${{&amp;nbsp;secrets.SSH_PRIVATE_KEY&amp;nbsp;}}&lt;/span&gt; &lt;br /&gt;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;passphrase:&amp;nbsp;${{&amp;nbsp;secrets.SSH_PASSPHRASE&amp;nbsp;}} &lt;br /&gt;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;args:&amp;nbsp;'-o&amp;nbsp;StrictHostKeyChecking=no' &lt;br /&gt;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;timeout:&amp;nbsp;'120s' &lt;/blockquote&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;위에 나와있는 옵션들의 정보는 다음과 같습니다.&lt;/p&gt;
&lt;ul style=&quot;list-style-type: disc;&quot; data-ke-list-type=&quot;disc&quot;&gt;
&lt;li&gt;&lt;b&gt;uses&lt;/b&gt;&lt;br /&gt;-사용할 액션 지정하는 옵션&lt;/li&gt;
&lt;li&gt;&lt;b&gt;host&lt;/b&gt;&lt;br /&gt;-SSH로 연결할 호스트 주소 지정&lt;/li&gt;
&lt;li&gt;&lt;b&gt;username&lt;/b&gt;&lt;br /&gt;-SSH 연결에 사용할 사용자 이름 지정&lt;/li&gt;
&lt;li&gt;&lt;b&gt;password&lt;/b&gt;&lt;br /&gt;-SSH 연결에 사용할 비밀번호 지정&lt;/li&gt;
&lt;li&gt;&lt;b&gt;port&lt;/b&gt;&lt;br /&gt;-SSH 연결에 사용할 포트번호 지정 (기본값은 22입니다.)&lt;/li&gt;
&lt;li&gt;&lt;b&gt;script&lt;/b&gt;&lt;br /&gt;-SSH 연결 이후에, 원격 서버에서 실행할 스크립트를 지정. (해당 서버의 OS에 맞게 스크립트를 작성해야 합니다.)&lt;/li&gt;
&lt;li&gt;&lt;b&gt;privateKey&lt;/b&gt;&lt;br /&gt;-SSH 연결에 사용할 개인키(.pem 파일)을 지정&lt;/li&gt;
&lt;li&gt;&lt;b&gt;args&lt;/b&gt;&lt;br /&gt;-SSH 명령에 추가로 전달할 인수를 지정&lt;/li&gt;
&lt;li&gt;&lt;b&gt;timeout&lt;/b&gt;&lt;br /&gt;-SSH 연결 시도 시간 지정 (기본값은 30초)&lt;/li&gt;
&lt;/ul&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;blockquote data-ke-style=&quot;style1&quot;&gt;&lt;span style=&quot;font-family: 'Noto Serif KR';&quot;&gt;&lt;b&gt;SCP-ACTION&lt;/b&gt;&lt;br /&gt;&lt;/span&gt;&lt;/blockquote&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;scp-action은 위에서 설명한것과 똑같이, scp를 좀더 사용하기 쉽게 만들어 놓은 액션입니다.&lt;br /&gt;ssh-action과 유사하게, 코드 몇줄로 scp를 가독성 있게 구현할 수 있습니다.&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;그럼이제 scp-action의 활용법을 알아보겠습니다.&lt;br /&gt;기본적인 전체 코드는 다음과 같으며, 파랑색의 코드들이 주로 많이 쓰이는 옵션입니다. &lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;blockquote data-ke-style=&quot;style3&quot;&gt;-&amp;nbsp;name:&amp;nbsp;SCP&amp;nbsp;Action &lt;br /&gt;&amp;nbsp;&amp;nbsp;uses:&amp;nbsp;appleboy/scp-action@master &lt;br /&gt;&amp;nbsp;&amp;nbsp;with: &lt;br /&gt;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&lt;span style=&quot;color: #006dd7;&quot;&gt;source:&amp;nbsp;'./file.txt'&lt;/span&gt; &lt;br /&gt;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&lt;span style=&quot;color: #006dd7;&quot;&gt;target:&amp;nbsp;'/remote/path/'&lt;/span&gt; &lt;br /&gt;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&lt;span style=&quot;color: #006dd7;&quot;&gt;host:&amp;nbsp;${{&amp;nbsp;secrets.SSH_HOST&amp;nbsp;}}&lt;/span&gt; &lt;br /&gt;&amp;nbsp;&amp;nbsp;&amp;nbsp;&lt;span style=&quot;color: #006dd7;&quot;&gt;&amp;nbsp;username:&amp;nbsp;${{&amp;nbsp;secrets.SSH_USERNAME&amp;nbsp;}}&lt;/span&gt; &lt;br /&gt;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;password:&amp;nbsp;${{&amp;nbsp;secrets.SSH_PASSWORD&amp;nbsp;}} &lt;br /&gt;&amp;nbsp;&amp;nbsp;&amp;nbsp;&lt;span style=&quot;color: #006dd7;&quot;&gt;&amp;nbsp;port:&amp;nbsp;${{&amp;nbsp;secrets.SSH_PORT&amp;nbsp;}}&lt;/span&gt; &lt;br /&gt;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&lt;span style=&quot;color: #006dd7;&quot;&gt;privateKey:&amp;nbsp;${{&amp;nbsp;secrets.SSH_PRIVATE_KEY&amp;nbsp;}}&lt;/span&gt; &lt;br /&gt;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;passphrase:&amp;nbsp;${{&amp;nbsp;secrets.SSH_PASSPHRASE&amp;nbsp;}} &lt;br /&gt;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;args:&amp;nbsp;'-o&amp;nbsp;StrictHostKeyChecking=no' &lt;br /&gt;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;timeout:&amp;nbsp;'120s' &lt;/blockquote&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;ssh-action에서 옵션들을 많이 설명한 관계로, 이미 설명했던 것들은 하지않고, 새롭게 추가된것만 정리하겠습니다.&lt;/p&gt;
&lt;ul style=&quot;list-style-type: disc;&quot; data-ke-list-type=&quot;disc&quot;&gt;
&lt;li&gt;&lt;b&gt;source&lt;/b&gt;&lt;br /&gt;-로컬 시스템에서 원격지로 전달할 파일의 경로를 지정&lt;/li&gt;
&lt;li&gt;&lt;b&gt;target&lt;/b&gt;&lt;br /&gt;-원격지에 전달받을 경로를 지정&lt;/li&gt;
&lt;/ul&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;또한, ssh-action과 다르게, scp는 파일전송만 하는 것이기 때문에 script옵션이 따로 존재하지 않습니다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;즉, 파일은 전송한 후 다른 작업을 하지 못합니다.&lt;/p&gt;</description>
      <category>클라우드</category>
      <category>scp</category>
      <category>SSH</category>
      <category>깃허브액션</category>
      <category>배포</category>
      <category>액션</category>
      <category>클라우드</category>
      <author>tioon</author>
      <guid isPermaLink="true">https://tioon.tistory.com/190</guid>
      <comments>https://tioon.tistory.com/190#entry190comment</comments>
      <pubDate>Sat, 2 Mar 2024 00:05:40 +0900</pubDate>
    </item>
    <item>
      <title>스프링 웹소켓 구현기 (feat. STOMP 먹통)</title>
      <link>https://tioon.tistory.com/189</link>
      <description>&lt;p data-ke-size=&quot;size16&quot;&gt;이번엔 제가 스프링으로 웹소켓을 구현하면서 겪었던 문제들을 해결하면서 고민했던것과 해결한 결과를 말씀 드리려 합니다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;예전에 웹소켓을 STOMP로 구현하면서 블로그 글을 올린 적이 있었는데, 그때엔 제대로 작동이 되었습니다.&lt;br /&gt;하지만, 최근에 STOMP 테스트 하는 것이 어떤 이유에서인지 모르겠지만, 막혔더라구요...ㅜ&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;그래서 다양한 방법으로 해결을 하려 했지만, STOMP를 결국 활용을 못하고, 스프링 WebSocket으로 리팩토링하여 해결으 하였습니다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;우선 다음과 같은 단계로 설명을 드리려합니다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;ol style=&quot;list-style-type: decimal;&quot; data-ke-list-type=&quot;decimal&quot;&gt;
&lt;li&gt;&lt;b&gt;현재 STOMP의 문제.&lt;/b&gt;&lt;/li&gt;
&lt;li&gt;&lt;b&gt;STOMP 테스트 시스템 부재&lt;/b&gt;&lt;/li&gt;
&lt;li&gt;&lt;b&gt;STOMP 대체제인 스프링 WebSocket 직접 구현&lt;/b&gt;&lt;/li&gt;
&lt;li&gt;&lt;b&gt;스프링 WebSocket 테스트&lt;/b&gt;&lt;/li&gt;
&lt;/ol&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;blockquote data-ke-style=&quot;style1&quot;&gt;&lt;b&gt;&lt;span style=&quot;font-family: 'Noto Serif KR';&quot;&gt;현재 STOMP의 문제.&lt;/span&gt;&lt;/b&gt;&lt;/blockquote&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;과거에 제가 STOMP를 이용해 웹소켓을 구현한 적이 있습니다. STOMP의 자세한 설명은 다음 글에서 확인해 주세요.&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://tioon.tistory.com/133&quot; target=&quot;_blank&quot; rel=&quot;noopener&amp;nbsp;noreferrer&quot;&gt;https://tioon.tistory.com/133&lt;/a&gt;&lt;/p&gt;
&lt;figure id=&quot;og_1708847864407&quot; contenteditable=&quot;false&quot; data-ke-type=&quot;opengraph&quot; data-ke-align=&quot;alignCenter&quot; data-og-type=&quot;article&quot; data-og-title=&quot;스프링 웹소켓(Web Socket) (STOMP)&quot; data-og-description=&quot;웹소켓(Web Socket)이란? -클라이언트와 서버간의 양방향 통신을 가능하게 하는 프로토콜입니다. HTTP와 같은 웹표준 프로토콜입니다. 이는 실시간, 이벤트 기반 통신이 필요한 애플리케이션을 개발&quot; data-og-host=&quot;tioon.tistory.com&quot; data-og-source-url=&quot;https://tioon.tistory.com/133&quot; data-og-url=&quot;https://tioon.tistory.com/133&quot; data-og-image=&quot;https://scrap.kakaocdn.net/dn/HvTxx/hyVqop34GF/nKPLi744nL90XeNCweVF50/img.png?width=800&amp;amp;height=800&amp;amp;face=0_0_800_800,https://scrap.kakaocdn.net/dn/bVFLwJ/hyVmRHruZ9/2aAIILrU00rmwnjeEQioH1/img.png?width=800&amp;amp;height=800&amp;amp;face=0_0_800_800,https://scrap.kakaocdn.net/dn/Ye5id/hyVmVpvkso/E91CWsoYaZLFkgZxQdAhbK/img.jpg?width=1080&amp;amp;height=1440&amp;amp;face=0_0_1080_1440&quot;&gt;&lt;a href=&quot;https://tioon.tistory.com/133&quot; target=&quot;_blank&quot; rel=&quot;noopener&quot; data-source-url=&quot;https://tioon.tistory.com/133&quot;&gt;
&lt;div class=&quot;og-image&quot; style=&quot;background-image: url('https://scrap.kakaocdn.net/dn/HvTxx/hyVqop34GF/nKPLi744nL90XeNCweVF50/img.png?width=800&amp;amp;height=800&amp;amp;face=0_0_800_800,https://scrap.kakaocdn.net/dn/bVFLwJ/hyVmRHruZ9/2aAIILrU00rmwnjeEQioH1/img.png?width=800&amp;amp;height=800&amp;amp;face=0_0_800_800,https://scrap.kakaocdn.net/dn/Ye5id/hyVmVpvkso/E91CWsoYaZLFkgZxQdAhbK/img.jpg?width=1080&amp;amp;height=1440&amp;amp;face=0_0_1080_1440');&quot;&gt;&amp;nbsp;&lt;/div&gt;
&lt;div class=&quot;og-text&quot;&gt;
&lt;p class=&quot;og-title&quot; data-ke-size=&quot;size16&quot;&gt;스프링 웹소켓(Web Socket) (STOMP)&lt;/p&gt;
&lt;p class=&quot;og-desc&quot; data-ke-size=&quot;size16&quot;&gt;웹소켓(Web Socket)이란? -클라이언트와 서버간의 양방향 통신을 가능하게 하는 프로토콜입니다. HTTP와 같은 웹표준 프로토콜입니다. 이는 실시간, 이벤트 기반 통신이 필요한 애플리케이션을 개발&lt;/p&gt;
&lt;p class=&quot;og-host&quot; data-ke-size=&quot;size16&quot;&gt;tioon.tistory.com&lt;/p&gt;
&lt;/div&gt;
&lt;/a&gt;&lt;/figure&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;STOMP 같은 경우는 스프링 웹소켓 위에 구현된 프로토콜입니다.&lt;br /&gt;즉, 스프링 웹소켓을 기반으로 구현된것이며, '메세지 브로커' 라는 개념을 활용하여 웹소켓을 구현합니다.&lt;/p&gt;
&lt;blockquote data-ke-style=&quot;style3&quot;&gt;@Configuration&lt;br /&gt;@EnableWebSocketMessageBroker&lt;br /&gt;public&amp;nbsp;class&amp;nbsp;WebSocketConfig&amp;nbsp;implements&amp;nbsp;&lt;br /&gt;WebSocketMessageBrokerConfigurer&lt;br /&gt;&amp;nbsp;{&lt;br /&gt;&lt;span style=&quot;color: #ee2323;&quot;&gt;//웹소켓 설정 클래스 (이 클래스로 웹소켓 설정을 다룰 수 있음)&lt;/span&gt;&lt;br /&gt;&lt;br /&gt;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;@Override&lt;br /&gt;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;public&amp;nbsp;void&amp;nbsp;&lt;br /&gt;configureMessageBroker&lt;br /&gt;(MessageBrokerRegistry&amp;nbsp;config)&amp;nbsp;{&lt;br /&gt;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;config.enableSimpleBroker(&quot;/topic&quot;);&lt;br /&gt;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;config.setApplicationDestinationPrefixes(&quot;/app&quot;);&lt;br /&gt;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;}&amp;nbsp;&lt;br /&gt;&lt;span style=&quot;color: #ee2323;&quot;&gt;//메시지 브로커 설정 메서드(브로드 캐스트)&lt;/span&gt;&lt;br /&gt;&lt;br /&gt;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;@Override&lt;br /&gt;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;public&amp;nbsp;void&lt;br /&gt;&amp;nbsp;registerStompEndpoints&lt;br /&gt;(StompEndpointRegistry&amp;nbsp;registry)&amp;nbsp;{&lt;br /&gt;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;registry.addEndpoint(&quot;/websocket-endpoint&quot;).withSockJS();&lt;br /&gt;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;}&amp;nbsp;&lt;br /&gt;&lt;span style=&quot;color: #ee2323;&quot;&gt;// 웹소켓 연결 설정 메서드 (메세지 전송)&lt;/span&gt;&lt;br /&gt;}&lt;/blockquote&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;위 코드처럼, registerStompEndpoints 메서드를 통해 웹소켓 연결을 시작할 수 있고,&lt;br /&gt;configureMessageBroker를 활용해 메세지 브로커를 통해 연결된 사용자들에게 웹소켓 메세지를 보낼 수 있습니다.&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;이렇게 웹소켓을 좀 더 사용하기 쉽게 HTTP와 유사한 구조를 가지고 있어 개발자들이 쉽게 적용할 수 있다 라는 장점이 있지만, 이걸 테스트할 수 있는 프로그램이 거의 없다 라는 게 문제입니다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;테스트 프로그램이 적으면 무엇이 문제일까요?&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;문제는 다음과 같습니다.&lt;/p&gt;
&lt;ul style=&quot;list-style-type: disc;&quot; data-ke-list-type=&quot;disc&quot;&gt;
&lt;li&gt;개발중에 혼란성 야기&lt;br /&gt;-웹소켓이 가뜩이나 어려운데, 테스트 까지 어려우면 개발중에 많은 혼란성을 야기합니다.&lt;/li&gt;
&lt;li&gt;프론트와 API 연동 문제 발생&lt;br /&gt;-저희가 웹소켓이 어려운만큼, 프론트 측에서 웹소켓 연동시키는게 많이 어렵습니다. 이러한 상황에서 테스트 API까지 주지 못한다면, 연동 과정에서 굉장히 많은 어려움이 생길 수 있습니다.&lt;/li&gt;
&lt;/ul&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;저는 그래서 과거에는 APIC이라는 테스트기를 이용해서 직접 웹소켓을 테스트 하기도 했고,&lt;br /&gt;프론트 분들에게 API 명세서를 작성하기도 하였습니다.&lt;br /&gt;따라서, 과거에는 STOMP의 큰 문제점은 없었습니다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;blockquote data-ke-style=&quot;style1&quot;&gt;&lt;b&gt;&lt;span style=&quot;font-family: 'Noto Serif KR';&quot;&gt;STOMP 테스트 시스템 부재&lt;/span&gt;&lt;/b&gt;&lt;/blockquote&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;과거에는 이렇게 APIC을 사용하여 테스트 했지만, 제가 최근(2024년 1월 기준)에 다시 테스트 하려고 했는데, APIC이 막혔습니다.....&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;왜 이러는진 모르겠어요. APIC이 정상적으로 되시는 분은 알려주시면 감사하겠습니다 ㅜㅜ&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;암튼, 원래 APIC을 크롬 Extensions에서 다운받아서 활용을 했는데 이게 어떤 이유에서인지 막히고, 이거에 대한 대안으로, APIC 웹앱을 사용하려고 했으나, 이것도 막혔더라구요....&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;APIC 웹앱 링크 -&amp;nbsp;&lt;a href=&quot;https://apic.app/online/#/tester&quot; target=&quot;_blank&quot; rel=&quot;noopener&amp;nbsp;noreferrer&quot;&gt;https://apic.app/online/#/tester&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;따라서, 현재 APIC은 STOMP 테스트기로 활용을 아예 할 수 없는 상황입니다.&lt;br /&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;그래서 이걸 다른걸로 STOMP 테스트를 할 순 없을까....고민하며 구글링을 한 결과,&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;PostMan에서 웹소켓 테스트를 할 수 있는 기능이 있다는걸 알았습니다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p&gt;&lt;figure class=&quot;imageblock alignCenter&quot; data-ke-mobileStyle=&quot;widthOrigin&quot; data-origin-width=&quot;943&quot; data-origin-height=&quot;696&quot;&gt;&lt;span data-url=&quot;https://blog.kakaocdn.net/dn/c7PZWZ/btsFeEqOtdT/1mXuqkks2akQrsRu5K27O0/img.png&quot; data-phocus=&quot;https://blog.kakaocdn.net/dn/c7PZWZ/btsFeEqOtdT/1mXuqkks2akQrsRu5K27O0/img.png&quot;&gt;&lt;img src=&quot;https://blog.kakaocdn.net/dn/c7PZWZ/btsFeEqOtdT/1mXuqkks2akQrsRu5K27O0/img.png&quot; srcset=&quot;https://img1.daumcdn.net/thumb/R1280x0/?scode=mtistory2&amp;fname=https%3A%2F%2Fblog.kakaocdn.net%2Fdn%2Fc7PZWZ%2FbtsFeEqOtdT%2F1mXuqkks2akQrsRu5K27O0%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;943&quot; height=&quot;696&quot; data-origin-width=&quot;943&quot; data-origin-height=&quot;696&quot;/&gt;&lt;/span&gt;&lt;/figure&gt;
&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;위 사진처럼 HTTP 대신 WebSocket을 테스트할 수 있는 기능이 있습니다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;br /&gt;&quot;이걸 한번 활용해서 테스트를 해보자!&quot; 싶었지만, 제 STOMP와 계속 연동 실패하는 것을 볼 수 있었습니다.&lt;br /&gt;이게 아직 WebSocket은 정식 버전이 아니라 그런가 싶었지만, 다른 분들의 블로그를 보니 다른분들도 안된다는 걸 확인할 수 있었습니다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;그래서&amp;nbsp; STOMP의 문제인거 같다 라고 판단을 하였습니다.&lt;br /&gt;이러한 판단의 이유는 STOMP는 결국 스프링의 웹소켓 기반으로 위에 구현된 프로토콜이기 때문에 현재 PostMan WebSocket 테스트기가 작동이 안된다라고 생각을 했습니다.&lt;br /&gt;따라서, PostMan에서 제공하는것은 STOMP가 아닌 일반 웹소켓 테스트기 라는 생각이 들었습니다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;br /&gt;그래서 결국, 웹소켓을 STOMP로 구현하는 것이 아닌, 직접 스프링 WebSocket을 활용해서 웹소켓을 구현하자라는 생각으로 기존 코드를 전부 리팩토링을 하였습니다.&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;(STOMP 테스트 할 수 있는 다른방법 알고 계시는분은 알려주세요 ㅎㅎ)&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;blockquote data-ke-style=&quot;style1&quot;&gt;&lt;b&gt;&lt;span style=&quot;font-family: 'Noto Serif KR';&quot;&gt;스프링 WebSocket 직접 구현&lt;/span&gt;&lt;/b&gt;&lt;/blockquote&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;이게 STOMP를 이용하지 않고 스프링 WebSocket을 직접 구현하겠습니다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;스프링 WebSocket의 방식은 간략하게 설명하면, 다음과 같습니다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;STOMP처럼 메세지브로커 같은 개념이 없어 원시적인(?) 방법으로 직접, 메세지 연결 후 메세지 전송하는 내용으로 구별을 해야합니다.&lt;/p&gt;
&lt;p&gt;&lt;figure class=&quot;imageblock alignCenter&quot; data-ke-mobileStyle=&quot;widthOrigin&quot; data-origin-width=&quot;886&quot; data-origin-height=&quot;832&quot;&gt;&lt;span data-url=&quot;https://blog.kakaocdn.net/dn/cZ4MoC/btsFgNHmblY/wI1xkwofsGhzpSmRksYIBK/img.png&quot; data-phocus=&quot;https://blog.kakaocdn.net/dn/cZ4MoC/btsFgNHmblY/wI1xkwofsGhzpSmRksYIBK/img.png&quot;&gt;&lt;img src=&quot;https://blog.kakaocdn.net/dn/cZ4MoC/btsFgNHmblY/wI1xkwofsGhzpSmRksYIBK/img.png&quot; srcset=&quot;https://img1.daumcdn.net/thumb/R1280x0/?scode=mtistory2&amp;fname=https%3A%2F%2Fblog.kakaocdn.net%2Fdn%2FcZ4MoC%2FbtsFgNHmblY%2FwI1xkwofsGhzpSmRksYIBK%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;552&quot; height=&quot;518&quot; data-origin-width=&quot;886&quot; data-origin-height=&quot;832&quot;/&gt;&lt;/span&gt;&lt;/figure&gt;
&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;위의 그림을 보시면처음에 웹소켓 연결을 하고, 이후에 메세지 전송을 하고 있습니다.&lt;br /&gt;이때, 원하는 대상에게만 메세지를 전송하게 하려면, id 같은 값을 추가해서 전달해야합니다.&amp;nbsp;&lt;br /&gt;이런 id 값을 추가함으로써 웹소켓 서버에서 어떤 대상이 연결되어 있고, 메세지를 어떤 대상에게 전송해야하는지 알 수 있습니다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;우선 기존에 STOMP를 이용하기 위해 사용했던 인터페이스인 &lt;span style=&quot;background-color: #fcfcfc; color: #666666; text-align: left;&quot;&gt;WebSocketMessageBrokerConfigurer 이거를 사용하는 것이 아닌, WebSocketConfigurer 인터페이스를 활용하여 Config 파일을 작성할것입니다.&lt;br /&gt;이후에 TextWebSocketHandler 인터페이스를 활용하여 Handler 파일을 작성합니다.&lt;/span&gt;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;span style=&quot;background-color: #fcfcfc; color: #666666; text-align: left;&quot;&gt;config 파일은 어떻게 웹소켓 접속을 할 수 있는지를 정하는 파일이며, 다음과 같습니다.&lt;/span&gt;&lt;/p&gt;
&lt;pre class=&quot;bash&quot; style=&quot;background-color: #2b2b2b; color: #a9b7c6;&quot; data-ke-language=&quot;bash&quot;&gt;&lt;code&gt;@Configuration
@RequiredArgsConstructor
@EnableWebSocket
public class WebSocketConfig implements WebSocketConfigurer {

    private final WebSocketHandler webSocketHandler;




    @Override
    public void registerWebSocketHandlers(WebSocketHandlerRegistry registry) {
        /*
         * 웹소켓 핸들러 추가 및 접속 경로 설정
         */
        registry.addHandler(webSocketHandler, &quot;websocket&quot;)
                .setAllowedOrigins(&quot;*&quot;);//CORS 설정
    }

}&lt;/code&gt;&lt;/pre&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;handler 파일은 웹소켓 요청이 들어왔을 때, 어떻게 처리할지 정하는 핸들러 파일이며, 다음과 같습니다.&lt;/p&gt;
&lt;pre class=&quot;bash&quot; style=&quot;background-color: #2b2b2b; color: #a9b7c6;&quot; data-ke-language=&quot;bash&quot;&gt;&lt;code&gt;@Slf4j
@RequiredArgsConstructor
@Component
public class WebSocketHandler extends TextWebSocketHandler {
    private final ObjectMapper objectMapper;
    private final ChatService chatService;
    private ChatRoom chatRoom;
    private String roomId;

    //웹소켓 연결되었을때 호출
    @Override
    public void afterConnectionEstablished(WebSocketSession session) throws Exception {
        log.info(&quot;웹소켓이 연결됨&quot;);
    }


	//클라이언트가 웹소켓 메세지를 전송했을 때 호출
    @Override
    protected void handleTextMessage(WebSocketSession session, TextMessage message) throws Exception {

        String payload = message.getPayload();
        log.info(&quot;{}&quot;, payload);
       
        //해당 코드의 경우, 방입장, 방퇴장에 대한 코드가 구현되어 있음.
        ChatMessage chatMessage = objectMapper.readValue(payload, ChatMessage.class);
        chatRoom = chatService.findRoomById(chatMessage.getRoomNumber());
        chatRoom.handlerEnterExit(session, chatMessage, chatService);
    }

    //해당 메서드의 경우, 방 이벤트 발생시 클라이언트에게 전송
    public void handleRoomEvent(ChatMessage chatMessage) throws Exception{

        chatRoom = chatService.findRoomById(chatMessage.getRoomNumber());
        chatRoom.handlerRoomEvent(chatMessage,chatService);
    }


    //웹소켓 연결 끊겼을 때 호출
    @Override
    public void afterConnectionClosed(WebSocketSession session, CloseStatus status) throws Exception {
        
        
        //해당 방을 제거
        chatRoom.getMembers().remove(session);
        chatService.deleteRoom(roomId);
        log.info(&quot;웹소켓이 닫힘&quot;);
    }

}&lt;/code&gt;&lt;/pre&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;저 같은 경우는 간단한 게임 방을 구현했기때문에 chatRoom같은 추가 코드가 구현되어 있습니다. 해당 config 파일과 handler 파일을 바탕으로 이제 웹소켓이 구현됩니다.&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;br /&gt;(저는 데이터가 날라가도 크게 상관없었기때문에 Room 데이터들을 Map를 사용해서 메모리에 저장했습니다.&lt;br /&gt;하지만 데이터가 필요하신 분들은 데이터베이스에 저장하는 추가 과정이 있어야하니, 참고 부탁드립니다.)&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;ChatMessage 코드&lt;/p&gt;
&lt;pre class=&quot;groovy&quot; style=&quot;background-color: #2b2b2b; color: #a9b7c6;&quot;&gt;&lt;code&gt;@Getter
@Setter
@Builder
public class ChatMessage {
    public enum MessageType{
        ENTER,// 방 입장
        EXIT, // 방 퇴장
        GAME_START, // 게임시작
        GET_QUESTION, // 질문 가져오기
        ANSWER_COMPLETE, //질문 답변 완료
        ROUND_CHANGE, // 라운드 변환
        CORRECT_COUNT, // 맞춘 정답 개수
        FINISH // 게임 종료

    }

    private MessageType type; // 메세지 타입
    private String roomNumber; // 방 Id값
    @Nullable
    private Long playerId; // 플레이어 Id값
    @Nullable
    private byte[] image; // 사진 이진데이터
    @Nullable
    private int currentRound; // 현재 라운드
    @Nullable
    private int correctCount; // 정답 개수
    @Nullable
    private String question; // 질문
}&lt;/code&gt;&lt;/pre&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;ChatRoom 코드&lt;/p&gt;
&lt;pre class=&quot;reasonml&quot; style=&quot;background-color: #2b2b2b; color: #a9b7c6;&quot;&gt;&lt;code&gt;@Getter
public class ChatRoom {
    private String roomNumber;
    private Set&amp;lt;WebSocketSession&amp;gt; members = new HashSet&amp;lt;&amp;gt;();

    private PlayerRepo playerRepo;

    @Builder
    public ChatRoom(String roomNumber,PlayerRepo playerRepo) {
        this.roomNumber = roomNumber;
        this.playerRepo = playerRepo;
    }

    //방입장, 퇴장시에 호출
    public void handlerEnterExit(WebSocketSession session, ChatMessage chatMessage, ChatService chatService) {

        switch (chatMessage.getType()) {
            // 방입장 (소켓 세션 연결)
            case ENTER:


                if(!playerRepo.findById(chatMessage.getPlayerId()).get().getRoom().getRoomId().equals(chatMessage.getRoomNumber()))
                    throw new RuntimeException(); // 사용자 정보가 일치하지 않으면 에러 처리.

                members.add(session);

                chatMessage.setType(chatMessage.getType());
                chatMessage.setRoomNumber(chatMessage.getRoomNumber());
                chatMessage.setPlayerId(chatMessage.getPlayerId());
                chatMessage.setImage(chatMessage.getImage());

                sendMessage(chatMessage, chatService);
                break;

            // 방퇴장 (소켓 세션 해제)
            case EXIT:
                if(playerRepo.findById(chatMessage.getPlayerId()).get().getRoom().equals(chatMessage.getRoomNumber()))
                    throw new RuntimeException(); // 사용자 정보가 일치하지 않으면 에러 처리.

                chatMessage.setType(chatMessage.getType());
                chatMessage.setRoomNumber(chatMessage.getRoomNumber());
                chatMessage.setPlayerId(chatMessage.getPlayerId());

                sendMessage(chatMessage, chatService);

                members.remove(session);
                break;

            // 필요한 경우 다른 case 블록 추가
        }
    }


    private &amp;lt;T&amp;gt; void sendMessage(T message, ChatService chatService) {
        // 연결되어있는 모든 세션에 메세지 전달.
        members.parallelStream()
                .forEach(session -&amp;gt; chatService.sendMessage(session, message));
    }

    public void handlerRoomEvent(ChatMessage chatMessage, ChatService chatService) {

        switch (chatMessage.getType()) {

            // 질문 답변 완료
            case ANSWER_COMPLETE:
                chatMessage.setType(chatMessage.getType());
                chatMessage.setRoomNumber(chatMessage.getRoomNumber());
                chatMessage.setPlayerId(chatMessage.getPlayerId());

                sendMessage(chatMessage, chatService);
                break;

            // 질문 가져오기
            case GET_QUESTION:
                chatMessage.setType(chatMessage.getType());
                chatMessage.setRoomNumber(chatMessage.getRoomNumber());
                chatMessage.setQuestion(chatMessage.getQuestion());

                sendMessage(chatMessage, chatService);
                break;

            //게임시작, 라운드 변환 (술래 체인지)
            case GAME_START:
            case ROUND_CHANGE:
                chatMessage.setType(chatMessage.getType());
                chatMessage.setRoomNumber(chatMessage.getRoomNumber());
                chatMessage.setPlayerId(chatMessage.getPlayerId());
                chatMessage.setCurrentRound(chatMessage.getCurrentRound());

                sendMessage(chatMessage, chatService);
                break;

            //맞춘 정답 개수
            case CORRECT_COUNT:
                chatMessage.setType(chatMessage.getType());
                chatMessage.setRoomNumber(chatMessage.getRoomNumber());
                chatMessage.setPlayerId(chatMessage.getPlayerId());
                chatMessage.setCorrectCount(chatMessage.getCorrectCount());

                sendMessage(chatMessage, chatService);
                break;

            //게임 종료
            case FINISH:
                chatMessage.setType(chatMessage.getType());
                chatMessage.setRoomNumber(chatMessage.getRoomNumber());

                sendMessage(chatMessage, chatService);
                chatService.deleteRoom(chatMessage.getRoomNumber());
                break;
        }
    }
}
&lt;/code&gt;&lt;/pre&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;ChatService 코드&lt;/p&gt;
&lt;pre class=&quot;arduino&quot; style=&quot;background-color: #2b2b2b; color: #a9b7c6;&quot;&gt;&lt;code&gt;@Slf4j
@RequiredArgsConstructor
@Service
public class ChatService {
    private final ObjectMapper objectMapper;

    @Autowired
    private RoomRepo roomRepo;

    @Autowired
    private PlayerRepo playerRepo;

    private Map&amp;lt;String, ChatRoom&amp;gt; chatRooms = new HashMap&amp;lt;&amp;gt;();


    //활성화된 모든 채팅방을 조회
    /*public List&amp;lt;ChatDto&amp;gt; findAllRoom() {
        List&amp;lt;ChatDto&amp;gt; collect = chatRooms.values().stream().map(chatRoom -&amp;gt; new ChatDto(chatRoom.getRoomId(), chatRoom.getName(), (long) chatRoom.getSessions().size())).collect(Collectors.toList());
        return collect;
    }*/

    //채팅방 하나를 조회
    public ChatRoom findRoomById(String roomNumber) {
        return chatRooms.get(roomNumber);
    }


    //새로운 방 생성
    public ChatRoom createRoom(String roomNumber) {
        ChatRoom chatRoom = ChatRoom.builder()
                .roomNumber(roomNumber)
                .playerRepo(playerRepo)
                .build();

        chatRooms.put(roomNumber, chatRoom);
        return chatRoom;
    }

    //방 삭제
    public void deleteRoom(String roomNumber) {
        //해당방에 아무도 없다면 자동 삭제
        chatRooms.remove(roomNumber);
    }

    public &amp;lt;T&amp;gt; void sendMessage(WebSocketSession session, T message) {
        try {
            session.sendMessage(new TextMessage(objectMapper.writeValueAsString(message)));
        } catch (IOException e) {
            log.error(e.getMessage(), e);
        }
    }
}&lt;/code&gt;&lt;/pre&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;blockquote data-ke-style=&quot;style1&quot;&gt;&lt;b&gt;&lt;span style=&quot;font-family: 'Noto Serif KR';&quot;&gt;스프링 WebSocket 테스트&lt;/span&gt;&lt;/b&gt;&lt;/blockquote&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;이런 식으로 코드를 작성하고 나면 PostMan에서 테스트를 직접 해볼 수 있습니다. 다음은 테스트 장면입니다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p&gt;&lt;figure class=&quot;imageblock alignCenter&quot; data-ke-mobileStyle=&quot;widthOrigin&quot; data-origin-width=&quot;1197&quot; data-origin-height=&quot;668&quot;&gt;&lt;span data-url=&quot;https://blog.kakaocdn.net/dn/bcFnvU/btsFgProd4S/qyk0WrG9vICgb10LXqfLoK/img.png&quot; data-phocus=&quot;https://blog.kakaocdn.net/dn/bcFnvU/btsFgProd4S/qyk0WrG9vICgb10LXqfLoK/img.png&quot;&gt;&lt;img src=&quot;https://blog.kakaocdn.net/dn/bcFnvU/btsFgProd4S/qyk0WrG9vICgb10LXqfLoK/img.png&quot; srcset=&quot;https://img1.daumcdn.net/thumb/R1280x0/?scode=mtistory2&amp;fname=https%3A%2F%2Fblog.kakaocdn.net%2Fdn%2FbcFnvU%2FbtsFgProd4S%2Fqyk0WrG9vICgb10LXqfLoK%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;631&quot; height=&quot;352&quot; data-origin-width=&quot;1197&quot; data-origin-height=&quot;668&quot;/&gt;&lt;/span&gt;&lt;/figure&gt;
&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;위 처럼 ws를 사용하여 웹소켓 연결을 할 수 있습니다.&lt;/p&gt;
&lt;p&gt;&lt;figure class=&quot;imageblock alignCenter&quot; data-ke-mobileStyle=&quot;widthOrigin&quot; data-origin-width=&quot;1214&quot; data-origin-height=&quot;854&quot;&gt;&lt;span data-url=&quot;https://blog.kakaocdn.net/dn/8YDBI/btsFgqyEfTM/6Qt87or9CgaisNI9oKXIGk/img.png&quot; data-phocus=&quot;https://blog.kakaocdn.net/dn/8YDBI/btsFgqyEfTM/6Qt87or9CgaisNI9oKXIGk/img.png&quot;&gt;&lt;img src=&quot;https://blog.kakaocdn.net/dn/8YDBI/btsFgqyEfTM/6Qt87or9CgaisNI9oKXIGk/img.png&quot; srcset=&quot;https://img1.daumcdn.net/thumb/R1280x0/?scode=mtistory2&amp;fname=https%3A%2F%2Fblog.kakaocdn.net%2Fdn%2F8YDBI%2FbtsFgqyEfTM%2F6Qt87or9CgaisNI9oKXIGk%2Fimg.png&quot; onerror=&quot;this.onerror=null; this.src='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png'; this.srcset='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png';&quot; loading=&quot;lazy&quot; width=&quot;645&quot; height=&quot;454&quot; data-origin-width=&quot;1214&quot; data-origin-height=&quot;854&quot;/&gt;&lt;/span&gt;&lt;/figure&gt;
&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;위 처럼 connet 성공시 아래쪽에 Conneted 알람이 뜹니다.&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;이제 메세지를 보낼 차례입니다. 저희는 메세지 브로커가 없기때문에 직접 type을 정하고, 등록하는 것을 직접해주어야 합니다.&lt;/p&gt;
&lt;p&gt;&lt;figure class=&quot;imageblock alignCenter&quot; data-ke-mobileStyle=&quot;widthOrigin&quot; data-origin-width=&quot;1192&quot; data-origin-height=&quot;795&quot;&gt;&lt;span data-url=&quot;https://blog.kakaocdn.net/dn/oh8kS/btsFftvPk4w/nbYLkki2NNVpY7UUKbBh6K/img.png&quot; data-phocus=&quot;https://blog.kakaocdn.net/dn/oh8kS/btsFftvPk4w/nbYLkki2NNVpY7UUKbBh6K/img.png&quot;&gt;&lt;img src=&quot;https://blog.kakaocdn.net/dn/oh8kS/btsFftvPk4w/nbYLkki2NNVpY7UUKbBh6K/img.png&quot; srcset=&quot;https://img1.daumcdn.net/thumb/R1280x0/?scode=mtistory2&amp;fname=https%3A%2F%2Fblog.kakaocdn.net%2Fdn%2Foh8kS%2FbtsFftvPk4w%2FnbYLkki2NNVpY7UUKbBh6K%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;1192&quot; height=&quot;795&quot; data-origin-width=&quot;1192&quot; data-origin-height=&quot;795&quot;/&gt;&lt;/span&gt;&lt;/figure&gt;
&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;위처럼 type을 'ENTER'로 정하고, roomNumber를 통해 ID값을 정해주었습니다.&lt;br /&gt;이를 통해, 해당 클라이언트는 1234라는 ID를 가질 수 있으며, 이제 1234로 들어오는 메세지는 해당 클라이언트로 들어오게 됩니다.&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;이를 통해 다른 클라이언트도 똑같이 등록할 수 있으며, 이제 서로 메세지를 주고 받게 된다면 아래와 같이 Response에 메세지가 들어오게 됩니다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;노랑색이 해당 클라이언트가 보낸 메세지이고, 파랑색이 해당 클라이언트가 다른 클라이언트로부터 메세지를 받은 것입니다.&lt;/p&gt;
&lt;p&gt;&lt;figure class=&quot;imageblock alignCenter&quot; data-ke-mobileStyle=&quot;widthOrigin&quot; data-origin-width=&quot;1539&quot; data-origin-height=&quot;1080&quot;&gt;&lt;span data-url=&quot;https://blog.kakaocdn.net/dn/wD62h/btsFg3b4Dqc/chD3x722duNKgxGfIVCaB0/img.png&quot; data-phocus=&quot;https://blog.kakaocdn.net/dn/wD62h/btsFg3b4Dqc/chD3x722duNKgxGfIVCaB0/img.png&quot;&gt;&lt;img src=&quot;https://blog.kakaocdn.net/dn/wD62h/btsFg3b4Dqc/chD3x722duNKgxGfIVCaB0/img.png&quot; srcset=&quot;https://img1.daumcdn.net/thumb/R1280x0/?scode=mtistory2&amp;fname=https%3A%2F%2Fblog.kakaocdn.net%2Fdn%2FwD62h%2FbtsFg3b4Dqc%2FchD3x722duNKgxGfIVCaB0%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;1539&quot; height=&quot;1080&quot; data-origin-width=&quot;1539&quot; data-origin-height=&quot;1080&quot;/&gt;&lt;/span&gt;&lt;/figure&gt;
&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;궁금하신점 있으시면 댓글 남겨주세요!&lt;br /&gt;저두 아직 웹소켓을 완벽히 이해한건 아니라서, 같이 공부해보면 재밌을거 같습니다 ㅎㅎ&lt;/p&gt;</description>
      <category>자바/스프링</category>
      <category>Postman</category>
      <category>stomp</category>
      <category>스프링</category>
      <category>웹소켓</category>
      <category>채팅</category>
      <category>핸들러</category>
      <author>tioon</author>
      <guid isPermaLink="true">https://tioon.tistory.com/189</guid>
      <comments>https://tioon.tistory.com/189#entry189comment</comments>
      <pubDate>Sun, 25 Feb 2024 20:40:35 +0900</pubDate>
    </item>
  </channel>
</rss>