<?xml version="1.0" encoding="UTF-8"?>
<rss version="2.0">
  <channel>
    <title>발자취</title>
    <link>https://tre2man.tistory.com/</link>
    <description>일상을 기록합니다.</description>
    <language>ko</language>
    <pubDate>Sat, 20 Jun 2026 20:15:07 +0900</pubDate>
    <generator>TISTORY</generator>
    <ttl>100</ttl>
    <managingEditor>트리맨스</managingEditor>
    <image>
      <title>발자취</title>
      <url>https://t1.daumcdn.net/cfile/tistory/99B4B14D5C6AC2AB29</url>
      <link>https://tre2man.tistory.com</link>
    </image>
    <item>
      <title>WIL (시작하는 마음)</title>
      <link>https://tre2man.tistory.com/376</link>
      <description>&lt;p data-ke-size=&quot;size16&quot;&gt;최근 개발을 하는 것에 대한 목적을 잃어버렸다는 생각이 많이 심하게 들었고, 이를 극복하기 위해 항해플러스에 등록을 진행했다. 이를 통해서 문제를 해결하는 능력과 좋은 개발자들과 많이 알게 되어 장기적으로 개발자로서 좋은 영향을 널리 퍼트리는 것이 목표이다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;이를 실현하기 위해 개인적으로 하루에 30분이라도 개발에 대한 나의 생각을 정리하는 시간을 가지려고 한다. 일을 진행하더라도 회고가 없으면 나의 성장에 걸림돌이 되기 때문이다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;항해플러스 과정을 무사히 마치고, 이를 기회로 삼아 장기적으로 도움이 되는 인사이트를 얻어낼 것이다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;</description>
      <category>항해</category>
      <author>트리맨스</author>
      <guid isPermaLink="true">https://tre2man.tistory.com/376</guid>
      <comments>https://tre2man.tistory.com/376#entry376comment</comments>
      <pubDate>Sun, 6 Jul 2025 22:23:13 +0900</pubDate>
    </item>
    <item>
      <title>SNS vs SQS</title>
      <link>https://tre2man.tistory.com/375</link>
      <description>&lt;p data-ke-size=&quot;size16&quot;&gt;최근 디커플링이 필요한 작업을 맡게 되었다. 이 때 보편적으로 사용한 AWS 서비스 중 SQS 를 사용하기로 했는데, 비슷한 서비스로 SNS 가 있는 것을 보았다. 그래서 SQS 와 SNS 의 차이점에 대해서 확실히 알고 가기로 했다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;h4 data-ke-size=&quot;size20&quot;&gt;서비스 사용 기반&lt;/h4&gt;
&lt;ul style=&quot;list-style-type: disc;&quot; data-ke-list-type=&quot;disc&quot;&gt;
&lt;li&gt;SQS 는 풀 기반이다. 수신자가 메시지를 직접 받아야 한다.&lt;/li&gt;
&lt;li&gt;SNS 는 푸시 기반이다.&amp;nbsp;&lt;/li&gt;
&lt;/ul&gt;
&lt;h4 data-ke-size=&quot;size20&quot;&gt;관계 유형&lt;/h4&gt;
&lt;ul style=&quot;list-style-type: disc;&quot; data-ke-list-type=&quot;disc&quot;&gt;
&lt;li&gt;SQS 는 한개의 소비자만 가능하다.&lt;/li&gt;
&lt;li&gt;SNS 는 구독자가 많다.&lt;/li&gt;
&lt;li&gt;공통적으로 메시지 생성자는 다수가 가능하다.&lt;/li&gt;
&lt;/ul&gt;
&lt;h4 data-ke-size=&quot;size20&quot;&gt;소비자&lt;/h4&gt;
&lt;ul style=&quot;list-style-type: disc;&quot; data-ke-list-type=&quot;disc&quot;&gt;
&lt;li&gt;SQS 는 SQS API 를 사용한다.&lt;/li&gt;
&lt;li&gt;SNS 는 다양한 목적지로 전송이 가능하다. 람다, SQS, 키테시스, HTTP, 문자메시지 등등...&lt;/li&gt;
&lt;/ul&gt;
&lt;h4 data-ke-size=&quot;size20&quot;&gt;보존&lt;/h4&gt;
&lt;ul style=&quot;list-style-type: disc;&quot; data-ke-list-type=&quot;disc&quot;&gt;
&lt;li&gt;SQS 는 일정 기간 동안 보존이 가능하다. 해당 기간 내 메시지를 확인하지 않으면 삭제된다.&lt;/li&gt;
&lt;li&gt;SNS 는 메시지 전달 보장을 하지 않는다.&lt;/li&gt;
&lt;/ul&gt;
&lt;h4 data-ke-size=&quot;size20&quot;&gt;재시도&lt;/h4&gt;
&lt;ul style=&quot;list-style-type: disc;&quot; data-ke-list-type=&quot;disc&quot;&gt;
&lt;li&gt;SQS 는 재시도 정책이 가능하다. DLQ 이동 전 재시도 횟수 정의가 가능하다.&lt;/li&gt;
&lt;li&gt;SNS 는 재시도를 할 수 없다.&lt;/li&gt;
&lt;/ul&gt;
&lt;h4 data-ke-size=&quot;size20&quot;&gt;배치&lt;/h4&gt;
&lt;ul style=&quot;list-style-type: disc;&quot; data-ke-list-type=&quot;disc&quot;&gt;
&lt;li&gt;SQS 는 배치가 가능하다. 표준 큐는 최대 10000개, FIFO 큐는 최대 10개가 가능하다.&lt;/li&gt;
&lt;li&gt;SNS 는 한번에 하나의 메시지만 처리 가능하다.&lt;/li&gt;
&lt;/ul&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;h3 data-ke-size=&quot;size23&quot;&gt;간단 정리&lt;/h3&gt;
&lt;hr contenteditable=&quot;false&quot; data-ke-type=&quot;horizontalRule&quot; data-ke-style=&quot;style5&quot; /&gt;
&lt;p data-ke-size=&quot;size18&quot;&gt;SQS 는 신뢰도를 중요하게 생각하는 작업 대기열, 배치 작업에 대해서 적합하다. 반면에 SNS 같은 경우에는 동시에 많은 구독자들에게 전달해야 하는 작업에 적합하다.&lt;/p&gt;
&lt;h4 data-ke-size=&quot;size20&quot;&gt;SQS 예시)&lt;/h4&gt;
&lt;p data-ke-size=&quot;size18&quot;&gt;투표 API -&amp;gt; SQS -&amp;gt; Lambda 에서 폴링, API 처리&lt;/p&gt;
&lt;h4 data-ke-size=&quot;size20&quot;&gt;SNS 예시)&lt;/h4&gt;
&lt;p data-ke-size=&quot;size18&quot;&gt;CloudWatch 알림 -&amp;gt; SNS -&amp;gt; SMS, 이메일, APP 알람 (재시도는 중요하게 생각하지 않음)&lt;/p&gt;
&lt;p data-ke-size=&quot;size18&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;h4 data-ke-size=&quot;size20&quot;&gt;팬아웃 패턴 (두 가지를 동시에 쓰는 예시)&lt;/h4&gt;
&lt;p data-ke-size=&quot;size18&quot;&gt;알림 생성 -&amp;gt; SNS -&amp;gt; 다수의 SQS&lt;/p&gt;
&lt;p data-ke-size=&quot;size18&quot;&gt;위와 같은 아키텍쳐를 구축하면 SNS 를 사용하여 다수의 Subscriber 에 대해서 이벤트 발송이 가능하고, 각각의 Subscriber 가 SQS 라면 해당 요청은 유실되지 않을 것이다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size18&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;h2 data-ke-size=&quot;size26&quot;&gt;참고 자료&lt;/h2&gt;
&lt;hr contenteditable=&quot;false&quot; data-ke-type=&quot;horizontalRule&quot; data-ke-style=&quot;style5&quot; /&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;a href=&quot;https://aws.amazon.com/ko/sqs/&quot; target=&quot;_blank&quot; rel=&quot;noopener&amp;nbsp;noreferrer&quot;&gt;https://aws.amazon.com/ko/sqs/&lt;/a&gt;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;a href=&quot;https://aws.amazon.com/ko/sns/&quot; target=&quot;_blank&quot; rel=&quot;noopener&amp;nbsp;noreferrer&quot;&gt;https://aws.amazon.com/ko/sns/&lt;/a&gt;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;a href=&quot;https://awsfundamentals.com/blog/aws-sns-vs-sqs-what-are-the-main-differences&quot; target=&quot;_blank&quot; rel=&quot;noopener&amp;nbsp;noreferrer&quot;&gt;https://awsfundamentals.com/blog/aws-sns-vs-sqs-what-are-the-main-differences&lt;/a&gt;&lt;/p&gt;</description>
      <category>서버 인프라/Aws</category>
      <author>트리맨스</author>
      <guid isPermaLink="true">https://tre2man.tistory.com/375</guid>
      <comments>https://tre2man.tistory.com/375#entry375comment</comments>
      <pubDate>Wed, 25 Jun 2025 00:04:32 +0900</pubDate>
    </item>
    <item>
      <title>AWS 에서 고가용성 MongoDB 구축하기</title>
      <link>https://tre2man.tistory.com/374</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;회사에서 MongoDB 를 구축할 필요가 생겼다. 원래 Atlas 를 사용해서 구축할 예정이였으나, 관리 포인트가 늘어난다는 점 때문에 직접 MongoDB 를 구축 하기로 했다. AWS 에서도 NOSQL 기반 DB 를 서비스하고 있었는데, DocumentDB 는 너무 비싸고 외부 접속이 안 되며 DynamoDB 는 사용 목적과 달라서 직접 구축하기로 했다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;h2 data-ke-size=&quot;size26&quot;&gt;사전 지식&lt;/h2&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;일단 나는 MongoDB 자체를 써 본 적이 없는 상황이라 MongoDB 및 NoSQL 에 대해서 간단히 정리가 필요했다.&lt;/p&gt;
&lt;h4 data-ke-size=&quot;size20&quot;&gt;NoSQL 장점&lt;/h4&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;/ol&gt;
&lt;h4 data-ke-size=&quot;size20&quot;&gt;NoSQL 단점&lt;/h4&gt;
&lt;ol style=&quot;list-style-type: decimal;&quot; data-ke-list-type=&quot;decimal&quot;&gt;
&lt;li&gt;DB 의 일관성이 부족하다.&lt;/li&gt;
&lt;li&gt;데이터의 중복으로 인해 데이터 update 의 속도가 느리다.&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;결론적으로 보면 RDS 와 사용 목적이 완전히 반하는 것으로 생각하면 될 것 같아 보인다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;그렇다면 이제 MongoDB 를 가용성 있게 구축하는 방법에 대해서 알아보자.&amp;nbsp;&lt;/p&gt;
&lt;h4 data-ke-size=&quot;size20&quot;&gt;MongoDB 설계&lt;/h4&gt;
&lt;p data-ke-size=&quot;size18&quot;&gt;공식 문서를 찾아 보니 PSS 방식과 PSA 방식이 있다고 나온다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size18&quot;&gt;PSS&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;/ul&gt;
&lt;p&gt;&lt;figure class=&quot;imageblock alignCenter&quot; data-ke-mobileStyle=&quot;widthOrigin&quot; data-origin-width=&quot;740&quot; data-origin-height=&quot;280&quot;&gt;&lt;span data-url=&quot;https://blog.kakaocdn.net/dn/bt1ue4/btsNjMlX5jm/3IKZ6TcEBpOklB2ODm5og0/img.jpg&quot; data-phocus=&quot;https://blog.kakaocdn.net/dn/bt1ue4/btsNjMlX5jm/3IKZ6TcEBpOklB2ODm5og0/img.jpg&quot;&gt;&lt;img src=&quot;https://blog.kakaocdn.net/dn/bt1ue4/btsNjMlX5jm/3IKZ6TcEBpOklB2ODm5og0/img.jpg&quot; srcset=&quot;https://img1.daumcdn.net/thumb/R1280x0/?scode=mtistory2&amp;fname=https%3A%2F%2Fblog.kakaocdn.net%2Fdn%2Fbt1ue4%2FbtsNjMlX5jm%2F3IKZ6TcEBpOklB2ODm5og0%2Fimg.jpg&quot; onerror=&quot;this.onerror=null; this.src='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png'; this.srcset='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png';&quot; loading=&quot;lazy&quot; width=&quot;740&quot; height=&quot;280&quot; data-origin-width=&quot;740&quot; data-origin-height=&quot;280&quot;/&gt;&lt;/span&gt;&lt;/figure&gt;
&lt;figure class=&quot;imageblock alignCenter&quot; data-ke-mobileStyle=&quot;widthOrigin&quot; data-origin-width=&quot;660&quot; data-origin-height=&quot;500&quot;&gt;&lt;span data-url=&quot;https://blog.kakaocdn.net/dn/lOjQD/btsNgxp95zp/gbh3HXeO6ILdB3Miuyepk0/img.jpg&quot; data-phocus=&quot;https://blog.kakaocdn.net/dn/lOjQD/btsNgxp95zp/gbh3HXeO6ILdB3Miuyepk0/img.jpg&quot;&gt;&lt;img src=&quot;https://blog.kakaocdn.net/dn/lOjQD/btsNgxp95zp/gbh3HXeO6ILdB3Miuyepk0/img.jpg&quot; srcset=&quot;https://img1.daumcdn.net/thumb/R1280x0/?scode=mtistory2&amp;fname=https%3A%2F%2Fblog.kakaocdn.net%2Fdn%2FlOjQD%2FbtsNgxp95zp%2Fgbh3HXeO6ILdB3Miuyepk0%2Fimg.jpg&quot; onerror=&quot;this.onerror=null; this.src='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png'; this.srcset='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png';&quot; loading=&quot;lazy&quot; width=&quot;660&quot; height=&quot;500&quot; data-origin-width=&quot;660&quot; data-origin-height=&quot;500&quot;/&gt;&lt;/span&gt;&lt;/figure&gt;
&lt;/p&gt;
&lt;p data-ke-size=&quot;size18&quot;&gt;PAA&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;/ul&gt;
&lt;p&gt;&lt;figure class=&quot;imageblock alignCenter&quot; data-ke-mobileStyle=&quot;widthOrigin&quot; data-origin-width=&quot;740&quot; data-origin-height=&quot;280&quot;&gt;&lt;span data-url=&quot;https://blog.kakaocdn.net/dn/cCc5gZ/btsNgwY5hjQ/opoCJWEKomH9jGeT4Qzbsk/img.jpg&quot; data-phocus=&quot;https://blog.kakaocdn.net/dn/cCc5gZ/btsNgwY5hjQ/opoCJWEKomH9jGeT4Qzbsk/img.jpg&quot;&gt;&lt;img src=&quot;https://blog.kakaocdn.net/dn/cCc5gZ/btsNgwY5hjQ/opoCJWEKomH9jGeT4Qzbsk/img.jpg&quot; srcset=&quot;https://img1.daumcdn.net/thumb/R1280x0/?scode=mtistory2&amp;fname=https%3A%2F%2Fblog.kakaocdn.net%2Fdn%2FcCc5gZ%2FbtsNgwY5hjQ%2FopoCJWEKomH9jGeT4Qzbsk%2Fimg.jpg&quot; onerror=&quot;this.onerror=null; this.src='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png'; this.srcset='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png';&quot; loading=&quot;lazy&quot; width=&quot;740&quot; height=&quot;280&quot; data-origin-width=&quot;740&quot; data-origin-height=&quot;280&quot;/&gt;&lt;/span&gt;&lt;/figure&gt;
&lt;figure class=&quot;imageblock alignCenter&quot; data-ke-mobileStyle=&quot;widthOrigin&quot; data-origin-width=&quot;660&quot; data-origin-height=&quot;500&quot;&gt;&lt;span data-url=&quot;https://blog.kakaocdn.net/dn/cnCImn/btsNiNlUGg8/QjE49KRUJ1sKSoMhPWgxQ1/img.jpg&quot; data-phocus=&quot;https://blog.kakaocdn.net/dn/cnCImn/btsNiNlUGg8/QjE49KRUJ1sKSoMhPWgxQ1/img.jpg&quot;&gt;&lt;img src=&quot;https://blog.kakaocdn.net/dn/cnCImn/btsNiNlUGg8/QjE49KRUJ1sKSoMhPWgxQ1/img.jpg&quot; srcset=&quot;https://img1.daumcdn.net/thumb/R1280x0/?scode=mtistory2&amp;fname=https%3A%2F%2Fblog.kakaocdn.net%2Fdn%2FcnCImn%2FbtsNiNlUGg8%2FQjE49KRUJ1sKSoMhPWgxQ1%2Fimg.jpg&quot; onerror=&quot;this.onerror=null; this.src='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png'; this.srcset='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png';&quot; loading=&quot;lazy&quot; width=&quot;660&quot; height=&quot;500&quot; data-origin-width=&quot;660&quot; data-origin-height=&quot;500&quot;/&gt;&lt;/span&gt;&lt;/figure&gt;
&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;나의 경우에는 조금 더 고가용성을 유지할 수 있는 PSS 방식을 선택했다.&lt;/p&gt;
&lt;h4 data-ke-size=&quot;size20&quot;&gt;아키텍쳐 설계&lt;/h4&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;데이터가 적재되는 양, 가용성, 예산을 고려할 때 다음과 같은 방식이 추진되었다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;먼저 데이터 적재는 연 1GB 정도 될 예정이라, 각 인스턴스마다 100GB 의 EBS 볼륨을 할당했다. 그리고 예산을 최대한 절감하여 최소한으로 고가용성을 유지하기 위해 3개의 인스턴스를 실행한다. 각 인스턴스는 다른 AZ 의 서브넷에 존재하며, 각 인스턴스에 1개의 mongo 프로세스를 실행한다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;한개의 인스턴스에는 한개의 몽고 프로세스가 도커 위에서 실행되며, 실제 데이터는 EBS 볼륨에 마운트하여 도커 프로세스가 종료되어도 데이터는 그대로 보존되게 했다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;프라이머리는 27017 포트로 외부에서 접속 가능하게 설정했으며, IP 소스 기반으로 보안그룹에 화이트리스트를 설정했다. 나머지 두개의 서브넷에 있는 세컨더리 서브넷은 프라이빗 서브넷으로 설정했으며, 여기 접속하기 위해서는 Bastion host 를 사용해야 한다. 세컨더리 서브넷의 보안그룹은 내부 네트워크의 요청만 열어 두었다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;간단하게 각 노드의 인프라 환경은 다음과 같은 그림으로 나타낼 수 있다.&lt;/p&gt;
&lt;p&gt;&lt;figure class=&quot;imageblock alignCenter&quot; data-ke-mobileStyle=&quot;widthOrigin&quot; data-filename=&quot;스크린샷 2025-04-12 11.08.42.png&quot; data-origin-width=&quot;2188&quot; data-origin-height=&quot;958&quot;&gt;&lt;span data-url=&quot;https://blog.kakaocdn.net/dn/qscwr/btsNjG7nAkB/Pp2nmMf0HVWhRazjk3NMXK/img.png&quot; data-phocus=&quot;https://blog.kakaocdn.net/dn/qscwr/btsNjG7nAkB/Pp2nmMf0HVWhRazjk3NMXK/img.png&quot;&gt;&lt;img src=&quot;https://blog.kakaocdn.net/dn/qscwr/btsNjG7nAkB/Pp2nmMf0HVWhRazjk3NMXK/img.png&quot; srcset=&quot;https://img1.daumcdn.net/thumb/R1280x0/?scode=mtistory2&amp;fname=https%3A%2F%2Fblog.kakaocdn.net%2Fdn%2Fqscwr%2FbtsNjG7nAkB%2FPp2nmMf0HVWhRazjk3NMXK%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;2188&quot; height=&quot;958&quot; data-filename=&quot;스크린샷 2025-04-12 11.08.42.png&quot; data-origin-width=&quot;2188&quot; data-origin-height=&quot;958&quot;/&gt;&lt;/span&gt;&lt;/figure&gt;
&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;각 노드를 실행하기 위한 docker-compose 파일은 다음과 같다.&lt;/p&gt;
&lt;pre id=&quot;code_1744424108900&quot; class=&quot;html xml&quot; data-ke-language=&quot;html&quot; data-ke-type=&quot;codeblock&quot;&gt;&lt;code&gt;services:
  mongo:
    image: mongo:8.0.6
    container_name: mongo
    restart: always
    network_mode: host
    ports:
      - &quot;27017:27017&quot;
    volumes:
      - ./data/db:/data/db
      - ./data/configdb:/data/configdb
      - ./mongo-keyfile:/etc/mongo-keyfile
    environment:
      MONGO_INITDB_ROOT_USERNAME: user
      MONGO_INITDB_ROOT_PASSWORD: password
    command: [&quot;mongod&quot;, &quot;--replSet&quot;, &quot;rs0&quot;, &quot;--bind_ip_all&quot;, &quot;--keyFile&quot;, &quot;/etc/mongo-keyfile&quot;]&lt;/code&gt;&lt;/pre&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;network mode 를 host 로 설정해야 인스턴스와 네트워크망 동기화가 되어 각 노드끼리 통신이 가능하다.&lt;/li&gt;
&lt;li&gt;포트는 기본 포트인 27017 을 사용한다.&lt;/li&gt;
&lt;li&gt;mongodb 의 데이터는 ebs 와 동기화 한다.&lt;/li&gt;
&lt;li&gt;rs0 의 이름을 가진 레플리카 셋을 설정한다.&lt;/li&gt;
&lt;li&gt;bind_ip_all 을 통해 ipv6 지원&lt;/li&gt;
&lt;li&gt;keyFile : 복제본 세트 인증 시 사용할 키파일&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;pre id=&quot;code_1744424530138&quot; class=&quot;html xml&quot; data-ke-language=&quot;html&quot; data-ke-type=&quot;codeblock&quot;&gt;&lt;code&gt;# mongo shell 접속

# 복제본 활성화
rs.initiate()

# secondary node 의 정보 입력
rs.add({ host: &quot;INTERNAL_IP:27017&quot;, priority: 0 })
rs.add({ host: &quot;INTERNAL_IP:27017&quot;, priority: 0 })&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;프라이머리에서 rs.status() 를 실행했을 때 다음과 같은 내용이 뜬다면 PSS 구축이 완료가 된 것이다.&lt;/p&gt;
&lt;pre id=&quot;code_1744424723466&quot; class=&quot;html xml&quot; data-ke-language=&quot;html&quot; data-ke-type=&quot;codeblock&quot;&gt;&lt;code&gt;{
  set: 'rs0',
  members: [
    {
      stateStr: 'PRIMARY',
      ...etc
    },
    {
      stateStr: 'SECONDARY',
      lastHeartbeatRecv: ISODate('2025-04-11T08:47:48.650Z'),
      ...etc
    },
    {
      stateStr: 'SECONDARY',
      lastHeartbeatRecv: ISODate('2025-04-11T08:47:49.528Z'),
      ...etc
    }
  ],
	...etc
}&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;세컨더리 노드의 heartbeatRecv 가 현재 시간으로 잘 보인다면, 구축이 완료된 것이다. 각자 간단히 읽기 요청을 했을 때 데이터 복제가 잘 되는지 확인하고, 프라이머리 노드가 죽었을 때 세컨더리 노드가 잘 동작한다면 PSS 구축이 잘 된 것이다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;이제 여기에 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;</description>
      <category>서버 인프라/Aws</category>
      <author>트리맨스</author>
      <guid isPermaLink="true">https://tre2man.tistory.com/374</guid>
      <comments>https://tre2man.tistory.com/374#entry374comment</comments>
      <pubDate>Sat, 12 Apr 2025 11:27:37 +0900</pubDate>
    </item>
    <item>
      <title>2024년 연말 개발 회고록</title>
      <link>https://tre2man.tistory.com/373</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;2025년에 쓰는 어딘가 이상한 연말 개발 회고록이다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;2024년은 참으로 다사다난 해 였던 것 같다. 올해 초부터 한번 복기를 해 보자.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;올해 초에는 이전 직장의 재정 상황이 안 좋아져서 살면서 처름으로 정리해고를 당했다. 해고를 당하고 나서 한 2달 동안은 아무 생각이 없던 것 같다. 일단은 다시 백수가 된 몸이라서 몇 달은 좀 쉬고 싶었다. 다행히도 실업급여도 받을 수 있어서 백수인 상태임에도 불구하고 돈이 계속 들어와서, 심적으로 안정되기는 했다. 이 때 개인적인 개발도 종종 하면서 이래저래 심적 안정을 받을 수 있었다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;하지만 다시 회사를 다니기 위해서 이래저래 이력서도 많이 쓰고 면접도 보러 갔는데, 시장이 차갑다는 것을 잘 알수 있는 상태였다. 기술 스택이 맞는 곳도 별로 없었고, 실제로 채용 공고도 잘 나오지 않았다. 하지만 이러한 일들이 있기에 내 수준을 잘 알수 있었고, 나에 대한 객관적 판단이 되었다. 결국에는 현재 회사에 취직을 하게 되었다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;다시 실무 코드를 보면서 좌절...했었지만...이러한 로직을 다시 개선할 수 있다는 생각이 드니까 또 나쁜 경험은 아니라고 생각이 되었다. 이러한 레거시가 있다는 것은 오히려 개선할 게 많고, 개선하면서 성장할 수 있는 기회라고 생각이 들었다. 여기에 AWS 자격증도 따면 좋을 것 같아서, 자격증 공부도 해 두어 자격증 취득도 생각해 보고 있다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;2025년에도 모든 일 무사히 잘 끝났으면 좋겠다!&lt;/p&gt;
&lt;p data-ke-size=&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;</description>
      <author>트리맨스</author>
      <guid isPermaLink="true">https://tre2man.tistory.com/373</guid>
      <comments>https://tre2man.tistory.com/373#entry373comment</comments>
      <pubDate>Wed, 1 Jan 2025 13:49:38 +0900</pubDate>
    </item>
    <item>
      <title>PM2 와 Cloudwatch 연동하여 로그 확인하기</title>
      <link>https://tre2man.tistory.com/372</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;현재 사내 프로그램은 PM2를 사용하여 배포를 하고 있다. 이 때 PM2 로그를 로컬에서 트래킹 하는 것이 아닌, CloudWatch를 사용해서 인스턴스가 아닌 인스턴스 외부에 로그를 저장하고, 거기서 로그를 확인하는 인프라를 간단히 구축해 보았다. 전체적인 기획은 다음과 같다.&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;NestJS 로그를 배포 환경에서는 JSON으로 출력하게 하기&lt;/li&gt;
&lt;li&gt;EC2 인스턴스에 Cloudwach Agent 설치&lt;/li&gt;
&lt;li&gt;CloudWatch 에서 로그를 잘 출력하는지 확인하기&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;해당 시스템 구축 이후 진행한 과정을 정리해 보았다.&lt;/p&gt;
&lt;hr contenteditable=&quot;false&quot; data-ke-type=&quot;horizontalRule&quot; data-ke-style=&quot;style5&quot; /&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;먼저 pm2 로그는 ~/.pm2/log 에 쌓이게 된다. 해당 폴더를 조회하면 app 이름을 가진 파일을 확인할 수 있다. 해당 로그 파일을 CloudWatch 에 지속적으로 전송시키면 될 것 같다.&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;여기서 더 나아가 날짜별로 로그 그룹이 생성되게 시도해 보았으나, 해당 방법은 매 00시마다 CloudwachAgent 서비스를 재시작해야 해서 딱히 좋은 방법은 아닌 것 같았다. 대신 로그 그룹의 보존 기간을 14일로 하고, stdout 과 stderr 를 분리해서 저장하기로 했다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;그 다음으로는 인스턴스에 Cloudwatch Agent를 설치했다. 해당 문서는 다음 링크에서 찾을 수 있다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;a href=&quot;https://docs.aws.amazon.com/AmazonCloudWatch/latest/monitoring/install-CloudWatch-Agent-commandline-fleet.html&quot; target=&quot;_blank&quot; rel=&quot;noopener&amp;nbsp;noreferrer&quot;&gt;https://docs.aws.amazon.com/AmazonCloudWatch/latest/monitoring/install-CloudWatch-Agent-commandline-fleet.html&lt;/a&gt;&lt;/p&gt;
&lt;figure id=&quot;og_1735141606891&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;Install and run the CloudWatch agent on your servers - Amazon CloudWatch&quot; data-og-description=&quot;Install and run the CloudWatch agent on your servers After you create your agent configuration file for an IAM role or IAM user, use the following steps to install and run the CloudWatch agent on your servers with that configuration. First, attach an IAM r&quot; data-og-host=&quot;docs.aws.amazon.com&quot; data-og-source-url=&quot;https://docs.aws.amazon.com/AmazonCloudWatch/latest/monitoring/install-CloudWatch-Agent-commandline-fleet.html&quot; data-og-url=&quot;https://docs.aws.amazon.com/AmazonCloudWatch/latest/monitoring/install-CloudWatch-Agent-commandline-fleet.html&quot; data-og-image=&quot;&quot;&gt;&lt;a href=&quot;https://docs.aws.amazon.com/AmazonCloudWatch/latest/monitoring/install-CloudWatch-Agent-commandline-fleet.html&quot; target=&quot;_blank&quot; rel=&quot;noopener&quot; data-source-url=&quot;https://docs.aws.amazon.com/AmazonCloudWatch/latest/monitoring/install-CloudWatch-Agent-commandline-fleet.html&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;Install and run the CloudWatch agent on your servers - Amazon CloudWatch&lt;/p&gt;
&lt;p class=&quot;og-desc&quot; data-ke-size=&quot;size16&quot;&gt;Install and run the CloudWatch agent on your servers After you create your agent configuration file for an IAM role or IAM user, use the following steps to install and run the CloudWatch agent on your servers with that configuration. First, attach an IAM r&lt;/p&gt;
&lt;p class=&quot;og-host&quot; data-ke-size=&quot;size16&quot;&gt;docs.aws.amazon.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;다음으로는 Cloudwatch 초기 설정이 필요하다. 이 때&amp;nbsp; config 파일을 생성할 때 ~ 경로는 사용하지 못한다. 절대 경로를 사용하자. 아래는 내가 사용한 예시 파일이다.&lt;/p&gt;
&lt;pre id=&quot;code_1735142374531&quot; class=&quot;sql&quot; data-ke-language=&quot;sql&quot; data-ke-type=&quot;codeblock&quot;&gt;&lt;code&gt;{
    &quot;agent&quot;: {
        &quot;metrics_collection_interval&quot;: 10,
        &quot;logfile&quot;: &quot;/opt/aws/amazon-cloudwatch-agent/logs/amazon-cloudwatch-agent.log&quot;
    },
    &quot;metrics&quot;: {
        &quot;append_dimensions&quot;: {
            &quot;InstanceId&quot;: &quot;${aws:InstanceId}&quot;,
            &quot;AutoScalingGroupName&quot;: &quot;${aws:AutoScalingGroupName}&quot;
        },
        &quot;metrics_collected&quot;: {
            &quot;cpu&quot;: {
                &quot;measurement&quot;: [
                    &quot;cpu_usage_idle&quot;,
                    &quot;cpu_usage_iowait&quot;
                ],
                &quot;metrics_collection_interval&quot;: 10,
                &quot;resources&quot;: [
                    &quot;*&quot;
                ]
            },
            &quot;disk&quot;: {
                &quot;measurement&quot;: [
                    &quot;used_percent&quot;
                ],
                &quot;metrics_collection_interval&quot;: 10,
                &quot;resources&quot;: [
                    &quot;/&quot;
                ]
            },
            &quot;mem&quot;: {
                &quot;measurement&quot;: [
                    &quot;mem_used_percent&quot;
                ],
                &quot;metrics_collection_interval&quot;: 10
            }
        }
    },
    &quot;logs&quot;: {
        &quot;logs_collected&quot;: {
            &quot;files&quot;: {
                &quot;collect_list&quot;: [
                    {
                        &quot;file_path&quot;: &amp;ldquo;home/ubuntu/.pm2/logs/로그 파일&quot;,
                        &quot;log_group_name&quot;: &quot;로그 그룹 이름&quot;,
                        &quot;log_stream_name&quot;: &quot;{instance_id}&quot;,
                        &quot;timezone&quot;: &quot;UTC&quot;
                    },
                                        {
                        &quot;file_path&quot;: &amp;ldquo;/home/ubuntu/.pm2/logs/에러 로그 파일&quot;,
                        &quot;log_group_name&quot;: &quot;에러 로그 그룹 이름&quot;,
                        &quot;log_stream_name&quot;: &quot;{instance_id}&quot;,
                        &quot;timezone&quot;: &quot;UTC&quot;
                    }
                ]
            }
        }
    }
}&lt;/code&gt;&lt;/pre&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;설정파일을 적용하고 Cloudwatch Agent를 실행시키면 된다.&lt;/p&gt;
&lt;pre id=&quot;code_1735142447893&quot; class=&quot;sql&quot; data-ke-language=&quot;sql&quot; data-ke-type=&quot;codeblock&quot;&gt;&lt;code&gt;sudo /opt/aws/amazon-cloudwatch-agent/bin/amazon-cloudwatch-agent-ctl     -a fetch-config     -m ec2     -c file:/opt/aws/amazon-cloudwatch-agent/bin/config.json     -s&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;다음으로는 인스턴스에 IAM 역할을 적용해야 한다. 이 때 EC2에서만 사용할 역할을 만든 후에, 해당 역할에 정책을 추가하여 사용한다. 클라우드 와치에 로그를 생성하는 정책은 다음과 같이 설정했다.&lt;/p&gt;
&lt;pre id=&quot;code_1735142629586&quot; class=&quot;sql&quot; data-ke-language=&quot;sql&quot; data-ke-type=&quot;codeblock&quot;&gt;&lt;code&gt;{
    &quot;Version&quot;: &quot;2012-10-17&quot;,
    &quot;Statement&quot;: [
        {
            &quot;Sid&quot;: &quot;VisualEditor0&quot;,
            &quot;Effect&quot;: &quot;Allow&quot;,
            &quot;Action&quot;: [
                &quot;logs:CreateLogGroup&quot;,
                &quot;logs:CreateLogStream&quot;,
                &quot;logs:PutLogEvents&quot;,
                &quot;logs:DescribeLogGroups&quot;,
                &quot;logs:DescribeLogStreams&quot;,
                &quot;cloudwatch:PutMetricData&quot;,
                &quot;ec2:DescribeTags&quot;,
                &quot;ec2:DescribeInstances&quot;,
                &quot;ec2:DescribeVolumes&quot;
            ],
            &quot;Resource&quot;: &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;해당 정책을 ec2에 연결시켜 두면은, CloudWatch에 로그를 올리고 IMDS 를 통해 인스턴스의 정보를 불러올 수 있다. (IMDS 는 외부 공격에 취약하기 때문에 NACL을 잘 설정해 두어야 한다)&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;이제 CloudWatch로 가게 되면 로그 스트림이 생기고, Logs instight를 통해서 로그를 필터링 할 수 있을 것이다.&lt;/p&gt;
&lt;p data-ke-size=&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;</description>
      <category>서버 인프라/모니터링</category>
      <author>트리맨스</author>
      <guid isPermaLink="true">https://tre2man.tistory.com/372</guid>
      <comments>https://tre2man.tistory.com/372#entry372comment</comments>
      <pubDate>Thu, 26 Dec 2024 01:08:38 +0900</pubDate>
    </item>
    <item>
      <title>Slow query 개선 경험기</title>
      <link>https://tre2man.tistory.com/371</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;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;현재 회사에서 운영하는 서버에서 특정 쿼리 (유저의 구매 데이터의 요약본을 알려줌) 에서 상당히 느린 쿼리가 있다는 것을 알게 되었다. 서비스 특성상 해당 쿼리를 자주 호출할 일은 없지만, 평균 10초정도 걸리는 쿼리가 있다는 것 조차 문제가 있다고 생각하여 해당 쿼리를 확인해 보았다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;pre id=&quot;code_1730209157465&quot; class=&quot;sql&quot; data-ke-language=&quot;sql&quot; data-ke-type=&quot;codeblock&quot;&gt;&lt;code&gt;SELECT
  DISTINCT &quot;i&quot;.*
FROM
  (
    SELECT
      &quot;department&quot;.&quot;name&quot; AS &quot;target&quot;,
      &quot;department&quot;.&quot;id&quot; AS &quot;id&quot;
    FROM
      &quot;order_info_entity&quot; &quot;oi&quot;
      INNER JOIN &quot;order_sheet_bundle_entity&quot; &quot;bundle&quot; ON &quot;bundle&quot;.&quot;orderInfoId&quot; = &quot;oi&quot;.&quot;id&quot;
      ... 그 외 5개 정도의 INNER JOIN 존재 (각 테이블마다 데이터가 10만~100만개 정도 존재)
    WHERE
      (
        (
          SELECT
            COUNT(&quot;orderCancel&quot;.&quot;id&quot;)
          FROM
            &quot;order_cancel_entity&quot; &quot;orderCancel&quot;
          WHERE
            ...orderCacel에 대한 단순한 where
        ) = 0
        ... 그 외 3개 정도의 and 연산자
      )
      AND (&quot;oi&quot;.&quot;deleteAt&quot; IS NULL)
  ) &quot;i&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; 특이한 점이 보인다. 전체 where 절에서 orderCancel의 수를 count하여 0인지 판별하는 로직이 들어가있다. 해당 로직이 문제가 되는 것이 inner join된 데이터에서 where로 데이터를 필터링할 때 마다 해당 쿼리가 실행되게 된다. 즉, 말도 안되게 많은 연산이 실행되는 것이다. 해당 쿼리를 explain anayse 해서 실행 계획을 보면 첫번째 줄은 다음과 같이 보인다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;pre id=&quot;code_1730209665228&quot; class=&quot;sql&quot; data-ke-language=&quot;sql&quot; data-ke-type=&quot;codeblock&quot;&gt;&lt;code&gt;Unique  (cost=13534.71..2088324.35 rows=1 width=19) (actual time=275.516..7426.945 rows=3 loops=1)&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;최종 cost가 200만이 나온다. 절대로 답이 없다. 중간에 select count 쿼리를 보니 다행히 join이 가능해서, left join을 수행한 다음 orderCancel에 대한 조건절을 추가하는 방식으로 쿼리를 변경했다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;여기서 추가적으로 알게된 점은 inner join 하는 테이블에서 where절의 위치를 변경한다고 해서 쿼리의 실행 계획이 개선되지는 않는다는 것 이였다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;위의 정보들을 바탕으로 쿼리를 개선했고, 쿼리와 결과를 확인해 보았다.&lt;/p&gt;
&lt;pre id=&quot;code_1730210906760&quot; class=&quot;sql&quot; data-ke-language=&quot;sql&quot; data-ke-type=&quot;codeblock&quot;&gt;&lt;code&gt;SELECT DISTINCT &quot;i&quot;.*
FROM (
    SELECT
        &quot;department&quot;.&quot;name&quot; AS &quot;target&quot;,
        &quot;department&quot;.&quot;id&quot; AS &quot;id&quot;
    FROM &quot;department_entity&quot; &quot;department&quot;
    INNER JOIN &quot;order_info_entity&quot; &quot;oi&quot; ON &quot;oi&quot;.&quot;departmentId&quot; = &quot;department&quot;.&quot;id&quot;
    ... 다수의 INNER JOIN
    LEFT JOIN &quot;order_cancel_entity&quot; &quot;orderCancel&quot; ON &quot;orderCancel&quot;.&quot;orderSheetId&quot; = &quot;orderSheet&quot;.&quot;id&quot;
    ... LEFT JOIN 조건절
    WHERE
        &quot;department&quot;.&quot;status&quot; = 'ACTIVATE'
        AND &quot;department&quot;.&quot;deleteAt&quot; IS NULL
        AND &quot;orderCancel&quot;.&quot;id&quot; IS NULL
) &quot;i&quot;;&lt;/code&gt;&lt;/pre&gt;
&lt;pre id=&quot;code_1730210936746&quot; class=&quot;sql&quot; data-ke-language=&quot;sql&quot; data-ke-type=&quot;codeblock&quot;&gt;&lt;code&gt;Unique  (cost=13341.45..13341.46 rows=1 width=19) (actual time=1121.992..1122.122 rows=3 loops=1)&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;최종 cost는 13000정도 나오고 실행 시간은 1.1초 정도가 나오게 된다. 하지만 해당 쿼리도 개선할 점이 있을 것이라고 생각하여, 인덱스를 추가하여 join 속도를 높이기로 했다.&lt;br /&gt;&lt;br /&gt;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;인덱스를 추가하기 전 query plan의 첫번째 줄은 다음과 같다.&lt;/p&gt;
&lt;pre id=&quot;code_1730210995879&quot; class=&quot;sql&quot; data-ke-language=&quot;sql&quot; data-ke-type=&quot;codeblock&quot;&gt;&lt;code&gt;Unique  (cost=13341.45..13341.46 rows=1 width=19) (actual time=1121.992..1122.122 rows=3 loops=1)&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;여기에 join하는 각 테이블에 대하여 모두 복합 컬럼 인덱스를 추가한 이후 query plan을 실행해 보았다.&lt;/p&gt;
&lt;pre id=&quot;code_1730211266185&quot; class=&quot;sql&quot; data-ke-language=&quot;sql&quot; data-ke-type=&quot;codeblock&quot;&gt;&lt;code&gt;Unique  (cost=12794.11..12794.12 rows=1 width=19) (actual time=54.479..59.815 rows=3 loops=1)&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;여기서 query plan의 맨 윗부분을 보면은 최종 cost는 13300 과 12794로 차이는 많이 나지 않는 것을 볼 수 있다. 하지만 actual time을 보게 되면 1100ms 와 59ms로 엄청난 속도의 차이를 보여준다. 이에 대한 원인을 찾기 위해서 query plan visualize를 실행해 보았다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p&gt;&lt;figure class=&quot;imagegridblock&quot;&gt;
  &lt;div class=&quot;image-container&quot;&gt;&lt;span data-url=&quot;https://blog.kakaocdn.net/dn/AOc16/btsKn4c2V0Y/ernkX0HNqeVdOxmEz3cx81/img.png&quot; data-phocus=&quot;https://blog.kakaocdn.net/dn/AOc16/btsKn4c2V0Y/ernkX0HNqeVdOxmEz3cx81/img.png&quot; data-is-animation=&quot;false&quot; data-origin-width=&quot;893&quot; data-origin-height=&quot;678&quot; data-filename=&quot;스크린샷 2024-10-29 23.29.21.png&quot; style=&quot;width: 57.7635%; margin-right: 10px;&quot; data-widthpercent=&quot;58.44&quot;&gt;&lt;img src=&quot;https://blog.kakaocdn.net/dn/AOc16/btsKn4c2V0Y/ernkX0HNqeVdOxmEz3cx81/img.png&quot; srcset=&quot;https://img1.daumcdn.net/thumb/R1280x0/?scode=mtistory2&amp;amp;fname=https%3A%2F%2Fblog.kakaocdn.net%2Fdn%2FAOc16%2FbtsKn4c2V0Y%2FernkX0HNqeVdOxmEz3cx81%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;893&quot; height=&quot;678&quot;/&gt;&lt;/span&gt;&lt;span data-url=&quot;https://blog.kakaocdn.net/dn/Ty1Ew/btsKpmKipeJ/ncWMpI7yTeqX5pVG370TDk/img.png&quot; data-phocus=&quot;https://blog.kakaocdn.net/dn/Ty1Ew/btsKpmKipeJ/ncWMpI7yTeqX5pVG370TDk/img.png&quot; data-is-animation=&quot;false&quot; data-origin-width=&quot;679&quot; data-origin-height=&quot;725&quot; data-filename=&quot;스크린샷 2024-10-29 23.29.30.png&quot; style=&quot;width: 41.0737%;&quot; data-widthpercent=&quot;41.56&quot;&gt;&lt;img src=&quot;https://blog.kakaocdn.net/dn/Ty1Ew/btsKpmKipeJ/ncWMpI7yTeqX5pVG370TDk/img.png&quot; srcset=&quot;https://img1.daumcdn.net/thumb/R1280x0/?scode=mtistory2&amp;amp;fname=https%3A%2F%2Fblog.kakaocdn.net%2Fdn%2FTy1Ew%2FbtsKpmKipeJ%2FncWMpI7yTeqX5pVG370TDk%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;679&quot; height=&quot;725&quot;/&gt;&lt;/span&gt;&lt;/div&gt;
&lt;/figure&gt;
&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;두 쿼리의 차이를 보면 nested loop의 유무의 차이가 존재한다. 인덱스를 통해서 결과값의 성능을 향상시킬 수 있었다.&lt;/p&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;최초 실행 속도 : 평균 10초 (최대 300초;;)&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;</description>
      <category>서버 인프라/DB</category>
      <author>트리맨스</author>
      <guid isPermaLink="true">https://tre2man.tistory.com/371</guid>
      <comments>https://tre2man.tistory.com/371#entry371comment</comments>
      <pubDate>Wed, 30 Oct 2024 00:05:28 +0900</pubDate>
    </item>
    <item>
      <title>AWS 운영중인 도메인 호스팅 이전하기</title>
      <link>https://tre2man.tistory.com/370</link>
      <description>&lt;h3 data-ke-size=&quot;size23&quot;&gt;&amp;nbsp;&lt;/h3&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;현재 상황은 예전 계정인 A에 호스팅 되어 있는 설정들을 현재 계정인 B 계정으로 모두 이동하는 작업이 필요한 상태이다. 현재 생성된 레코드는 100개가 조금 넘는 상황으로, 수동으로 옮기기에는 문제가 있는 상황이다. 그래서 AWS의 document를 참고하여 호스팅 영역을 이전한 내용을 정리하려 한다. 다음의 문서를 참고했다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;a href=&quot;https://docs.aws.amazon.com/ko_kr/Route53/latest/DeveloperGuide/hosted-zones-migrating.html&quot; target=&quot;_blank&quot; rel=&quot;noopener&amp;nbsp;noreferrer&quot;&gt;https://docs.aws.amazon.com/ko_kr/Route53/latest/DeveloperGuide/hosted-zones-migrating.html&lt;/a&gt;&lt;/p&gt;
&lt;figure id=&quot;og_1727707940936&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;호스팅 영역을 다른 AWS 계정으로 이전 - Amazon Route 53&quot; data-og-description=&quot;새 호스팅 영역을 위한 이름 서버를 사용하도록 도메인 등록을 업데이트하지 않으면 Route&amp;nbsp;53가 계속 기존 호스팅 영역을 사용하여 도메인에 대한 트래픽을 라우팅하게 됩니다. 도메인 등록의 이&quot; data-og-host=&quot;docs.aws.amazon.com&quot; data-og-source-url=&quot;https://docs.aws.amazon.com/ko_kr/Route53/latest/DeveloperGuide/hosted-zones-migrating.html&quot; data-og-url=&quot;https://docs.aws.amazon.com/ko_kr/Route53/latest/DeveloperGuide/hosted-zones-migrating.html&quot; data-og-image=&quot;&quot;&gt;&lt;a href=&quot;https://docs.aws.amazon.com/ko_kr/Route53/latest/DeveloperGuide/hosted-zones-migrating.html&quot; target=&quot;_blank&quot; rel=&quot;noopener&quot; data-source-url=&quot;https://docs.aws.amazon.com/ko_kr/Route53/latest/DeveloperGuide/hosted-zones-migrating.html&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;호스팅 영역을 다른 AWS 계정으로 이전 - Amazon Route 53&lt;/p&gt;
&lt;p class=&quot;og-desc&quot; data-ke-size=&quot;size16&quot;&gt;새 호스팅 영역을 위한 이름 서버를 사용하도록 도메인 등록을 업데이트하지 않으면 Route&amp;nbsp;53가 계속 기존 호스팅 영역을 사용하여 도메인에 대한 트래픽을 라우팅하게 됩니다. 도메인 등록의 이&lt;/p&gt;
&lt;p class=&quot;og-host&quot; data-ke-size=&quot;size16&quot;&gt;docs.aws.amazon.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;먼저 aws-cli 를 컴퓨터에 설치한다. aws-cli 설치법은 설명서에 잘 나와있다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;a href=&quot;https://docs.aws.amazon.com/ko_kr/cli/latest/userguide/getting-started-install.html&quot; target=&quot;_blank&quot; rel=&quot;noopener&amp;nbsp;noreferrer&quot;&gt;https://docs.aws.amazon.com/ko_kr/cli/latest/userguide/getting-started-install.html&lt;/a&gt;&lt;/p&gt;
&lt;figure id=&quot;og_1727707966354&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;최신 버전의 AWS CLI설치 또는 업데이트 - AWS Command Line Interface&quot; data-og-description=&quot;이전 버전에서 업데이트하는 경우 unzip 명령을 실행하면 기존 파일을 덮어쓸지 묻는 메시지가 표시됩니다. 스크립트 자동화와 같은 경우에 이러한 프롬프트를 건너뛰려면 unzip에 대한 -u 업데이&quot; data-og-host=&quot;docs.aws.amazon.com&quot; data-og-source-url=&quot;https://docs.aws.amazon.com/ko_kr/cli/latest/userguide/getting-started-install.html&quot; data-og-url=&quot;https://docs.aws.amazon.com/ko_kr/cli/latest/userguide/getting-started-install.html&quot; data-og-image=&quot;&quot;&gt;&lt;a href=&quot;https://docs.aws.amazon.com/ko_kr/cli/latest/userguide/getting-started-install.html&quot; target=&quot;_blank&quot; rel=&quot;noopener&quot; data-source-url=&quot;https://docs.aws.amazon.com/ko_kr/cli/latest/userguide/getting-started-install.html&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;최신 버전의 AWS CLI설치 또는 업데이트 - AWS Command Line Interface&lt;/p&gt;
&lt;p class=&quot;og-desc&quot; data-ke-size=&quot;size16&quot;&gt;이전 버전에서 업데이트하는 경우 unzip 명령을 실행하면 기존 파일을 덮어쓸지 묻는 메시지가 표시됩니다. 스크립트 자동화와 같은 경우에 이러한 프롬프트를 건너뛰려면 unzip에 대한 -u 업데이&lt;/p&gt;
&lt;p class=&quot;og-host&quot; data-ke-size=&quot;size16&quot;&gt;docs.aws.amazon.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;다음으로는 B 계정에 수동으로 호스팅 영역을 생성한다.&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;호스팅 영역이 생성되었으면, A 계정에 저장된 레코드를 파일로 뽑아낼 필요가 있다. 해당 파일을 살짝 변형하여 B 계정에 적용할 것이다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;먼저 A 계정으로 aws-cli에 로그인한 다음, 아래의 명령어를 입력하여 file을 생성한다. (hosted-zone-id 와 path-to-output-file 은 직접 확인 후 수정해야한다)&lt;/p&gt;
&lt;pre id=&quot;code_1727708054472&quot; class=&quot;bash&quot; data-ke-language=&quot;bash&quot; data-ke-type=&quot;codeblock&quot;&gt;&lt;code&gt;aws route53 list-resource-record-sets --hosted-zone-id hosted-zone-id &amp;gt; path-to-output-file&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;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;이제 해당 파일을 수정한다. 맨 위에 Changes 필드를 배열로 선언하고, 해당 배열에 Action: CREATE 필드와 ResouceRecordSet 필드에 위에서 생성한 레코드 정보를 입력한다. 수정된 새 파일의 형식은 다음과 같이 나오게 된다.&lt;/p&gt;
&lt;p&gt;&lt;figure class=&quot;imageblock alignCenter&quot; data-ke-mobileStyle=&quot;widthOrigin&quot; data-filename=&quot;스크린샷 2024-09-30 23.57.21.png&quot; data-origin-width=&quot;1044&quot; data-origin-height=&quot;1338&quot;&gt;&lt;span data-url=&quot;https://blog.kakaocdn.net/dn/cvHe25/btsJRqHQxwP/RxFfYwRH5px11uWT5DVzoK/img.png&quot; data-phocus=&quot;https://blog.kakaocdn.net/dn/cvHe25/btsJRqHQxwP/RxFfYwRH5px11uWT5DVzoK/img.png&quot;&gt;&lt;img src=&quot;https://blog.kakaocdn.net/dn/cvHe25/btsJRqHQxwP/RxFfYwRH5px11uWT5DVzoK/img.png&quot; srcset=&quot;https://img1.daumcdn.net/thumb/R1280x0/?scode=mtistory2&amp;fname=https%3A%2F%2Fblog.kakaocdn.net%2Fdn%2FcvHe25%2FbtsJRqHQxwP%2FRxFfYwRH5px11uWT5DVzoK%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;473&quot; height=&quot;606&quot; data-filename=&quot;스크린샷 2024-09-30 23.57.21.png&quot; data-origin-width=&quot;1044&quot; data-origin-height=&quot;1338&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;이제 해당 파일을 B 계정에 적용하자. B 계정으로 aws-cli 로그인한 다음, 해당 파일을 적용하는 다음 명령어를 입력한다.&lt;/p&gt;
&lt;pre id=&quot;code_1727708301199&quot; class=&quot;bash&quot; data-ke-language=&quot;bash&quot; data-ke-type=&quot;codeblock&quot;&gt;&lt;code&gt;aws route53 change-resource-record-sets --hosted-zone-id id-of-new-hosted-zone --change-batch file://path-to-file-that-contains-records&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;이제 B 계정에 레코드가 적용이 되어 있다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;이제 네임서버를 변경하면 된다. 네임서버는 도메인 등록 업체에서 B 계정에 있는 네임서버를 적용하면된다. 이렇게 등록해 두면 라우팅이 B의 네임서버를 타고 가게 된다. 이렇게 했을 경우에 정상적으로 이전이 되었다면 상용 서비스에 영향을 미치지 않고 도메인 이전이 가능해진다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;마지막으로 네임서버가 정상적으로 이전이 되어 보이면, A 계정에 있는 기존의 레코드를 모두 지우면은 호스팅 이전이 끝이 나게 된다.&lt;/p&gt;
&lt;p data-ke-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>서버 인프라/Aws</category>
      <author>트리맨스</author>
      <guid isPermaLink="true">https://tre2man.tistory.com/370</guid>
      <comments>https://tre2man.tistory.com/370#entry370comment</comments>
      <pubDate>Tue, 1 Oct 2024 00:01:13 +0900</pubDate>
    </item>
    <item>
      <title>NestJS TypeORM bulk insert로 데이터 입력하기</title>
      <link>https://tre2man.tistory.com/369</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;최근에 대량의 데이터 (4개의 칼럼, 200만개 가까이 되는 row)를 입력해야 할 일이 생겨서 NestJS 로직에서 데이터를 생성하는 중, Entity 생성 후 repository를 통해 데이터를 입력하는 방법은 상당히 속도가 느리다는 것을 알고 데이터 insert 속도를 찾기 위해 방법을 강구하던 중, bulk insert를 통해 속도를 올릴 수 있다는 것을 알게 되었다. 테스트를 위해서 간단히 환경을 만들어 보았다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;AppService&lt;/p&gt;
&lt;pre id=&quot;code_1718633892967&quot; class=&quot;typescript&quot; data-ke-language=&quot;typescript&quot; data-ke-type=&quot;codeblock&quot;&gt;&lt;code&gt;@Injectable()
export class AppService {
  constructor(
    @InjectRepository(UserEntity)
    private usersRepository: Repository&amp;lt;UserEntity&amp;gt;,
  ) {}

  async createUsers() {
    // faker.js를 사용한 가짜 유저 1만명 생성

    const data = this.getMockUserDatas();

    console.time('createUsers');
    for (const item of data) {
      const user = new UserEntity();
      user.name = item.name;
      user.email = item.email;
      await this.usersRepository.save(user);
    }
    console.timeEnd('createUsers');
  }

  async createUsersBulk() {
    const data = this.getMockUserDatas();

    console.time('createUsers');
    const users = data.map((item) =&amp;gt; ({
      name: item.name,
      email: item.email,
    }));

    await this.usersRepository
      .createQueryBuilder()
      .insert()
      .into(UserEntity)
      .values(users)
      .execute();
    console.timeEnd('createUsers');
  }

  private getMockUserDatas() {
    return Array.from({ length: 10000 }, () =&amp;gt; {
      return {
        name: faker.internet.userName(),
        email: faker.internet.email(),
      };
    });
  }
}&lt;/code&gt;&lt;/pre&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;UserEntity&lt;/p&gt;
&lt;pre id=&quot;code_1718633920412&quot; class=&quot;typescript&quot; data-ke-language=&quot;typescript&quot; data-ke-type=&quot;codeblock&quot;&gt;&lt;code&gt;@Entity()
export class UserEntity {
  @PrimaryGeneratedColumn()
  id: number;

  @Column()
  name: string;

  @Column()
  email: string;
}&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;createUsers와 createUsersBulk를 통해서 시간을 비교해 보았다.&lt;/p&gt;
&lt;table style=&quot;border-collapse: collapse; width: 100%;&quot; border=&quot;1&quot; data-ke-align=&quot;alignLeft&quot;&gt;
&lt;tbody&gt;
&lt;tr&gt;
&lt;td style=&quot;width: 50%; text-align: center;&quot;&gt;Entity&lt;/td&gt;
&lt;td style=&quot;width: 50%; text-align: center;&quot;&gt;Bulk&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td style=&quot;width: 50%; text-align: center;&quot;&gt;5.202s&lt;/td&gt;
&lt;td style=&quot;width: 50%; text-align: center;&quot;&gt;104.152ms&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td style=&quot;width: 50%; text-align: center;&quot;&gt;5.011s&lt;/td&gt;
&lt;td style=&quot;width: 50%; text-align: center;&quot;&gt;79.357ms&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td style=&quot;width: 50%; text-align: center;&quot;&gt;4.522s&lt;/td&gt;
&lt;td style=&quot;width: 50%; text-align: center;&quot;&gt;73.578ms&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td style=&quot;width: 50%; text-align: center;&quot;&gt;6.430s&lt;/td&gt;
&lt;td style=&quot;width: 50%; text-align: center;&quot;&gt;87.201ms&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td style=&quot;width: 50%; text-align: center;&quot;&gt;4.806s&lt;/td&gt;
&lt;td style=&quot;width: 50%; text-align: center;&quot;&gt;69.907ms&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;확실히 Bulk가 빠른 것을 볼 수 있다. 대량 데이터가 RDB에 입력되어야 할 때, 속도 최적화가 필요하다면 Bulk를 사용하는 것이 좋아 보인다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;</description>
      <category>프레임워크/NestJS</category>
      <author>트리맨스</author>
      <guid isPermaLink="true">https://tre2man.tistory.com/369</guid>
      <comments>https://tre2man.tistory.com/369#entry369comment</comments>
      <pubDate>Mon, 17 Jun 2024 23:25:45 +0900</pubDate>
    </item>
    <item>
      <title>NestJS 디버깅 설정 하기</title>
      <link>https://tre2man.tistory.com/368</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;최근 NestJS 개발에 사용한 IDE를 vscode에서 Webstorm으로 바꾸면서 사용중인데, 만족감이 매우 높다. 메서드 분리, getter 및 setter 추가, 리팩토링 제안, import 자동 리팩토링 등의 다양한 기능을 사용하면서 역시 비싼 IDE(?)를 사용하는 것이 생산성에 좋다고 생각이 들었다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;이 때 vscode에서 어려움이 있던 디버깅 설정을 Webstorm에서도 해 보기로 했다. 먼저 NodeJS 환경에서 디버그를 할수 있도록 공식 문서를 확인해 보았다.&lt;/p&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://www.jetbrains.com/help/webstorm/running-and-debugging-node-js.html#before_you_start&quot; target=&quot;_blank&quot; rel=&quot;noopener&amp;nbsp;noreferrer&quot;&gt;https://www.jetbrains.com/help/webstorm/running-and-debugging-node-js.html#before_you_start&lt;/a&gt;&lt;/p&gt;
&lt;figure id=&quot;og_1715262524327&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;Running and debugging Node.js | WebStorm&quot; data-og-description=&quot; &quot; data-og-host=&quot;www.jetbrains.com&quot; data-og-source-url=&quot;https://www.jetbrains.com/help/webstorm/running-and-debugging-node-js.html#before_you_start&quot; data-og-url=&quot;https://www.jetbrains.com/help/webstorm/running-and-debugging-node-js.html&quot; data-og-image=&quot;https://scrap.kakaocdn.net/dn/bpsZPy/hyV2EFZt1Z/IRmrCnjBBO3KsMOZ68gllk/img.png?width=1280&amp;amp;height=720&amp;amp;face=0_0_1280_720,https://scrap.kakaocdn.net/dn/cti9MP/hyV2wVsXfF/YhTGsiQ2K7bK6rF8nx3AW0/img.png?width=1200&amp;amp;height=420&amp;amp;face=0_0_1200_420&quot;&gt;&lt;a href=&quot;https://www.jetbrains.com/help/webstorm/running-and-debugging-node-js.html#before_you_start&quot; target=&quot;_blank&quot; rel=&quot;noopener&quot; data-source-url=&quot;https://www.jetbrains.com/help/webstorm/running-and-debugging-node-js.html#before_you_start&quot;&gt;
&lt;div class=&quot;og-image&quot; style=&quot;background-image: url('https://scrap.kakaocdn.net/dn/bpsZPy/hyV2EFZt1Z/IRmrCnjBBO3KsMOZ68gllk/img.png?width=1280&amp;amp;height=720&amp;amp;face=0_0_1280_720,https://scrap.kakaocdn.net/dn/cti9MP/hyV2wVsXfF/YhTGsiQ2K7bK6rF8nx3AW0/img.png?width=1200&amp;amp;height=420&amp;amp;face=0_0_1200_420');&quot;&gt;&amp;nbsp;&lt;/div&gt;
&lt;div class=&quot;og-text&quot;&gt;
&lt;p class=&quot;og-title&quot; data-ke-size=&quot;size16&quot;&gt;Running and debugging Node.js | WebStorm&lt;/p&gt;
&lt;p class=&quot;og-desc&quot; data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p class=&quot;og-host&quot; data-ke-size=&quot;size16&quot;&gt;www.jetbrains.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;해당 부분을 확인하면, 먼저 빌드된 파일을 node --inspect-brk 옵션을 사용하여 실행시켜야 하는 것 처럼 보였다. 그래서 먼저 해당 옵션을 사용하는 스크립트를 package.json에 추가했다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;pre id=&quot;code_1715262636799&quot; class=&quot;javascript&quot; data-ke-language=&quot;javascript&quot; data-ke-type=&quot;codeblock&quot;&gt;&lt;code&gt;...
&quot;start:test:debug&quot;: &quot;rimraf dist &amp;amp;&amp;amp; nest build &amp;amp;&amp;amp; node --inspect-brk ./dist/main.js&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; build결과물 제어 후 nest를 빌드하고, 빌드된 파일을 --inspect-brk 옵션을 통해서 실행을 한다.&lt;/p&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;먼저 Edit Configurations를 클릭한다.&lt;/p&gt;
&lt;p&gt;&lt;figure class=&quot;imageblock alignCenter&quot; data-ke-mobileStyle=&quot;widthOrigin&quot; data-filename=&quot;스크린샷 2024-05-09 오후 10.52.11.png&quot; data-origin-width=&quot;564&quot; data-origin-height=&quot;230&quot;&gt;&lt;span data-url=&quot;https://blog.kakaocdn.net/dn/pc3bd/btsHiE3cTJX/kZB9Rf96k57OdKv3XVBMd0/img.png&quot; data-phocus=&quot;https://blog.kakaocdn.net/dn/pc3bd/btsHiE3cTJX/kZB9Rf96k57OdKv3XVBMd0/img.png&quot;&gt;&lt;img src=&quot;https://blog.kakaocdn.net/dn/pc3bd/btsHiE3cTJX/kZB9Rf96k57OdKv3XVBMd0/img.png&quot; srcset=&quot;https://img1.daumcdn.net/thumb/R1280x0/?scode=mtistory2&amp;fname=https%3A%2F%2Fblog.kakaocdn.net%2Fdn%2Fpc3bd%2FbtsHiE3cTJX%2FkZB9Rf96k57OdKv3XVBMd0%2Fimg.png&quot; onerror=&quot;this.onerror=null; this.src='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png'; this.srcset='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png';&quot; loading=&quot;lazy&quot; width=&quot;424&quot; height=&quot;173&quot; data-filename=&quot;스크린샷 2024-05-09 오후 10.52.11.png&quot; data-origin-width=&quot;564&quot; data-origin-height=&quot;230&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&gt;&lt;figure class=&quot;imageblock alignCenter&quot; data-ke-mobileStyle=&quot;widthOrigin&quot; data-filename=&quot;스크린샷 2024-05-09 오후 10.52.15.png&quot; data-origin-width=&quot;1824&quot; data-origin-height=&quot;1584&quot;&gt;&lt;span data-url=&quot;https://blog.kakaocdn.net/dn/b9t03c/btsHlK1GpLq/RyRKXpkQSrnBe8gMjz5uR0/img.png&quot; data-phocus=&quot;https://blog.kakaocdn.net/dn/b9t03c/btsHlK1GpLq/RyRKXpkQSrnBe8gMjz5uR0/img.png&quot;&gt;&lt;img src=&quot;https://blog.kakaocdn.net/dn/b9t03c/btsHlK1GpLq/RyRKXpkQSrnBe8gMjz5uR0/img.png&quot; srcset=&quot;https://img1.daumcdn.net/thumb/R1280x0/?scode=mtistory2&amp;fname=https%3A%2F%2Fblog.kakaocdn.net%2Fdn%2Fb9t03c%2FbtsHlK1GpLq%2FRyRKXpkQSrnBe8gMjz5uR0%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;1824&quot; height=&quot;1584&quot; data-filename=&quot;스크린샷 2024-05-09 오후 10.52.15.png&quot; data-origin-width=&quot;1824&quot; data-origin-height=&quot;1584&quot;/&gt;&lt;/span&gt;&lt;/figure&gt;
&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;옵션 중 npm을 선택한다.&lt;/p&gt;
&lt;p&gt;&lt;figure class=&quot;imageblock alignCenter&quot; data-ke-mobileStyle=&quot;widthOrigin&quot; data-filename=&quot;스크린샷 2024-05-09 오후 10.52.25.png&quot; data-origin-width=&quot;706&quot; data-origin-height=&quot;1168&quot;&gt;&lt;span data-url=&quot;https://blog.kakaocdn.net/dn/bOyd99/btsHi2QjmWO/mNiVhSQkFoyktm9ih1nAa1/img.png&quot; data-phocus=&quot;https://blog.kakaocdn.net/dn/bOyd99/btsHi2QjmWO/mNiVhSQkFoyktm9ih1nAa1/img.png&quot;&gt;&lt;img src=&quot;https://blog.kakaocdn.net/dn/bOyd99/btsHi2QjmWO/mNiVhSQkFoyktm9ih1nAa1/img.png&quot; srcset=&quot;https://img1.daumcdn.net/thumb/R1280x0/?scode=mtistory2&amp;fname=https%3A%2F%2Fblog.kakaocdn.net%2Fdn%2FbOyd99%2FbtsHi2QjmWO%2FmNiVhSQkFoyktm9ih1nAa1%2Fimg.png&quot; onerror=&quot;this.onerror=null; this.src='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png'; this.srcset='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png';&quot; loading=&quot;lazy&quot; width=&quot;458&quot; height=&quot;758&quot; data-filename=&quot;스크린샷 2024-05-09 오후 10.52.25.png&quot; data-origin-width=&quot;706&quot; data-origin-height=&quot;1168&quot;/&gt;&lt;/span&gt;&lt;/figure&gt;
&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;옵션의 설정을 아래와 같이 한다. 이렇게 해두면 앞에서 설정한 package.json에 있는 스크립트 실행이 가능해진다.&lt;/p&gt;
&lt;p&gt;&lt;figure class=&quot;imageblock alignCenter&quot; data-ke-mobileStyle=&quot;widthOrigin&quot; data-filename=&quot;스크린샷 2024-05-09 오후 10.56.53.png&quot; data-origin-width=&quot;1824&quot; data-origin-height=&quot;1680&quot;&gt;&lt;span data-url=&quot;https://blog.kakaocdn.net/dn/cbeXpa/btsHkj46A5d/zReLtrRwH323kjwf2qed00/img.png&quot; data-phocus=&quot;https://blog.kakaocdn.net/dn/cbeXpa/btsHkj46A5d/zReLtrRwH323kjwf2qed00/img.png&quot;&gt;&lt;img src=&quot;https://blog.kakaocdn.net/dn/cbeXpa/btsHkj46A5d/zReLtrRwH323kjwf2qed00/img.png&quot; srcset=&quot;https://img1.daumcdn.net/thumb/R1280x0/?scode=mtistory2&amp;fname=https%3A%2F%2Fblog.kakaocdn.net%2Fdn%2FcbeXpa%2FbtsHkj46A5d%2FzReLtrRwH323kjwf2qed00%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;586&quot; height=&quot;540&quot; data-filename=&quot;스크린샷 2024-05-09 오후 10.56.53.png&quot; data-origin-width=&quot;1824&quot; data-origin-height=&quot;1680&quot;/&gt;&lt;/span&gt;&lt;/figure&gt;
&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;설정이 끝났으면 이제 실행해본다. 실행을 하게 되면 화면이 아래와 같이 나올 것이다. 여기 있는 websocket 주소를 클릭하면 디버깅 콘솔이 보이게 된다.&lt;/p&gt;
&lt;p&gt;&lt;figure class=&quot;imageblock alignCenter&quot; data-ke-mobileStyle=&quot;widthOrigin&quot; data-filename=&quot;스크린샷 2024-05-09 오후 10.58.57.png&quot; data-origin-width=&quot;1312&quot; data-origin-height=&quot;476&quot;&gt;&lt;span data-url=&quot;https://blog.kakaocdn.net/dn/tYsli/btsHjWvigoo/OJlTKTukCQCKxbJlwu2llK/img.png&quot; data-phocus=&quot;https://blog.kakaocdn.net/dn/tYsli/btsHjWvigoo/OJlTKTukCQCKxbJlwu2llK/img.png&quot;&gt;&lt;img src=&quot;https://blog.kakaocdn.net/dn/tYsli/btsHjWvigoo/OJlTKTukCQCKxbJlwu2llK/img.png&quot; srcset=&quot;https://img1.daumcdn.net/thumb/R1280x0/?scode=mtistory2&amp;fname=https%3A%2F%2Fblog.kakaocdn.net%2Fdn%2FtYsli%2FbtsHjWvigoo%2FOJlTKTukCQCKxbJlwu2llK%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;706&quot; height=&quot;256&quot; data-filename=&quot;스크린샷 2024-05-09 오후 10.58.57.png&quot; data-origin-width=&quot;1312&quot; data-origin-height=&quot;476&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;콘솔이 확인되었으면, 이제 breakpoint를 찍고 프로그램을 실행시켜 보자.&lt;/p&gt;
&lt;p&gt;&lt;figure class=&quot;imageblock alignCenter&quot; data-ke-mobileStyle=&quot;widthOrigin&quot; data-filename=&quot;스크린샷 2024-05-09 오후 11.00.00.png&quot; data-origin-width=&quot;1568&quot; data-origin-height=&quot;1954&quot;&gt;&lt;span data-url=&quot;https://blog.kakaocdn.net/dn/baGMOa/btsHi6SGr1X/WDh9hcvxKns5QEn86miwak/img.png&quot; data-phocus=&quot;https://blog.kakaocdn.net/dn/baGMOa/btsHi6SGr1X/WDh9hcvxKns5QEn86miwak/img.png&quot;&gt;&lt;img src=&quot;https://blog.kakaocdn.net/dn/baGMOa/btsHi6SGr1X/WDh9hcvxKns5QEn86miwak/img.png&quot; srcset=&quot;https://img1.daumcdn.net/thumb/R1280x0/?scode=mtistory2&amp;fname=https%3A%2F%2Fblog.kakaocdn.net%2Fdn%2FbaGMOa%2FbtsHi6SGr1X%2FWDh9hcvxKns5QEn86miwak%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;618&quot; height=&quot;770&quot; data-filename=&quot;스크린샷 2024-05-09 오후 11.00.00.png&quot; data-origin-width=&quot;1568&quot; data-origin-height=&quot;1954&quot;/&gt;&lt;/span&gt;&lt;/figure&gt;
&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;7번째 줄에서 프로그램이 잠시 멈추었다. 이 때 하단의 Variables 탭을 확인하면 로컬변수, 클로저, 글로벌 변수까지 다 확인할수 있다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;이러한 설정을 통해서 NestJS의 디버깅을 잘 할수 있게 되었다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;</description>
      <category>프레임워크/NestJS</category>
      <author>트리맨스</author>
      <guid isPermaLink="true">https://tre2man.tistory.com/368</guid>
      <comments>https://tre2man.tistory.com/368#entry368comment</comments>
      <pubDate>Thu, 9 May 2024 23:01:12 +0900</pubDate>
    </item>
    <item>
      <title>AWS IPv4 요금변경</title>
      <link>https://tre2man.tistory.com/367</link>
      <description>&lt;p data-ke-size=&quot;size16&quot;&gt;사이드 프로젝트를 하던 중, 요금이 너무 많이 나오는 것 같아서 요금을 추적해 보니, IPv4에 대한 새로운 요금 정산 방식이 추가되어서 그렇다고 한다.&lt;br /&gt;&lt;br /&gt;&lt;a href=&quot;https://aws.amazon.com/ko/blogs/aws/new-aws-public-ipv4-address-charge-public-ip-insights/&quot; target=&quot;_blank&quot; rel=&quot;noopener&amp;nbsp;noreferrer&quot;&gt;https://aws.amazon.com/ko/blogs/aws/new-aws-public-ipv4-address-charge-public-ip-insights/&lt;/a&gt;&lt;/p&gt;
&lt;figure id=&quot;og_1709437878244&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;New &amp;ndash; AWS Public IPv4 Address Charge + Public IP Insights | Amazon Web Services&quot; data-og-description=&quot;We are introducing a new charge for public IPv4 addresses. Effective February 1, 2024 there will be a charge of $0.005 per IP per hour for all public IPv4 addresses, whether attached to a service or not (there is already a charge for public IPv4 addresses &quot; data-og-host=&quot;aws.amazon.com&quot; data-og-source-url=&quot;https://aws.amazon.com/ko/blogs/aws/new-aws-public-ipv4-address-charge-public-ip-insights/&quot; data-og-url=&quot;https://aws.amazon.com/blogs/aws/new-aws-public-ipv4-address-charge-public-ip-insights/&quot; data-og-image=&quot;https://scrap.kakaocdn.net/dn/cMdWjl/hyVuipQRf8/vD6HaKWcifOkaguMhaZFQ1/img.png?width=768&amp;amp;height=385&amp;amp;face=0_0_768_385,https://scrap.kakaocdn.net/dn/5G4Lt/hyVqvK9rCr/1wgkKWPcHR6yOBCSC91UHK/img.png?width=768&amp;amp;height=385&amp;amp;face=0_0_768_385,https://scrap.kakaocdn.net/dn/gd21K/hyVudPB878/EkdWfBDABedT6keK5U4QI1/img.jpg?width=1825&amp;amp;height=1825&amp;amp;face=558_630_1327_1471&quot;&gt;&lt;a href=&quot;https://aws.amazon.com/ko/blogs/aws/new-aws-public-ipv4-address-charge-public-ip-insights/&quot; target=&quot;_blank&quot; rel=&quot;noopener&quot; data-source-url=&quot;https://aws.amazon.com/ko/blogs/aws/new-aws-public-ipv4-address-charge-public-ip-insights/&quot;&gt;
&lt;div class=&quot;og-image&quot; style=&quot;background-image: url('https://scrap.kakaocdn.net/dn/cMdWjl/hyVuipQRf8/vD6HaKWcifOkaguMhaZFQ1/img.png?width=768&amp;amp;height=385&amp;amp;face=0_0_768_385,https://scrap.kakaocdn.net/dn/5G4Lt/hyVqvK9rCr/1wgkKWPcHR6yOBCSC91UHK/img.png?width=768&amp;amp;height=385&amp;amp;face=0_0_768_385,https://scrap.kakaocdn.net/dn/gd21K/hyVudPB878/EkdWfBDABedT6keK5U4QI1/img.jpg?width=1825&amp;amp;height=1825&amp;amp;face=558_630_1327_1471');&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;New &amp;ndash; AWS Public IPv4 Address Charge + Public IP Insights | Amazon Web Services&lt;/p&gt;
&lt;p class=&quot;og-desc&quot; data-ke-size=&quot;size16&quot;&gt;We are introducing a new charge for public IPv4 addresses. Effective February 1, 2024 there will be a charge of $0.005 per IP per hour for all public IPv4 addresses, whether attached to a service or not (there is already a charge for public IPv4 addresses&lt;/p&gt;
&lt;p class=&quot;og-host&quot; data-ke-size=&quot;size16&quot;&gt;aws.amazon.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;해당 내용을 간략히 요약하면, 퍼블릭 IPv4 주소의 리소스가 모자라서 해당 IPv4주소당 새로 요금을 부과한다는 것이고, AWS에서는 IPv6 주소를 사용하는 것을 권장하는 듯 하다. 이 IPv4주소는 사용자가 직접 할당하든, Managed service에 연결된 주소든 상관없이 모두 요금이 부과되는 것 같다. 이것 때문에 새로 생긴 VPC IP 주소 관리자를 통해 현재 프로젝트의 퍼블릭 IP주소를 확인해 보았다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p&gt;&lt;figure class=&quot;imageblock alignCenter&quot; data-ke-mobileStyle=&quot;widthOrigin&quot; data-filename=&quot;스크린샷 2024-03-03 12.54.46.png&quot; data-origin-width=&quot;1436&quot; data-origin-height=&quot;1522&quot;&gt;&lt;span data-url=&quot;https://blog.kakaocdn.net/dn/5VrcD/btsFp8Ebohs/4zZWtkJKzUhllQyCLJkAg1/img.png&quot; data-phocus=&quot;https://blog.kakaocdn.net/dn/5VrcD/btsFp8Ebohs/4zZWtkJKzUhllQyCLJkAg1/img.png&quot;&gt;&lt;img src=&quot;https://blog.kakaocdn.net/dn/5VrcD/btsFp8Ebohs/4zZWtkJKzUhllQyCLJkAg1/img.png&quot; srcset=&quot;https://img1.daumcdn.net/thumb/R1280x0/?scode=mtistory2&amp;fname=https%3A%2F%2Fblog.kakaocdn.net%2Fdn%2F5VrcD%2FbtsFp8Ebohs%2F4zZWtkJKzUhllQyCLJkAg1%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;565&quot; height=&quot;599&quot; data-filename=&quot;스크린샷 2024-03-03 12.54.46.png&quot; data-origin-width=&quot;1436&quot; data-origin-height=&quot;1522&quot;/&gt;&lt;/span&gt;&lt;/figure&gt;
&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;현재 EC2의 IP주소, RDS와 Load Balancer의 IP주소가 모두 요금으로 나가는 중이였다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;해당 내용을 인지하여 기존 서비스의 IPv4주소를 IPv6로 마이그레이션 하고, 신규 서비스는 되도록 IPv6를 사용하도록 했다. 사용하지 않는 IPv4는 최대한 줄이고, 프라이빗 리소스로 전환을 진행했다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;</description>
      <category>서버 인프라/Aws</category>
      <author>트리맨스</author>
      <guid isPermaLink="true">https://tre2man.tistory.com/367</guid>
      <comments>https://tre2man.tistory.com/367#entry367comment</comments>
      <pubDate>Sun, 3 Mar 2024 13:15:18 +0900</pubDate>
    </item>
    <item>
      <title>SSH 트랩 만들기</title>
      <link>https://tre2man.tistory.com/366</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;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;a href=&quot;https://github.com/shizunge/endlessh-go&quot; target=&quot;_blank&quot; rel=&quot;noopener&amp;nbsp;noreferrer&quot;&gt;https://github.com/shizunge/endlessh-go&lt;/a&gt;&lt;/p&gt;
&lt;figure id=&quot;og_1701591176383&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 - shizunge/endlessh-go: A golang implementation of endlessh exporting Prometheus metrics, visualized by a Grafana dashboa&quot; data-og-description=&quot;A golang implementation of endlessh exporting Prometheus metrics, visualized by a Grafana dashboard. - GitHub - shizunge/endlessh-go: A golang implementation of endlessh exporting Prometheus metric...&quot; data-og-host=&quot;github.com&quot; data-og-source-url=&quot;https://github.com/shizunge/endlessh-go&quot; data-og-url=&quot;https://github.com/shizunge/endlessh-go&quot; data-og-image=&quot;https://scrap.kakaocdn.net/dn/jsvkl/hyUIq2KT5F/XO5JpzHJGOLm1u6vM9xFsk/img.png?width=1597&amp;amp;height=799&amp;amp;face=0_0_1597_799&quot;&gt;&lt;a href=&quot;https://github.com/shizunge/endlessh-go&quot; target=&quot;_blank&quot; rel=&quot;noopener&quot; data-source-url=&quot;https://github.com/shizunge/endlessh-go&quot;&gt;
&lt;div class=&quot;og-image&quot; style=&quot;background-image: url('https://scrap.kakaocdn.net/dn/jsvkl/hyUIq2KT5F/XO5JpzHJGOLm1u6vM9xFsk/img.png?width=1597&amp;amp;height=799&amp;amp;face=0_0_1597_799');&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 - shizunge/endlessh-go: A golang implementation of endlessh exporting Prometheus metrics, visualized by a Grafana dashboa&lt;/p&gt;
&lt;p class=&quot;og-desc&quot; data-ke-size=&quot;size16&quot;&gt;A golang implementation of endlessh exporting Prometheus metrics, visualized by a Grafana dashboard. - GitHub - shizunge/endlessh-go: A golang implementation of endlessh exporting Prometheus metric...&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;endlessh 라는 프로그램인데, ssh 트랩을 만드는 것 같아 보였다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;해당 앱의 기능은 endlessh를 켜 둔 pc에 외부에서 ssh 접속을 시도하려고 하면, 무한대기하는 앱이다. 그래서 이 앱은 왜 만들어졌을까? 바로 임의로 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;아무튼 동작 구조를 보니까 endlessh 프로그램이 있고, 공격자들의 현황을 파악하기 위해서 prometheus로 메트릭을 뽑아서 grafana로 시각화 하는 과정까지 있는 것 같다. 그래서 집에서 간단하게 만들어 보았다.&lt;/p&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 b+, 라즈비안 환경에서 실행했다.&lt;/p&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와 docker-compose를 설치했다. 설치는 다음 자료들을 참고했다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;a href=&quot;https://docs.docker.com/engine/install/raspberry-pi-os/&quot; target=&quot;_blank&quot; rel=&quot;noopener&amp;nbsp;noreferrer&quot;&gt;https://docs.docker.com/engine/install/raspberry-pi-os/&lt;/a&gt;&lt;/p&gt;
&lt;figure id=&quot;og_1701591424539&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;Install Docker Engine on Raspberry Pi OS (32-bit)&quot; data-og-description=&quot;Learn how to install Docker Engine on a 32-bit Raspberry Pi OS system. These instructions cover the different installation methods, how to uninstall, and next steps.&quot; data-og-host=&quot;docs.docker.com&quot; data-og-source-url=&quot;https://docs.docker.com/engine/install/raspberry-pi-os/&quot; data-og-url=&quot;https://docs.docker.com/engine/install/raspberry-pi-os/&quot; data-og-image=&quot;https://scrap.kakaocdn.net/dn/bLp3Sg/hyUE65Vz8v/J9Y0nKkM0vy20jqXMb4kCK/img.png?width=129&amp;amp;height=128&amp;amp;face=0_0_129_128&quot;&gt;&lt;a href=&quot;https://docs.docker.com/engine/install/raspberry-pi-os/&quot; target=&quot;_blank&quot; rel=&quot;noopener&quot; data-source-url=&quot;https://docs.docker.com/engine/install/raspberry-pi-os/&quot;&gt;
&lt;div class=&quot;og-image&quot; style=&quot;background-image: url('https://scrap.kakaocdn.net/dn/bLp3Sg/hyUE65Vz8v/J9Y0nKkM0vy20jqXMb4kCK/img.png?width=129&amp;amp;height=128&amp;amp;face=0_0_129_128');&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;Install Docker Engine on Raspberry Pi OS (32-bit)&lt;/p&gt;
&lt;p class=&quot;og-desc&quot; data-ke-size=&quot;size16&quot;&gt;Learn how to install Docker Engine on a 32-bit Raspberry Pi OS system. These instructions cover the different installation methods, how to uninstall, and next steps.&lt;/p&gt;
&lt;p class=&quot;og-host&quot; data-ke-size=&quot;size16&quot;&gt;docs.docker.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;설치한 이후 github project의 /examples/docker-simple 폴더에 있는 docker-compose 파일을 통해 실행시켜 보았다. 여기서 미리 설정해야 되는 곳이 있는데 GF_SECURITY_ADMIN_USER, GF_SECURITY_ADMIN_PASSWORD를 자신만의 아이디와 비밀번호로 변경해야 한다. 그리고 공식 grafana 도커 이미지는 글 작성 시점에 라즈베리파이를 지원하지 않았다. 이유를 찾아보니 go 컴파일러의 ARM 관련 버그가 있어서 지금은 배포를 중단한다는 내용이였다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;a href=&quot;https://grafana.com/blog/2023/09/29/grafana-and-grafana-enterprise-updates-for-armv6-and-armv7-will-be-temporarily-paused/&quot; target=&quot;_blank&quot; rel=&quot;noopener&amp;nbsp;noreferrer&quot;&gt;https://grafana.com/blog/2023/09/29/grafana-and-grafana-enterprise-updates-for-armv6-and-armv7-will-be-temporarily-paused/&lt;/a&gt;&lt;/p&gt;
&lt;figure id=&quot;og_1701591624369&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;Grafana and Grafana Enterprise updates for ARMv6 and ARMv7 will be temporarily paused | Grafana Labs&quot; data-og-description=&quot;Thank you! Your message has been received!&quot; data-og-host=&quot;grafana.com&quot; data-og-source-url=&quot;https://grafana.com/blog/2023/09/29/grafana-and-grafana-enterprise-updates-for-armv6-and-armv7-will-be-temporarily-paused/&quot; data-og-url=&quot;https://grafana.com/blog/2023/09/29/grafana-and-grafana-enterprise-updates-for-armv6-and-armv7-will-be-temporarily-paused/&quot; data-og-image=&quot;https://scrap.kakaocdn.net/dn/cQVW6g/hyUIARPoKC/7w55XgN1HSBZV0Xz9kN0Kk/img.png?width=1200&amp;amp;height=630&amp;amp;face=0_0_1200_630,https://scrap.kakaocdn.net/dn/uSWeS/hyUIrN7GBm/MYlP2U9vHtw0Ok0UDc604K/img.png?width=1200&amp;amp;height=630&amp;amp;face=0_0_1200_630&quot;&gt;&lt;a href=&quot;https://grafana.com/blog/2023/09/29/grafana-and-grafana-enterprise-updates-for-armv6-and-armv7-will-be-temporarily-paused/&quot; target=&quot;_blank&quot; rel=&quot;noopener&quot; data-source-url=&quot;https://grafana.com/blog/2023/09/29/grafana-and-grafana-enterprise-updates-for-armv6-and-armv7-will-be-temporarily-paused/&quot;&gt;
&lt;div class=&quot;og-image&quot; style=&quot;background-image: url('https://scrap.kakaocdn.net/dn/cQVW6g/hyUIARPoKC/7w55XgN1HSBZV0Xz9kN0Kk/img.png?width=1200&amp;amp;height=630&amp;amp;face=0_0_1200_630,https://scrap.kakaocdn.net/dn/uSWeS/hyUIrN7GBm/MYlP2U9vHtw0Ok0UDc604K/img.png?width=1200&amp;amp;height=630&amp;amp;face=0_0_1200_630');&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;Grafana and Grafana Enterprise updates for ARMv6 and ARMv7 will be temporarily paused | Grafana Labs&lt;/p&gt;
&lt;p class=&quot;og-desc&quot; data-ke-size=&quot;size16&quot;&gt;Thank you! Your message has been received!&lt;/p&gt;
&lt;p class=&quot;og-host&quot; data-ke-size=&quot;size16&quot;&gt;grafana.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;그래서 grafana를 이전 버전으로 사용하기 위해서 해당 버전의 도커 이미지를 사용하기로 했다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;a href=&quot;https://hub.docker.com/layers/grafana/grafana/8.4.3-armv7/images/sha256-7e2e22d0b59157aa1483ce401ededaa60d7e122484cc410934587f8da9bc5eea?context=explore&quot; target=&quot;_blank&quot; rel=&quot;noopener&amp;nbsp;noreferrer&quot;&gt;https://hub.docker.com/layers/grafana/grafana/8.4.3-armv7/images/sha256-7e2e22d0b59157aa1483ce401ededaa60d7e122484cc410934587f8da9bc5eea?context=explore&lt;/a&gt;&lt;/p&gt;
&lt;figure id=&quot;og_1701591672678&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;Docker&quot; data-og-description=&quot;&quot; data-og-host=&quot;hub.docker.com&quot; data-og-source-url=&quot;https://hub.docker.com/layers/grafana/grafana/8.4.3-armv7/images/sha256-7e2e22d0b59157aa1483ce401ededaa60d7e122484cc410934587f8da9bc5eea?context=explore&quot; data-og-url=&quot;https://hub.docker.com/layers/grafana/grafana/8.4.3-armv7/images/sha256-7e2e22d0b59157aa1483ce401ededaa60d7e122484cc410934587f8da9bc5eea?context=explore&quot; data-og-image=&quot;&quot;&gt;&lt;a href=&quot;https://hub.docker.com/layers/grafana/grafana/8.4.3-armv7/images/sha256-7e2e22d0b59157aa1483ce401ededaa60d7e122484cc410934587f8da9bc5eea?context=explore&quot; target=&quot;_blank&quot; rel=&quot;noopener&quot; data-source-url=&quot;https://hub.docker.com/layers/grafana/grafana/8.4.3-armv7/images/sha256-7e2e22d0b59157aa1483ce401ededaa60d7e122484cc410934587f8da9bc5eea?context=explore&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;Docker&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;hub.docker.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;이렇게 프로그램을 설정한 이후, 외부에서 22번 포트로 접속하면 내부의 2222번 포트로 트래픽을 보낼 수 있게 포트포워딩 설정을 하면 트랩이 완성된다. 트랩을 만들고 하루 뒤에 얼마나 많은 공격이 왔는지 확인해 보았다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p&gt;&lt;figure class=&quot;imageblock alignCenter&quot; data-ke-mobileStyle=&quot;widthOrigin&quot; data-filename=&quot;스크린샷 2023-12-03 17.22.37.png&quot; data-origin-width=&quot;1224&quot; data-origin-height=&quot;825&quot;&gt;&lt;span data-url=&quot;https://blog.kakaocdn.net/dn/bctQit/btsBgxPG2or/3Btut7GaqJo9Y2ZNXuEF51/img.png&quot; data-phocus=&quot;https://blog.kakaocdn.net/dn/bctQit/btsBgxPG2or/3Btut7GaqJo9Y2ZNXuEF51/img.png&quot;&gt;&lt;img src=&quot;https://blog.kakaocdn.net/dn/bctQit/btsBgxPG2or/3Btut7GaqJo9Y2ZNXuEF51/img.png&quot; srcset=&quot;https://img1.daumcdn.net/thumb/R1280x0/?scode=mtistory2&amp;fname=https%3A%2F%2Fblog.kakaocdn.net%2Fdn%2FbctQit%2FbtsBgxPG2or%2F3Btut7GaqJo9Y2ZNXuEF51%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;1224&quot; height=&quot;825&quot; data-filename=&quot;스크린샷 2023-12-03 17.22.37.png&quot; data-origin-width=&quot;1224&quot; data-origin-height=&quot;825&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;&amp;nbsp;&lt;/p&gt;</description>
      <category>잡다한 이야기</category>
      <author>트리맨스</author>
      <guid isPermaLink="true">https://tre2man.tistory.com/366</guid>
      <comments>https://tre2man.tistory.com/366#entry366comment</comments>
      <pubDate>Sun, 3 Dec 2023 17:23:39 +0900</pubDate>
    </item>
    <item>
      <title>Github action 개선하기</title>
      <link>https://tre2man.tistory.com/365</link>
      <description>&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;br&gt;먼저, 현재 프로젝트의 github action은 한개의 step으로 이루어져 현재 진행 단계를 알기가 힘들었다.&lt;br&gt;&amp;nbsp;&lt;/p&gt;&lt;figure class=&quot;imageblock alignCenter&quot; data-ke-mobileStyle=&quot;widthOrigin&quot; data-origin-width=&quot;734&quot; data-origin-height=&quot;334&quot;&gt;&lt;span data-url=&quot;https://blog.kakaocdn.net/dn/11KYo/btsAThetHyH/mGmvzNcOd6WkB1Ro3b3Kn0/img.png&quot; data-phocus=&quot;https://blog.kakaocdn.net/dn/11KYo/btsAThetHyH/mGmvzNcOd6WkB1Ro3b3Kn0/img.png&quot;&gt;&lt;img src=&quot;https://blog.kakaocdn.net/dn/11KYo/btsAThetHyH/mGmvzNcOd6WkB1Ro3b3Kn0/img.png&quot; srcset=&quot;https://img1.daumcdn.net/thumb/R1280x0/?scode=mtistory2&amp;fname=https%3A%2F%2Fblog.kakaocdn.net%2Fdn%2F11KYo%2FbtsAThetHyH%2FmGmvzNcOd6WkB1Ro3b3Kn0%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;609&quot; height=&quot;277&quot; data-origin-width=&quot;734&quot; data-origin-height=&quot;334&quot;/&gt;&lt;/span&gt;&lt;/figure&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;이것은 빌드 및 배포를 통합한 github action이다. 그래서 해당 작업을 좀 더 알아보기 쉽게 바꾸어 보기로 했다. 먼저 배포 과정부터 정리해 보았다.&lt;br&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;tag 입력 확인&lt;/li&gt;&lt;li&gt;디펜던시 다운로드&lt;/li&gt;&lt;li&gt;빌드&lt;/li&gt;&lt;li&gt;배포환경 설정&lt;/li&gt;&lt;li&gt;배포&lt;/li&gt;&lt;li&gt;성공 또는 실패 결과 슬랙 전송&lt;/li&gt;&lt;/ol&gt;&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;br&gt;위의 4가지 단계로 정규화를 시킬수 있었다. 여기서 공통으로 묶을 수 있는 작업을을 function 형태로 묶어보고 싶었다. 그래서 찾아보던 중 function과 비슷하게 사용 가능한 reusable workflows가 있었다. 해당 기능은 말 그대로 workflow를 재사용 가능하게 하는 기능이였다. 출시된지 얼마 안된거 같지만, 내가 원하는 기능이기에 바로 도입을 시도해 보았다. 사용법은 아래 공식문서를 참고했다.&lt;br&gt;&lt;a href=&quot;https://docs.github.com/en/actions/using-workflows/reusing-workflows&quot; target=&quot;_blank&quot;&gt;&lt;span&gt;https://docs.github.com/en/actions/using-workflows/reusing-workflows&lt;/span&gt;&lt;/a&gt;&lt;/p&gt;&lt;figure data-ke-type=&quot;opengraph&quot; data-og-title=&quot;Reusing workflows - GitHub Docs&quot; data-ke-align=&quot;alignCenter&quot; data-og-description=&quot;Learn how to avoid duplication when creating a workflow by reusing existing workflows.&quot; data-og-host=&quot;docs.github.com&quot; data-og-source-url=&quot;https://docs.github.com/en/actions/using-workflows/reusing-workflows&quot; data-og-image=&quot;https://scrap.kakaocdn.net/dn/Y7gAQ/hyUCgN0jFG/0O07nfdjV5yaksLI19AzS0/img.png?width=1200&amp;amp;height=1200&amp;amp;face=0_0_1200_1200,https://scrap.kakaocdn.net/dn/dQrHS4/hyUB9BgPSA/I7Gh2Oh8JZP5Y3gf3TXGyK/img.png?width=1896&amp;amp;height=596&amp;amp;face=0_0_1896_596&quot; data-og-url=&quot;https://ghdocs-prod.azurewebsites.net/en/actions/using-workflows/reusing-workflows&quot;&gt;&lt;a href=&quot;https://ghdocs-prod.azurewebsites.net/en/actions/using-workflows/reusing-workflows&quot; target=&quot;_blank&quot; data-source-url=&quot;https://docs.github.com/en/actions/using-workflows/reusing-workflows&quot;&gt;&lt;div class=&quot;og-image&quot; style=&quot;background-image: url('https://scrap.kakaocdn.net/dn/Y7gAQ/hyUCgN0jFG/0O07nfdjV5yaksLI19AzS0/img.png?width=1200&amp;amp;height=1200&amp;amp;face=0_0_1200_1200,https://scrap.kakaocdn.net/dn/dQrHS4/hyUB9BgPSA/I7Gh2Oh8JZP5Y3gf3TXGyK/img.png?width=1896&amp;amp;height=596&amp;amp;face=0_0_1896_596')&quot;&gt; &lt;/div&gt;&lt;div class=&quot;og-text&quot;&gt;&lt;p class=&quot;og-title&quot;&gt;Reusing workflows - GitHub Docs&lt;/p&gt;&lt;p class=&quot;og-desc&quot;&gt;Learn how to avoid duplication when creating a workflow by reusing existing workflows.&lt;/p&gt;&lt;p class=&quot;og-host&quot;&gt;docs.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;br&gt;내가 사용한 기능만 추리면 다음과 같다.&lt;/p&gt;&lt;ul style=&quot;list-style-type: circle;&quot; data-ke-list-type=&quot;circle&quot;&gt; 
 &lt;li&gt;워크플로우 정의&lt;br&gt; 
  &lt;ul style=&quot;list-style-type: circle;&quot; data-ke-list-type=&quot;disc&quot;&gt; 
   &lt;li&gt;워크플로우 정의는 말 그대로 작업을 정의하는 것이다. 코딩하는 관점에서 보면 재사용 가능한 function 형태를 만드는 것이고, 워크플로우의 소스는 같은 파일, 같은 repo, 공개된 reusable workflow 등을 사용 가능하다. 간단하게 reusable_workflow_job을 정의하여 사용할 수 있다. 자세한 내용은 공식문서 참고 -&amp;gt; 해당 기능을 사용해서 각 workflow를 파일로 만들고, main workflow는 각 workflow를 참조하는 방식으로 작성할 수 있다.&lt;/li&gt; 
  &lt;/ul&gt; &lt;/li&gt; 
 &lt;li&gt;워크플로우 사용&lt;br&gt; 
  &lt;ul style=&quot;list-style-type: circle;&quot; data-ke-list-type=&quot;disc&quot;&gt; 
   &lt;li&gt;위에서 정의 한 workflow를 사용한다. uses 선언을 통해서 사용이 가능하다.&lt;/li&gt; 
  &lt;/ul&gt; &lt;/li&gt; 
 &lt;li&gt;inputs 사용 
  &lt;ul style=&quot;list-style-type: circle;&quot; data-ke-list-type=&quot;disc&quot;&gt; 
   &lt;li&gt;함수면은 당연히 입력 변수가 있어야 할 것이다. 이는 워크플로우 정의 시 inputs를 선언하여 타입, required, default 등 다양한 옵션을 설정할 수 있다. 사용할 때는 with를 사용하여 입력변수를 넣어줄 수 있다.&lt;/li&gt; 
  &lt;/ul&gt; &lt;/li&gt; 
 &lt;li&gt;secret pass 
  &lt;ul style=&quot;list-style-type: circle;&quot; data-ke-list-type=&quot;disc&quot;&gt; 
   &lt;li&gt;외부에 드러내기 싫은 민감한 정보를 pass해야 할 수도 있다. 이럴 때는 secrets: inherit 옵션을 사용하여 secret을 보낼 수 있다.&lt;/li&gt; 
  &lt;/ul&gt; &lt;/li&gt; 
&lt;/ul&gt;&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;br&gt;그리고 여기에 작업을 하는 순서를 임의로 지정할 수 있다. 예를 들어 실수로 빌드가 되지 않는 코드를 올렸는데, 빌드와 배포가 동시에 이루어진다면 잘못된 파일이 배포가 될 수도 있다. (물론 빌드가 안되어서 막히겠지만, 그냥 예시 이니 넘겨주시길...) 그래서 빌드 다음 단계에 배포가 이루어져야 한다. kubernetes의 depends_on과 비슷해 보인다. 해당 기능은 needs 옵션을 사용하면 된다.&lt;br&gt;&amp;nbsp;&lt;br&gt;해당 기능을 적용하면 workflow는 다음과 같이 바뀐다.&lt;/p&gt;&lt;figure class=&quot;imageblock alignCenter&quot; data-ke-mobileStyle=&quot;widthOrigin&quot; data-origin-width=&quot;888&quot; data-origin-height=&quot;1110&quot;&gt;&lt;span data-url=&quot;https://blog.kakaocdn.net/dn/bOBppQ/btsATkPJlW7/TMRkG1fLQvOFaV63VXIKlk/img.png&quot; data-phocus=&quot;https://blog.kakaocdn.net/dn/bOBppQ/btsATkPJlW7/TMRkG1fLQvOFaV63VXIKlk/img.png&quot;&gt;&lt;img src=&quot;https://blog.kakaocdn.net/dn/bOBppQ/btsATkPJlW7/TMRkG1fLQvOFaV63VXIKlk/img.png&quot; srcset=&quot;https://img1.daumcdn.net/thumb/R1280x0/?scode=mtistory2&amp;fname=https%3A%2F%2Fblog.kakaocdn.net%2Fdn%2FbOBppQ%2FbtsATkPJlW7%2FTMRkG1fLQvOFaV63VXIKlk%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;486&quot; height=&quot;608&quot; data-origin-width=&quot;888&quot; data-origin-height=&quot;1110&quot;/&gt;&lt;/span&gt;&lt;/figure&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;해당 main github action 코드는 500줄 정도에서 100줄 정도로 줄어들어서, 가독성이 훨씬 좋아졌다. (물론 reusable workflow를 제외한 길이...)&lt;br&gt;&amp;nbsp;&lt;/p&gt;</description>
      <category>서버 인프라/DevOps</category>
      <author>트리맨스</author>
      <guid isPermaLink="true">https://tre2man.tistory.com/365</guid>
      <comments>https://tre2man.tistory.com/365#entry365comment</comments>
      <pubDate>Sat, 25 Nov 2023 17:08:50 +0900</pubDate>
    </item>
    <item>
      <title>Postgres 자연어 검색하기</title>
      <link>https://tre2man.tistory.com/364</link>
      <description>&lt;p data-ke-size=&quot;size16&quot;&gt;Postgres에 있는 문자를 자연어 검색을 할 필요가 있어서 자연어 검색에 대해서 찾은 내용들을 정리해 볼까 한다. 먼저 테스트 환경은 다음과 같다.&lt;br&gt;&amp;nbsp;&lt;br&gt;M1 Pro, postgres 14.x, organization 테이블의 name 칼럼에 100만개의 문자열 입력&lt;br&gt;&amp;nbsp;&lt;br&gt;동일한 문자열을 구하는 방법은 다음과 같다.&lt;/p&gt;&lt;pre data-ke-type=&quot;codeblock&quot; class=&quot;sql&quot; data-ke-language=&quot;sql&quot;&gt;&lt;code&gt;select name from organization where name = '문자열'&lt;/code&gt;&lt;/pre&gt;&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;br&gt;하지만 해당 sql은 완벽히 매치가 되는 문자열에 대해서만 검색이 가능하므로, index를 추가하면 검색 속도는 좋아지겠으나 exact match만 지원하므로 검색에 제한이 생긴다.&lt;br&gt;&amp;nbsp;&lt;br&gt;다음 방법은 like를 사용한 full text search를 사용하는 방법이다. 사용법은 다음과 같다.&lt;/p&gt;&lt;pre data-ke-type=&quot;codeblock&quot; class=&quot;sql&quot; data-ke-language=&quot;sql&quot;&gt;&lt;code&gt;select name from organization where name like '%문자열%'&lt;/code&gt;&lt;/pre&gt;&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;br&gt;해당 방법은 name 중간에 '문자열' 이라는 문구가 있으면 모두 출력한다. 하지만 일반적인 index를 추가하게 되면 문맥에 상관없이 순서대로 인덱싱이 되므로, seq scan을 하게 된다. 이것을 해결하기 위해서 현대의 DB에는 full text search를 지원하는 인덱스가 있다. postgres에서도 full text search를 지원하는 인덱스가 있다. gin index를 사용하면 된다. 사용법은 다음과 같다.&lt;/p&gt;&lt;pre data-ke-type=&quot;codeblock&quot; class=&quot;sql&quot; data-ke-language=&quot;sql&quot;&gt;&lt;code&gt;-- 확장 프로그램 설치
CREATE EXTENSION pg_trgm;
-- name_origin_gin_idx 인덱스를 추가한다. name 칼럼을 gin_trgm_ops를 사용한 gin 인덱스를 사용한다.
CREATE INDEX name_origin_gin_idx ON organization USING gin(name gin_trgm_ops);&lt;/code&gt;&lt;/pre&gt;&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;br&gt;이제 gin index가 없을 때와 있을 때의 cost를 확인해 보자.&lt;/p&gt;&lt;figure class=&quot;imagegridblock&quot;&gt;
  &lt;div class=&quot;image-container&quot;&gt;&lt;span data-url=&quot;https://blog.kakaocdn.net/dn/cycArC/btsAvViXQjM/Lm3D24MsCDtIMX4wk8oKlk/img.png&quot; data-phocus=&quot;https://blog.kakaocdn.net/dn/cycArC/btsAvViXQjM/Lm3D24MsCDtIMX4wk8oKlk/img.png&quot; data-origin-width=&quot;966&quot; data-origin-height=&quot;1042&quot; style=&quot;width: 50.3742%;&quot;&gt;&lt;img src=&quot;https://blog.kakaocdn.net/dn/cycArC/btsAvViXQjM/Lm3D24MsCDtIMX4wk8oKlk/img.png&quot; alt=&quot;&quot; srcset=&quot;https://img1.daumcdn.net/thumb/R1280x0/?scode=mtistory2&amp;amp;fname=https%3A%2F%2Fblog.kakaocdn.net%2Fdn%2FcycArC%2FbtsAvViXQjM%2FLm3D24MsCDtIMX4wk8oKlk%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;966&quot; height=&quot;1042&quot;/&gt;&lt;/span&gt;&lt;span data-url=&quot;https://blog.kakaocdn.net/dn/d64oUM/btsAzAY227h/nnbSI5k2hqEE6GodULiKt1/img.png&quot; data-phocus=&quot;https://blog.kakaocdn.net/dn/d64oUM/btsAzAY227h/nnbSI5k2hqEE6GodULiKt1/img.png&quot; data-origin-width=&quot;924&quot; data-origin-height=&quot;1036&quot; style=&quot;width: 48.463%;&quot;&gt;&lt;img src=&quot;https://blog.kakaocdn.net/dn/d64oUM/btsAzAY227h/nnbSI5k2hqEE6GodULiKt1/img.png&quot; alt=&quot;&quot; srcset=&quot;https://img1.daumcdn.net/thumb/R1280x0/?scode=mtistory2&amp;amp;fname=https%3A%2F%2Fblog.kakaocdn.net%2Fdn%2Fd64oUM%2FbtsAzAY227h%2FnnbSI5k2hqEE6GodULiKt1%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;924&quot; height=&quot;1036&quot;/&gt;&lt;/span&gt;&lt;/div&gt;
&lt;/figure&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;br&gt;좌측이 gin index 없을때, 우측이 gin index가 있을 때다. 좌측을 보면 cost가 많이 들어 병렬 쿼리를 사용함에도 불구하고, 우측 cost에 비해서 비용이 많이 드는 것을 볼 수 있다. (gin index 없을 때 25500, gin index 있을 떄 440, 숫자가 적을수록 대체로 비용이 적고 빨리 검색이 된다) 그래서 정규표현식 쿼리도 사용이 가능하긴 하나, 사용 경우에 따라서 index를 사용하지 못할 확률이 매우 높다.&lt;br&gt;&amp;nbsp;&lt;br&gt;그래서 여기서 조금 더 찾아보니 tsvector와 tsquery가 있었다. 해당 기능들을 사용해서 검색 쿼리에 대한 매칭 ranking, 오타 허용 오차 등에 대해서 다룰 수 있는 것 같았다.&lt;br&gt;&amp;nbsp;&lt;br&gt;tsvector는 string을 어휘소 기반으로 바꾼 결과 자료형이고, to_tsvector는 문자열을 tsvector 형태로 바꾸는 함수이다. 사용법은 다음과 같다.&lt;/p&gt;&lt;pre data-ke-type=&quot;codeblock&quot; class=&quot;sql&quot; data-ke-language=&quot;sql&quot;&gt;&lt;code&gt;select * from to_tsvector('Gleason, Gottlieb and Dicki 88');

-- 결과물

to_tsvector
'88':5 'dicki':4 'gleason':1 'gottlieb':2&lt;/code&gt;&lt;/pre&gt;&lt;p data-ke-size=&quot;size16&quot;&gt;and나 ',' 같은 의미가 거의 없는 단어를 제외하고는 단어와 단어의 위치가 역인덱스되어 보이는 것을 알수 있다. 그래서 이것을 활용해서 인덱스 칼럼을 새로 생성해서 검색을 진행할 수 있다. 인덱스 칼럼을 새로 만들어 검색을 해 보자.&lt;/p&gt;&lt;pre data-ke-type=&quot;codeblock&quot; class=&quot;sql&quot; data-ke-language=&quot;sql&quot;&gt;&lt;code&gt;-- 칼럼 추가
alter table organization add column tsvec_words tsvector;
-- 칼럼에 내용 추가
UPDATE organization SET tsvec_words = to_tsvector(name);
-- 칼럼에 인덱스 추가 (실제 애플리케이션에 사용하려면 name 업데이트 시 tsvec_words도 같이 업데이트하는 전략 필요)
CREATE INDEX name_gin_idx ON organization USING gin(tsvec_words);&lt;/code&gt;&lt;/pre&gt;&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;br&gt;이제 tsvec_words는 인덱스 칼럼이다. 해당 인덱스를 사용해서 검색을 해 보자.&lt;/p&gt;&lt;pre data-ke-type=&quot;codeblock&quot; class=&quot;bash&quot; data-ke-language=&quot;bash&quot;&gt;&lt;code&gt;select * from organization where tsvec_words @@ to_tsquery('King &amp;amp; (Group | Erdman)');

-- 추가 쿼리

-- King과 10이 근접한 경우
select * from organization where tsvec_words @@ to_tsquery('King &amp;lt;-&amp;gt; 10');
-- King과 10 사이에 1개의 단어(2개의 공백)가 있는 경우
select * from organization where tsvec_words @@ to_tsquery('King &amp;lt;2&amp;gt; 10');&lt;/code&gt;&lt;/pre&gt;&lt;p data-ke-size=&quot;size16&quot;&gt;첫번째 쿼리를 확인해 보았다. King이라는 단어가 포함되고 Group 또는 Erdman이 포함되어야 한다. 해당 쿼리의 결과는 다음과 같이 잘 나온다.&lt;/p&gt;&lt;pre data-ke-type=&quot;codeblock&quot; class=&quot;bash&quot; data-ke-language=&quot;bash&quot;&gt;&lt;code&gt;King Group 58,'58':3 'group':2 'king':1
King Group 69,'69':3 'group':2 'king':1
King Group 41,'41':3 'group':2 'king':1
King Group 74,'74':3 'group':2 'king':1
King Group 78,'78':3 'group':2 'king':1
King - Erdman 17,'17':3 'erdman':2 'king':1
...&lt;/code&gt;&lt;/pre&gt;&lt;p data-ke-size=&quot;size16&quot;&gt;해당 쿼리의 cost를 확인해 보면 다음과 같다.&lt;/p&gt;&lt;figure class=&quot;imageblock alignCenter&quot; data-ke-mobileStyle=&quot;widthOrigin&quot; data-origin-width=&quot;876&quot; data-origin-height=&quot;986&quot;&gt;&lt;span data-url=&quot;https://blog.kakaocdn.net/dn/NeFus/btsAvsIexrW/bKMTmggDtWXA3iHgUMsrM0/img.png&quot; data-phocus=&quot;https://blog.kakaocdn.net/dn/NeFus/btsAvsIexrW/bKMTmggDtWXA3iHgUMsrM0/img.png&quot;&gt;&lt;img src=&quot;https://blog.kakaocdn.net/dn/NeFus/btsAvsIexrW/bKMTmggDtWXA3iHgUMsrM0/img.png&quot; srcset=&quot;https://img1.daumcdn.net/thumb/R1280x0/?scode=mtistory2&amp;fname=https%3A%2F%2Fblog.kakaocdn.net%2Fdn%2FNeFus%2FbtsAvsIexrW%2FbKMTmggDtWXA3iHgUMsrM0%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;453&quot; height=&quot;510&quot; data-origin-width=&quot;876&quot; data-origin-height=&quot;986&quot;/&gt;&lt;/span&gt;&lt;/figure&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;단순한 like 검색도 아니고 or, and 조건 + 문자열 중간에 포함 이라는 비교적 복잡한 조건이 들어갔음에도 불구하고, 1400대의 cost가 나왔다. 이정도면 괜찮아 보이는 검색 성능이다. 이 방법은 앞에서 말했듯이 name 칼럼이 바뀔때마다 tsvec_words 칼럼이 자동으로 업데이트되어야 하는 전략이 필요하다. ORM에서 설정하거나 DB function에서 설정하면 될 것 같다.&lt;br&gt;&amp;nbsp;&lt;br&gt;많은 방법들을 찾았지만, 실제 검색 엔진에서 결과를 보여줄 때는 일치도를 포함해서 결과를 출력할 수도 있다. ts_rank는 단어의 빈도를 확인하고, ts_rank_cd는 일치하는 어휘의 근접성을 고려한다고 한다. rank를 포함한 결과를 확인해보자.&lt;/p&gt;&lt;pre data-ke-type=&quot;codeblock&quot; class=&quot;sql&quot; data-ke-language=&quot;sql&quot;&gt;&lt;code&gt;SELECT *, ts_rank(tsvec_words, query) AS rank
FROM organization, to_tsquery('King|Group') query
WHERE query @@ tsvec_words
ORDER BY rank DESC;&lt;/code&gt;&lt;/pre&gt;&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;br&gt;해당 쿼리를 사용해서 일치도가 존재하는 쿼리 결과를 볼 수 있다. 해당 쿼리의 cost도 확인해보자.&lt;/p&gt;&lt;figure class=&quot;imageblock alignCenter&quot; data-ke-mobileStyle=&quot;widthOrigin&quot; data-origin-width=&quot;1566&quot; data-origin-height=&quot;1742&quot;&gt;&lt;span data-url=&quot;https://blog.kakaocdn.net/dn/boZ3EP/btsAyruEbAP/5SyCD3J8MtmK0ydkEMXKMK/img.png&quot; data-phocus=&quot;https://blog.kakaocdn.net/dn/boZ3EP/btsAyruEbAP/5SyCD3J8MtmK0ydkEMXKMK/img.png&quot;&gt;&lt;img src=&quot;https://blog.kakaocdn.net/dn/boZ3EP/btsAyruEbAP/5SyCD3J8MtmK0ydkEMXKMK/img.png&quot; srcset=&quot;https://img1.daumcdn.net/thumb/R1280x0/?scode=mtistory2&amp;fname=https%3A%2F%2Fblog.kakaocdn.net%2Fdn%2FboZ3EP%2FbtsAyruEbAP%2F5SyCD3J8MtmK0ydkEMXKMK%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;643&quot; height=&quot;715&quot; data-origin-width=&quot;1566&quot; data-origin-height=&quot;1742&quot;/&gt;&lt;/span&gt;&lt;/figure&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;Rank를 사용하는 쿼리는 상당히 cost가 많이 드는 것을 확인할 수 있다. 해당 원인은 postgres의 공식 문서에서 확인할 수 있었다.&lt;br&gt;&amp;nbsp;&lt;/p&gt;&lt;blockquote data-ke-style=&quot;style3&quot;&gt;Ranking can be expensive since it requires consulting the tsvector of each matching document, which can be I/O bound and therefore slow. Unfortunately, it is almost impossible to avoid since practical queries often result in large numbers of matches.&lt;/blockquote&gt;&lt;p data-ke-size=&quot;size16&quot;&gt;즉, 각 ROW에 대항하는 모든 tsvector를 참고해야해서 비용이 많이 든다는 말이다. 그래서 검색 관련한 쿼리의 요청이 많을 경우에는 해당 쿼리를 사용하는 것에 대해서 생각할 필요가 있어 보인다.&lt;br&gt;&amp;nbsp;&lt;/p&gt;&lt;h3 data-ke-size=&quot;size23&quot;&gt;참고자료&lt;/h3&gt;&lt;hr data-ke-type=&quot;horizontalRule&quot; data-ke-style=&quot;style5&quot;&gt;&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;a href=&quot;https://xata.io/blog/postgres-full-text-search-engine&quot; target=&quot;_blank&quot;&gt;&lt;span&gt;https://xata.io/blog/postgres-full-text-search-engine&lt;/span&gt;&lt;/a&gt;&lt;/p&gt;&lt;figure data-ke-type=&quot;opengraph&quot; data-og-title=&quot;Create an advanced search engine with PostgreSQL&quot; data-ke-align=&quot;alignCenter&quot; data-og-description=&quot;PostgreSQL provides the necessary building blocks for you to combine and create your own search engine for full-text search. Let's see how far we can take it.&quot; data-og-host=&quot;xata.io&quot; data-og-source-url=&quot;https://xata.io/blog/postgres-full-text-search-engine&quot; data-og-image=&quot;https://scrap.kakaocdn.net/dn/jdulb/hyUyzzH71Z/3Peg8ZmkS0Aj1KCwnuXbkK/img.png?width=1200&amp;amp;height=600&amp;amp;face=0_0_1200_600,https://scrap.kakaocdn.net/dn/pGmUG/hyUypqj96J/AiMtbQMm1Kqm9H5n0wIMpK/img.png?width=1200&amp;amp;height=600&amp;amp;face=0_0_1200_600,https://scrap.kakaocdn.net/dn/UlDKf/hyUys8oJyl/79kVSa0L7psfTvI4SZt6Z0/img.png?width=440&amp;amp;height=466&amp;amp;face=0_0_440_466&quot; data-og-url=&quot;https://xata.io/blog/postgres-full-text-search-engine&quot;&gt;&lt;a href=&quot;https://xata.io/blog/postgres-full-text-search-engine&quot; target=&quot;_blank&quot; data-source-url=&quot;https://xata.io/blog/postgres-full-text-search-engine&quot;&gt;&lt;div class=&quot;og-image&quot; style=&quot;background-image: url('https://scrap.kakaocdn.net/dn/jdulb/hyUyzzH71Z/3Peg8ZmkS0Aj1KCwnuXbkK/img.png?width=1200&amp;amp;height=600&amp;amp;face=0_0_1200_600,https://scrap.kakaocdn.net/dn/pGmUG/hyUypqj96J/AiMtbQMm1Kqm9H5n0wIMpK/img.png?width=1200&amp;amp;height=600&amp;amp;face=0_0_1200_600,https://scrap.kakaocdn.net/dn/UlDKf/hyUys8oJyl/79kVSa0L7psfTvI4SZt6Z0/img.png?width=440&amp;amp;height=466&amp;amp;face=0_0_440_466')&quot;&gt; &lt;/div&gt;&lt;div class=&quot;og-text&quot;&gt;&lt;p class=&quot;og-title&quot;&gt;Create an advanced search engine with PostgreSQL&lt;/p&gt;&lt;p class=&quot;og-desc&quot;&gt;PostgreSQL provides the necessary building blocks for you to combine and create your own search engine for full-text search. Let's see how far we can take it.&lt;/p&gt;&lt;p class=&quot;og-host&quot;&gt;xata.io&lt;/p&gt;&lt;/div&gt;&lt;/a&gt;&lt;/figure&gt;&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;a href=&quot;https://m.blog.naver.com/PostView.naver?blogId=geartec82&amp;amp;logNo=221724879807&amp;amp;categoryNo=32&amp;amp;proxyReferer=&quot; target=&quot;_blank&quot;&gt;&lt;span&gt;https://m.blog.naver.com/PostView.naver?blogId=geartec82&amp;amp;logNo=221724879807&amp;amp;categoryNo=32&amp;amp;proxyReferer=&lt;/span&gt;&lt;/a&gt;&lt;/p&gt;&lt;figure data-ke-type=&quot;opengraph&quot; data-og-title=&quot;PostgreSQL에서 NLP 자연어 기반 구문 검색하기(Full Text 검색 기능)&quot; data-ke-align=&quot;alignCenter&quot; data-og-description=&quot;오늘은 PostgreSQL을 MongoDB처럼 패턴 검색과 색인 기능을 사용하는 방법을 안내하고자 합니다. P...&quot; data-og-host=&quot;blog.naver.com&quot; data-og-source-url=&quot;https://m.blog.naver.com/PostView.naver?blogId=geartec82&amp;amp;logNo=221724879807&amp;amp;categoryNo=32&amp;amp;proxyReferer=&quot; data-og-image=&quot;https://scrap.kakaocdn.net/dn/KQ22P/hyUyruTLG3/KIqkRY0ND8mpxC00SNxztK/img.png?width=270&amp;amp;height=270&amp;amp;face=0_0_270_270&quot; data-og-url=&quot;https://blog.naver.com/geartec82/221724879807&quot;&gt;&lt;a href=&quot;https://blog.naver.com/geartec82/221724879807&quot; target=&quot;_blank&quot; data-source-url=&quot;https://m.blog.naver.com/PostView.naver?blogId=geartec82&amp;amp;logNo=221724879807&amp;amp;categoryNo=32&amp;amp;proxyReferer=&quot;&gt;&lt;div class=&quot;og-image&quot; style=&quot;background-image: url('https://scrap.kakaocdn.net/dn/KQ22P/hyUyruTLG3/KIqkRY0ND8mpxC00SNxztK/img.png?width=270&amp;amp;height=270&amp;amp;face=0_0_270_270')&quot;&gt; &lt;/div&gt;&lt;div class=&quot;og-text&quot;&gt;&lt;p class=&quot;og-title&quot;&gt;PostgreSQL에서 NLP 자연어 기반 구문 검색하기(Full Text 검색 기능)&lt;/p&gt;&lt;p class=&quot;og-desc&quot;&gt;오늘은 PostgreSQL을 MongoDB처럼 패턴 검색과 색인 기능을 사용하는 방법을 안내하고자 합니다. P...&lt;/p&gt;&lt;p class=&quot;og-host&quot;&gt;blog.naver.com&lt;/p&gt;&lt;/div&gt;&lt;/a&gt;&lt;/figure&gt;&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;a href=&quot;https://junhkang.tistory.com/2&quot; target=&quot;_blank&quot;&gt;&lt;span&gt;https://junhkang.tistory.com/2&lt;/span&gt;&lt;/a&gt;&lt;/p&gt;&lt;figure data-ke-type=&quot;opengraph&quot; data-og-title=&quot;Full Text Search를 활용한 데이터베이스 성능 향상&quot; data-ke-align=&quot;alignCenter&quot; data-og-description=&quot;1. 문제상황 긴 텍스트에서 단순 like 조합 외 방법으로 유사 문자열 검색 (Ex. Susan loves hiking 을 “love hike” 이라는 키워드로 검색하고자 함) RDBMS에서 수천만 건의 데이터 처리 시 긴 문자열 검색 &quot; data-og-host=&quot;junhkang.tistory.com&quot; data-og-source-url=&quot;https://junhkang.tistory.com/2&quot; data-og-image=&quot;https://scrap.kakaocdn.net/dn/c6RIMb/hyUyvxi69N/qtMDR1cfQKtupkXt7QfFQk/img.png?width=750&amp;amp;height=384&amp;amp;face=0_0_750_384,https://scrap.kakaocdn.net/dn/b8YV8G/hyUyzT0eWw/owrAkjCPA4F1ZTcYNUJvL1/img.png?width=750&amp;amp;height=384&amp;amp;face=0_0_750_384,https://scrap.kakaocdn.net/dn/jM1aX/hyUyAyCt7X/CIx5uzsUABeaYyBuJf4k50/img.png?width=540&amp;amp;height=557&amp;amp;face=0_0_540_557&quot; data-og-url=&quot;https://junhkang.tistory.com/2&quot;&gt;&lt;a href=&quot;https://junhkang.tistory.com/2&quot; target=&quot;_blank&quot; data-source-url=&quot;https://junhkang.tistory.com/2&quot;&gt;&lt;div class=&quot;og-image&quot; style=&quot;background-image: url('https://scrap.kakaocdn.net/dn/c6RIMb/hyUyvxi69N/qtMDR1cfQKtupkXt7QfFQk/img.png?width=750&amp;amp;height=384&amp;amp;face=0_0_750_384,https://scrap.kakaocdn.net/dn/b8YV8G/hyUyzT0eWw/owrAkjCPA4F1ZTcYNUJvL1/img.png?width=750&amp;amp;height=384&amp;amp;face=0_0_750_384,https://scrap.kakaocdn.net/dn/jM1aX/hyUyAyCt7X/CIx5uzsUABeaYyBuJf4k50/img.png?width=540&amp;amp;height=557&amp;amp;face=0_0_540_557')&quot;&gt; &lt;/div&gt;&lt;div class=&quot;og-text&quot;&gt;&lt;p class=&quot;og-title&quot;&gt;Full Text Search를 활용한 데이터베이스 성능 향상&lt;/p&gt;&lt;p class=&quot;og-desc&quot;&gt;1. 문제상황 긴 텍스트에서 단순 like 조합 외 방법으로 유사 문자열 검색 (Ex. Susan loves hiking 을 “love hike” 이라는 키워드로 검색하고자 함) RDBMS에서 수천만 건의 데이터 처리 시 긴 문자열 검색 &lt;/p&gt;&lt;p class=&quot;og-host&quot;&gt;junhkang.tistory.com&lt;/p&gt;&lt;/div&gt;&lt;/a&gt;&lt;/figure&gt;&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;a href=&quot;https://www.postgresql.org/docs/current/textsearch-controls.html&quot; target=&quot;_blank&quot;&gt;&lt;span&gt;https://www.postgresql.org/docs/current/textsearch-controls.html&lt;/span&gt;&lt;/a&gt;&lt;/p&gt;&lt;figure data-ke-type=&quot;opengraph&quot; data-og-title=&quot;12.3.&amp;nbsp;Controlling Text Search&quot; data-ke-align=&quot;alignCenter&quot; data-og-description=&quot;12.3.&amp;nbsp;Controlling Text Search # 12.3.1. Parsing Documents 12.3.2. Parsing Queries 12.3.3. Ranking Search Results 12.3.4. Highlighting Results To implement full …&quot; data-og-host=&quot;www.postgresql.org&quot; data-og-source-url=&quot;https://www.postgresql.org/docs/current/textsearch-controls.html&quot; data-og-image=&quot;https://scrap.kakaocdn.net/dn/Ydgl2/hyUyAyCuda/YYPXR3WOl3FpBqEjT4XWJK/img.png?width=540&amp;amp;height=557&amp;amp;face=0_0_540_557&quot; data-og-url=&quot;https://www.postgresql.org/docs/16/textsearch-controls.html&quot;&gt;&lt;a href=&quot;https://www.postgresql.org/docs/16/textsearch-controls.html&quot; target=&quot;_blank&quot; data-source-url=&quot;https://www.postgresql.org/docs/current/textsearch-controls.html&quot;&gt;&lt;div class=&quot;og-image&quot; style=&quot;background-image: url('https://scrap.kakaocdn.net/dn/Ydgl2/hyUyAyCuda/YYPXR3WOl3FpBqEjT4XWJK/img.png?width=540&amp;amp;height=557&amp;amp;face=0_0_540_557')&quot;&gt; &lt;/div&gt;&lt;div class=&quot;og-text&quot;&gt;&lt;p class=&quot;og-title&quot;&gt;12.3.&amp;nbsp;Controlling Text Search&lt;/p&gt;&lt;p class=&quot;og-desc&quot;&gt;12.3.&amp;nbsp;Controlling Text Search # 12.3.1. Parsing Documents 12.3.2. Parsing Queries 12.3.3. Ranking Search Results 12.3.4. Highlighting Results To implement full …&lt;/p&gt;&lt;p class=&quot;og-host&quot;&gt;www.postgresql.org&lt;/p&gt;&lt;/div&gt;&lt;/a&gt;&lt;/figure&gt;&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;br&gt;&amp;nbsp;&lt;/p&gt;</description>
      <category>서버 인프라/DB</category>
      <author>트리맨스</author>
      <guid isPermaLink="true">https://tre2man.tistory.com/364</guid>
      <comments>https://tre2man.tistory.com/364#entry364comment</comments>
      <pubDate>Sat, 18 Nov 2023 16:16:51 +0900</pubDate>
    </item>
    <item>
      <title>Typeorm querybuilder에서 bigint number로 받기</title>
      <link>https://tre2man.tistory.com/363</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;typeorm에서 querybuilder를 사용해서 데이터를 뽑아오는 작업을 했다. postgres에서 pk를 Bigint로 설정 후 querybuilder에서 받아오니 무조건 string으로 가져오는 문제가 있었다. 공식 문서를 찾아 보니 bigint가 string으로 넘어오는 것은 의도한 동작으로 보인다.&lt;br /&gt;&lt;br /&gt;&lt;a href=&quot;https://typeorm.io/entities#column-types&quot; target=&quot;_blank&quot; rel=&quot;noopener&amp;nbsp;noreferrer&quot;&gt;https://typeorm.io/entities&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;하지만 타 ORM을 살펴보니 Bigint 타입은 바로 숫자로 사용이 되는 것으로 보였다. (JPA는 Long으로, Django는 정수 타입으로 바로 캐스팅) 그리고 기존의 개발된 코드를 보니 transformer를 사용해서 repository에서 바로 가져올 때는 number 타입으로 가져오는 것을 볼 수 있었다. 그래서 기존의 repository와 dao의 규칙을 유지하면서 코드의 가독성을 동시에 만족하기 위해서 decorator를 사용하기로 했다. (더 좋은 방법이 있을 수 있다. 그냥 decorator로 구현했을 뿐...)&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;h3 data-ke-size=&quot;size23&quot;&gt;제작&lt;/h3&gt;
&lt;hr contenteditable=&quot;false&quot; data-ke-type=&quot;horizontalRule&quot; data-ke-style=&quot;style5&quot; /&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;먼저 dao의 method에서 쿼리문이 실행되므로 메서드 데코레이터를 사용해야 할 것이다. 그리고 async한 작업이기 때문에 decorator에서도 promise한 데이터를 처리하기 위한 방법이 있어야 할 것이다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;또한 반환값에 대한 제한을 몇 개 걸어두어야 하는데 먼저 nested한 데이터는 없다고 생각해야 한다. 애초에 쿼리 결과값은 nested하게 출력이 되지 않기도 하고, nested한 데이터의 경우 가능한 경우의 수가 너무 많아지기 때문이다. 그리고 값은 항상 promise하게 온다고 생각해야 한다. db에 접근하는 async한 로직이기 때문에, 반환값은 항상 promise하다고 생각하자.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;가능한 경우의 수를 정리하자. 먼저 nested하지 않은 단일 object의 경우에는 해당 value를 받아와서 number 타입을 Number()로 다시 시 씌워준다. 이 때 typeof로 number type을 분류하려고 했는데, interface에서 number로 지정해도 orm에서 string으로 반환해버리니 강지제로 string이 되어 버렸다. 그래서 decorator에 직접 property의 key를 받아서 Number로 씌워주기로 했다. 다음으로 array object의 경우에는 map 함수를 통해서 각 value마다 데이터 변환을 시도했다. 작업을 마친 결과물은 다음과 같다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;pre id=&quot;code_1699775534294&quot; class=&quot;typescript&quot; data-ke-language=&quot;typescript&quot; data-ke-type=&quot;codeblock&quot;&gt;&lt;code&gt;function ConvertToNumber(convertKey: string[]) {
  return function (target: any, propertyKey: string, descriptor: PropertyDescriptor) {
    // 입력받은 value를 number로 변환
    function convertValue(targetValue: any) {
      Object.entries(targetValue).forEach(([key, value]) =&amp;gt; {
        if (convertKey.includes(key)) {
          targetValue[key] = Number(value);
        }
      });
      return targetValue;
    }

    const originalMethod = descriptor.value;

    descriptor.value = async function (...args: any[]) {
      const result = await originalMethod.apply(this, args);

      // 배열일 경우
      if (Array.isArray(result)) {
        result.map((value) =&amp;gt; convertValue(value));
        return result;
      }

      // 단일 타입일 경우
      convertValue(result);
      return result;
    };

    return descriptor;
  };
}


@CustomRepository(User)
export class TypeormUserDao extends Repository&amp;lt;User&amp;gt; implements UserDao {
  @ConvertToNumber(['userId'])
  async findAllByIds(ids: number[]): Promise&amp;lt;UserInfoResponse[]&amp;gt; {
    if (ids.length === 0) {
      return [];
    }
    return await this.createQueryBuilder('user')
      .select('user.id', 'userId')
      .addSelect('user.name', 'name')
      .addSelect('user.nickname', 'nickName')
      .addSelect('user.gender', 'gender')
      .addSelect('user.grade', 'grade')
      .addSelect('user.profileImageUrl', 'profileImageUrl')
      .addSelect('organization.name', 'organizationName')
      .innerJoin(Organization, 'organization', 'organization.id = user.organizationId')
      .where('user.id IN (:...ids)', { ids })
      .andWhere('user.status = :status', { status: AccountStatus.ACTIVATED })
      .getRawMany&amp;lt;UserInfoResponse&amp;gt;();
  }
}&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;해당 코드에서는 ConvertToNumber 데코레이터를 사용해서 값을 변환한다. 이 때 userId라는 key를 입력해 주어. UserInfoResponse.userId에 대해서 모두 컨버팅을 하게 하도록 했다.&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에 대한 변환, async 하지 않는 데이터에 대해서도 컨버팅을 할 수 있게 확장하면 좋을 것 같다.&lt;/p&gt;
&lt;p data-ke-size=&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;</description>
      <category>프레임워크/NestJS</category>
      <author>트리맨스</author>
      <guid isPermaLink="true">https://tre2man.tistory.com/363</guid>
      <comments>https://tre2man.tistory.com/363#entry363comment</comments>
      <pubDate>Sun, 12 Nov 2023 16:55:28 +0900</pubDate>
    </item>
    <item>
      <title>Sonoma 업데이트 후 react native 실행 안되는 오류</title>
      <link>https://tre2man.tistory.com/362</link>
      <description>&lt;p data-ke-size=&quot;size16&quot; style=&quot;text-align: left;&quot;&gt;&amp;nbsp;&lt;br&gt;역시 맥 업데이트는 함부로 하면 안된다. 이번에 Monterey에서 Sonoma 업데이트 이후 ios 시뮬레이터를 돌리려는 순간, 다음과 같은 에러가 떴다.&lt;br&gt;&amp;nbsp;&lt;/p&gt;&lt;pre data-ke-type=&quot;codeblock&quot; class=&quot;bash&quot; data-ke-language=&quot;bash&quot;&gt;&lt;code&gt;error Error: Command was killed with SIGABRT (Aborted): xcodebuild -list -json
2023-10-03 17:13:06.373 xcodebuild[63549:4107744]&amp;nbsp;&amp;nbsp;DVTErrorPresenter: Unable to load simulator devices.
Domain: DVTCoreSimulatorAdditionsErrorDomain
Code: 3
Failure Reason: The version of the CoreSimulator framework installed on this Mac is out-of-date and not supported by this version of Xcode.
Recovery Suggestion: Please ensure that you have installed all available updates to your Mac's software, and that you are running the most recent version of Xcode supported by macOS.&lt;/code&gt;&lt;/pre&gt;&lt;p data-ke-size=&quot;size16&quot; style=&quot;text-align: left;&quot;&gt;&amp;nbsp;&lt;br&gt;시물레이터의 버전이 안맞는거 같은데, 이거는 xcode를 킨 다음 시뮬레이터를 키면 자동으로 맞는 버전으로 업데이트한다. 이후 임의 시뮬레이터 하나를 킨 다음, react native 애플리케이션을 실행하면 알아서 잘 켜진다.&lt;br&gt;&amp;nbsp;&lt;br&gt;개발중인 컴퓨터 메이저 업데이트는 큰 스프린트가 끝난 이후에 하자...&lt;br&gt;&amp;nbsp;&lt;br&gt;&amp;nbsp;&lt;/p&gt;</description>
      <author>트리맨스</author>
      <guid isPermaLink="true">https://tre2man.tistory.com/362</guid>
      <comments>https://tre2man.tistory.com/362#entry362comment</comments>
      <pubDate>Tue, 3 Oct 2023 17:43:44 +0900</pubDate>
    </item>
    <item>
      <title>VPC Peering 사용하기</title>
      <link>https://tre2man.tistory.com/361</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;이번에 서버를 글로벌 확장을 하게 되면서 각 VPC간에 연결을 해야 할 상황이 왔다. 그래서 Peering을 하면서 간단히 정리해 보려고 한다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;h3 data-ke-size=&quot;size23&quot;&gt;개요&lt;/h3&gt;
&lt;hr contenteditable=&quot;false&quot; data-ke-type=&quot;horizontalRule&quot; data-ke-style=&quot;style5&quot; /&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;처음에는 각 국가의 서버 정보가 달라야 하기 때문에 아키텍쳐 전체를 복사하려고 했으나, 모니터링 및 기타 중앙에서 관리해야 하는 앱이 있고, 해당 앱에 대해서는 처음부터&amp;nbsp; 구축하기에는 공수가 많이 들었다. 그래서 서비스되는 애플리케이션은 Terraform을 사용해서 복사하고, BI 또는 모니터링 애플리케이션에 대해서는&amp;nbsp;&lt;span style=&quot;font-family: -apple-system, BlinkMacSystemFont, 'Helvetica Neue', 'Apple SD Gothic Neo', Arial, sans-serif; letter-spacing: 0px;&quot;&gt;AWS의 내부 네트워크를 사용해서 타 리전의 애플리케이션에 접속하여 외부 네트워크를 거치지 않고 안전하게 데이터에 접근할 수 있게 할 계획이다.&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 Peering과 비슷한 서비스인 Transit Gateway에 대해서 조사해 보았다.&lt;/p&gt;
&lt;table style=&quot;border-collapse: collapse; width: 100%; height: 91px;&quot; border=&quot;1&quot; data-ke-align=&quot;alignLeft&quot; data-ke-style=&quot;style4&quot;&gt;
&lt;tbody&gt;
&lt;tr style=&quot;height: 17px;&quot;&gt;
&lt;td style=&quot;width: 50%; height: 17px;&quot;&gt;VPC Peering&lt;/td&gt;
&lt;td style=&quot;width: 50%; height: 17px;&quot;&gt;Transit Gateway&lt;/td&gt;
&lt;/tr&gt;
&lt;tr style=&quot;height: 19px;&quot;&gt;
&lt;td style=&quot;width: 50%; height: 19px;&quot;&gt;VPC간 1대1 연결&lt;/td&gt;
&lt;td style=&quot;width: 50%; height: 19px;&quot;&gt;허브&amp;amp;스포크 방식으로 연결&lt;/td&gt;
&lt;/tr&gt;
&lt;tr style=&quot;height: 19px;&quot;&gt;
&lt;td style=&quot;width: 50%; height: 19px;&quot;&gt;최대 125개의 VPC 연결&lt;/td&gt;
&lt;td style=&quot;width: 50%; height: 19px;&quot;&gt;최대 5000개의 VPC연결&lt;/td&gt;
&lt;/tr&gt;
&lt;tr style=&quot;height: 19px;&quot;&gt;
&lt;td style=&quot;width: 50%; height: 19px;&quot;&gt;연결 비용 없음&lt;/td&gt;
&lt;td style=&quot;width: 50%; height: 19px;&quot;&gt;시간단 0.07$&lt;/td&gt;
&lt;/tr&gt;
&lt;tr style=&quot;height: 17px;&quot;&gt;
&lt;td style=&quot;width: 50%; height: 17px;&quot;&gt;리전간 데이터 이동 표준 요금&lt;/td&gt;
&lt;td style=&quot;width: 50%; height: 17px;&quot;&gt;리전간 데이터 이동 표준 요금&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;VPC Peering은 1대1 연결이기 때문에 A, B, C, D 총 4개의 VPC를 모두 연결하기 위해서는 6개의 connection이 필요하다. 하지만 Transit Gateway는 허브가 있기 때문에 4개의 Connection만 필요하다. 하지만 연결마다 비용이 따로 든다. 나의 경우에는 VPC가 많은 편도 아니고, 모두 연결하는 것이 아닌 메인VPC에만 모두 연결만 하면 되는 VPC Peering을 선택했다. 비용도 비교적 저렴하다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;h3 data-ke-size=&quot;size23&quot;&gt;테스트&lt;/h3&gt;
&lt;hr contenteditable=&quot;false&quot; data-ke-type=&quot;horizontalRule&quot; data-ke-style=&quot;style5&quot; /&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;간단히 테스트할려는 계획은 다음과 같다.&lt;/p&gt;
&lt;p&gt;&lt;figure class=&quot;imageblock alignCenter&quot; data-ke-mobileStyle=&quot;widthOrigin&quot; data-filename=&quot;스크린샷 2023-09-23 오후 4.55.58.png&quot; data-origin-width=&quot;1652&quot; data-origin-height=&quot;690&quot;&gt;&lt;span data-url=&quot;https://blog.kakaocdn.net/dn/b3tY0U/btsvfASnSbn/tP8zQLUroOiwG6DhPJN8o0/img.png&quot; data-phocus=&quot;https://blog.kakaocdn.net/dn/b3tY0U/btsvfASnSbn/tP8zQLUroOiwG6DhPJN8o0/img.png&quot;&gt;&lt;img src=&quot;https://blog.kakaocdn.net/dn/b3tY0U/btsvfASnSbn/tP8zQLUroOiwG6DhPJN8o0/img.png&quot; srcset=&quot;https://img1.daumcdn.net/thumb/R1280x0/?scode=mtistory2&amp;fname=https%3A%2F%2Fblog.kakaocdn.net%2Fdn%2Fb3tY0U%2FbtsvfASnSbn%2FtP8zQLUroOiwG6DhPJN8o0%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;1652&quot; height=&quot;690&quot; data-filename=&quot;스크린샷 2023-09-23 오후 4.55.58.png&quot; data-origin-width=&quot;1652&quot; data-origin-height=&quot;690&quot;/&gt;&lt;/span&gt;&lt;/figure&gt;
&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;VPC1의 EC2는 퍼블릭으로 열려 있어서, 외부에서 접속이 가능하다. 하지만 VPC2의 EC2는 외부에서 접속을 못하게 프라이빗 서브넷에 두었다. 여기 접속하기 위해서는 VPC1의 EC2에 접속한 다음, VPC Peering을 통해 VPC2의 EC2에 접속해야 한다. 이 구성을 테스트 해 볼 것이다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;먼저 VPC2개를 만들어 둔다. 그리고 VPC1의 퍼블릭 서브넷에 인스턴스까지 하나 만들어 둔다. 그리고 VPC2에는 프라이빗 서브넷 내에 EC2를 만들어 둔다. 이제 외부에서 해당 EC2에는 접속할 방법이 없다. 여기서 주의할 점은, &lt;b&gt;각 VPC의 내부IP가 중복되지 않게 CIDR을 설정해야 한다.&lt;/b&gt;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;VPC1에서 피어링 연결을 요청한다.&lt;/p&gt;
&lt;p&gt;&lt;figure class=&quot;imageblock alignCenter&quot; data-ke-mobileStyle=&quot;widthOrigin&quot; data-filename=&quot;스크린샷 2023-09-23 오후 5.05.39.png&quot; data-origin-width=&quot;1608&quot; data-origin-height=&quot;1572&quot;&gt;&lt;span data-url=&quot;https://blog.kakaocdn.net/dn/bOZsYc/btsviR0w8Io/vqy6lLtvU4ADevcV1ObNv0/img.png&quot; data-phocus=&quot;https://blog.kakaocdn.net/dn/bOZsYc/btsviR0w8Io/vqy6lLtvU4ADevcV1ObNv0/img.png&quot;&gt;&lt;img src=&quot;https://blog.kakaocdn.net/dn/bOZsYc/btsviR0w8Io/vqy6lLtvU4ADevcV1ObNv0/img.png&quot; srcset=&quot;https://img1.daumcdn.net/thumb/R1280x0/?scode=mtistory2&amp;fname=https%3A%2F%2Fblog.kakaocdn.net%2Fdn%2FbOZsYc%2FbtsviR0w8Io%2Fvqy6lLtvU4ADevcV1ObNv0%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;678&quot; height=&quot;663&quot; data-filename=&quot;스크린샷 2023-09-23 오후 5.05.39.png&quot; data-origin-width=&quot;1608&quot; data-origin-height=&quot;1572&quot;/&gt;&lt;/span&gt;&lt;/figure&gt;
&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;요청자 VPC ID에 VPC1 아이디를 입력하고, 수락자 VPC ID에 VPC2의 아이디를 입력한다. 아후 VPC2에서 수락하면 일단은 연결이 된 것이다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;다음으로는 라우팅 테이블에서 타 VPC에 접속할 때 라우터를 잘 탈수 있도록 라우터를 설정하는 것이다. 먼저 VPC1의 EC2가 있는 서브넷의 라우팅 테이블을에 라우팅 규칙을 추가하자.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p&gt;&lt;figure class=&quot;imageblock alignCenter&quot; data-ke-mobileStyle=&quot;widthOrigin&quot; data-filename=&quot;스크린샷 2023-09-23 오후 5.25.23.png&quot; data-origin-width=&quot;1280&quot; data-origin-height=&quot;470&quot;&gt;&lt;span data-url=&quot;https://blog.kakaocdn.net/dn/kFk33/btsvdp4op3n/Tjz71IV8ExLJplbJ1vkoh1/img.png&quot; data-phocus=&quot;https://blog.kakaocdn.net/dn/kFk33/btsvdp4op3n/Tjz71IV8ExLJplbJ1vkoh1/img.png&quot;&gt;&lt;img src=&quot;https://blog.kakaocdn.net/dn/kFk33/btsvdp4op3n/Tjz71IV8ExLJplbJ1vkoh1/img.png&quot; srcset=&quot;https://img1.daumcdn.net/thumb/R1280x0/?scode=mtistory2&amp;fname=https%3A%2F%2Fblog.kakaocdn.net%2Fdn%2FkFk33%2Fbtsvdp4op3n%2FTjz71IV8ExLJplbJ1vkoh1%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;724&quot; height=&quot;266&quot; data-filename=&quot;스크린샷 2023-09-23 오후 5.25.23.png&quot; data-origin-width=&quot;1280&quot; data-origin-height=&quot;470&quot;/&gt;&lt;/span&gt;&lt;/figure&gt;
&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;172.31.0.0/16은 VPC2의 내부 IP주소이다. 해당 라우팅은 172.31.0.0/16으로 요청하는 네트워크에 대해서는 VPC Peering을 사용하도록 설정한 것이다. VPC Peering 대상에 특별한 설정이 없다면 pcx-xxxx...로 시작하는 이름이 있을 것이다. 이제 VPC2의 EC2가 있는 서브넷의 라우팅 테이블도 편집해 주자.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p&gt;&lt;figure class=&quot;imageblock alignCenter&quot; data-ke-mobileStyle=&quot;widthOrigin&quot; data-filename=&quot;스크린샷 2023-09-23 오후 5.28.45.png&quot; data-origin-width=&quot;1232&quot; data-origin-height=&quot;460&quot;&gt;&lt;span data-url=&quot;https://blog.kakaocdn.net/dn/8ZGv4/btsvmyMs94w/ndYf4kyKEC3rDIZVdu1AU1/img.png&quot; data-phocus=&quot;https://blog.kakaocdn.net/dn/8ZGv4/btsvmyMs94w/ndYf4kyKEC3rDIZVdu1AU1/img.png&quot;&gt;&lt;img src=&quot;https://blog.kakaocdn.net/dn/8ZGv4/btsvmyMs94w/ndYf4kyKEC3rDIZVdu1AU1/img.png&quot; srcset=&quot;https://img1.daumcdn.net/thumb/R1280x0/?scode=mtistory2&amp;fname=https%3A%2F%2Fblog.kakaocdn.net%2Fdn%2F8ZGv4%2FbtsvmyMs94w%2FndYf4kyKEC3rDIZVdu1AU1%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;734&quot; height=&quot;274&quot; data-filename=&quot;스크린샷 2023-09-23 오후 5.28.45.png&quot; data-origin-width=&quot;1232&quot; data-origin-height=&quot;460&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;192.168.0.0/16은 VPC1의 내부 IP주소이다. 각 인스턴스의 보안 그룹은 22번 포트로 접속할 수 있게 한다. IP주소 대역 제한은 요청하는 호스트의 아이피 주소에 따라서 제한을 준다.&lt;/p&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에서 ssh접속을 테스트 해볼 수 있다. scp명령어를 통해서 VPC1의 EC2에 VPC2의 EC2 키 파일을 넣어 두고, ssh 접속을 테스트해보자. 먼저 VPC1의 EC2에 접속한다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p&gt;&lt;figure class=&quot;imageblock alignCenter&quot; data-ke-mobileStyle=&quot;widthOrigin&quot; data-filename=&quot;스크린샷 2023-09-23 오후 5.31.55.png&quot; data-origin-width=&quot;1210&quot; data-origin-height=&quot;438&quot;&gt;&lt;span data-url=&quot;https://blog.kakaocdn.net/dn/bilSW3/btsvfy1k0n1/sBacCekFr4ccyRL7GgIbHK/img.png&quot; data-phocus=&quot;https://blog.kakaocdn.net/dn/bilSW3/btsvfy1k0n1/sBacCekFr4ccyRL7GgIbHK/img.png&quot;&gt;&lt;img src=&quot;https://blog.kakaocdn.net/dn/bilSW3/btsvfy1k0n1/sBacCekFr4ccyRL7GgIbHK/img.png&quot; srcset=&quot;https://img1.daumcdn.net/thumb/R1280x0/?scode=mtistory2&amp;fname=https%3A%2F%2Fblog.kakaocdn.net%2Fdn%2FbilSW3%2Fbtsvfy1k0n1%2FsBacCekFr4ccyRL7GgIbHK%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;689&quot; height=&quot;249&quot; data-filename=&quot;스크린샷 2023-09-23 오후 5.31.55.png&quot; data-origin-width=&quot;1210&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;아이피 주소를 보면 192.168.1.183인 것을 보아 192.168.0.0/16 안에 있는 것을 알 수 있다. VPC1의 EC2이다. 이제 VPC2의 EC2에 접속해보자.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p&gt;&lt;figure class=&quot;imageblock alignCenter&quot; data-ke-mobileStyle=&quot;widthOrigin&quot; data-filename=&quot;스크린샷 2023-09-23 오후 5.35.31.png&quot; data-origin-width=&quot;1258&quot; data-origin-height=&quot;496&quot;&gt;&lt;span data-url=&quot;https://blog.kakaocdn.net/dn/cUZwDE/btsvdqvn9Ly/kNdGaTQmqqY5ySkKsnXbNK/img.png&quot; data-phocus=&quot;https://blog.kakaocdn.net/dn/cUZwDE/btsvdqvn9Ly/kNdGaTQmqqY5ySkKsnXbNK/img.png&quot;&gt;&lt;img src=&quot;https://blog.kakaocdn.net/dn/cUZwDE/btsvdqvn9Ly/kNdGaTQmqqY5ySkKsnXbNK/img.png&quot; srcset=&quot;https://img1.daumcdn.net/thumb/R1280x0/?scode=mtistory2&amp;fname=https%3A%2F%2Fblog.kakaocdn.net%2Fdn%2FcUZwDE%2Fbtsvdqvn9Ly%2FkNdGaTQmqqY5ySkKsnXbNK%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;267&quot; data-filename=&quot;스크린샷 2023-09-23 오후 5.35.31.png&quot; data-origin-width=&quot;1258&quot; data-origin-height=&quot;496&quot;/&gt;&lt;/span&gt;&lt;/figure&gt;
&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;아이피 주소가 172.31.64.162인 것을 보아 172.31.0.0/16안에 있는 것을 잘 알수 있다. VPC Peering이 정상적으로 잘 이루어진 것 같다.&lt;/p&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 접속과 S3 엔드포인트 접속을 위해 DNS 주소까지 사용할 것 같으므로, 나중에 DNS 설정까지 켜두면 좋을 것 같다. 주고받는 데이터는 오직 관리자를 위한 데이터이므로, 사용자 수준에서 데이터의 전송은 없다고 봐도 무방할 것 같다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;</description>
      <category>서버 인프라/Aws</category>
      <author>트리맨스</author>
      <guid isPermaLink="true">https://tre2man.tistory.com/361</guid>
      <comments>https://tre2man.tistory.com/361#entry361comment</comments>
      <pubDate>Sat, 23 Sep 2023 17:44:31 +0900</pubDate>
    </item>
    <item>
      <title>NestJS Graceful shutdown</title>
      <link>https://tre2man.tistory.com/360</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;실행중인 NestJS를 종료하고 싶은데, 실행중인 작업이 있으면 종료하기 애매할 것이다. 해당 세션은 종료가 된 이후에 업데이트가 되어야 하기 때문이다. 이러한 문제를 NestJS는 shutdown에 관련한 인터페이스를 제공하여 SIGTERM 신호가 들어올 때의 동작을 정의한다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;h3 data-ke-size=&quot;size23&quot;&gt;구현&lt;/h3&gt;
&lt;hr contenteditable=&quot;false&quot; data-ke-type=&quot;horizontalRule&quot; data-ke-style=&quot;style5&quot; /&gt;
&lt;pre id=&quot;code_1691854854967&quot; class=&quot;javascript&quot; data-ke-language=&quot;javascript&quot; data-ke-type=&quot;codeblock&quot;&gt;&lt;code&gt;import { NestFactory } from '@nestjs/core';
import { AppModule } from './app.module';
import { ShutdownSignal } from '@nestjs/common';

const PORT = 3001;

async function bootstrap() {
  const app = await NestFactory.create(AppModule);
  app.enableShutdownHooks([ShutdownSignal.SIGTERM]);
  await app.listen(PORT);

  console.log(`Application is running on: ${PORT}`);
  console.log(`Pid is ${process.pid}`);
}
bootstrap();&lt;/code&gt;&lt;/pre&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;enableShutdownHooks 메서드를 호출한다. 이 기능은 기본으로는 비활성화 되어있는데, 이는 공식 문서에서는 메모리를 많이 소비하므로 필요할 때만 키는 것 같다.&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;이제 service를 보자. 기본적으로 implement할 수 있는 인터페이스를 제공하므로, 해당 메서드를 사용하여 구현해 보았다.&lt;/p&gt;
&lt;pre id=&quot;code_1691854909751&quot; class=&quot;javascript&quot; data-ke-language=&quot;javascript&quot; data-ke-type=&quot;codeblock&quot;&gt;&lt;code&gt;import {
  Injectable,
  OnModuleDestroy,
  OnApplicationShutdown,
  BeforeApplicationShutdown,
} from '@nestjs/common';

@Injectable()
export class AppService
  implements OnModuleDestroy, BeforeApplicationShutdown, OnApplicationShutdown
{
  /**
   * 5초 대기하는 함수
   */
  getHello(): Promise&amp;lt;string&amp;gt; {
    console.log('5초 대기');
    return new Promise((resolve) =&amp;gt; {
      setTimeout(() =&amp;gt; {
        console.log('5초 대기 완료');
        resolve('Hello World! ');
      }, 5000);
    });
  }

  async onModuleDestroy() {
    console.warn('모듈이 종료됩니다');
  }

  async beforeApplicationShutdown(signal: string) {
    console.warn(`프로그램이 종료되기 전에 ${signal} 시그널을 받았습니다`);
  }

  async onApplicationShutdown() {
    console.warn('프로그램이 종료됩니다');
  }
}&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;해당 메서드를 구현한 이후 IDLE 상태의 애플리케이션에&amp;nbsp; SIGTERM 신호를 주게되면, 애플리케이션이 onModuleDestory -&amp;gt; beforeAplicationShutdown-&amp;gt; onApplicationShutdown 순서대로 실행되는 것을 볼 수 있다.&lt;/p&gt;
&lt;p&gt;&lt;figure class=&quot;imageblock alignCenter&quot; data-ke-mobileStyle=&quot;widthOrigin&quot; data-filename=&quot;스크린샷 2023-08-13 00.43.42.png&quot; data-origin-width=&quot;756&quot; data-origin-height=&quot;146&quot;&gt;&lt;span data-url=&quot;https://blog.kakaocdn.net/dn/cGzAez/btsq1FJfSFM/SEX1VF1KVQcFjUKxqCt6g1/img.png&quot; data-phocus=&quot;https://blog.kakaocdn.net/dn/cGzAez/btsq1FJfSFM/SEX1VF1KVQcFjUKxqCt6g1/img.png&quot;&gt;&lt;img src=&quot;https://blog.kakaocdn.net/dn/cGzAez/btsq1FJfSFM/SEX1VF1KVQcFjUKxqCt6g1/img.png&quot; srcset=&quot;https://img1.daumcdn.net/thumb/R1280x0/?scode=mtistory2&amp;fname=https%3A%2F%2Fblog.kakaocdn.net%2Fdn%2FcGzAez%2Fbtsq1FJfSFM%2FSEX1VF1KVQcFjUKxqCt6g1%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;756&quot; height=&quot;146&quot; data-filename=&quot;스크린샷 2023-08-13 00.43.42.png&quot; data-origin-width=&quot;756&quot; data-origin-height=&quot;146&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;여기서 5초가 걸리는 getHello 로직(세션)이 실행 중일때 SIGTERM을 주게 되면, 애플리케이션이 바로 종료되지 않는다. 여기서 몇가지 실험을 해 봤는데, getHello 세션 실행 중에 SIGTERM을 주면은 어플리케이션이 종료가 되지는 않는다. 하지만 getHello 세션이 종료가 되어도 프로그램이 종료되지 않는 것을 보았다. 똑같은 요청을 날리면 여전히 실행되는 것을 보았다. 그러다가 몇초 뒤에 갑자기 애플리케이션이 종료가 되었다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;실무에서 사용할때는 보통 SIGTERM이 들어간 이후 몇 분 뒤에 SIGKILL이 들어가서 애플리케이션을 완전히 죽여버린다. SIGTERM은 애플리케이션이 종료되기 전에 준비 과정(?)을 할 시간을 준다. 하지만 SIGKILL은 준비 과정을 거치지 않고 강제로 죽여버린다. 그래서 애플리케이션을 종료할 때는 SIGTERM을 먼저 보내서 애플리케이션에서 종료 준비를 한 후에 SIGKILL로 완전하게 프로세스를 죽인다고 한다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;</description>
      <category>프레임워크/NestJS</category>
      <author>트리맨스</author>
      <guid isPermaLink="true">https://tre2man.tistory.com/360</guid>
      <comments>https://tre2man.tistory.com/360#entry360comment</comments>
      <pubDate>Sun, 13 Aug 2023 00:48:11 +0900</pubDate>
    </item>
    <item>
      <title>express에서 공통 트랜잭션 ID 부여하기</title>
      <link>https://tre2man.tistory.com/359</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;express를 사용한 서버를 만들면서 각 트랜잭션마다 공통적인 ID가 있으면 좋겠다는 생각이 들었다. 이러한 생각에 기반하여 여러가지 방법을 찾아 보았는데, 역시 동시성 제어 관련해서 문제가 있었다. 해당 문제와 관련해서 해결 방법을 생각해 보았다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;h3 data-ke-size=&quot;size23&quot;&gt;개요&lt;/h3&gt;
&lt;hr contenteditable=&quot;false&quot; data-ke-type=&quot;horizontalRule&quot; data-ke-style=&quot;style5&quot; /&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;한개의 요청마다 고유ID를 부여하여 로그를 쉽게 남길 수 있게 하고 싶다. 이 때 동시성 제어와 관련한 문제를 해결해 보려고 한다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;h3 data-ke-size=&quot;size23&quot;&gt;사례 조사&lt;/h3&gt;
&lt;hr contenteditable=&quot;false&quot; data-ke-type=&quot;horizontalRule&quot; data-ke-style=&quot;style5&quot; /&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;오래전부터 사용되었던 Spring 프레임워크에서는 ThreadLocal를 사용하고 있었다. Java는 기본적으로 멀티스레드를 지원하는 언어이기 때문에 동시에 여러개의 트랜잭션을 처리할 때 스레드를 나누어서 처리한다. 이 때 ThreadLocal를 사용하여 각 스레드마다 특정 값을 저장할 수 있다. 이 덕분에 각 스레드 간의 ThreadLocal에 있는 값은 서로 확인할 수가 없다. 각 스레드에 대해서 값을 따로 저장할 수 있다는 장점이 있지만, 직접 remove()를 호출하여 삭제해야 한다. 그렇지 않으면 메모리 누수가 일어날 가능성이 매우 높아진다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;NodeJS는 싱글스레드를 기반으로 동작한다. 하지만 libuv를 사용한 이벤트 루프를 활용하여 멀티스레드 흉내를 낼 수 있다. 각 Tick을 루프로 돌리면서 작업을 처리한다. 그래서 express도 동시에 여러개의 요청을 받아서 처리할 수 있는 것이다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;그렇다면 각 가상의 스레드마다 값을 저장할 수 없을까? 검색해보니 cls-hooked라는 라이브러리가 있었다. 해당 문서에서는 &quot;ThreadLocalStorage같이 동작하지만 노드 스타일 콜백 체인을 기반으로 사용한다&quot; 라는 문구가 있었다. 즉, 개발자 입장에서는 ThreadLocal처럼 사용할 수 있는 인터페이스가 제공되는 것이다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;이제 해당 라이브러리와 express를 결합하여 목표한 것을 만들어 보자.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;h3 data-ke-size=&quot;size23&quot;&gt;구현하기&lt;/h3&gt;
&lt;hr contenteditable=&quot;false&quot; data-ke-type=&quot;horizontalRule&quot; data-ke-style=&quot;style5&quot; /&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;사실 express에서 단순히 해당 로직을 구현하기 위해서는 변수값을 계속 하위 함수로 내려서 사용하면 되기는 한다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;pre id=&quot;code_1691848202103&quot; class=&quot;javascript&quot; data-ke-language=&quot;javascript&quot; data-ke-type=&quot;codeblock&quot;&gt;&lt;code&gt;const express = require(&quot;express&quot;);
const { v4: uuidv4 } = require(&quot;uuid&quot;);

/**
 * 1초동안 실행되는 mock 함수
 */
const mockDB = (id) =&amp;gt; {
  return new Promise((resolve) =&amp;gt; {
    setTimeout(() =&amp;gt; {
      console.log(`Log with id: ${id}`);
      resolve();
    }, 1000);
  });
};

const app = express();

app.get(&quot;/&quot;, function (req, res, next) {
  const id = uuidv4();
  console.log(`Created new log with id: ${id}`);

  mockDB(id).then(() =&amp;gt; {
    console.log(&quot;Timer done&quot;);
  });
  res.send(&quot;Hello World&quot;);
});

app.listen(4000);&lt;/code&gt;&lt;/pre&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;위의 코드에서 1초 이내로 두번의 요청을 보내도, 각 요청마다 ID가 구분이 된다. 하지만 로직이 복잡해질수록 id를 계속 내려야 하고, 이는 (&lt;s&gt;마치 react의 props를 계속 내려보내는 요청같아보이는데&lt;/s&gt;) 프로젝트가 커질수록 코드의 가독성을 해치고, 로직의 복잡도를 증가시킨다. 이를 해결하기 위해 logger 객체를 새로 만들고, cls-hooked를 사용해서 id를 구분할 것이다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;pre id=&quot;code_1691848462642&quot; class=&quot;javascript&quot; data-ke-language=&quot;javascript&quot; data-ke-type=&quot;codeblock&quot;&gt;&lt;code&gt;const express = require(&quot;express&quot;);
const { v4: uuidv4 } = require(&quot;uuid&quot;);
var { createNamespace } = require(&quot;cls-hooked&quot;);

const writer = createNamespace(&quot;writer&quot;);

function Logger() {}

Logger.prototype.create = () =&amp;gt; {
  const id = uuidv4();
  const log = `Created new log with id: ${id}`;
  console.log(log);
  writer.set(&quot;id&quot;, id);
};
Logger.prototype.log = () =&amp;gt; {
  const id = writer.get(&quot;id&quot;);
  const log = `Log with id: ${id}`;
  console.log(log);
};
const logger = new Logger();

/**
 * cls-hooked를 사용한 미들웨어
 */
const loggingMiddleware = (req, res, next) =&amp;gt; {
  writer.bindEmitter(req);
  writer.bindEmitter(res);
  writer.run(() =&amp;gt; {
    next();
  });
};

/**
 * 1초동안 실행되는 mock 함수
 */
const mockDB = () =&amp;gt; {
  return new Promise((resolve) =&amp;gt; {
    setTimeout(() =&amp;gt; {
      logger.log();
      resolve();
    }, 1000);
  });
};

const app = express();

app.use(loggingMiddleware);

app.get(&quot;/&quot;, function (req, res, next) {
  logger.create();
  mockDB().then(() =&amp;gt; {
    console.log(&quot;Timer done&quot;);
  });
  res.send(&quot;Hello World&quot;);
});

app.listen(4000);&lt;/code&gt;&lt;/pre&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;기존의 코드보다 길이가 늘어나긴 했지만, 상관없다. 주의해서 볼 곳은 &lt;b&gt;mockDB에 ID를 넘기지 않고서도 id를 불러온다는&lt;/b&gt; 것이다. 새로 logMiddleWare를 생성하여 로깅의 역할을 분리한다. 새로 만든 logMiddleWare에서 bindEmitter를 사용한다. bindEmitter는 EventEmitter를 네임스페이스에 바인딩하여 스레드(같은)를 분리한다. (express의 콜백 함수 인자인 req와 res가 EventEmitter로 간주되는 것 같다) 이후 run메서드를 호출하여 컨텍스트를 생성한 이후, 콜백 함수에서 next()를 호출하여 비즈니스 로직을 실행한다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;대부분 cls-hooked를 바로 사용하지 않고 각 프레임워크에 맞게 커스텀한 라이브러리를 많이 쓰는 것 같다.&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;h3 data-ke-size=&quot;size23&quot;&gt;참고자료&lt;/h3&gt;
&lt;hr contenteditable=&quot;false&quot; data-ke-type=&quot;horizontalRule&quot; data-ke-style=&quot;style5&quot; /&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;a href=&quot;https://www.npmjs.com/package/cls-hooked&quot; target=&quot;_blank&quot; rel=&quot;noopener&quot;&gt;https://www.npmjs.com/package/cls-hooked&lt;/a&gt;&lt;/p&gt;
&lt;figure id=&quot;og_1691849832295&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;cls-hooked&quot; data-og-description=&quot;CLS using AsynWrap instead of async-listener - Node &amp;gt;= 4.7.0. Latest version: 4.2.2, last published: 6 years ago. Start using cls-hooked in your project by running &amp;#96;npm i cls-hooked&amp;#96;. There are 723 other projects in the npm registry using cls-hooked.&quot; data-og-host=&quot;www.npmjs.com&quot; data-og-source-url=&quot;https://www.npmjs.com/package/cls-hooked&quot; data-og-url=&quot;https://www.npmjs.com/package/cls-hooked&quot; data-og-image=&quot;https://scrap.kakaocdn.net/dn/XDNr8/hyTCyO9C76/nLBOiYpzVugaQkv1AVZwa1/img.png?width=1200&amp;amp;height=630&amp;amp;face=0_0_1200_630&quot;&gt;&lt;a href=&quot;https://www.npmjs.com/package/cls-hooked&quot; target=&quot;_blank&quot; rel=&quot;noopener&quot; data-source-url=&quot;https://www.npmjs.com/package/cls-hooked&quot;&gt;
&lt;div class=&quot;og-image&quot; style=&quot;background-image: url('https://scrap.kakaocdn.net/dn/XDNr8/hyTCyO9C76/nLBOiYpzVugaQkv1AVZwa1/img.png?width=1200&amp;amp;height=630&amp;amp;face=0_0_1200_630');&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;cls-hooked&lt;/p&gt;
&lt;p class=&quot;og-desc&quot; data-ke-size=&quot;size16&quot;&gt;CLS using AsynWrap instead of async-listener - Node &amp;gt;= 4.7.0. Latest version: 4.2.2, last published: 6 years ago. Start using cls-hooked in your project by running `npm i cls-hooked`. There are 723 other projects in the npm registry using cls-hooked.&lt;/p&gt;
&lt;p class=&quot;og-host&quot; data-ke-size=&quot;size16&quot;&gt;www.npmjs.com&lt;/p&gt;
&lt;/div&gt;
&lt;/a&gt;&lt;/figure&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;a href=&quot;https://kyungyeon.dev/posts/43&quot; target=&quot;_blank&quot; rel=&quot;noopener&quot;&gt;https://kyungyeon.dev/posts/43&lt;/a&gt;&lt;/p&gt;
&lt;figure id=&quot;og_1691849884341&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;CLS에 대해 알아보자&quot; data-og-description=&quot;HTTP Request ID 추적하기 in Node.js&quot; data-og-host=&quot;kyungyeon.dev&quot; data-og-source-url=&quot;https://kyungyeon.dev/posts/43&quot; data-og-url=&quot;https://kyungyeon.dev/posts/43&quot; data-og-image=&quot;https://scrap.kakaocdn.net/dn/5VHFW/hyTCLubfW3/kk144n3im1ulgXu7zXz6l0/img.jpg?width=1200&amp;amp;height=630&amp;amp;face=0_0_1200_630&quot;&gt;&lt;a href=&quot;https://kyungyeon.dev/posts/43&quot; target=&quot;_blank&quot; rel=&quot;noopener&quot; data-source-url=&quot;https://kyungyeon.dev/posts/43&quot;&gt;
&lt;div class=&quot;og-image&quot; style=&quot;background-image: url('https://scrap.kakaocdn.net/dn/5VHFW/hyTCLubfW3/kk144n3im1ulgXu7zXz6l0/img.jpg?width=1200&amp;amp;height=630&amp;amp;face=0_0_1200_630');&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;CLS에 대해 알아보자&lt;/p&gt;
&lt;p class=&quot;og-desc&quot; data-ke-size=&quot;size16&quot;&gt;HTTP Request ID 추적하기 in Node.js&lt;/p&gt;
&lt;p class=&quot;og-host&quot; data-ke-size=&quot;size16&quot;&gt;kyungyeon.dev&lt;/p&gt;
&lt;/div&gt;
&lt;/a&gt;&lt;/figure&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;</description>
      <category>프로그래밍 언어/JS TS</category>
      <author>트리맨스</author>
      <guid isPermaLink="true">https://tre2man.tistory.com/359</guid>
      <comments>https://tre2man.tistory.com/359#entry359comment</comments>
      <pubDate>Sat, 12 Aug 2023 23:18:19 +0900</pubDate>
    </item>
    <item>
      <title>12 Factor App 이란?</title>
      <link>https://tre2man.tistory.com/358</link>
      <description>&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;h3 data-ke-size=&quot;size23&quot;&gt;개요&lt;/h3&gt;
&lt;hr contenteditable=&quot;false&quot; data-ke-type=&quot;horizontalRule&quot; data-ke-style=&quot;style5&quot; /&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;개발 경험이 많고 Heroku 플랫폼을 운영한 사람들이 정리한 규칙이다. 현대의 클라우드 애플리케이션 (SaaS)을 효율적으로 운영하기 위한 개발 규칙이다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;h3 data-ke-size=&quot;size23&quot;&gt;주요 내용&lt;/h3&gt;
&lt;hr contenteditable=&quot;false&quot; data-ke-type=&quot;horizontalRule&quot; data-ke-style=&quot;style5&quot; /&gt;
&lt;h4 data-ke-size=&quot;size20&quot;&gt;1. Codebase&lt;/h4&gt;
&lt;ul style=&quot;list-style-type: disc;&quot; data-ke-list-type=&quot;disc&quot;&gt;
&lt;li&gt;코드베이스와 앱은 일대일 상관관계가 있다. &amp;rarr; 코드는 한곳에서 개발 및 배포되어야 한다.&lt;/li&gt;
&lt;li&gt;코드베이스가 여러개인 경우 &amp;rarr; 분산 시스템으로 간주한다. 각각의 코드베이스는 12 factor를 준수한다.&lt;/li&gt;
&lt;li&gt;여러앱이 동일한 코드를 공유하는경우 &amp;rarr; 12 factor를 위반한 경우이다. 공유되는 코드를 라이브러리화 하고 각 앱에 종속성을 주입한다.&lt;/li&gt;
&lt;li&gt;앱 배포가 여러개인경우 &amp;rarr; branch를 활용하여 배포 분리&lt;/li&gt;
&lt;/ul&gt;
&lt;h4 data-ke-size=&quot;size20&quot;&gt;2. Dependencies&lt;/h4&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;h4 data-ke-size=&quot;size20&quot;&gt;3. Config&lt;/h4&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;/li&gt;
&lt;li&gt;config들이 올바르게 구성되었는지에 대한 리트머스 테스트는 안정성을 높여주고, 코드베이스를 오픈시킬수 있다.&lt;/li&gt;
&lt;li&gt;주로 환경 변수에 구성들을 저장한다. 환경변수는 코드에 들어갈 가능성이 거의 없으며, OS에 종속적이지 않다.&lt;/li&gt;
&lt;li&gt;config들을 그룹화하는것도 좋다 (이거는 좀 찾아봐야할듯)&lt;/li&gt;
&lt;/ul&gt;
&lt;h4 data-ke-size=&quot;size20&quot;&gt;4. Backing Service&lt;/h4&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;DB, 메시지 큐, SMTP, Cache 등의 모든 서비스를 의미한다. 여기에 서드파티까지 다 포함된다.&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;/ul&gt;
&lt;h4 data-ke-size=&quot;size20&quot;&gt;5. Build, Release, Run&lt;/h4&gt;
&lt;ul style=&quot;list-style-type: disc;&quot; data-ke-list-type=&quot;disc&quot;&gt;
&lt;li&gt;빌드 단계에서 config와 결합한다. 이것으로 릴리스를 제작한다.&lt;/li&gt;
&lt;li&gt;런타임에 코드를 변경하는 것은 불가능하다.&lt;/li&gt;
&lt;li&gt;이전 릴리스로 롤백이 되어야 한다. 타임스탬프 또는 버전같이 고유ID가 있어야 한다.&lt;/li&gt;
&lt;li&gt;가능한 사용자가 적은 시간에 배포되어야 한다.&lt;/li&gt;
&lt;/ul&gt;
&lt;h4 data-ke-size=&quot;size20&quot;&gt;6. Process&lt;/h4&gt;
&lt;ul style=&quot;list-style-type: disc;&quot; data-ke-list-type=&quot;disc&quot;&gt;
&lt;li&gt;stateless 환경에서 한개 이상의 프로세스로 앱이 실행된다.&lt;/li&gt;
&lt;li&gt;유지해야 하는 모든 데이터는 백업 서비스에 저장 (DB, S3, MQ 등등)&lt;/li&gt;
&lt;li&gt;sticky session은 12 factor 위반 (인스턴스에 상태를 저장하므로) &amp;rarr; redis 또는 memcached를 사용하자&lt;/li&gt;
&lt;/ul&gt;
&lt;h4 data-ke-size=&quot;size20&quot;&gt;7. Port binding&lt;/h4&gt;
&lt;ul style=&quot;list-style-type: disc;&quot; data-ke-list-type=&quot;disc&quot;&gt;
&lt;li&gt;실행 환경에 웹 서버의 런타임 인젝션에 의존하지 않고 HTTP서비스로 접근할수 있게 한다 &amp;rarr; 적절한 포트(80) 개방&lt;/li&gt;
&lt;li&gt;각 어플리케이션은 각자의 책임만 진다.&lt;/li&gt;
&lt;/ul&gt;
&lt;h4 data-ke-size=&quot;size20&quot;&gt;8. Concurrency&lt;/h4&gt;
&lt;ul style=&quot;list-style-type: disc;&quot; data-ke-list-type=&quot;disc&quot;&gt;
&lt;li&gt;애플리케이션은 stateless하므로, 빠르게 scale-out이 되어야 한다.&lt;/li&gt;
&lt;li&gt;12 factor에서 프로세스는 1급 시민이다. &amp;rarr; HTTP요청은 웹 프로세스가 처리하며, 시간이 오래 걸리는 백그라운드 작업은 worker 프로세스가 처리한다.&lt;/li&gt;
&lt;li&gt;1급 시민이란?&amp;nbsp;
&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;/ul&gt;
&lt;/li&gt;
&lt;/ul&gt;
&lt;h4 data-ke-size=&quot;size20&quot;&gt;9. Disposability&lt;/h4&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;시작시간을 최소화해야 한다 &amp;rarr; scale-out/up 최대한 빠르게&lt;/li&gt;
&lt;li&gt;현재 처리중인 요청은 처리해야한다.&lt;/li&gt;
&lt;li&gt;worker프로세스일 경우 현재 처리중인 작업을 job queue로 돌리는 방법으로 구현함.&lt;/li&gt;
&lt;li&gt;예상치못한 작업 종료는 큐잉 백엔드로 돌리는 것을 권장함.&lt;/li&gt;
&lt;/ul&gt;
&lt;h4 data-ke-size=&quot;size20&quot;&gt;10. Dev/Prod parity&lt;/h4&gt;
&lt;ul style=&quot;list-style-type: disc;&quot; data-ke-list-type=&quot;disc&quot;&gt;
&lt;li&gt;deployment, staging, production 환경을 최대한 비슷하게 유지하기&lt;/li&gt;
&lt;li&gt;개발 환경과 배포 환경의 차이를 최대한 적게 유지하여 지속적인 배포가 되게 하는것이 목표임&lt;/li&gt;
&lt;/ul&gt;
&lt;h4 data-ke-size=&quot;size20&quot;&gt;11. Log&lt;/h4&gt;
&lt;ul style=&quot;list-style-type: disc;&quot; data-ke-list-type=&quot;disc&quot;&gt;
&lt;li&gt;app은 로그 파일을 작성하거나 관리해서는 안된다. 대신 이벤트 스트림을 버퍼링 없이 stdout에 출력한다.&lt;/li&gt;
&lt;li&gt;배포 환경에서는 애플리케이션의 스트림을 실행 환경에서 수집한 후 로그 수집 파트에서 수집한다.&lt;/li&gt;
&lt;/ul&gt;
&lt;h4 data-ke-size=&quot;size20&quot;&gt;12. Admin 프로세스&lt;/h4&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;관리/유지보수 및 admin 작업은 release와 같이 실행&lt;/li&gt;
&lt;li&gt;REPL shell을 최대한 활용하는것도 좋을듯함&lt;/li&gt;
&lt;/ul&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;</description>
      <category>서버 인프라/DevOps</category>
      <author>트리맨스</author>
      <guid isPermaLink="true">https://tre2man.tistory.com/358</guid>
      <comments>https://tre2man.tistory.com/358#entry358comment</comments>
      <pubDate>Wed, 2 Aug 2023 01:12:11 +0900</pubDate>
    </item>
    <item>
      <title>SSH Bastion 안전하게 연결하기</title>
      <link>https://tre2man.tistory.com/357</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;일반적인 웹(서버) 애플리케이션을 배포할 때는 외부에 보여지는 계층(80, 443포트)을 제외하고 다른 외부 출입은 다 막아버린다. 이 때 만약 개발자가 외부에서 직접 애플리케이션에 접속해야 할 때는 Bastion(배스쳔) 호스트를 통해서 접속한다.&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;h3 data-ke-size=&quot;size23&quot;&gt;애플리케이션 구조&lt;/h3&gt;
&lt;hr contenteditable=&quot;false&quot; data-ke-type=&quot;horizontalRule&quot; data-ke-style=&quot;style5&quot; /&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;먼저 실행되는 애플리케이션은 프라이빗 서브넷에 있다고 가정하자. 프라이빗 서브넷의 애플리케이션은 일반적으로 외부에서 접속하지 못한다. 하지만 ssh연결을 해야 할 경우에는, 퍼블릭 서브넷에 있는 배스쳔 호스트를 통해서 애플리케이션으로 접속한다. ssh 터널링이다. 그림으로 그리게 되면 다음과 같다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p&gt;&lt;figure class=&quot;imageblock alignCenter&quot; data-ke-mobileStyle=&quot;widthOrigin&quot; data-filename=&quot;IMG_15A1686338D4-1.jpeg&quot; data-origin-width=&quot;2004&quot; data-origin-height=&quot;400&quot;&gt;&lt;span data-url=&quot;https://blog.kakaocdn.net/dn/nggc8/btsnD5w78N2/1a7Bkj91mOcU4mc2GHakCK/img.jpg&quot; data-phocus=&quot;https://blog.kakaocdn.net/dn/nggc8/btsnD5w78N2/1a7Bkj91mOcU4mc2GHakCK/img.jpg&quot;&gt;&lt;img src=&quot;https://blog.kakaocdn.net/dn/nggc8/btsnD5w78N2/1a7Bkj91mOcU4mc2GHakCK/img.jpg&quot; srcset=&quot;https://img1.daumcdn.net/thumb/R1280x0/?scode=mtistory2&amp;fname=https%3A%2F%2Fblog.kakaocdn.net%2Fdn%2Fnggc8%2FbtsnD5w78N2%2F1a7Bkj91mOcU4mc2GHakCK%2Fimg.jpg&quot; onerror=&quot;this.onerror=null; this.src='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png'; this.srcset='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png';&quot; loading=&quot;lazy&quot; width=&quot;2004&quot; height=&quot;400&quot; data-filename=&quot;IMG_15A1686338D4-1.jpeg&quot; data-origin-width=&quot;2004&quot; data-origin-height=&quot;400&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;아마존 ec2 인스턴스는 공개키를 사용해서 인스턴스에 접속하는 방식을 기본적으로 채택하고 있다. ssh에 pem파일을 태워서 접속하면 된다. 이 때 주의할 것이 배스쳔에 애플리케이션 pem파일을 저장하는 것은 권장하지 않는 방식이다. 이렇게 사용하게 어떻게든 배스쳔에 접속하게 되면 애플리케이션에 접속할 수 있는 키가 있기 때문에 보안에 취약하다. 그래서 호스트에 배스쳔 공개키와 애플리케이션 공개키를 두고 접속하는 방식이 권장된다.&lt;/p&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;ssh-add [애플리케이션 key파일] : 프라이빗 서브넷의 애플리케이션의 key파일을 ssh-add로 실어준다&lt;/li&gt;
&lt;li&gt;ssh-add -L : 1에서 실어둔 pem파일 확인&lt;/li&gt;
&lt;li&gt;ssh -A -i [배스쳔 key파일] user@bastion-ip : 배스쳔에 접속한다&lt;/li&gt;
&lt;li&gt;ssh user@application-ip : 애플리케이션에 접속한다. 1에서 애플리케이션의 key파일을 실어주었으므로 접속 가능&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;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;</description>
      <category>서버 인프라/Aws</category>
      <author>트리맨스</author>
      <guid isPermaLink="true">https://tre2man.tistory.com/357</guid>
      <comments>https://tre2man.tistory.com/357#entry357comment</comments>
      <pubDate>Fri, 14 Jul 2023 15:16:55 +0900</pubDate>
    </item>
    <item>
      <title>AWS 솔루션 아키텍트 어소시에시트 합격 후기</title>
      <link>https://tre2man.tistory.com/356</link>
      <description>&lt;p style=&quot;text-align: left;&quot; data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;br /&gt;최근들어 클라우드 관련한 아키텍쳐를 설계할 일이 생겼는데, 아키텍쳐를 설계할 때 마다 전체적인 구성이 그려지지 않는다는 생각이 너무 들었다. 그래서 전체적인 흐름을 알 수 있는 방법에 대해서 알아보다가 AWS에서 운영하는 자격증 시스템이 있어서 확인해 봤다. 내 수준에 적합한 자격증으로 SAA자격증이 있는거 같아서, 한번 따 보기로 했다.&lt;br /&gt;&amp;nbsp;&lt;/p&gt;
&lt;h3 style=&quot;text-align: left;&quot; data-ke-size=&quot;size23&quot;&gt;AWS 자격증 종류&lt;/h3&gt;
&lt;hr data-ke-type=&quot;horizontalRule&quot; data-ke-style=&quot;style5&quot; /&gt;
&lt;p style=&quot;text-align: left;&quot; data-ke-size=&quot;size16&quot;&gt;사실 공홈이 잘 정리가 되어있다.&lt;br /&gt;&lt;a href=&quot;https://aws.amazon.com/ko/certification/exams/&quot; target=&quot;_blank&quot; rel=&quot;noopener&quot;&gt;&lt;span&gt;https://aws.amazon.com/ko/certification/exams/&lt;/span&gt;&lt;/a&gt;&lt;/p&gt;
&lt;figure data-ke-type=&quot;opengraph&quot; data-og-title=&quot;모든 AWS Certification 시험 살펴보기 | AWS 클라우드 기술 검증 | AWS&quot; data-ke-align=&quot;alignCenter&quot; data-og-description=&quot;언어: 영어, 프랑스어(프랑스), 독일어, 인도네시아어, 이탈리아어, 일본어, 한국어, 포르투갈어(브라질), 중국어 간체, 스페인어(라틴 아메리카)&quot; data-og-host=&quot;aws.amazon.com&quot; data-og-source-url=&quot;https://aws.amazon.com/ko/certification/exams/&quot; data-og-image=&quot;https://scrap.kakaocdn.net/dn/b0rD72/hySQEpudUw/t3WKChsPJczHdFZloE0iwK/img.png?width=1200&amp;amp;height=630&amp;amp;face=0_0_1200_630,https://scrap.kakaocdn.net/dn/VE2Gt/hySQBGjMYP/VS4sZIsiSSqgKGVeWVJ5A1/img.png?width=179&amp;amp;height=109&amp;amp;face=0_0_179_109,https://scrap.kakaocdn.net/dn/biJTn5/hySQH0NEtB/d25WmDShRogH9CckIFPU8K/img.png?width=300&amp;amp;height=300&amp;amp;face=0_0_300_300&quot; data-og-url=&quot;https://aws.amazon.com/ko/certification/exams/&quot;&gt;&lt;a href=&quot;https://aws.amazon.com/ko/certification/exams/&quot; target=&quot;_blank&quot; rel=&quot;noopener&quot; data-source-url=&quot;https://aws.amazon.com/ko/certification/exams/&quot;&gt;
&lt;div class=&quot;og-image&quot; style=&quot;background-image: url('https://scrap.kakaocdn.net/dn/b0rD72/hySQEpudUw/t3WKChsPJczHdFZloE0iwK/img.png?width=1200&amp;amp;height=630&amp;amp;face=0_0_1200_630,https://scrap.kakaocdn.net/dn/VE2Gt/hySQBGjMYP/VS4sZIsiSSqgKGVeWVJ5A1/img.png?width=179&amp;amp;height=109&amp;amp;face=0_0_179_109,https://scrap.kakaocdn.net/dn/biJTn5/hySQH0NEtB/d25WmDShRogH9CckIFPU8K/img.png?width=300&amp;amp;height=300&amp;amp;face=0_0_300_300');&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;모든 AWS Certification 시험 살펴보기 | AWS 클라우드 기술 검증 | AWS&lt;/p&gt;
&lt;p class=&quot;og-desc&quot; data-ke-size=&quot;size16&quot;&gt;언어: 영어, 프랑스어(프랑스), 독일어, 인도네시아어, 이탈리아어, 일본어, 한국어, 포르투갈어(브라질), 중국어 간체, 스페인어(라틴 아메리카)&lt;/p&gt;
&lt;p class=&quot;og-host&quot; data-ke-size=&quot;size16&quot;&gt;aws.amazon.com&lt;/p&gt;
&lt;/div&gt;
&lt;/a&gt;&lt;/figure&gt;
&lt;p style=&quot;text-align: left;&quot; data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&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;2040&quot; data-origin-height=&quot;1290&quot;&gt;&lt;span data-url=&quot;https://blog.kakaocdn.net/dn/b5oyVO/btsit02cmJy/ahxVKuCU0SEBNTiCPDyFM1/img.png&quot; data-phocus=&quot;https://blog.kakaocdn.net/dn/b5oyVO/btsit02cmJy/ahxVKuCU0SEBNTiCPDyFM1/img.png&quot;&gt;&lt;img src=&quot;https://blog.kakaocdn.net/dn/b5oyVO/btsit02cmJy/ahxVKuCU0SEBNTiCPDyFM1/img.png&quot; srcset=&quot;https://img1.daumcdn.net/thumb/R1280x0/?scode=mtistory2&amp;fname=https%3A%2F%2Fblog.kakaocdn.net%2Fdn%2Fb5oyVO%2Fbtsit02cmJy%2FahxVKuCU0SEBNTiCPDyFM1%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;2040&quot; height=&quot;1290&quot; data-origin-width=&quot;2040&quot; data-origin-height=&quot;1290&quot;/&gt;&lt;/span&gt;&lt;/figure&gt;
&lt;/p&gt;
&lt;p style=&quot;text-align: left;&quot; data-ke-size=&quot;size16&quot;&gt;아이...뭔가 많다. &lt;s&gt;이런걸로 아마존은 용돈을 버는구나&lt;/s&gt;. 간단히 보면 기초, 어소시에이트, 프로페셔널, 전문 분야 총 4개로 나눌수 있다. 뒤로 갈수록 전문성이 많이 요구되며, 시험도 비싸진다. 기초 단계의 클라우드 프랙티셔너는 인프라에 아예 처음이거나, 취미 또는 다른 분야의 직군이 입문하기 좋다. 어소시에이트는 주니어 단계의 엔지니어들이 알면 좋은 지식들로 이루어져 있다. 프로페셔널은 말그대로 전문가 영역이고, 전문 분야까지 가면 뭐...결론은 점점 단계가 올라갈수록 전문화되고 딥한 내용들이 많이 나온다.&lt;br /&gt;나의 경우에는 주변에서 솔루션 아키텍트 어소시에이트 시험을 많이 보기도 하고 AWS의 제품군에 대해서 알아볼 수 있는 기회라 생각해서, 이걸 따 보기로 했다.&lt;br /&gt;&amp;nbsp;&lt;/p&gt;
&lt;h3 style=&quot;text-align: left;&quot; data-ke-size=&quot;size23&quot;&gt;솔루션 아키텍트 어소시에이트는 무엇인가?&lt;/h3&gt;
&lt;hr data-ke-type=&quot;horizontalRule&quot; data-ke-style=&quot;style5&quot; /&gt;
&lt;p style=&quot;text-align: left;&quot; data-ke-size=&quot;size16&quot;&gt;솔루션 어소시에이트 자격증은 AWS에 대한 전반적인 제품들에 대해서 다루고, 이들을 사용해서 권장 사항에 의거하여 아키텍쳐를 설계하는 것을 돕는 것들을 알아야 하는 자격증이다. 사실 이거 땄다고 해서 다 할수 있는거는 아니고, 현업 기준으로는 맛보기 수준이라고 생각한다. 하지맛 맛보기 수준이라고 해서 만만하게 보면 안되는게, AWS의 서비스 영역이 매우 방대하고 설계 우선순위에 따라서 설계방식을 다르게 해야 할 수도 있기 때문에&amp;nbsp;&amp;nbsp;생각보다 공부할게 많다.&lt;br /&gt;사설 자격증이다 보니, 몇 가지 알아두어야 할 사항들이 있다.&lt;/p&gt;
&lt;ul style=&quot;list-style-type: disc;&quot; data-ke-list-type=&quot;disc&quot;&gt;
&lt;li&gt;유효기간이 3년이다. 3년 이후에도 자격증을 유지하고 싶으면 재시험 봐야한다.&lt;/li&gt;
&lt;li&gt;다음 시험 50% 할인 쿠폰 준다. 단, 나만 쓸수 있고 자격증 유효기간 내 사용해야 한다.&lt;/li&gt;
&lt;li&gt;뱃지를 준다. 링크드인에 올리거나 여기저기 자랑하고 다닐수 있다.&lt;/li&gt;
&lt;/ul&gt;
&lt;p style=&quot;text-align: left;&quot; data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;h3 style=&quot;text-align: left;&quot; data-ke-size=&quot;size23&quot;&gt;시험 보기&lt;/h3&gt;
&lt;hr data-ke-type=&quot;horizontalRule&quot; data-ke-style=&quot;style5&quot; /&gt;
&lt;p style=&quot;text-align: left;&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;22년 8월에 문제 유형이 한번 바뀌었다. 이전 버전은 C02 버전이고, 현재 버전은 C03 버전이다. 글 작성일 기준으로는 기출문제가 슬슬 쌓인 시점이므로, 그렇게 걱정할 필요는 없다.&lt;/li&gt;
&lt;li&gt;할인 쿠폰이 자주 풀린다. 50%할인 쿠폰은 비교적 자주 풀리니, 쿠폰을 잘 활용하자.&lt;/li&gt;
&lt;/ul&gt;
&lt;p style=&quot;text-align: left;&quot; data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;br /&gt;나의 경우에는 직접 시험장에 가서 오프라인으로 시험을 봤다. 비대면으로 하기에는 집의 네트워크 환경이 믿음직스럽지 못해서 그냥 SRTC에서 직접 가서 봤다. 비대면으로 봐도 문제는 없다.&lt;br /&gt;&amp;nbsp;&lt;br /&gt;시험 접수는 공식 홈페이지에 가서 하면 된다. 시험은 원래 영어인데, 비영어권 사람들을 위해서 한국어 시험도 제공한다. 번역이 종종 이상하니, 한국어 문맥상 뭔가 이상하다면 영어로 바꿔서 풀어보자. 영어 문제를 해석하기는 그렇게 어렵지는 않았다. 그리고 한국어로 시험을 보면 30분을 더 제공하니, 시험 편의사항 요청에 30분을 더 달라고하자.&lt;/p&gt;
&lt;p&gt;&lt;figure class=&quot;imageblock alignCenter&quot; data-ke-mobileStyle=&quot;widthOrigin&quot; data-origin-width=&quot;1146&quot; data-origin-height=&quot;229&quot;&gt;&lt;span data-url=&quot;https://blog.kakaocdn.net/dn/bgUHH0/btsitT93gkq/sOE0zi2ghP1ay1f1Knxj6k/img.png&quot; data-phocus=&quot;https://blog.kakaocdn.net/dn/bgUHH0/btsitT93gkq/sOE0zi2ghP1ay1f1Knxj6k/img.png&quot; data-alt=&quot;우측 중간에 있다&quot;&gt;&lt;img src=&quot;https://blog.kakaocdn.net/dn/bgUHH0/btsitT93gkq/sOE0zi2ghP1ay1f1Knxj6k/img.png&quot; srcset=&quot;https://img1.daumcdn.net/thumb/R1280x0/?scode=mtistory2&amp;fname=https%3A%2F%2Fblog.kakaocdn.net%2Fdn%2FbgUHH0%2FbtsitT93gkq%2FsOE0zi2ghP1ay1f1Knxj6k%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;1146&quot; height=&quot;229&quot; data-origin-width=&quot;1146&quot; data-origin-height=&quot;229&quot;/&gt;&lt;/span&gt;&lt;figcaption&gt;우측 중간에 있다&lt;/figcaption&gt;
&lt;/figure&gt;
&lt;/p&gt;
&lt;p style=&quot;text-align: left;&quot; data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;br /&gt;시험을 볼때는 신분증과 신용카드가 있어야 하는데, 반드시 &lt;b&gt;&lt;u&gt;신분증의 영문이름, 신용카드의 영문이름, 접수할 때의 영어 이름이 모두 같아야 한다.&lt;/u&gt;&lt;/b&gt; 괜히 틀리게 썼다가 불편해지지 말자. 여기서 영문이름이 있는 신분증은 &lt;b&gt;&lt;u&gt;여권&lt;/u&gt;&lt;/b&gt;이 제일 무난하다. 나도 여권으로 시험장에 들어갔다.&amp;nbsp;&lt;br /&gt;&amp;nbsp;&lt;br /&gt;시험 시간은 130분에 비영어권 30분 더해서 160분이 주어졌다. 근데 사실 160분을 다 쓸리는 없고, 나는 50분 풀고 10분 검토하고 바로 나갔다. 어차피 객관식 문제라서 고민해봤자 답이 달라지지 않는것을 알아서였다. 문제는 총 65문제가 나오고, 50문제가 점수에 반영되고 15문제는 점수에 반영이 안된다. 15문제는 문제의 적절성을 판별하기 위한 문제라고 한다. 만점은 1000점이고, 720점 이상만 나오면 합격이다.&lt;br /&gt;&amp;nbsp;&lt;br /&gt;결과 발표는 즉시 알려준다는 얘기가 있던데, 나는 바로 알려주지 않고 10시간 정도 뒤에 홈페이지에서 확인했다. 5일안에 통보가 된다고 하는데, 생각보다 빨리빨리 알려주는거 같다.&amp;nbsp;&lt;br /&gt;&amp;nbsp;&lt;/p&gt;
&lt;h3 style=&quot;text-align: left;&quot; data-ke-size=&quot;size23&quot;&gt;시험공부&lt;/h3&gt;
&lt;hr data-ke-type=&quot;horizontalRule&quot; data-ke-style=&quot;style5&quot; /&gt;
&lt;p style=&quot;text-align: left;&quot; data-ke-size=&quot;size16&quot;&gt;일단 나는 1년 6개월정도 AWS에서 돌아가는 프로덕트를 인프라 수준에서 관리를 해본 경험이 있고, 4개월 정도는 Azure에서 인프라 관리를 해 보았다. 즉 클라우드에 대한 이해가 조금이나마 있는 상태였다. 시험 공부에 필요한 소스는 크게 3개로 요약할 수 있을 것 같다.&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;AWS 공식문서&lt;/li&gt;
&lt;/ol&gt;
&lt;p style=&quot;text-align: left;&quot; data-ke-size=&quot;size16&quot;&gt;1번은, 너무 유명한 강의다. 유데미에 들어가면 스테판씨가 강의하는 주황색 배경의 강의가 있다. 이 강의 들으면서 AWS의 제품군과 Best Practice들을 한번씩 보면 좋다. 한글 번역이 좀 안타까운데, 못들을 만큼은 아니다. 애매한 내용이 있으면 옆에 AWS 공식문서 켜서 같이 보면 된다.&amp;nbsp;&lt;br /&gt;2번은 모의고사를 풀어보는것이다. 나는 개인 또는 기타 사이트에서 덤프를 여러 개 사지는 않았는데, 사실 덤프 살 돈도 아까웠고 1에서 말한 스테판씨가 판매하는 모의고사를 구매해서 돌려보는것도 충분하다고 생각했기 때문이다. 그리고 덤프가 틀린 답변들이 많다고 해서 시간을 낭비할 것 같았다. 데이비스씨가 파는 모의고사 (파란색 배경) 가 더 좋다고 하는 사람들도 있는데 나는 별로 신경쓰지 않았다.&lt;br /&gt;3번이 좀 중요하다. SAA에 나오는 문제들은 공식 문서에 있는 내용들을 기반으로 출제한다. 시나리오 예시도 공식 문서에서 자주 나오므로, 문제를 풀다가 이해가 안되는게 있으면 공식문서를 확인하는게 좋다. &lt;s&gt;사실 이해라기보다는 공식문서에서 제시하는 시나리오 암기다&lt;/s&gt;&lt;br /&gt;&amp;nbsp;&lt;br /&gt;나는 시험준비를 5일정도 했는데, 2일간 유데미 강의를 보고 3일간 모의고사를 풀어봤다. 근데 모의고사를 6개 묶음으로 파는데, 마지막에 시간이 없어서 5개밖에 풀지 못했다. 비상사태였다. 그래서 &lt;b&gt;&lt;u&gt;오답 + 찍어서 맞춘 문제들을 다 모아두고 공식 문서랑 내용을 맞추었고, 시험보기 전까지 해당 문제들을 집중적으로 복습했다.&lt;/u&gt;&lt;/b&gt; 나는 IAM 파트가 매우 취약해서, 그쪽 위주로 오답노트를 작성했다. 모의고사 외 기타 풀어본 문제들을 모두 합하면 350개정도 본 것 같다. 그 당시 나의 정답률은 60% 언저리에 있어서 매우 불안한 상태였다.&lt;br /&gt;&amp;nbsp;&lt;br /&gt;그리고 오늘 시험을 봤는데, 어떻게 운이 좋아서 합격이 나오긴 했다. 점수도 760점대로 생각보다 잘 나왔다. 합격자 뱃지도 주고, 이제 여기저기 자랑할수 있다. &lt;s&gt;(날짜 깨지는거 해결좀)&lt;/s&gt;&lt;/p&gt;
&lt;p&gt;&lt;figure class=&quot;imageblock alignCenter&quot; data-ke-mobileStyle=&quot;widthOrigin&quot; data-origin-width=&quot;912&quot; data-origin-height=&quot;679&quot;&gt;&lt;span data-url=&quot;https://blog.kakaocdn.net/dn/beNPRf/btsisNWYChH/5CKZ3fwE4qQpK6kkj18eC1/img.png&quot; data-phocus=&quot;https://blog.kakaocdn.net/dn/beNPRf/btsisNWYChH/5CKZ3fwE4qQpK6kkj18eC1/img.png&quot;&gt;&lt;img src=&quot;https://blog.kakaocdn.net/dn/beNPRf/btsisNWYChH/5CKZ3fwE4qQpK6kkj18eC1/img.png&quot; srcset=&quot;https://img1.daumcdn.net/thumb/R1280x0/?scode=mtistory2&amp;fname=https%3A%2F%2Fblog.kakaocdn.net%2Fdn%2FbeNPRf%2FbtsisNWYChH%2F5CKZ3fwE4qQpK6kkj18eC1%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;912&quot; height=&quot;679&quot; data-origin-width=&quot;912&quot; data-origin-height=&quot;679&quot;/&gt;&lt;/span&gt;&lt;/figure&gt;
&lt;/p&gt;
&lt;p style=&quot;text-align: left;&quot; data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;h3 style=&quot;text-align: left;&quot; data-ke-size=&quot;size23&quot;&gt;후기&lt;/h3&gt;
&lt;hr data-ke-type=&quot;horizontalRule&quot; data-ke-style=&quot;style5&quot; /&gt;
&lt;p style=&quot;text-align: left;&quot; data-ke-size=&quot;size16&quot;&gt;사실 운이 좀 좋았던것같다. 5일이라는 짧은 시간안에 오답과 공식문서를 중점적으로 보니 합격하지 않았나 싶다. 이제 어디가서 아마존 좀 친다고 얘기는 못하겠지만, 자격증이 있다는 것 만으로도 든든하기도 하고 AWS 기본은 알고있다는 것을 쉽게 알릴 수 있지 않나 싶다.&lt;br /&gt;&lt;a href=&quot;https://www.credly.com/badges/e051f69c-6aca-4eb4-a25d-e3da7af2ac18/public_url&quot; target=&quot;_blank&quot; rel=&quot;noopener&quot;&gt;&lt;span&gt;https://www.credly.com/badges/e051f69c-6aca-4eb4-a25d-e3da7af2ac18/public_url&lt;/span&gt;&lt;/a&gt;&lt;/p&gt;
&lt;figure data-ke-type=&quot;opengraph&quot; data-og-title=&quot;AWS Certified Solutions Architect &amp;ndash; Associate was issued by Amazon Web Services Training and Certification to NAMWOO KIM.&quot; data-ke-align=&quot;alignCenter&quot; data-og-description=&quot;Earners of this certification have a comprehensive understanding of AWS services and technologies. They demonstrated the ability to build secure and robust solutions using architectural design principles based on customer requirements. Badge owners are abl&quot; data-og-host=&quot;www.credly.com&quot; data-og-source-url=&quot;https://www.credly.com/badges/e051f69c-6aca-4eb4-a25d-e3da7af2ac18/public_url&quot; data-og-image=&quot;https://scrap.kakaocdn.net/dn/bIq8wf/hySQLhWdWt/kVrug7UikbjtqXMAQr3WG1/img.png?width=672&amp;amp;height=352&amp;amp;face=0_0_672_352,https://scrap.kakaocdn.net/dn/bSS8tW/hySQxjFDFJ/i6w3CceRmIliIu2TSVo5G0/img.png?width=512&amp;amp;height=512&amp;amp;face=0_0_512_512&quot; data-og-url=&quot;https://www.credly.com/badges/e051f69c-6aca-4eb4-a25d-e3da7af2ac18&quot;&gt;&lt;a href=&quot;https://www.credly.com/badges/e051f69c-6aca-4eb4-a25d-e3da7af2ac18&quot; target=&quot;_blank&quot; rel=&quot;noopener&quot; data-source-url=&quot;https://www.credly.com/badges/e051f69c-6aca-4eb4-a25d-e3da7af2ac18/public_url&quot;&gt;
&lt;div class=&quot;og-image&quot; style=&quot;background-image: url('https://scrap.kakaocdn.net/dn/bIq8wf/hySQLhWdWt/kVrug7UikbjtqXMAQr3WG1/img.png?width=672&amp;amp;height=352&amp;amp;face=0_0_672_352,https://scrap.kakaocdn.net/dn/bSS8tW/hySQxjFDFJ/i6w3CceRmIliIu2TSVo5G0/img.png?width=512&amp;amp;height=512&amp;amp;face=0_0_512_512');&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;AWS Certified Solutions Architect &amp;ndash; Associate was issued by Amazon Web Services Training and Certification to NAMWOO KIM.&lt;/p&gt;
&lt;p class=&quot;og-desc&quot; data-ke-size=&quot;size16&quot;&gt;Earners of this certification have a comprehensive understanding of AWS services and technologies. They demonstrated the ability to build secure and robust solutions using architectural design principles based on customer requirements. Badge owners are abl&lt;/p&gt;
&lt;p class=&quot;og-host&quot; data-ke-size=&quot;size16&quot;&gt;www.credly.com&lt;/p&gt;
&lt;/div&gt;
&lt;/a&gt;&lt;/figure&gt;
&lt;p style=&quot;text-align: left;&quot; data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;</description>
      <category>서버 인프라/Aws</category>
      <author>트리맨스</author>
      <guid isPermaLink="true">https://tre2man.tistory.com/356</guid>
      <comments>https://tre2man.tistory.com/356#entry356comment</comments>
      <pubDate>Fri, 2 Jun 2023 19:40:24 +0900</pubDate>
    </item>
    <item>
      <title>통관번호 조회 어플 제작기</title>
      <link>https://tre2man.tistory.com/355</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;주말에 잠깐 시간내서 통관번호를 사용해서 택배를 조회하는 어플을 만들어 보았다. 몇년전에 안드로이드 스튜디오를 사용해서 꾸역꾸역 만들던 때와는 달리 요즘에는 빨리빨리 만들 수 있어서 좋았다. 제작기를 작성하긴 하는데, 사실 공식 문서만 보고 개발환경을 잘 설치하고 react를 어느정도 사용할 줄 안다면 단순한 웹 통신과 뷰만 있다고 하면 쉽게 제작할 수 있어서 간단히 설명만 할려고 한다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;h3 data-ke-size=&quot;size23&quot;&gt;기술스택&lt;/h3&gt;
&lt;hr contenteditable=&quot;false&quot; data-ke-type=&quot;horizontalRule&quot; data-ke-style=&quot;style5&quot; /&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;기술스택이라 하기에도 민망하다. React Native를 사용해서 만들어 보았다. 이미 React를 다룰 줄은 알기도 하고, 크로스 플랫폼이 가능해서 사용하기로 했다. 또한 Typescript를 사용해서 타입에 안정성을 주기로 했다. 하지만 막상 다 만들고 보니 별로 사용하지는 않았다는 게 함정...&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;개발환경은 M1 Pro 맥북이다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;h3 data-ke-size=&quot;size23&quot;&gt;초기설정&lt;/h3&gt;
&lt;hr contenteditable=&quot;false&quot; data-ke-type=&quot;horizontalRule&quot; data-ke-style=&quot;style5&quot; /&gt;
&lt;p 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://reactnative.dev/docs/environment-setup&quot;&gt;https://reactnative.dev/docs/environment-setup&lt;/a&gt;&lt;/p&gt;
&lt;figure id=&quot;og_1683556048075&quot; style=&quot;color: #333333; text-align: start;&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;Setting up the development environment &amp;middot; React Native&quot; data-og-description=&quot;This page will help you install and build your first React Native app.&quot; data-og-host=&quot;reactnative.dev&quot; data-og-source-url=&quot;https://reactnative.dev/docs/environment-setup&quot; data-og-url=&quot;https://reactnative.dev/docs/environment-setup&quot; data-og-image=&quot;https://scrap.kakaocdn.net/dn/bzvGOq/hySyp5Zfsr/y8K6tZrioFzQzGkLZa1LP1/img.png?width=1200&amp;amp;height=630&amp;amp;face=0_0_1200_630,https://scrap.kakaocdn.net/dn/bcQKiB/hySwSaT5EO/CQotguBAkM4TEw5rF266rk/img.png?width=1200&amp;amp;height=630&amp;amp;face=0_0_1200_630,https://scrap.kakaocdn.net/dn/6oX7D/hySwOl0Wcd/D4MG0ZScvuB0eNPo9hwri0/img.png?width=1724&amp;amp;height=1158&amp;amp;face=0_0_1724_1158&quot;&gt;&lt;a style=&quot;color: #000000;&quot; href=&quot;https://reactnative.dev/docs/environment-setup&quot; data-source-url=&quot;https://reactnative.dev/docs/environment-setup&quot;&gt;
&lt;div class=&quot;og-image&quot; style=&quot;background-image: url('https://scrap.kakaocdn.net/dn/bzvGOq/hySyp5Zfsr/y8K6tZrioFzQzGkLZa1LP1/img.png?width=1200&amp;amp;height=630&amp;amp;face=0_0_1200_630,https://scrap.kakaocdn.net/dn/bcQKiB/hySwSaT5EO/CQotguBAkM4TEw5rF266rk/img.png?width=1200&amp;amp;height=630&amp;amp;face=0_0_1200_630,https://scrap.kakaocdn.net/dn/6oX7D/hySwOl0Wcd/D4MG0ZScvuB0eNPo9hwri0/img.png?width=1724&amp;amp;height=1158&amp;amp;face=0_0_1724_1158');&quot;&gt;&amp;nbsp;&lt;/div&gt;
&lt;div class=&quot;og-text&quot;&gt;
&lt;p class=&quot;og-title&quot; style=&quot;color: #000000;&quot; data-ke-size=&quot;size16&quot;&gt;Setting up the development environment &amp;middot; React Native&lt;/p&gt;
&lt;p class=&quot;og-desc&quot; style=&quot;color: #909090;&quot; data-ke-size=&quot;size16&quot;&gt;This page will help you install and build your first React Native app.&lt;/p&gt;
&lt;p class=&quot;og-host&quot; style=&quot;color: #909090;&quot; data-ke-size=&quot;size16&quot;&gt;reactnative.dev&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;&lt;a href=&quot;https://reactnative.dev/docs/typescript&quot;&gt;https://reactnative.dev/docs/typescript&lt;/a&gt;&lt;/p&gt;
&lt;figure id=&quot;og_1683556048076&quot; style=&quot;color: #333333; text-align: start;&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;Using TypeScript &amp;middot; React Native&quot; data-og-description=&quot;TypeScript is a language which extends JavaScript by adding type definitions. New React Native projects target TypeScript by default, but also support JavaScript and Flow.&quot; data-og-host=&quot;reactnative.dev&quot; data-og-source-url=&quot;https://reactnative.dev/docs/typescript&quot; data-og-url=&quot;https://reactnative.dev/docs/typescript&quot; data-og-image=&quot;https://scrap.kakaocdn.net/dn/d7y9ET/hySwFP9UMg/lOIRp6JDmPYh0UjWyDPaPK/img.png?width=1200&amp;amp;height=630&amp;amp;face=0_0_1200_630,https://scrap.kakaocdn.net/dn/cvgclU/hySwFbz7o7/kg0KhNA8bFbE29mLbvwuak/img.png?width=1200&amp;amp;height=630&amp;amp;face=0_0_1200_630&quot;&gt;&lt;a style=&quot;color: #000000;&quot; href=&quot;https://reactnative.dev/docs/typescript&quot; data-source-url=&quot;https://reactnative.dev/docs/typescript&quot;&gt;
&lt;div class=&quot;og-image&quot; style=&quot;background-image: url('https://scrap.kakaocdn.net/dn/d7y9ET/hySwFP9UMg/lOIRp6JDmPYh0UjWyDPaPK/img.png?width=1200&amp;amp;height=630&amp;amp;face=0_0_1200_630,https://scrap.kakaocdn.net/dn/cvgclU/hySwFbz7o7/kg0KhNA8bFbE29mLbvwuak/img.png?width=1200&amp;amp;height=630&amp;amp;face=0_0_1200_630');&quot;&gt;&amp;nbsp;&lt;/div&gt;
&lt;div class=&quot;og-text&quot;&gt;
&lt;p class=&quot;og-title&quot; style=&quot;color: #000000;&quot; data-ke-size=&quot;size16&quot;&gt;Using TypeScript &amp;middot; React Native&lt;/p&gt;
&lt;p class=&quot;og-desc&quot; style=&quot;color: #909090;&quot; data-ke-size=&quot;size16&quot;&gt;TypeScript is a language which extends JavaScript by adding type definitions. New React Native projects target TypeScript by default, but also support JavaScript and Flow.&lt;/p&gt;
&lt;p class=&quot;og-host&quot; style=&quot;color: #909090;&quot; data-ke-size=&quot;size16&quot;&gt;reactnative.dev&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;h3 style=&quot;color: #333333; text-align: start;&quot; data-ke-size=&quot;size23&quot;&gt;API 설정&lt;/h3&gt;
&lt;hr contenteditable=&quot;false&quot; data-ke-type=&quot;horizontalRule&quot; data-ke-style=&quot;style5&quot; /&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;API는 현재 새 Key 발급이 막혀 있다. 혹시나 해서 예전에 발급받은 기록이 있나 싶어서 확인해보니 다행히 존재했다. 서버에서 데이터를 받아오는 로직은 Hook으로 분리하여 Loading 상태를 분리할 수 있게 하였다. 데이터를 불러올 때는 익숙한 axios를 Promise 패턴으로 작성했다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;h3 data-ke-size=&quot;size23&quot;&gt;스크린 전환&lt;/h3&gt;
&lt;hr contenteditable=&quot;false&quot; data-ke-type=&quot;horizontalRule&quot; data-ke-style=&quot;style5&quot; /&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;놀랍게도 RN은 아직 Beta버전이며(!) 공식적으로 스크린 전환을 지원하지 않는다! SPA를 여기까지는 끌고 올 필요는 없을거 같은데... 하지만 공식문서를 검색해보니 라이브러리 설치로 적용이 가능했다. 해당 공식 문서를 사용해서 스크린 전환을 구현했다.&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://reactnative.dev/docs/navigation&quot;&gt;https://reactnative.dev/docs/navigation&lt;/a&gt;&lt;/p&gt;
&lt;figure id=&quot;og_1683556635458&quot; style=&quot;color: #333333; text-align: start;&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;Navigating Between Screens &amp;middot; React Native&quot; data-og-description=&quot;Mobile apps are rarely made up of a single screen. Managing the presentation of, and transition between, multiple screens is typically handled by what is known as a navigator.&quot; data-og-host=&quot;reactnative.dev&quot; data-og-source-url=&quot;https://reactnative.dev/docs/navigation&quot; data-og-url=&quot;https://reactnative.dev/docs/navigation&quot; data-og-image=&quot;https://scrap.kakaocdn.net/dn/jBFN1/hySyoMM53v/27rbgaLUuCWaFVdsJmikM0/img.png?width=1200&amp;amp;height=630&amp;amp;face=0_0_1200_630,https://scrap.kakaocdn.net/dn/crB47N/hySwRXnJkb/INJ0OXfKIGtjYRp5Wixyo1/img.png?width=1200&amp;amp;height=630&amp;amp;face=0_0_1200_630&quot;&gt;&lt;a style=&quot;color: #000000;&quot; href=&quot;https://reactnative.dev/docs/navigation&quot; data-source-url=&quot;https://reactnative.dev/docs/navigation&quot;&gt;
&lt;div class=&quot;og-image&quot; style=&quot;background-image: url('https://scrap.kakaocdn.net/dn/jBFN1/hySyoMM53v/27rbgaLUuCWaFVdsJmikM0/img.png?width=1200&amp;amp;height=630&amp;amp;face=0_0_1200_630,https://scrap.kakaocdn.net/dn/crB47N/hySwRXnJkb/INJ0OXfKIGtjYRp5Wixyo1/img.png?width=1200&amp;amp;height=630&amp;amp;face=0_0_1200_630');&quot;&gt;&amp;nbsp;&lt;/div&gt;
&lt;div class=&quot;og-text&quot;&gt;
&lt;p class=&quot;og-title&quot; style=&quot;color: #000000;&quot; data-ke-size=&quot;size16&quot;&gt;Navigating Between Screens &amp;middot; React Native&lt;/p&gt;
&lt;p class=&quot;og-desc&quot; style=&quot;color: #909090;&quot; data-ke-size=&quot;size16&quot;&gt;Mobile apps are rarely made up of a single screen. Managing the presentation of, and transition between, multiple screens is typically handled by what is known as a navigator.&lt;/p&gt;
&lt;p class=&quot;og-host&quot; style=&quot;color: #909090;&quot; data-ke-size=&quot;size16&quot;&gt;reactnative.dev&lt;/p&gt;
&lt;/div&gt;
&lt;/a&gt;&lt;/figure&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;h3 data-ke-size=&quot;size23&quot;&gt;SVG 사용하기&lt;/h3&gt;
&lt;hr contenteditable=&quot;false&quot; data-ke-type=&quot;horizontalRule&quot; data-ke-style=&quot;style5&quot; /&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;디자인을 하다보니 SVG를 사용할 일이 있어서 찾아보니 역시 누군가가 만들어 둔 라이브러리가 있었다. react-native-svg만 사용하게 되면 앱 내부에 있는 파일을 사용하기 못해서 transformer 라이브러리까지 사용해야 한다. 두개를 사용하면 간단히 svg를 사용할 수 있다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;a href=&quot;https://www.npmjs.com/package/react-native-svg&quot; target=&quot;_blank&quot; rel=&quot;noopener&quot;&gt;https://www.npmjs.com/package/react-native-svg&lt;/a&gt;&lt;/p&gt;
&lt;figure id=&quot;og_1683472574143&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;react-native-svg&quot; data-og-description=&quot;SVG library for react-native. Latest version: 13.9.0, last published: a month ago. Start using react-native-svg in your project by running &amp;#96;npm i react-native-svg&amp;#96;. There are 1450 other projects in the npm registry using react-native-svg.&quot; data-og-host=&quot;www.npmjs.com&quot; data-og-source-url=&quot;https://www.npmjs.com/package/react-native-svg&quot; data-og-url=&quot;https://www.npmjs.com/package/react-native-svg&quot; data-og-image=&quot;https://scrap.kakaocdn.net/dn/cAhvr3/hySwNm8psB/7sSRlnIAD2BWAV8ez6AAn1/img.png?width=1200&amp;amp;height=630&amp;amp;face=0_0_1200_630,https://scrap.kakaocdn.net/dn/j8QgP/hySypZdVx4/KGnUSmqVDO5cPRHAI7yZlk/img.png?width=1100&amp;amp;height=382&amp;amp;face=0_0_1100_382&quot;&gt;&lt;a href=&quot;https://www.npmjs.com/package/react-native-svg&quot; target=&quot;_blank&quot; rel=&quot;noopener&quot; data-source-url=&quot;https://www.npmjs.com/package/react-native-svg&quot;&gt;
&lt;div class=&quot;og-image&quot; style=&quot;background-image: url('https://scrap.kakaocdn.net/dn/cAhvr3/hySwNm8psB/7sSRlnIAD2BWAV8ez6AAn1/img.png?width=1200&amp;amp;height=630&amp;amp;face=0_0_1200_630,https://scrap.kakaocdn.net/dn/j8QgP/hySypZdVx4/KGnUSmqVDO5cPRHAI7yZlk/img.png?width=1100&amp;amp;height=382&amp;amp;face=0_0_1100_382');&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;react-native-svg&lt;/p&gt;
&lt;p class=&quot;og-desc&quot; data-ke-size=&quot;size16&quot;&gt;SVG library for react-native. Latest version: 13.9.0, last published: a month ago. Start using react-native-svg in your project by running `npm i react-native-svg`. There are 1450 other projects in the npm registry using react-native-svg.&lt;/p&gt;
&lt;p class=&quot;og-host&quot; data-ke-size=&quot;size16&quot;&gt;www.npmjs.com&lt;/p&gt;
&lt;/div&gt;
&lt;/a&gt;&lt;/figure&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;a href=&quot;https://www.npmjs.com/package/react-native-svg-transformer&quot; target=&quot;_blank&quot; rel=&quot;noopener&quot;&gt;https://www.npmjs.com/package/react-native-svg-transformer&lt;/a&gt;&lt;/p&gt;
&lt;figure id=&quot;og_1683472590218&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;react-native-svg-transformer&quot; data-og-description=&quot;SVG transformer for react-native. Latest version: 1.0.0, last published: a year ago. Start using react-native-svg-transformer in your project by running &amp;#96;npm i react-native-svg-transformer&amp;#96;. There are 107 other projects in the npm registry using react-nati&quot; data-og-host=&quot;www.npmjs.com&quot; data-og-source-url=&quot;https://www.npmjs.com/package/react-native-svg-transformer&quot; data-og-url=&quot;https://www.npmjs.com/package/react-native-svg-transformer&quot; data-og-image=&quot;https://scrap.kakaocdn.net/dn/bsPpDe/hySwFWXCyG/qdmh0zJasQoy24RUE8fZK0/img.png?width=1200&amp;amp;height=630&amp;amp;face=0_0_1200_630,https://scrap.kakaocdn.net/dn/BwnV6/hySwNHoWCI/5GqcUO96ENR8NsxPUgMj0k/img.png?width=1001&amp;amp;height=1160&amp;amp;face=0_0_1001_1160&quot;&gt;&lt;a href=&quot;https://www.npmjs.com/package/react-native-svg-transformer&quot; target=&quot;_blank&quot; rel=&quot;noopener&quot; data-source-url=&quot;https://www.npmjs.com/package/react-native-svg-transformer&quot;&gt;
&lt;div class=&quot;og-image&quot; style=&quot;background-image: url('https://scrap.kakaocdn.net/dn/bsPpDe/hySwFWXCyG/qdmh0zJasQoy24RUE8fZK0/img.png?width=1200&amp;amp;height=630&amp;amp;face=0_0_1200_630,https://scrap.kakaocdn.net/dn/BwnV6/hySwNHoWCI/5GqcUO96ENR8NsxPUgMj0k/img.png?width=1001&amp;amp;height=1160&amp;amp;face=0_0_1001_1160');&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;react-native-svg-transformer&lt;/p&gt;
&lt;p class=&quot;og-desc&quot; data-ke-size=&quot;size16&quot;&gt;SVG transformer for react-native. Latest version: 1.0.0, last published: a year ago. Start using react-native-svg-transformer in your project by running `npm i react-native-svg-transformer`. There are 107 other projects in the npm registry using react-nati&lt;/p&gt;
&lt;p class=&quot;og-host&quot; data-ke-size=&quot;size16&quot;&gt;www.npmjs.com&lt;/p&gt;
&lt;/div&gt;
&lt;/a&gt;&lt;/figure&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;h3 data-ke-size=&quot;size23&quot;&gt;기타 문제 해결&lt;/h3&gt;
&lt;hr contenteditable=&quot;false&quot; data-ke-type=&quot;horizontalRule&quot; data-ke-style=&quot;style5&quot; /&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;API에서 html 코드가 나오는 경우가 있었다. 이를 decode하기 위해서 아래 라이브러리를 사용했다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;a href=&quot;https://www.npmjs.com/package/he&quot; target=&quot;_blank&quot; rel=&quot;noopener&quot;&gt;https://www.npmjs.com/package/he&lt;/a&gt;&lt;/p&gt;
&lt;figure id=&quot;og_1683472610082&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;he&quot; data-og-description=&quot;A robust HTML entities encoder/decoder with full Unicode support.. Latest version: 1.2.0, last published: 5 years ago. Start using he in your project by running &amp;#96;npm i he&amp;#96;. There are 1712 other projects in the npm registry using he.&quot; data-og-host=&quot;www.npmjs.com&quot; data-og-source-url=&quot;https://www.npmjs.com/package/he&quot; data-og-url=&quot;https://www.npmjs.com/package/he&quot; data-og-image=&quot;https://scrap.kakaocdn.net/dn/h2dKu/hySyoMM8mg/F5flbyBvGazKedoVzN1Rg1/img.png?width=1200&amp;amp;height=630&amp;amp;face=0_0_1200_630&quot;&gt;&lt;a href=&quot;https://www.npmjs.com/package/he&quot; target=&quot;_blank&quot; rel=&quot;noopener&quot; data-source-url=&quot;https://www.npmjs.com/package/he&quot;&gt;
&lt;div class=&quot;og-image&quot; style=&quot;background-image: url('https://scrap.kakaocdn.net/dn/h2dKu/hySyoMM8mg/F5flbyBvGazKedoVzN1Rg1/img.png?width=1200&amp;amp;height=630&amp;amp;face=0_0_1200_630');&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;he&lt;/p&gt;
&lt;p class=&quot;og-desc&quot; data-ke-size=&quot;size16&quot;&gt;A robust HTML entities encoder/decoder with full Unicode support.. Latest version: 1.2.0, last published: 5 years ago. Start using he in your project by running `npm i he`. There are 1712 other projects in the npm registry using he.&lt;/p&gt;
&lt;p class=&quot;og-host&quot; data-ke-size=&quot;size16&quot;&gt;www.npmjs.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;date 타입이 YYYYMMDDHHMMSS 타입으로 나와서 보기 힘들었다. dayjs를 사용해서 날짜 형식을 쉽게 쓰기 위해서 아래 라이브러리를 사용했다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;a href=&quot;https://www.npmjs.com/package/dayjs&quot; target=&quot;_blank&quot; rel=&quot;noopener&quot;&gt;https://www.npmjs.com/package/dayjs&lt;/a&gt;&lt;/p&gt;
&lt;figure id=&quot;og_1683472657688&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;dayjs&quot; data-og-description=&quot;2KB immutable date time library alternative to Moment.js with the same modern API . Latest version: 1.11.7, last published: 5 months ago. Start using dayjs in your project by running &amp;#96;npm i dayjs&amp;#96;. There are 11471 other projects in the npm registry using d&quot; data-og-host=&quot;www.npmjs.com&quot; data-og-source-url=&quot;https://www.npmjs.com/package/dayjs&quot; data-og-url=&quot;https://www.npmjs.com/package/dayjs&quot; data-og-image=&quot;https://scrap.kakaocdn.net/dn/cHiyjD/hySyozfn1L/GmcZpoWuyb87rFLAFDKid1/img.png?width=1200&amp;amp;height=630&amp;amp;face=0_0_1200_630,https://scrap.kakaocdn.net/dn/xA5e0/hySyrJvUcz/GvhMrzF8Y5dG8KNWwMED9k/img.png?width=876&amp;amp;height=362&amp;amp;face=0_0_876_362,https://scrap.kakaocdn.net/dn/bsGm69/hySwMuZs0P/DPnMdOUrh6yGovqbOgrJZK/img.png?width=1546&amp;amp;height=204&amp;amp;face=0_0_1546_204&quot;&gt;&lt;a href=&quot;https://www.npmjs.com/package/dayjs&quot; target=&quot;_blank&quot; rel=&quot;noopener&quot; data-source-url=&quot;https://www.npmjs.com/package/dayjs&quot;&gt;
&lt;div class=&quot;og-image&quot; style=&quot;background-image: url('https://scrap.kakaocdn.net/dn/cHiyjD/hySyozfn1L/GmcZpoWuyb87rFLAFDKid1/img.png?width=1200&amp;amp;height=630&amp;amp;face=0_0_1200_630,https://scrap.kakaocdn.net/dn/xA5e0/hySyrJvUcz/GvhMrzF8Y5dG8KNWwMED9k/img.png?width=876&amp;amp;height=362&amp;amp;face=0_0_876_362,https://scrap.kakaocdn.net/dn/bsGm69/hySwMuZs0P/DPnMdOUrh6yGovqbOgrJZK/img.png?width=1546&amp;amp;height=204&amp;amp;face=0_0_1546_204');&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;dayjs&lt;/p&gt;
&lt;p class=&quot;og-desc&quot; data-ke-size=&quot;size16&quot;&gt;2KB immutable date time library alternative to Moment.js with the same modern API . Latest version: 1.11.7, last published: 5 months ago. Start using dayjs in your project by running `npm i dayjs`. There are 11471 other projects in the npm registry using d&lt;/p&gt;
&lt;p class=&quot;og-host&quot; data-ke-size=&quot;size16&quot;&gt;www.npmjs.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;dotenv로 코드의 중요한 값들을 가릴 수 있다. 아래의 라이브러리를 사용했다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;a href=&quot;https://www.npmjs.com/package/react-native-dotenv&quot; target=&quot;_blank&quot; rel=&quot;noopener&quot;&gt;https://www.npmjs.com/package/react-native-dotenv&lt;/a&gt;&lt;/p&gt;
&lt;figure id=&quot;og_1683472693458&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;react-native-dotenv&quot; data-og-description=&quot;Load environment variables using import statements.. Latest version: 3.4.8, last published: 2 months ago. Start using react-native-dotenv in your project by running &amp;#96;npm i react-native-dotenv&amp;#96;. There are 68 other projects in the npm registry using react-na&quot; data-og-host=&quot;www.npmjs.com&quot; data-og-source-url=&quot;https://www.npmjs.com/package/react-native-dotenv&quot; data-og-url=&quot;https://www.npmjs.com/package/react-native-dotenv&quot; data-og-image=&quot;https://scrap.kakaocdn.net/dn/eEgoc0/hySwJLMDEY/nCEWHg2ZgyCkE2WKBsJpIk/img.png?width=1200&amp;amp;height=630&amp;amp;face=0_0_1200_630&quot;&gt;&lt;a href=&quot;https://www.npmjs.com/package/react-native-dotenv&quot; target=&quot;_blank&quot; rel=&quot;noopener&quot; data-source-url=&quot;https://www.npmjs.com/package/react-native-dotenv&quot;&gt;
&lt;div class=&quot;og-image&quot; style=&quot;background-image: url('https://scrap.kakaocdn.net/dn/eEgoc0/hySwJLMDEY/nCEWHg2ZgyCkE2WKBsJpIk/img.png?width=1200&amp;amp;height=630&amp;amp;face=0_0_1200_630');&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;react-native-dotenv&lt;/p&gt;
&lt;p class=&quot;og-desc&quot; data-ke-size=&quot;size16&quot;&gt;Load environment variables using import statements.. Latest version: 3.4.8, last published: 2 months ago. Start using react-native-dotenv in your project by running `npm i react-native-dotenv`. There are 68 other projects in the npm registry using react-na&lt;/p&gt;
&lt;p class=&quot;og-host&quot; data-ke-size=&quot;size16&quot;&gt;www.npmjs.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;h3 data-ke-size=&quot;size23&quot;&gt;완성본&lt;/h3&gt;
&lt;hr contenteditable=&quot;false&quot; data-ke-type=&quot;horizontalRule&quot; data-ke-style=&quot;style5&quot; /&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;s&gt;스토어에 업로드하게 되면 나중에 링크를 올릴 예정이다.&lt;/s&gt;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;a href=&quot;https://play.google.com/store/apps/details?id=com.watchcustomclearance&quot;&gt;https://play.google.com/store/apps/details?id=com.watchcustomclearance&lt;/a&gt;&amp;nbsp;&lt;/p&gt;
&lt;figure id=&quot;og_1684204082772&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;통관검색기 - Google Play 앱&quot; data-og-description=&quot;통관부호로 택배를 조회하는 앱입니다.&quot; data-og-host=&quot;play.google.com&quot; data-og-source-url=&quot;https://play.google.com/store/apps/details?id=com.watchcustomclearance&quot; data-og-url=&quot;https://play.google.com/store/apps/details?id=com.watchcustomclearance&amp;amp;hl=ko&quot; data-og-image=&quot;https://scrap.kakaocdn.net/dn/PmwfA/hySE3aFrmg/guCCqOeP7woSy6mXpx1Z01/img.png?width=600&amp;amp;height=300&amp;amp;face=0_0_600_300,https://scrap.kakaocdn.net/dn/fQf49/hySCQYjQjT/K4ju8MqEBofApmZ8KAVOpK/img.png?width=240&amp;amp;height=240&amp;amp;face=0_0_240_240&quot;&gt;&lt;a href=&quot;https://play.google.com/store/apps/details?id=com.watchcustomclearance&quot; target=&quot;_blank&quot; rel=&quot;noopener&quot; data-source-url=&quot;https://play.google.com/store/apps/details?id=com.watchcustomclearance&quot;&gt;
&lt;div class=&quot;og-image&quot; style=&quot;background-image: url('https://scrap.kakaocdn.net/dn/PmwfA/hySE3aFrmg/guCCqOeP7woSy6mXpx1Z01/img.png?width=600&amp;amp;height=300&amp;amp;face=0_0_600_300,https://scrap.kakaocdn.net/dn/fQf49/hySCQYjQjT/K4ju8MqEBofApmZ8KAVOpK/img.png?width=240&amp;amp;height=240&amp;amp;face=0_0_240_240');&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;통관검색기 - Google Play 앱&lt;/p&gt;
&lt;p class=&quot;og-desc&quot; data-ke-size=&quot;size16&quot;&gt;통관부호로 택배를 조회하는 앱입니다.&lt;/p&gt;
&lt;p class=&quot;og-host&quot; data-ke-size=&quot;size16&quot;&gt;play.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;앱의 플로우는 다음과 같다.&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;해당 물품의 현재 위치(상태)&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;&lt;a href=&quot;https://github.com/tre2man/watch-custom-clearance/tree/master&quot; target=&quot;_blank&quot; rel=&quot;noopener&quot;&gt;https://github.com/tre2man/watch-custom-clearance/tree/master&lt;/a&gt;&lt;/p&gt;
&lt;figure id=&quot;og_1683558484230&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 - tre2man/watch-custom-clearance: 통관조회 앱&quot; data-og-description=&quot;통관조회 앱. Contribute to tre2man/watch-custom-clearance development by creating an account on GitHub.&quot; data-og-host=&quot;github.com&quot; data-og-source-url=&quot;https://github.com/tre2man/watch-custom-clearance/tree/master&quot; data-og-url=&quot;https://github.com/tre2man/watch-custom-clearance&quot; data-og-image=&quot;https://scrap.kakaocdn.net/dn/bkgENi/hySyizMPfA/q7VKmMoLhIIoj6K21n3pnk/img.png?width=1200&amp;amp;height=600&amp;amp;face=0_0_1200_600&quot;&gt;&lt;a href=&quot;https://github.com/tre2man/watch-custom-clearance/tree/master&quot; target=&quot;_blank&quot; rel=&quot;noopener&quot; data-source-url=&quot;https://github.com/tre2man/watch-custom-clearance/tree/master&quot;&gt;
&lt;div class=&quot;og-image&quot; style=&quot;background-image: url('https://scrap.kakaocdn.net/dn/bkgENi/hySyizMPfA/q7VKmMoLhIIoj6K21n3pnk/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 - tre2man/watch-custom-clearance: 통관조회 앱&lt;/p&gt;
&lt;p class=&quot;og-desc&quot; data-ke-size=&quot;size16&quot;&gt;통관조회 앱. Contribute to tre2man/watch-custom-clearance development by creating an account on GitHub.&lt;/p&gt;
&lt;p class=&quot;og-host&quot; data-ke-size=&quot;size16&quot;&gt;github.com&lt;/p&gt;
&lt;/div&gt;
&lt;/a&gt;&lt;/figure&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;</description>
      <category>메이킹/메이킹 프로젝트</category>
      <author>트리맨스</author>
      <guid isPermaLink="true">https://tre2man.tistory.com/355</guid>
      <comments>https://tre2man.tistory.com/355#entry355comment</comments>
      <pubDate>Mon, 8 May 2023 00:26:41 +0900</pubDate>
    </item>
    <item>
      <title>풀스택 개발 프로젝트 준비하기 (1)</title>
      <link>https://tre2man.tistory.com/354</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;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;Fcm을 통해 푸시 메시지를 제어한다.&lt;/li&gt;
&lt;li&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;ul style=&quot;list-style-type: disc;&quot; data-ke-list-type=&quot;disc&quot;&gt;
&lt;li&gt;백엔드 : NestJS : Typescript 기반으로 빠르게 작성하고 테스트가 가능하고 익숙함&lt;/li&gt;
&lt;li&gt;ORM : Typeorm : 가장 많이 사용하고, 나중에 확장할 때 빠르게 할수 있다.&lt;/li&gt;
&lt;li&gt;DB : Postgres : 오픈소스, 제일 익숙하고 Join 연산에 최적화가 되어있다.&lt;/li&gt;
&lt;li&gt;배포 : 아직 미정, Docker를 사용하는 AWS 제품 또는 DigitalOcean 사용 예정 (AWS는 익숙하고 DigitalOcean은 쿠버네티스를 사용하고싶어서).&lt;/li&gt;
&lt;li&gt;인프라 관련 기술 : Docker를 사용하는 이유는 추후 확장이 쉽게 가능하고 버전 태그를 사용하게 되면 롤백을 쉽게 할 수 있기 때문이다.&lt;/li&gt;
&lt;li&gt;APM : sentry : 비교적 저렴하여 초기 시작에 유용함. 슬랙과 Github 통합에 매우 유리하다.&lt;/li&gt;
&lt;li&gt;애플리케이션 : React-Native : IOS와 Android 앱을 동시에 제작할 수 있다. 하드웨어에 접근하는 로직은 없을 것이고 (아마도) 러닝커브가 비교적 낮기 때문&lt;/li&gt;
&lt;li&gt;어드민 : React : 익숙하다&lt;/li&gt;
&lt;li&gt;통신 방식 : GraphQL : 클라이언트 관점에서 데이터를 Type safe하게 다룰 수 있고, 내부 상태 관리 같은 경우에도 Apollo를 사용해서 Object 단위로 컨트롤이 가능하다. 다만 인증, E2E, 서버사이드에서의 overfetch 등 백엔드에서 생각해야 할 문제가 조금 있다.&lt;/li&gt;
&lt;li&gt;인증 방식 : JWT : 서버에 최대한 부담이 가지 않게 인증할 수 있다.&lt;/li&gt;
&lt;li&gt;푸시 : FCM : 푸시를 FCM에서 한번에 관리할 수 있게 한다.&lt;/li&gt;
&lt;li&gt;공지사항 및 강제 업데이트 : Remote Config : 이것으로 특정 부분에서 앱의 상태를 관리자가 직접 지정할 수 있다.&lt;/li&gt;
&lt;li&gt;파일 저장소 : 미정. AWS를 사용하게 된다면 S3, 다른것을 사용한다면 Cloudflare의 R2 (매우 저렴하다)&lt;/li&gt;
&lt;li&gt;호스팅 : 미정. AWS를 사용하게 된다면 Route53, 다른것을 사용한다면 Cloudflare&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;현재는 백앤드 로직을 제작 중이다. ERD는 일단 mermaidjs를 사용하여 작성 중이다. vscode로 미리보기를 할 수 있는데, 생각보다 퀄리티가 좋아서 조금 놀랬다. 아직 추가해야 할 테이블과 칼럼들이 많다. 1차 출시에 포함할 기능들을 확실하게 해야 할거같다.&lt;/p&gt;
&lt;p&gt;&lt;figure class=&quot;imageblock alignCenter&quot; data-ke-mobileStyle=&quot;widthOrigin&quot; data-filename=&quot;스크린샷 2023-05-04 00.19.43.png&quot; data-origin-width=&quot;948&quot; data-origin-height=&quot;927&quot;&gt;&lt;span data-url=&quot;https://blog.kakaocdn.net/dn/bLCStP/btsdRg9EPCh/x7PK8i3RCWi3FFuEcmMPHk/img.png&quot; data-phocus=&quot;https://blog.kakaocdn.net/dn/bLCStP/btsdRg9EPCh/x7PK8i3RCWi3FFuEcmMPHk/img.png&quot;&gt;&lt;img src=&quot;https://blog.kakaocdn.net/dn/bLCStP/btsdRg9EPCh/x7PK8i3RCWi3FFuEcmMPHk/img.png&quot; srcset=&quot;https://img1.daumcdn.net/thumb/R1280x0/?scode=mtistory2&amp;fname=https%3A%2F%2Fblog.kakaocdn.net%2Fdn%2FbLCStP%2FbtsdRg9EPCh%2Fx7PK8i3RCWi3FFuEcmMPHk%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;638&quot; height=&quot;624&quot; data-filename=&quot;스크린샷 2023-05-04 00.19.43.png&quot; data-origin-width=&quot;948&quot; data-origin-height=&quot;927&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;</description>
      <category>메이킹/메이킹 준비</category>
      <author>트리맨스</author>
      <guid isPermaLink="true">https://tre2man.tistory.com/354</guid>
      <comments>https://tre2man.tistory.com/354#entry354comment</comments>
      <pubDate>Thu, 4 May 2023 00:23:00 +0900</pubDate>
    </item>
    <item>
      <title>Mermaid를 사용해서 ERD 작성하기</title>
      <link>https://tre2man.tistory.com/353</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;최근에 ERD를 작성할 일이 생겼는데, ERD 툴을 새로 사용하자니 매우 귀찮은 생각이 들었다. Markdown처럼 ERD도 코드 형식으로 사용할 수 있게 하면 좋겠다는 생각이 들어서 여기저기 찾아보다가, mermaid라는 좋은 오픈소스 프로그램을 발견했다. 문법도 비교적 쉬운 편에 속해서 빨리 익힐 수 있었다. 공식 홈페이지는 다음 주소에 있다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;a href=&quot;https://mermaid.js.org/&quot; target=&quot;_blank&quot; rel=&quot;noopener&quot;&gt;https://mermaid.js.org/&lt;/a&gt;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;h2 data-ke-size=&quot;size26&quot;&gt;빠른 사용법&lt;/h2&gt;
&lt;hr contenteditable=&quot;false&quot; data-ke-type=&quot;horizontalRule&quot; data-ke-style=&quot;style5&quot; /&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;공식 홈페이지를 보면 텍스트와 코드를 사용해서 시각화를 빠르게 할 수 있는 툴 이라고 광고한다. 기본적인 문법은 Markdown을 많이 참고했고, 기본 목표는 ERD가 아닌 다이어그램을 표시하는 것이 원래 목적이다. 여기서 응용하여 마인드맵, 타임라인, ERD, 파이 차트 등의 다양한 작업에 활용할 수 있어 보인다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;그래서 얘를 ERD로 어떻게 활용하냐? 빠르게 알아보자.&lt;/p&gt;
&lt;pre id=&quot;code_1682264302461&quot; class=&quot;shell&quot; data-ke-language=&quot;shell&quot; data-ke-type=&quot;codeblock&quot;&gt;&lt;code&gt;erDiagram
    CUSTOMER {
        string name
        string custNumber
        string sector
    }
    ORDER {
        int orderNumber
        string deliveryAddress
    }
    LINE-ITEM {
        string productCode
        int quantity
        float pricePerUnit
    }
    
    CUSTOMER ||--o{ ORDER : places
    ORDER ||--|{ LINE-ITEM : contains&lt;/code&gt;&lt;/pre&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;위의 예시는 공식 문서에 있는 예제의 순서를 살짝만 바꾸어 두었다. 문법은 어렵지 않다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;해당 코드에는 3개의 테이블이 있다. customer, ordwer, line-item이 있으며, customer의 칼럼으로는 name, custNumber, sector가 있다. 각 테이블의 릴레이션은 customer : order가 1대 n으로 되어 있고 order: line-item이 1대 n으로 되어 있다.&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;여기에 추가해서 PK, UK, FK 와 description까지 달 수 있다. 기타 옵션들은 공식 문서를 참고하면 좋을 것 같다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;a href=&quot;https://mermaid.js.org/syntax/entityRelationshipDiagram.html&quot; target=&quot;_blank&quot; rel=&quot;noopener&quot;&gt;https://mermaid.js.org/syntax/entityRelationshipDiagram.html&lt;/a&gt;&lt;/p&gt;
&lt;figure id=&quot;og_1682264554035&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;Entity Relationship Diagrams | Mermaid&quot; data-og-description=&quot;&quot; data-og-host=&quot;mermaid.js.org&quot; data-og-source-url=&quot;https://mermaid.js.org/syntax/entityRelationshipDiagram.html&quot; data-og-url=&quot;https://mermaid.js.org/syntax/entityRelationshipDiagram.html&quot; data-og-image=&quot;&quot;&gt;&lt;a href=&quot;https://mermaid.js.org/syntax/entityRelationshipDiagram.html&quot; target=&quot;_blank&quot; rel=&quot;noopener&quot; data-source-url=&quot;https://mermaid.js.org/syntax/entityRelationshipDiagram.html&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;Entity Relationship Diagrams | Mermaid&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;mermaid.js.org&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;h2 data-ke-size=&quot;size26&quot;&gt;응용하기&lt;/h2&gt;
&lt;hr contenteditable=&quot;false&quot; data-ke-type=&quot;horizontalRule&quot; data-ke-style=&quot;style5&quot; /&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;그래서 얘를 어떻게 실생활에 사용할까? 나의 경우에는 github의 readme와 notion에 잘 사용하고 있다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;github에는 .md 파일로 올려 두었는데, 해당 파일은 github에서 네이티브로 지원하기 때문에 문제없이 사용이 가능하다. 단, 코드 주석으로 취급하기 때문에 mermaid 속성을 지정해야지 확인이 가능하다. vscode에서는 아래의 두 플러그인을 사용하면 린팅과 프리뷰가 가능하다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;a href=&quot;https://marketplace.visualstudio.com/items?itemName=bierner.markdown-mermaid&quot;&gt;https://marketplace.visualstudio.com/items?itemName=bierner.markdown-mermaid&lt;/a&gt;&amp;nbsp;&lt;/p&gt;
&lt;figure id=&quot;og_1682264730201&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;Markdown Preview Mermaid Support - Visual Studio Marketplace&quot; data-og-description=&quot;Extension for Visual Studio Code - Adds Mermaid diagram and flowchart support to VS Code's builtin markdown preview&quot; data-og-host=&quot;marketplace.visualstudio.com&quot; data-og-source-url=&quot;https://marketplace.visualstudio.com/items?itemName=bierner.markdown-mermaid&quot; data-og-url=&quot;https://marketplace.visualstudio.com/items?itemName=bierner.markdown-mermaid&quot; data-og-image=&quot;https://scrap.kakaocdn.net/dn/fjyBb/hySoFHURhp/keKVQaCp2YVPrlAvF9lRn0/img.png?width=128&amp;amp;height=128&amp;amp;face=0_0_128_128,https://scrap.kakaocdn.net/dn/bwghn5/hySmVr43Ev/pfGQoXYk5OkZfHk997rvM0/img.png?width=2280&amp;amp;height=1112&amp;amp;face=0_0_2280_1112&quot;&gt;&lt;a href=&quot;https://marketplace.visualstudio.com/items?itemName=bierner.markdown-mermaid&quot; target=&quot;_blank&quot; rel=&quot;noopener&quot; data-source-url=&quot;https://marketplace.visualstudio.com/items?itemName=bierner.markdown-mermaid&quot;&gt;
&lt;div class=&quot;og-image&quot; style=&quot;background-image: url('https://scrap.kakaocdn.net/dn/fjyBb/hySoFHURhp/keKVQaCp2YVPrlAvF9lRn0/img.png?width=128&amp;amp;height=128&amp;amp;face=0_0_128_128,https://scrap.kakaocdn.net/dn/bwghn5/hySmVr43Ev/pfGQoXYk5OkZfHk997rvM0/img.png?width=2280&amp;amp;height=1112&amp;amp;face=0_0_2280_1112');&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;Markdown Preview Mermaid Support - Visual Studio Marketplace&lt;/p&gt;
&lt;p class=&quot;og-desc&quot; data-ke-size=&quot;size16&quot;&gt;Extension for Visual Studio Code - Adds Mermaid diagram and flowchart support to VS Code's builtin markdown preview&lt;/p&gt;
&lt;p class=&quot;og-host&quot; data-ke-size=&quot;size16&quot;&gt;marketplace.visualstudio.com&lt;/p&gt;
&lt;/div&gt;
&lt;/a&gt;&lt;/figure&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;a href=&quot;https://marketplace.visualstudio.com/items?itemName=bpruitt-goddard.mermaid-markdown-syntax-highlighting&quot;&gt;https://marketplace.visualstudio.com/items?itemName=bpruitt-goddard.mermaid-markdown-syntax-highlighting&lt;/a&gt;&amp;nbsp;&lt;/p&gt;
&lt;figure id=&quot;og_1682264734702&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;Mermaid Markdown Syntax Highlighting - Visual Studio Marketplace&quot; data-og-description=&quot;Extension for Visual Studio Code - Markdown syntax support for the Mermaid charting language&quot; data-og-host=&quot;marketplace.visualstudio.com&quot; data-og-source-url=&quot;https://marketplace.visualstudio.com/items?itemName=bpruitt-goddard.mermaid-markdown-syntax-highlighting&quot; data-og-url=&quot;https://marketplace.visualstudio.com/items?itemName=bpruitt-goddard.mermaid-markdown-syntax-highlighting&quot; data-og-image=&quot;https://scrap.kakaocdn.net/dn/bK2zzL/hySmYoMGJO/LIpUrB7PlEscPkH45N3Zw1/img.png?width=128&amp;amp;height=128&amp;amp;face=0_0_128_128,https://scrap.kakaocdn.net/dn/C1feY/hySoFunrmC/k90UKWKtKmNxBJtwh2jDa0/img.png?width=928&amp;amp;height=1566&amp;amp;face=0_0_928_1566,https://scrap.kakaocdn.net/dn/l8toj/hySot8xWS7/niIZYylxH8UolLPHnlHZR0/img.png?width=716&amp;amp;height=1720&amp;amp;face=0_0_716_1720&quot;&gt;&lt;a href=&quot;https://marketplace.visualstudio.com/items?itemName=bpruitt-goddard.mermaid-markdown-syntax-highlighting&quot; target=&quot;_blank&quot; rel=&quot;noopener&quot; data-source-url=&quot;https://marketplace.visualstudio.com/items?itemName=bpruitt-goddard.mermaid-markdown-syntax-highlighting&quot;&gt;
&lt;div class=&quot;og-image&quot; style=&quot;background-image: url('https://scrap.kakaocdn.net/dn/bK2zzL/hySmYoMGJO/LIpUrB7PlEscPkH45N3Zw1/img.png?width=128&amp;amp;height=128&amp;amp;face=0_0_128_128,https://scrap.kakaocdn.net/dn/C1feY/hySoFunrmC/k90UKWKtKmNxBJtwh2jDa0/img.png?width=928&amp;amp;height=1566&amp;amp;face=0_0_928_1566,https://scrap.kakaocdn.net/dn/l8toj/hySot8xWS7/niIZYylxH8UolLPHnlHZR0/img.png?width=716&amp;amp;height=1720&amp;amp;face=0_0_716_1720');&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;Mermaid Markdown Syntax Highlighting - Visual Studio Marketplace&lt;/p&gt;
&lt;p class=&quot;og-desc&quot; data-ke-size=&quot;size16&quot;&gt;Extension for Visual Studio Code - Markdown syntax support for the Mermaid charting language&lt;/p&gt;
&lt;p class=&quot;og-host&quot; data-ke-size=&quot;size16&quot;&gt;marketplace.visualstudio.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;그리고 notion에서도 사용이 가능한데, /code 입력한 후 mermaid를 지정하면 바로 사용이 가능하다.&lt;/p&gt;
&lt;p&gt;&lt;figure class=&quot;imageblock alignCenter&quot; data-ke-mobileStyle=&quot;widthOrigin&quot; data-filename=&quot;스크린샷 2023-04-24 00.43.38.png&quot; data-origin-width=&quot;706&quot; data-origin-height=&quot;980&quot;&gt;&lt;span data-url=&quot;https://blog.kakaocdn.net/dn/dFPuec/btsbUq8pKck/AhoIfIVBki98M3AWskcwzK/img.png&quot; data-phocus=&quot;https://blog.kakaocdn.net/dn/dFPuec/btsbUq8pKck/AhoIfIVBki98M3AWskcwzK/img.png&quot;&gt;&lt;img src=&quot;https://blog.kakaocdn.net/dn/dFPuec/btsbUq8pKck/AhoIfIVBki98M3AWskcwzK/img.png&quot; srcset=&quot;https://img1.daumcdn.net/thumb/R1280x0/?scode=mtistory2&amp;fname=https%3A%2F%2Fblog.kakaocdn.net%2Fdn%2FdFPuec%2FbtsbUq8pKck%2FAhoIfIVBki98M3AWskcwzK%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;706&quot; height=&quot;980&quot; data-filename=&quot;스크린샷 2023-04-24 00.43.38.png&quot; data-origin-width=&quot;706&quot; data-origin-height=&quot;980&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;&amp;nbsp;&lt;/p&gt;</description>
      <category>IT  이야기</category>
      <author>트리맨스</author>
      <guid isPermaLink="true">https://tre2man.tistory.com/353</guid>
      <comments>https://tre2man.tistory.com/353#entry353comment</comments>
      <pubDate>Mon, 24 Apr 2023 00:46:59 +0900</pubDate>
    </item>
    <item>
      <title>Azure cli에서 oauth2 없이 바로 로그인하기</title>
      <link>https://tre2man.tistory.com/352</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 image안에 azure cil을 설치하고, 해당 프로그램에 인자로 key값을 받는 작업을 수행했다. azure는 aws와 다르게 cli로 로그인 하기 위해서는 거의 무조건 web으로 연결해야 하는 로직이 있었다. oauth2를 사용해서 보안 강화에 초점을 맞춘 것 같다. 여하튼 해당 플로우를 구현하기 위해서 방법을 찾아봤는데, aws와 다른점이 있어서 방법을 찾을때 약간 고생좀 했다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;h3 data-ke-size=&quot;size23&quot;&gt;구현하기&lt;/h3&gt;
&lt;hr contenteditable=&quot;false&quot; data-ke-type=&quot;horizontalRule&quot; data-ke-style=&quot;style5&quot; /&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;az login 실행 (--use-device-code 도 비슷한 플로우)&lt;/li&gt;
&lt;li&gt;웹사이트 오픈&lt;/li&gt;
&lt;li&gt;로그인 인증 후 cli로 전환&lt;/li&gt;
&lt;li&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;여기서 웹사이트를 오픈하는 과정 자체가 보안에는 좋을지 몰라도 사용성에서는 상당히 별로인 것 같아 보였다. 그래서 찾아보니 서비스 주체를 사용하여 로그인을 할 수 있다고 한다. 공식 문서에 따르면 다음 항목이 필요하다고 한다.&lt;/p&gt;
&lt;ul style=&quot;list-style-type: disc;&quot; data-ke-list-type=&quot;disc&quot;&gt;
&lt;li&gt;서비스 주체와 연결된 URL 또는 이름&lt;/li&gt;
&lt;li&gt;서비스 주체 암호 또는 X509 인증서&lt;/li&gt;
&lt;li&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;ol style=&quot;list-style-type: decimal;&quot; data-ke-list-type=&quot;decimal&quot;&gt;
&lt;li&gt;앱을 생성하자. portal에서 Active Directory -&amp;gt; 앱 등록 -&amp;gt; 새 등록 -&amp;gt; 앱 생성&lt;/li&gt;
&lt;li&gt;앱의 암호를 생성하자 : 해당 앱 -&amp;gt; 인증서 및 암호 -&amp;gt; 암호 생성 (여기서 &quot;값&quot; 이라고 쓰인 곳이 암호이고, 해당 화면을 벗어나면 암호를 찾을 수 없으므로 다른 곳에 적어두자)&lt;/li&gt;
&lt;li&gt;방금 만든 앱에 구독 권한을 추가하자 : portal -&amp;gt; 구독 -&amp;gt; 엑세스 제어 (IAM) -&amp;gt; 역할 할당 -&amp;gt; 역할 할당 추가&lt;/li&gt;
&lt;li&gt;여기서는 특정한 세부 권한을 줄건지, 전체 권한을 줄 건지에 따라 다르다. 나의 경우에는 테스트용으로 전체 리소스에 엑세스 할 권한을 줄 것이다 : 권한 있는 관리자 역할 -&amp;gt; 소유자 -&amp;gt; 구성원에 방금 만든 app 추가 -&amp;gt; 할당&lt;/li&gt;
&lt;li&gt;azure cli에서 로그인하기&lt;/li&gt;
&lt;/ol&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;appId : 1 에서 만든 앱의 ID&lt;/li&gt;
&lt;li&gt;password : 2에서 만든 암호 값&lt;/li&gt;
&lt;li&gt;tenantId : 구독의 ID&lt;/li&gt;
&lt;/ul&gt;
&lt;pre id=&quot;code_1681916927987&quot; class=&quot;shell&quot; data-ke-language=&quot;shell&quot; data-ke-type=&quot;codeblock&quot;&gt;&lt;code&gt;az login --service-principal --username &quot;appId&quot; --password &quot;password&quot; --tenant &quot;tenantId&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;위의 값들을 docker image를 run 할때 외부 변수로 빼 두면, 이미지가 run 할때 특정 계정이 인증된 azure image가 생성이 된다.&lt;/p&gt;
&lt;p data-ke-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>서버 인프라/Azure</category>
      <author>트리맨스</author>
      <guid isPermaLink="true">https://tre2man.tistory.com/352</guid>
      <comments>https://tre2man.tistory.com/352#entry352comment</comments>
      <pubDate>Thu, 20 Apr 2023 00:10:53 +0900</pubDate>
    </item>
    <item>
      <title>AKS에서 파드가 네트워크에 연결하지 못할 때</title>
      <link>https://tre2man.tistory.com/351</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;현재 AKS에 NestJS 파드를 올려야 하는 상황이다. 분명히 네트워크 엔드포인트 잘 맞추고, 서비스 잘 올렸는데 자꾸 백엔드에서 DB에 접속을 하지 못하는 이슈가 있었다. 현재 이 상황에서 내가 해본 것들은 다음과 같다.&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;Service 포트 확인하기&lt;/li&gt;
&lt;li&gt;Deployment, Service 라벨 확인하기&lt;/li&gt;
&lt;li&gt;Pod에 접속해서 curl 날려서 DB 연결 확인하기&lt;/li&gt;
&lt;li&gt;Azure 가상 머신에 접속하여 이미지 빌드 후 푸시하기&lt;/li&gt;
&lt;li&gt;원격 저장소에 있는 이전 이미지 배포하기(이건 잘됨)&lt;/li&gt;
&lt;li&gt;클러스터 처음부터 다시 만들기&lt;/li&gt;
&lt;li&gt;CNI 설정 처음부터 다시하기&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;이정도의 엄청난 삽질을 하고 있었다...그런데 CRI에 대해서 한번 확인해 보라고 주변 분께서 제안해 보셨다. 그래서 CRI에 대해서 잠깐 알아본 이후 바로 확인해 보았다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;h3 data-ke-size=&quot;size23&quot;&gt;CRI&lt;/h3&gt;
&lt;hr contenteditable=&quot;false&quot; data-ke-type=&quot;horizontalRule&quot; data-ke-style=&quot;style5&quot; /&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;CRI란 Container Runtime Interface의 약자이다. 쿠버네티스는 기본적으로 컨테이너 애플리케이션을 오케스트레이션 하는 도구이다. 그러므로 도커만 지원하는 것이 아닌 cri-o, containerd, rkt 등의 컨테이너 런타임을 지원한다. 하지만 이러한 런타임 도구들은 여러가지가 있기 때문에 쿠버네티스에서는 CRI라는 표준 규격을 제시하여 다른 컨테이너 런타임 도구들이 CRI에 맞추어 제작되면 알아서 잘 돌아가는 것이다.&lt;/p&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;a href=&quot;https://learn.microsoft.com/ko-kr/azure/aks/cluster-configuration&quot; target=&quot;_blank&quot; rel=&quot;noopener&quot;&gt;https://learn.microsoft.com/ko-kr/azure/aks/cluster-configuration&lt;/a&gt;&lt;/p&gt;
&lt;figure id=&quot;og_1681743305151&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;AKS(Azure Kubernetes Service)의 클러스터 구성 - Azure Kubernetes Service&quot; data-og-description=&quot;AKS(Azure Kubernetes Service)에서 클러스터를 구성하는 방법 알아보기&quot; data-og-host=&quot;learn.microsoft.com&quot; data-og-source-url=&quot;https://learn.microsoft.com/ko-kr/azure/aks/cluster-configuration&quot; data-og-url=&quot;https://learn.microsoft.com/ko-kr/azure/aks/cluster-configuration&quot; data-og-image=&quot;https://scrap.kakaocdn.net/dn/XaRxl/hySk0L4ued/dVz4rPnKGYThQis14ZvyhK/img.png?width=400&amp;amp;height=400&amp;amp;face=0_0_400_400,https://scrap.kakaocdn.net/dn/7fIqS/hySi3DDz9W/qJ7ll2UWkB8xiPYx8Mgr0k/img.png?width=887&amp;amp;height=227&amp;amp;face=0_0_887_227&quot;&gt;&lt;a href=&quot;https://learn.microsoft.com/ko-kr/azure/aks/cluster-configuration&quot; target=&quot;_blank&quot; rel=&quot;noopener&quot; data-source-url=&quot;https://learn.microsoft.com/ko-kr/azure/aks/cluster-configuration&quot;&gt;
&lt;div class=&quot;og-image&quot; style=&quot;background-image: url('https://scrap.kakaocdn.net/dn/XaRxl/hySk0L4ued/dVz4rPnKGYThQis14ZvyhK/img.png?width=400&amp;amp;height=400&amp;amp;face=0_0_400_400,https://scrap.kakaocdn.net/dn/7fIqS/hySi3DDz9W/qJ7ll2UWkB8xiPYx8Mgr0k/img.png?width=887&amp;amp;height=227&amp;amp;face=0_0_887_227');&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;AKS(Azure Kubernetes Service)의 클러스터 구성 - Azure Kubernetes Service&lt;/p&gt;
&lt;p class=&quot;og-desc&quot; data-ke-size=&quot;size16&quot;&gt;AKS(Azure Kubernetes Service)에서 클러스터를 구성하는 방법 알아보기&lt;/p&gt;
&lt;p class=&quot;og-host&quot; data-ke-size=&quot;size16&quot;&gt;learn.microsoft.com&lt;/p&gt;
&lt;/div&gt;
&lt;/a&gt;&lt;/figure&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;해당 문서를 보면 aks 클러스터에서 docker는 사용 중지되고 있으며, 2023년 5월(다음달???) 부터는 지원하지 않는 다는 문서를 보았다. 나는 지금까지 docker를 사용해서 이미지를 빌드한 이후 acr에 업로드하여 테스트하고 있었다. 지금까지 헛수고만 하고 있었다는 것이다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;h3 data-ke-size=&quot;size23&quot;&gt;해결하기&lt;/h3&gt;
&lt;hr contenteditable=&quot;false&quot; data-ke-type=&quot;horizontalRule&quot; data-ke-style=&quot;style5&quot; /&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;그래서 어떻게 해결했나. 답은 containerd를 사용하거나 acr를 사용하는 것이다. containerd는 컨테이너 런타임 중의 하나로서, 도커도 사실 근간은 containerd 위에서 돌아가는 애플리케이션이다. 여튼 containerd를 사용해서 빌드를 해도 되지만, 이미 az-cli 환경이 익숙하고 내 pc에 설치되어 있어서 acr 명령어를 통해서 빌드 이후 원격 레포지토리에 푸시하는 편이 좋아 보였다. 상식적으로 azure에서 제공하는 서비스끼리 호환이 되지 않으면 문제가 심각하지 않겠는가? 라는 생각으로 진행했다...&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;pre id=&quot;code_1681743859481&quot; class=&quot;shell&quot; data-ke-language=&quot;shell&quot; data-ke-type=&quot;codeblock&quot;&gt;&lt;code&gt;az acr build \
  --image myregistry.azurecr.io/my-image \
  --registry myregistry \
  --file Dockerfile \
  .&lt;/code&gt;&lt;/pre&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;예시로 로컬의 Dockerfile을 사용해서 빌드 후 푸시했다. 이후에 쿠버네티스 클러스터에 deploy하니 잘된다...5일동안 삽질한게 너무 허무하게 끝나버렸다. 여기서 내가 docker를 써도 된다고 믿었던 이유는 정상적으로 실행은 되었기 때문이다. docker exec 명령어로 접속하면 접속은 잘되는데 네트워크 관련해서 계속 문제가 생기고 결국 죽텔죽...아니 계속 죽어버리는 문제가 생겼다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;이번 일을 계기로 CRI의 중요성에 대해서 알게 되었고, 쿠버네티스의 트러블 슈팅에 대해서 조금 더 잘 알게 되었다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;</description>
      <category>서버 인프라/Azure</category>
      <author>트리맨스</author>
      <guid isPermaLink="true">https://tre2man.tistory.com/351</guid>
      <comments>https://tre2man.tistory.com/351#entry351comment</comments>
      <pubDate>Tue, 18 Apr 2023 00:07:35 +0900</pubDate>
    </item>
    <item>
      <title>쿠버네티스의 Network Solution에 대하여</title>
      <link>https://tre2man.tistory.com/350</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;쿠버네티스는 컨테이너 애플리케이션을 제어할 수 있는 컨테이너 오케스트레이션 프로그램 중 하나이다. 현재 컨테이너 오케스트레이션 도구 중에서 독보적인 위치를 지니고 있으며, MSA를 사용하는 기업들은 거의 다 쿠버네티스를 사용하고 있다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;쿠버네티스는 각 컨테이너를 다루는 도구이므로, 당연히 그와 관련된 네트워크 설정들이 있을 것이다. 쿠버네티스에서 기본적으로 제공하는 네트워크에 대해서 간단히 알아보자.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;h3 data-ke-size=&quot;size23&quot;&gt;쿠버네티스의 기본 네트워크&lt;/h3&gt;
&lt;hr contenteditable=&quot;false&quot; data-ke-type=&quot;horizontalRule&quot; data-ke-style=&quot;style5&quot; /&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;쿠버네티스의 구조에 대해서 간단히 알아보자. 쿠버네티스의 가장 작은 단위는 파드이다. 한 개의 도커 컨테이너라고 보면 된다. 파드들을 관리하는 상위 단위는 노드이다. 노드는 한 대의 pc라고 생각하면 된다. 이러한 노드들을 관리하는 것이 쿠버네티스 클러스터이다. 그림으로 보면 다음과 같다.&lt;/p&gt;
&lt;p&gt;&lt;figure class=&quot;imageblock alignCenter&quot; data-ke-mobileStyle=&quot;widthOrigin&quot; data-filename=&quot;스크린샷 2023-04-13 23.26.19.png&quot; data-origin-width=&quot;889&quot; data-origin-height=&quot;732&quot;&gt;&lt;span data-url=&quot;https://blog.kakaocdn.net/dn/bW9F3q/btr92vQPrfx/vVH4ITRye5VBDYLOFvzv6k/img.png&quot; data-phocus=&quot;https://blog.kakaocdn.net/dn/bW9F3q/btr92vQPrfx/vVH4ITRye5VBDYLOFvzv6k/img.png&quot;&gt;&lt;img src=&quot;https://blog.kakaocdn.net/dn/bW9F3q/btr92vQPrfx/vVH4ITRye5VBDYLOFvzv6k/img.png&quot; srcset=&quot;https://img1.daumcdn.net/thumb/R1280x0/?scode=mtistory2&amp;fname=https%3A%2F%2Fblog.kakaocdn.net%2Fdn%2FbW9F3q%2Fbtr92vQPrfx%2FvVH4ITRye5VBDYLOFvzv6k%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;541&quot; height=&quot;445&quot; data-filename=&quot;스크린샷 2023-04-13 23.26.19.png&quot; data-origin-width=&quot;889&quot; data-origin-height=&quot;732&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;여하튼, 쿠버네티스는 자체적인 네트워크가 있긴 하다. 바로 service 네트워크, ingress 네트워크, networkPolicy 등의 기본적으로 제공되는 도구들이 있다. 하지만 이러한 도구들은 파드 또는 컨테이너가 업데이트 될 때 네트워크 구성을 수동으로 변경해야 하고, 네트워크 구성을 변경할 때 모든 컨테이너를 수동으로 업데이트해야 하는 번거로움이 있다. 이를 해결하기 위해서 쿠버네티스에서는 CNI (Container Network Interface) 라는 표준 인터페이스를 제공한다. CNI를 사용해서 보다 수월하게 네트워크 관리(보안, 격리, 트래킹 등등)를 할 수 있다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;h3 data-ke-size=&quot;size23&quot;&gt;Service Mash&lt;/h3&gt;
&lt;hr contenteditable=&quot;false&quot; data-ke-type=&quot;horizontalRule&quot; data-ke-style=&quot;style5&quot; /&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;CNI보다는 비슷한 솔루션을 먼저 정리해 보겠다. 직관적으로 생각했을 때 각 파드의 트래픽을 모니터링 하기 위해서는 각 파드에 트래킹하는 무언가를 설치해야 한다. 쿠버네티스에서는 이를 위해서 사이드카 패턴을 사용한다. 간단히 말하면, 하나의 파드 안에 메인 컨테이너를 제외한 1개 이상의 프록시(모니터링) 기능을 하는 컨테이너를 하나 더 추가해서 사용하는 것이다. 이는 yaml파일에서 근거를 찾아볼 수 있다.&lt;/p&gt;
&lt;pre id=&quot;code_1681401167980&quot; class=&quot;less&quot; style=&quot;background-color: #f8f8f8; color: #383a42; text-align: start;&quot; data-ke-language=&quot;shell&quot; data-ke-type=&quot;codeblock&quot;&gt;&lt;code&gt;apiVersion: v1
kind: Pod
metadata:
  name: nginx
spec:
  containers:
  - name: nginx
    image: nginx:1.14.2
    ports:
    - containerPort: 80&lt;/code&gt;&lt;/pre&gt;
&lt;p style=&quot;color: #333333; text-align: start;&quot; data-ke-size=&quot;size16&quot;&gt;파드를 만드는 예시 yaml 파일이다. containers 항목이 배열로 이루어진 것을 알 수 있다. 즉 하나의 파드에 여러개의 컨테이너가 있을 수 있다는 것이다.&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 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;이러한 서비스 매쉬의 예시 중 하나로 istio가 있다. istio는 사이드카 패턴으로 프록시를 설치하고, istio controlplane에서 트래픽을 제어할 수 있게 한다. istio는 두 가지의 통신 방법이 있다. data plane과 control plane이다.&lt;/p&gt;
&lt;p style=&quot;color: #333333; text-align: start;&quot; data-ke-size=&quot;size16&quot;&gt;data plane : 서비스 간의 통신이다.&amp;nbsp;&lt;/p&gt;
&lt;p style=&quot;color: #333333; text-align: start;&quot; data-ke-size=&quot;size16&quot;&gt;control plane : 프록시 서버를 동적으로 프로그래밍해 규칙이나 환경이 변경되면 업데이트한다.&lt;/p&gt;
&lt;p&gt;&lt;figure class=&quot;imageblock alignCenter&quot; data-ke-mobileStyle=&quot;widthOrigin&quot; data-filename=&quot;234.svg&quot; data-origin-width=&quot;800&quot; data-origin-height=&quot;524&quot;&gt;&lt;span data-url=&quot;https://blog.kakaocdn.net/dn/bnckUD/btr97ob6nEo/HDbMo4Wddgjw77tlUkS6z1/tfile.svg&quot; data-phocus=&quot;https://blog.kakaocdn.net/dn/bnckUD/btr97ob6nEo/HDbMo4Wddgjw77tlUkS6z1/tfile.svg&quot;&gt;&lt;img src=&quot;https://blog.kakaocdn.net/dn/bnckUD/btr97ob6nEo/HDbMo4Wddgjw77tlUkS6z1/tfile.svg&quot; srcset=&quot;https://img1.daumcdn.net/thumb/R1280x0/?scode=mtistory2&amp;fname=https%3A%2F%2Fblog.kakaocdn.net%2Fdn%2FbnckUD%2Fbtr97ob6nEo%2FHDbMo4Wddgjw77tlUkS6z1%2Ftfile.svg&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;624&quot; height=&quot;409&quot; data-filename=&quot;234.svg&quot; data-origin-width=&quot;800&quot; data-origin-height=&quot;524&quot;/&gt;&lt;/span&gt;&lt;/figure&gt;
&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;h3 data-ke-size=&quot;size23&quot;&gt;CNI&lt;/h3&gt;
&lt;hr contenteditable=&quot;false&quot; data-ke-type=&quot;horizontalRule&quot; data-ke-style=&quot;style5&quot; /&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;CNI를 말하기 전에 BPF에 대해서 먼저 알아봐야 할 것 같다. BPF는 Berkeley Packet Filter의 약자인데, 리눅스 커널 단위에서 실행되는 가상 머신이며 주로 패킷 모니터링에 대해 사용되었다. 여기서 eBPF라는 확장된 개념이 나오는데, 기존의 BPF에서 확장 기능이 추가되었다고 생각하면 된다. process에서 systemCall이 scheduler에 가기 저넹 eBPF가 중간에 가로채는(?) 방식으로 작동이 된다. BPF는 오래 전에 나온 개념이고 요즘에는 컨테이너 기술이 활성화되면서 eBPF로 이전한 추세인 것 같다.&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;CNI와 Service Mesh의 차이점을 알아보자. 만약 Service Mesh 네트워킹을 사용하면 다음과 같은 노드의 모양이 나올 것이다.&lt;/p&gt;
&lt;p&gt;&lt;figure class=&quot;imageblock alignCenter&quot; data-ke-mobileStyle=&quot;widthOrigin&quot; data-filename=&quot;스크린샷 2023-04-14 00.36.59.png&quot; data-origin-width=&quot;508&quot; data-origin-height=&quot;420&quot;&gt;&lt;span data-url=&quot;https://blog.kakaocdn.net/dn/bfjPK4/btr93nSA4wi/SdJF2BrLhbYBM7gWoH7jy0/img.png&quot; data-phocus=&quot;https://blog.kakaocdn.net/dn/bfjPK4/btr93nSA4wi/SdJF2BrLhbYBM7gWoH7jy0/img.png&quot;&gt;&lt;img src=&quot;https://blog.kakaocdn.net/dn/bfjPK4/btr93nSA4wi/SdJF2BrLhbYBM7gWoH7jy0/img.png&quot; srcset=&quot;https://img1.daumcdn.net/thumb/R1280x0/?scode=mtistory2&amp;fname=https%3A%2F%2Fblog.kakaocdn.net%2Fdn%2FbfjPK4%2Fbtr93nSA4wi%2FSdJF2BrLhbYBM7gWoH7jy0%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;508&quot; height=&quot;420&quot; data-filename=&quot;스크린샷 2023-04-14 00.36.59.png&quot; data-origin-width=&quot;508&quot; data-origin-height=&quot;420&quot;/&gt;&lt;/span&gt;&lt;/figure&gt;
&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;각 파드에 사이드카가 달려 있는 형태가 나오게 된다. 하지만 여기서 cilium을 사용하게 되면 다음과 같이 사용할 수 있다.&lt;/p&gt;
&lt;p&gt;&lt;figure class=&quot;imageblock alignCenter&quot; data-ke-mobileStyle=&quot;widthOrigin&quot; data-filename=&quot;스크린샷 2023-04-14 00.37.02.png&quot; data-origin-width=&quot;494&quot; data-origin-height=&quot;411&quot;&gt;&lt;span data-url=&quot;https://blog.kakaocdn.net/dn/EJXM1/btr94vQl6A0/kbKtKux48zjaiukBB93MCk/img.png&quot; data-phocus=&quot;https://blog.kakaocdn.net/dn/EJXM1/btr94vQl6A0/kbKtKux48zjaiukBB93MCk/img.png&quot;&gt;&lt;img src=&quot;https://blog.kakaocdn.net/dn/EJXM1/btr94vQl6A0/kbKtKux48zjaiukBB93MCk/img.png&quot; srcset=&quot;https://img1.daumcdn.net/thumb/R1280x0/?scode=mtistory2&amp;fname=https%3A%2F%2Fblog.kakaocdn.net%2Fdn%2FEJXM1%2Fbtr94vQl6A0%2FkbKtKux48zjaiukBB93MCk%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;494&quot; height=&quot;411&quot; data-filename=&quot;스크린샷 2023-04-14 00.37.02.png&quot; data-origin-width=&quot;494&quot; data-origin-height=&quot;411&quot;/&gt;&lt;/span&gt;&lt;/figure&gt;
&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;해당 아키텍쳐는 사이드카 없이 네트워크 트래킹이 가능하다! 사이드카리스 프록시 모델이라고 하며, 이렇게 사용하면 사이드카 패턴에 비해서 각 파드에서 사용 가능한 리소스가 더 많아지게 된다. 요거는 eBPF 패턴을 사용했다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;여하튼 각 서비스마다 장단점이 있고, 네트워크를 관리하기 위한 훌륭한 도구임에는 틀림없다. cilium과 istio를 둘 다 활용한 방법도 있어 보였다. 이러한 네트워크 솔루션을 사용하여 grafana 또는 kibana 같은 모니터링 툴을 활용하여 클러스터의 상태를 확인하게 할 수도 있다. 여하튼 각자 상황에 맞는 네트워크 서비스가 있으니 적당히 맞는 솔루션을 활용하여 사용하면 될 것 같다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;만약 특정 파드에서 다른 파드로의 네트워크 연결을 확인하기 위해서는 하드의 /etc/hosts 를 확인하거나 kubectl get service &amp;lt;podName&amp;gt; -o yaml 명령어를 통해서 서비스명을 확인한 후 curl 또는 telnet을 날려서 확인한다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;h3 data-ke-size=&quot;size23&quot;&gt;참고자료&lt;/h3&gt;
&lt;hr contenteditable=&quot;false&quot; data-ke-type=&quot;horizontalRule&quot; data-ke-style=&quot;style5&quot; /&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;a href=&quot;https://thenewstack.io/how-ebpf-streamlines-the-service-mesh/&quot; target=&quot;_blank&quot; rel=&quot;noopener&quot;&gt;https://thenewstack.io/how-ebpf-streamlines-the-service-mesh/&lt;/a&gt;&lt;/p&gt;
&lt;figure id=&quot;og_1681400370351&quot; contenteditable=&quot;false&quot; data-ke-type=&quot;opengraph&quot; data-ke-align=&quot;alignCenter&quot; data-og-type=&quot;article&quot; data-og-title=&quot;How eBPF Streamlines the Service Mesh&quot; data-og-description=&quot;Let&amp;rsquo;s explore how eBPF allows us to streamline the service mesh, making the data plane more efficient and easier to deploy.&amp;nbsp;&quot; data-og-host=&quot;thenewstack.io&quot; data-og-source-url=&quot;https://thenewstack.io/how-ebpf-streamlines-the-service-mesh/&quot; data-og-url=&quot;https://thenewstack.io/how-ebpf-streamlines-the-service-mesh/&quot; data-og-image=&quot;https://scrap.kakaocdn.net/dn/WXEDN/hyShDcgQwv/6M6yqquKlwkWt5ywI9LnIk/img.png?width=640&amp;amp;height=426&amp;amp;face=0_0_640_426,https://scrap.kakaocdn.net/dn/pRIoT/hyShBMhWeb/vcoeGqKncGQAK1C5Lia2K1/img.png?width=960&amp;amp;height=463&amp;amp;face=0_0_960_463,https://scrap.kakaocdn.net/dn/vCxWE/hyShAs4LJN/FmWNrkgLUHK6t1i3HxTLPK/img.png?width=868&amp;amp;height=373&amp;amp;face=0_0_868_373&quot;&gt;&lt;a href=&quot;https://thenewstack.io/how-ebpf-streamlines-the-service-mesh/&quot; target=&quot;_blank&quot; rel=&quot;noopener&quot; data-source-url=&quot;https://thenewstack.io/how-ebpf-streamlines-the-service-mesh/&quot;&gt;
&lt;div class=&quot;og-image&quot; style=&quot;background-image: url('https://scrap.kakaocdn.net/dn/WXEDN/hyShDcgQwv/6M6yqquKlwkWt5ywI9LnIk/img.png?width=640&amp;amp;height=426&amp;amp;face=0_0_640_426,https://scrap.kakaocdn.net/dn/pRIoT/hyShBMhWeb/vcoeGqKncGQAK1C5Lia2K1/img.png?width=960&amp;amp;height=463&amp;amp;face=0_0_960_463,https://scrap.kakaocdn.net/dn/vCxWE/hyShAs4LJN/FmWNrkgLUHK6t1i3HxTLPK/img.png?width=868&amp;amp;height=373&amp;amp;face=0_0_868_373');&quot;&gt;&amp;nbsp;&lt;/div&gt;
&lt;div class=&quot;og-text&quot;&gt;
&lt;p class=&quot;og-title&quot; data-ke-size=&quot;size16&quot;&gt;How eBPF Streamlines the Service Mesh&lt;/p&gt;
&lt;p class=&quot;og-desc&quot; data-ke-size=&quot;size16&quot;&gt;Let&amp;rsquo;s explore how eBPF allows us to streamline the service mesh, making the data plane more efficient and easier to deploy.&amp;nbsp;&lt;/p&gt;
&lt;p class=&quot;og-host&quot; data-ke-size=&quot;size16&quot;&gt;thenewstack.io&lt;/p&gt;
&lt;/div&gt;
&lt;/a&gt;&lt;/figure&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;a href=&quot;https://docs.cilium.io/en/stable/network/istio/&quot; target=&quot;_blank&quot; rel=&quot;noopener&quot;&gt;https://docs.cilium.io/en/stable/network/istio/&lt;/a&gt;&lt;/p&gt;
&lt;figure id=&quot;og_1681400389320&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;Getting Started Using Istio &amp;mdash; Cilium 1.13.1 documentation&quot; data-og-description=&quot;This document serves as an introduction to using Cilium Istio integration to enforce security policies in Kubernetes micro-services managed with Istio. It is a detailed walk-through of getting a single-node Cilium + Istio environment running on your machin&quot; data-og-host=&quot;docs.cilium.io&quot; data-og-source-url=&quot;https://docs.cilium.io/en/stable/network/istio/&quot; data-og-url=&quot;https://docs.cilium.io/en/stable/network/istio/&quot; data-og-image=&quot;&quot;&gt;&lt;a href=&quot;https://docs.cilium.io/en/stable/network/istio/&quot; target=&quot;_blank&quot; rel=&quot;noopener&quot; data-source-url=&quot;https://docs.cilium.io/en/stable/network/istio/&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;Getting Started Using Istio &amp;mdash; Cilium 1.13.1 documentation&lt;/p&gt;
&lt;p class=&quot;og-desc&quot; data-ke-size=&quot;size16&quot;&gt;This document serves as an introduction to using Cilium Istio integration to enforce security policies in Kubernetes micro-services managed with Istio. It is a detailed walk-through of getting a single-node Cilium + Istio environment running on your machin&lt;/p&gt;
&lt;p class=&quot;og-host&quot; data-ke-size=&quot;size16&quot;&gt;docs.cilium.io&lt;/p&gt;
&lt;/div&gt;
&lt;/a&gt;&lt;/figure&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;</description>
      <category>서버 인프라/DevOps</category>
      <author>트리맨스</author>
      <guid isPermaLink="true">https://tre2man.tistory.com/350</guid>
      <comments>https://tre2man.tistory.com/350#entry350comment</comments>
      <pubDate>Fri, 14 Apr 2023 20:15:23 +0900</pubDate>
    </item>
    <item>
      <title>Terraform 훑어보기</title>
      <link>https://tre2man.tistory.com/349</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;이번에 회사에서 terraform을 사용해서 간단히 정리해 보았다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;h2 data-ke-size=&quot;size26&quot;&gt;Terraform이란&lt;/h2&gt;
&lt;hr contenteditable=&quot;false&quot; data-ke-type=&quot;horizontalRule&quot; data-ke-style=&quot;style5&quot; /&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;클라우드, 온프레미스 리소스들을 코드로 정의할 수 있는 도구이다. 컴퓨팅 리소스, 스토리지, 네트워킹 리소스, DNS, SaaS 등의 다양한 구성 용소를 코드로 정의할 수도 있다. 테라폼 API를 통해서 앞에서 말한 리소스들을 지정할 수 있다.&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;테라폼의 워크플로에는 주로 3개의 단계가 있다. 먼저 코드를 작성하는 write 단계가 있다. 말 그대로 리소스를 코드로 작성한다. 두 번쨰는 plan이다. 기존에 있던 인프라를 기반으로 이후 변화될 계획을 구상한다. 구상하는 과정은 테라폼 자체적으로 알아서 순서를 정한다. 마지막으로는 apply를 한다. 이전 단계에 있던 plan을 실제로 적용하는 단계이다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;h3 data-ke-size=&quot;size23&quot;&gt;권장하는 테라폼 프로젝트 구조&lt;/h3&gt;
&lt;hr contenteditable=&quot;false&quot; data-ke-type=&quot;horizontalRule&quot; data-ke-style=&quot;style5&quot; /&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;readme&lt;/li&gt;
&lt;li&gt;main.tf, variables.tf, outputs.tf&lt;/li&gt;
&lt;li&gt;modules 폴더 아래에 중첩 모듈 사용, example 폴더 아래에 중첩 모듈 사용&lt;/li&gt;
&lt;li&gt;key, secret 같은 중요 파일에 대한 내용은 *.tfvars 파일 안에 저장한 후, gitignore에 *.tfvars를 추가하여 보안을 유지한다.&lt;/li&gt;
&lt;/ul&gt;
&lt;pre id=&quot;code_1680444805333&quot; class=&quot;bash&quot; data-ke-language=&quot;bash&quot; data-ke-type=&quot;codeblock&quot;&gt;&lt;code&gt;$ tree complete-module/
.
├── README.md
├── main.tf
├── variables.tf
├── outputs.tf
├── ...
├── modules/
│   ├── nestedA/
│   │   ├── README.md
│   │   ├── variables.tf
│   │   ├── main.tf
│   │   ├── outputs.tf
│   ├── nestedB/
│   ├── .../
├── examples/
│   ├── exampleA/
│   │   ├── main.tf
│   ├── exampleB/
│   ├── .../&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;aks에 kubernetes를 올리는 예시 파일을 확인하자.&lt;/p&gt;
&lt;pre id=&quot;code_1680444988842&quot; class=&quot;bash&quot; data-ke-language=&quot;bash&quot; data-ke-type=&quot;codeblock&quot;&gt;&lt;code&gt;# main.tf

provider &quot;azurerm&quot; {
  features {}
}

resource &quot;azurerm_resource_group&quot; &quot;example&quot; {
  name     = &quot;${var.prefix}-k8s-resources&quot;
  location = var.location
}

resource &quot;azurerm_kubernetes_cluster&quot; &quot;example&quot; {
  name                = &quot;${var.prefix}-k8s&quot;
  location            = azurerm_resource_group.example.location
  resource_group_name = azurerm_resource_group.example.name
  dns_prefix          = &quot;${var.prefix}-k8s&quot;

  default_node_pool {
    name       = &quot;default&quot;
    node_count = 1
    vm_size    = &quot;Standard_DS2_v2&quot;
  }

  identity {
    type = &quot;SystemAssigned&quot;
  }
}&lt;/code&gt;&lt;/pre&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;main.tf 파일을 보면 resource로 생성할 리소스에 대해서 설정을 해 두었다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;pre id=&quot;code_1680445946158&quot; class=&quot;bash&quot; data-ke-language=&quot;bash&quot; data-ke-type=&quot;codeblock&quot;&gt;&lt;code&gt;# outputs.tf
output &quot;id&quot; {
  value = azurerm_kubernetes_cluster.example.id
}

output &quot;kube_config&quot; {
  value = azurerm_kubernetes_cluster.example.kube_config_raw
}

output &quot;client_key&quot; {
  value = azurerm_kubernetes_cluster.example.kube_config.0.client_key
}

output &quot;client_certificate&quot; {
  value = azurerm_kubernetes_cluster.example.kube_config.0.client_certificate
}

output &quot;cluster_ca_certificate&quot; {
  value = azurerm_kubernetes_cluster.example.kube_config.0.cluster_ca_certificate
}

output &quot;host&quot; {
  value = azurerm_kubernetes_cluster.example.kube_config.0.host
}&lt;/code&gt;&lt;/pre&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;outputs.tf는 다른 곳에서 생성된 클러스터에 대한 정보를 알 수 있게 지정한다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;pre id=&quot;code_1680446055394&quot; class=&quot;bash&quot; data-ke-language=&quot;bash&quot; data-ke-type=&quot;codeblock&quot;&gt;&lt;code&gt;# variable.tf
variable &quot;prefix&quot; {
  description = &quot;A prefix used for all resources in this example&quot;
}

variable &quot;location&quot; {
  description = &quot;The Azure Region in which all resources in this example should be provisioned&quot;
}&lt;/code&gt;&lt;/pre&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;여기서는 사실상 의미없는 변수들만 적어 둔 파일이다. 만약 secret같이 중요한 내용이 있는 경우에는 위에서 말했듯 .tfvars로 끝나는 파일에 변수를 지정한 다음, gitignore에 *.tfvars를 추가해 secret을 보호한다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;h2 data-ke-size=&quot;size26&quot;&gt;참고자료&lt;/h2&gt;
&lt;hr contenteditable=&quot;false&quot; data-ke-type=&quot;horizontalRule&quot; data-ke-style=&quot;style5&quot; /&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://developer.hashicorp.com/terraform/language&quot; target=&quot;_blank&quot; rel=&quot;noopener&quot;&gt;https://developer.hashicorp.com/terraform/language&lt;/a&gt;&lt;/p&gt;
&lt;figure id=&quot;og_1680446173479&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;Overview - Configuration Language | Terraform | HashiCorp Developer&quot; data-og-description=&quot;Use the Terraform configuration language to describe the infrastructure that Terraform manages.&quot; data-og-host=&quot;developer.hashicorp.com&quot; data-og-source-url=&quot;https://developer.hashicorp.com/terraform/language&quot; data-og-url=&quot;https://developer.hashicorp.com/terraform/language&quot; data-og-image=&quot;https://scrap.kakaocdn.net/dn/c4nl45/hyR9C5EWXf/xJ0ehws9WsDRm7xJVrc8Xk/img.jpg?width=3200&amp;amp;height=1800&amp;amp;face=0_0_3200_1800,https://scrap.kakaocdn.net/dn/dy2rtz/hyR7zW3GNt/GLWvvsjoKwpIB4BZ902ud1/img.jpg?width=3200&amp;amp;height=1800&amp;amp;face=0_0_3200_1800&quot;&gt;&lt;a href=&quot;https://developer.hashicorp.com/terraform/language&quot; target=&quot;_blank&quot; rel=&quot;noopener&quot; data-source-url=&quot;https://developer.hashicorp.com/terraform/language&quot;&gt;
&lt;div class=&quot;og-image&quot; style=&quot;background-image: url('https://scrap.kakaocdn.net/dn/c4nl45/hyR9C5EWXf/xJ0ehws9WsDRm7xJVrc8Xk/img.jpg?width=3200&amp;amp;height=1800&amp;amp;face=0_0_3200_1800,https://scrap.kakaocdn.net/dn/dy2rtz/hyR7zW3GNt/GLWvvsjoKwpIB4BZ902ud1/img.jpg?width=3200&amp;amp;height=1800&amp;amp;face=0_0_3200_1800');&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;Overview - Configuration Language | Terraform | HashiCorp Developer&lt;/p&gt;
&lt;p class=&quot;og-desc&quot; data-ke-size=&quot;size16&quot;&gt;Use the Terraform configuration language to describe the infrastructure that Terraform manages.&lt;/p&gt;
&lt;p class=&quot;og-host&quot; data-ke-size=&quot;size16&quot;&gt;developer.hashicorp.com&lt;/p&gt;
&lt;/div&gt;
&lt;/a&gt;&lt;/figure&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;</description>
      <category>서버 인프라/DevOps</category>
      <author>트리맨스</author>
      <guid isPermaLink="true">https://tre2man.tistory.com/349</guid>
      <comments>https://tre2man.tistory.com/349#entry349comment</comments>
      <pubDate>Sun, 2 Apr 2023 23:35:32 +0900</pubDate>
    </item>
    <item>
      <title>유용한 쉘 스크립트 시나리오 정리</title>
      <link>https://tre2man.tistory.com/348</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;쉘 스크립트의 exit code 분리하기&lt;/p&gt;
&lt;pre id=&quot;code_1680015312561&quot; class=&quot;bash&quot; data-ke-language=&quot;bash&quot; data-ke-type=&quot;codeblock&quot;&gt;&lt;code&gt;# 예시 : azure cli 로그인 여부 확인하기
if az account show &amp;amp;&amp;gt;/dev/null; then
    echo &quot;Azure Logined!&quot;
else
    echo &quot;You need to login Azure!&quot;
fi&lt;/code&gt;&lt;/pre&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;amp;&amp;gt;/dev/null 을 붙이면 쓰레기통(/dev/null)에 출력 결과물이 들어가고, exit code만 남게 된다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;마지막으로 실행한 스크립트의 exit code 확인하기&lt;/p&gt;
&lt;pre id=&quot;code_1680015457581&quot; class=&quot;bash&quot; data-ke-language=&quot;bash&quot; data-ke-type=&quot;codeblock&quot;&gt;&lt;code&gt;$?&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;/p&gt;
&lt;pre id=&quot;code_1680015569547&quot; class=&quot;bash&quot; data-ke-language=&quot;bash&quot; data-ke-type=&quot;codeblock&quot;&gt;&lt;code&gt;# nowGroup에 group이라는 변수를 저장 후 출력한다
nowGroup=$(echo &quot;group&quot;)
echo $nowGroup&lt;/code&gt;&lt;/pre&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;pre id=&quot;code_1680015640624&quot; class=&quot;bash&quot; data-ke-language=&quot;bash&quot; data-ke-type=&quot;codeblock&quot;&gt;&lt;code&gt;# 폴더 존재 확인
FOLDER_NAME=&quot;tf&quot;

if [ ! -d &quot;$FOLDER_NAME&quot; ]; then
    mkdir &quot;$FOLDER_NAME&quot;
fi&lt;/code&gt;&lt;/pre&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;tf 폴더가 존재하는지 확인 후, 폴더가 없으면 폴더를 만든다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;직전에 사용한 쉘 명령어 불러오기&lt;/p&gt;
&lt;pre id=&quot;code_1680015806196&quot; class=&quot;bash&quot; data-ke-language=&quot;bash&quot; data-ke-type=&quot;codeblock&quot;&gt;&lt;code&gt;!!&lt;/code&gt;&lt;/pre&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;</description>
      <category>프로그래밍 언어/Shell</category>
      <author>트리맨스</author>
      <guid isPermaLink="true">https://tre2man.tistory.com/348</guid>
      <comments>https://tre2man.tistory.com/348#entry348comment</comments>
      <pubDate>Wed, 29 Mar 2023 00:04:24 +0900</pubDate>
    </item>
    <item>
      <title>오픈소스에 대한 고찰</title>
      <link>https://tre2man.tistory.com/347</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;지금 다니는 회사에서 Typeorm에 관해서 대화를 나눈 적이 있었다. 다들 자기 의견에 대해서 말을 하는데, typeorm의 최초 개발자가 typeorm을 주도적으로 개발하지 않아 곧 deprecated 된다는 얘기를 들었다. 사실 nodejs 서버 진영에서는 (적어도 한국에서는) ORM 프로그램 중에서 가장 많이 쓰이는 것이 typeorm이라고 알고 있었다. 하지만 지속적인 지원이 없는 프로그램을 가장 많이 쓴다는 것이 둘 다 모순이 된다고 생각했다. 그래서 이것에 대해서 궁금증이 생겨서 나름 내용을 찾아봤다.&lt;/p&gt;
&lt;h3 data-ke-size=&quot;size23&quot;&gt;&amp;nbsp;&lt;/h3&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;일단 typeorm이 많이 쓰이는 이유를 찾아보니 오래되기도 했고 typescript 지원에다가 다른 orm에서 주로 쓰는 패턴과 비슷해서 적응이 빠른것이 장점인 것 같았다. 하지만 typeorm은 소수의 인원들로부터 시작한 프로젝트이고, node의 생태계가 급성장함에 따라서 typeorm을 사용하는 사람이 많아졌고, 이에 비례해서 PR 및 Issue들이 기하급수적으로 늘어나게 되었다. 하지만 현장 인력은 늘어나지도 않고, 더욱이 오픈소스 프로그램이여서 보수도 들어오지 않았다. 보수라고 해도 다른 개발자들의 sponsor 또는 donate 밖에 없던 상황이였다. 결국 typeorm의 개발자는 해당 Issue를 올리게 되었다.&lt;/p&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/typeorm/typeorm/issues/3267&quot;&gt;https://github.com/typeorm/typeorm/issues/3267&lt;/a&gt;&lt;/p&gt;
&lt;figure id=&quot;og_1676988483780&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;Future of TypeORM &amp;middot; Issue #3267 &amp;middot; typeorm/typeorm&quot; data-og-description=&quot;We are working full-time on TypeORM for almost three years. That&amp;rsquo;s why we have such a powerful and really amazing ORM. And this time wasn't funded or sponsored by anybody except our core develo...&quot; data-og-host=&quot;github.com&quot; data-og-source-url=&quot;https://github.com/typeorm/typeorm/issues/3267&quot; data-og-url=&quot;https://github.com/typeorm/typeorm/issues/3267&quot; data-og-image=&quot;https://scrap.kakaocdn.net/dn/bOS7y0/hyRHw0Yc9O/rKdk7Y6hj4s0hWAMVw13ak/img.png?width=1200&amp;amp;height=600&amp;amp;face=0_0_1200_600&quot;&gt;&lt;a href=&quot;https://github.com/typeorm/typeorm/issues/3267&quot; target=&quot;_blank&quot; rel=&quot;noopener&quot; data-source-url=&quot;https://github.com/typeorm/typeorm/issues/3267&quot;&gt;
&lt;div class=&quot;og-image&quot; style=&quot;background-image: url('https://scrap.kakaocdn.net/dn/bOS7y0/hyRHw0Yc9O/rKdk7Y6hj4s0hWAMVw13ak/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;Future of TypeORM &amp;middot; Issue #3267 &amp;middot; typeorm/typeorm&lt;/p&gt;
&lt;p class=&quot;og-desc&quot; data-ke-size=&quot;size16&quot;&gt;We are working full-time on TypeORM for almost three years. That&amp;rsquo;s why we have such a powerful and really amazing ORM. And this time wasn't funded or sponsored by anybody except our core develo...&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;간단히 요약하면, typeorm을 유지하기 위해서는 시간과 인력, 그리고 돈이 필요한데 지금 그것을 관리하기 위해서는 너무 거대해졌고 인력과 시간이 없다는 것이다. 그 이후로는 간간히 업데이트가 올라오는 것을 봐서는 뭔가 개발을 하는 것 같기는 한데, 타 ORM에 비해서는 업데이트 주기도 늦고 언제 deprecated 될지 모르는 프로젝트가 되었다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;사실 오픈소스라는 것은 자본주의 사회에서 보았을 때는 이해를 할 수 없는 프로그램이긴 하다. 자신의 힘으로 프로그램을 작성했고 타인이 사용한다고 하면 거기에 저작권 또는 가격을 붙여 수익을 내는 것이 맞는데, 몇몇 개발자들은 해당 프로그램의 작동 원리(소스) 를 공개하기 시작했다. 이것이 오픈 소스의 시작이고, 이러한 코드들이 github라는 커뮤니티에 모이면서 오픈소스 프로그램은 널리 퍼지게 되었다. 그리고 몇몇 퀄리티 있는 오픈소스들은 많은 인기를 얻게 되었다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;여기까지만 보면 좋은 문화같아 보인다. 하지만 이러한 오픈소스에는 맹점도 분명히 존재한다. 그것은 돈이 안 된다는 것이다. 사실 돈이 안 된다는 것만 보면은 문제가 없다. 하지만 해당 프로그램에 버그가 있거나 개선을 요구하는 다른 사람들이 있을 수 있다. 이러한 요구사항들이 고치기 어렵지 않고 몇 개 안되면은 취미로 할 수도 있다. 하지만 해당 프로그램을 사용하는 사람들이 너무 많고 PR도 몇천개씩 들어오기 시작하면 여기서부터 문제가 생긴다. 나는 내가 작성한 프로그램을 타인이 소소하게 잘 사용했으면 하는 바람에 공개를 했는데, 컴플레인이 몇천개씩 들어오게 되면은 고민에 빠지게 된다. 물론 이런 문제를 해결할 능력과 시간이 충분하다면 할 수도 있으나, 대부분의 사람들은 이러한 시간과 돈이 없는 경우가 많을 것이다. 여기에 개선을 요구하는 것을 넘어서 강력한 항의가 들어올 수도 있고, 저작권에 따라 다르겠지만 해당 프로그램을 사용해 엄청난 돈을 버는 사용자도 있을 것이다. 이러한 상황 속에서 오픈소스 개발자들은 당연히 엄청난 스트레스를 받을 것이다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;nextjs에서 사용하는 컴파일러인 swc의 개발자도 같은 고민에 빠진 것을 본 적이 있다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;a href=&quot;https://kdy1.github.io/post/2022/10/stc/&quot; target=&quot;_blank&quot; rel=&quot;noopener&quot;&gt;https://kdy1.github.io/post/2022/10/stc/&lt;/a&gt;&lt;/p&gt;
&lt;figure id=&quot;og_1676989158928&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;타입 체커는 오픈소스화하지 않을 생각이다&quot; data-og-description=&quot;난 오픈소스 커뮤니티를 더 이상 믿지 않는다. swc 덕분에 지금 상당히 좋은 대우를 받고 있고 명예도 어느 정도 얻었지만, 솔직히 말해서 난 swc 때문에 인성 버렸다. 원래 대부&quot; data-og-host=&quot;kdy1.github.io&quot; data-og-source-url=&quot;https://kdy1.github.io/post/2022/10/stc/&quot; data-og-url=&quot;https://kdy1.github.io/post/2022/10/stc/&quot; data-og-image=&quot;&quot;&gt;&lt;a href=&quot;https://kdy1.github.io/post/2022/10/stc/&quot; target=&quot;_blank&quot; rel=&quot;noopener&quot; data-source-url=&quot;https://kdy1.github.io/post/2022/10/stc/&quot;&gt;
&lt;div class=&quot;og-image&quot; style=&quot;background-image: url();&quot;&gt;&amp;nbsp;&lt;/div&gt;
&lt;div class=&quot;og-text&quot;&gt;
&lt;p class=&quot;og-title&quot; data-ke-size=&quot;size16&quot;&gt;타입 체커는 오픈소스화하지 않을 생각이다&lt;/p&gt;
&lt;p class=&quot;og-desc&quot; data-ke-size=&quot;size16&quot;&gt;난 오픈소스 커뮤니티를 더 이상 믿지 않는다. swc 덕분에 지금 상당히 좋은 대우를 받고 있고 명예도 어느 정도 얻었지만, 솔직히 말해서 난 swc 때문에 인성 버렸다. 원래 대부&lt;/p&gt;
&lt;p class=&quot;og-host&quot; data-ke-size=&quot;size16&quot;&gt;kdy1.github.io&lt;/p&gt;
&lt;/div&gt;
&lt;/a&gt;&lt;/figure&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;참 안타까운 현실이라고 생각한다. 이러한 현상 때문에 자바나 ES같은 상황이 생기는 것 같다. 자바 또한 오픈소스 프로그램이였는데, 구글에서 자바를 사용해서 막대한 돈을 벌어들이면서 갈등이 생기게 되었고, 결국 자바는 여러개의 다른 버전이 생기게 되었다. 또한 ES (엘라스틱서치) 도 원래 오픈소스 프로그램이였지만 아마존에서 해당 프로그램을 상품으로 판매하게 되면서 갈등이 생겼고, 결국 특정 버전을 기점으로 ElasticSearch 와 OpenSearch로 나뉘게 되었다. 개인적인 생각으로는 기업의 가치가 매우 거대해지면 사용하는 오픈소스 프로그램에는 어느정도 보상을 해 주는것이 맞다고 생각은 한다. 하지만 그게 도덕상의 문제지, 법적으로 문제는 되지 않아서 사용해도 문제는 없다. 특히나 이윤을 추구하는 기업이라면 더더욱 기여를 하지 않을 확률이 높다. 만약 이대로 가면은 오픈소스 프로그램이 옛말이 되지 않을까 무섭다는 생각도 든다.&lt;/p&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도 하지 않는 사람들이 있기도 하다. 아무리 가상 공간에서 있다고 한들, 최소한에 &lt;b&gt;상호간에 존중하고 배려하는 모습&lt;/b&gt;은 갖추어야 하는 것이 개발자로서 필요한 0순위라고 생각한다.&lt;/p&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;</description>
      <category>IT  이야기</category>
      <author>트리맨스</author>
      <guid isPermaLink="true">https://tre2man.tistory.com/347</guid>
      <comments>https://tre2man.tistory.com/347#entry347comment</comments>
      <pubDate>Tue, 21 Feb 2023 23:31:42 +0900</pubDate>
    </item>
    <item>
      <title>local에서 https로 개발하기</title>
      <link>https://tre2man.tistory.com/346</link>
      <description>&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;h3 data-ke-size=&quot;size23&quot;&gt;개요&lt;/h3&gt;
&lt;hr contenteditable=&quot;false&quot; data-ke-type=&quot;horizontalRule&quot; data-ke-style=&quot;style5&quot; /&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;로컬에서 개발할때는 주소가 localhost:xxxx 형식으로 주어진다. 하지만 해당 주소는 SSL인증서가 없기에 기본적으로 http 프로토콜로 통신을 하게 된다. 이렇게 개발을 하게 되면 HTTPS, SSL, TLS, cookie 등의 기술을 테스트하기에 어려움이 있었다. 그래서 로컬에서 HTTPS 개발을 할 수 있게 하는 도구들을 찾아보던 중, 좋은 도구를 발견했다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;h3 data-ke-size=&quot;size23&quot;&gt;MKCert&lt;/h3&gt;
&lt;hr contenteditable=&quot;false&quot; data-ke-type=&quot;horizontalRule&quot; data-ke-style=&quot;style5&quot; /&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;일단 HTTPS를 사용하기 위해서는 인증 기관에서 인증된 TLS 인증서가 필요하다. 여기서 서명된 인증서를 생성하고, 애플리케이션에 적용시켜 실행을 하면 HTTPS로 실행이 된다. 해당 인증서는 mkcert를 사용해서 만들 것이다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;a href=&quot;https://github.com/FiloSottile/mkcert&quot; target=&quot;_blank&quot; rel=&quot;noopener&quot;&gt;https://github.com/FiloSottile/mkcert&lt;/a&gt;&lt;/p&gt;
&lt;figure id=&quot;og_1676034277953&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 - FiloSottile/mkcert: A simple zero-config tool to make locally trusted development certificates with any names you'd lik&quot; data-og-description=&quot;A simple zero-config tool to make locally trusted development certificates with any names you'd like. - GitHub - FiloSottile/mkcert: A simple zero-config tool to make locally trusted developmen...&quot; data-og-host=&quot;github.com&quot; data-og-source-url=&quot;https://github.com/FiloSottile/mkcert&quot; data-og-url=&quot;https://github.com/FiloSottile/mkcert&quot; data-og-image=&quot;https://scrap.kakaocdn.net/dn/dSIGgo/hyRAhHNBWc/vDtOzq2kQcOMVbYdC04gLk/img.png?width=1690&amp;amp;height=962&amp;amp;face=0_0_1690_962&quot;&gt;&lt;a href=&quot;https://github.com/FiloSottile/mkcert&quot; target=&quot;_blank&quot; rel=&quot;noopener&quot; data-source-url=&quot;https://github.com/FiloSottile/mkcert&quot;&gt;
&lt;div class=&quot;og-image&quot; style=&quot;background-image: url('https://scrap.kakaocdn.net/dn/dSIGgo/hyRAhHNBWc/vDtOzq2kQcOMVbYdC04gLk/img.png?width=1690&amp;amp;height=962&amp;amp;face=0_0_1690_962');&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 - FiloSottile/mkcert: A simple zero-config tool to make locally trusted development certificates with any names you'd lik&lt;/p&gt;
&lt;p class=&quot;og-desc&quot; data-ke-size=&quot;size16&quot;&gt;A simple zero-config tool to make locally trusted development certificates with any names you'd like. - GitHub - FiloSottile/mkcert: A simple zero-config tool to make locally trusted developmen...&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;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;여기서 주의할 점은, 해당 작업을 완료한 이후에 만들어지는 &lt;span style=&quot;color: #ee2323;&quot;&gt;&lt;b&gt;pem 파일은 절대로 외부에 공유해서는 안된다!&lt;/b&gt;&lt;/span&gt; 악의적인 공격에 쉽게 노출이 될 수 있다. 과정은 다음과 같이 실행하면 된다.&lt;/p&gt;
&lt;pre id=&quot;code_1676038900694&quot; class=&quot;bash&quot; data-ke-language=&quot;bash&quot; data-ke-type=&quot;codeblock&quot;&gt;&lt;code&gt;# mkcert 설치
brew install mkcert
# 인증서 파일 설치
mkcert -install
# 프로그램 실행
mkcert localhost&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;로컬에서의 준비는 다 끝났고, 이제 애플리케이션에서 HTTPS를 사용하기 위한 준비를 해야 한다. NestJS의 경우에는 다음과 같이 설정한다. 나머지는 신경쓰지 말고, httpOptions 객체 부분의 코드를 보면 된다.&lt;/p&gt;
&lt;pre id=&quot;code_1676039150275&quot; class=&quot;javascript&quot; data-ke-language=&quot;javascript&quot; data-ke-type=&quot;codeblock&quot;&gt;&lt;code&gt;// main.ts
import { ValidationPipe } from '@nestjs/common';
import { NestFactory } from '@nestjs/core';
import * as cookieParser from 'cookie-parser';
import { AppModule } from './app.module';
import { ErrorExceptionFiter } from './middleware/logger.middleware';
import * as fs from 'fs';
import { homedir } from 'os';

const PORT = process.env.PORT || 4000;
const env = process.env.NODE_ENV;

async function bootstrap() {
  # https에 사용할 인증서를 직접 불러올 수 있다.
  const httpsOptions =
    env === 'dev'
      ? {
          key: fs.readFileSync(`${homedir}/localhost-key.pem`),
          cert: fs.readFileSync(`${homedir}/localhost.pem`),
        }
      : {};

  const app = await NestFactory.create(AppModule, {
    bodyParser: true,
    httpsOptions,
  });
  app.enableCors({
    origin: ['https://localhost:3000'],
    credentials: true,
  });
  app.useGlobalPipes(new ValidationPipe());
  app.useGlobalFilters(new ErrorExceptionFiter());
  app.use(cookieParser());
  await app.listen(PORT);
  console.log(`  Application is running on: ${PORT}`);
}
bootstrap();&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;Create-React-App 부분에서는 다음과 같이 환경변수를 설정하면 알아서 HTTPS로 실행이 된다. start:dev의 스크립트를 보면 된다.&lt;/p&gt;
&lt;pre id=&quot;code_1676039234210&quot; class=&quot;javascript&quot; data-ke-language=&quot;javascript&quot; data-ke-type=&quot;codeblock&quot;&gt;&lt;code&gt;// package.json
...
  &quot;scripts&quot;: {
    &quot;start&quot;: &quot;REACT_APP_NODE_ENV=prod react-scripts start&quot;,
    &quot;start:dev&quot;: &quot;HTTPS=true SSL_CRT_FILE=~/localhost.pem SSL_KEY_FILE=~/localhost-key.pem REACT_APP_NODE_ENV=dev react-scripts start&quot;,
    &quot;build&quot;: &quot;react-scripts build&quot;,
    &quot;test&quot;: &quot;react-scripts test&quot;,
    &quot;eject&quot;: &quot;react-scripts eject&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;위에 있는 방법으로 local에서도 HTTPS를 사용해 개발할 수 있다!&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;h3 data-ke-size=&quot;size23&quot;&gt;참고자료&lt;/h3&gt;
&lt;hr contenteditable=&quot;false&quot; data-ke-type=&quot;horizontalRule&quot; data-ke-style=&quot;style5&quot; /&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;a href=&quot;https://web.dev/i18n/ko/how-to-use-local-https/&quot; target=&quot;_blank&quot; rel=&quot;noopener&quot;&gt;https://web.dev/i18n/ko/how-to-use-local-https/&lt;/a&gt;&lt;/p&gt;
&lt;figure id=&quot;og_1676039335738&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;로컬 개발에 HTTPS를 사용하는 방법&quot; data-og-description=&quot; &quot; data-og-host=&quot;web.dev&quot; data-og-source-url=&quot;https://web.dev/i18n/ko/how-to-use-local-https/&quot; data-og-url=&quot;https://web.dev/i18n/ko/how-to-use-local-https/&quot; data-og-image=&quot;https://scrap.kakaocdn.net/dn/dmDRlB/hyRAoUvANQ/dgmcPkbEKGYJP5LgJt9Rs0/img.jpg?width=376&amp;amp;height=240&amp;amp;face=0_0_376_240,https://scrap.kakaocdn.net/dn/cUKZE3/hyRzsEdv0l/JdlcTqvuvUnxIHOCuDapc0/img.jpg?width=376&amp;amp;height=240&amp;amp;face=0_0_376_240,https://scrap.kakaocdn.net/dn/fwXBD/hyRzC08C0f/cza7BdfSQGkXAy5KMcSjm1/img.jpg?width=3200&amp;amp;height=960&amp;amp;face=0_0_3200_960&quot;&gt;&lt;a href=&quot;https://web.dev/i18n/ko/how-to-use-local-https/&quot; target=&quot;_blank&quot; rel=&quot;noopener&quot; data-source-url=&quot;https://web.dev/i18n/ko/how-to-use-local-https/&quot;&gt;
&lt;div class=&quot;og-image&quot; style=&quot;background-image: url('https://scrap.kakaocdn.net/dn/dmDRlB/hyRAoUvANQ/dgmcPkbEKGYJP5LgJt9Rs0/img.jpg?width=376&amp;amp;height=240&amp;amp;face=0_0_376_240,https://scrap.kakaocdn.net/dn/cUKZE3/hyRzsEdv0l/JdlcTqvuvUnxIHOCuDapc0/img.jpg?width=376&amp;amp;height=240&amp;amp;face=0_0_376_240,https://scrap.kakaocdn.net/dn/fwXBD/hyRzC08C0f/cza7BdfSQGkXAy5KMcSjm1/img.jpg?width=3200&amp;amp;height=960&amp;amp;face=0_0_3200_960');&quot;&gt;&amp;nbsp;&lt;/div&gt;
&lt;div class=&quot;og-text&quot;&gt;
&lt;p class=&quot;og-title&quot; data-ke-size=&quot;size16&quot;&gt;로컬 개발에 HTTPS를 사용하는 방법&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;web.dev&lt;/p&gt;
&lt;/div&gt;
&lt;/a&gt;&lt;/figure&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;</description>
      <category>IT  이야기</category>
      <author>트리맨스</author>
      <guid isPermaLink="true">https://tre2man.tistory.com/346</guid>
      <comments>https://tre2man.tistory.com/346#entry346comment</comments>
      <pubDate>Fri, 10 Feb 2023 23:29:03 +0900</pubDate>
    </item>
    <item>
      <title>ts-jest 설치 안되는 에러 해결</title>
      <link>https://tre2man.tistory.com/345</link>
      <description>&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;h3 data-ke-size=&quot;size23&quot;&gt;개요&lt;/h3&gt;
&lt;hr contenteditable=&quot;false&quot; data-ke-type=&quot;horizontalRule&quot; data-ke-style=&quot;style5&quot; /&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;오늘 ts-jest 라이브러리를 새로 받고 있던 도중, 진행이 안되는 것을 확인. 여기저기 검색해도 답이 잘 보이지 않았다. 하지만 여러가지 상태를 종합해본 결과, kt 통신망을 사용할 때 ts-jest가 다운로드가 안 되는 것을 발견. 아래는 해당 버그와 관련된 이슈이다. 현재 2023년 2월 1일 기준으로도 다운로드가 안된다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;a href=&quot;https://github.com/kulshekhar/ts-jest/issues/3992&quot; target=&quot;_blank&quot; rel=&quot;noopener&quot;&gt;https://github.com/kulshekhar/ts-jest/issues/3992&lt;/a&gt;&lt;/p&gt;
&lt;figure id=&quot;og_1675254625515&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;[Bug]: npm install hang &amp;middot; Issue #3992 &amp;middot; kulshekhar/ts-jest&quot; data-og-description=&quot;Version 27.1.4 Steps to reproduce init new project cd ~ mkdir ts-jest-hang-test cd ts-jest-hang-test npm init -y install ts-jest npm i -D ts-jest Expected behavior install successfully Actual behav...&quot; data-og-host=&quot;github.com&quot; data-og-source-url=&quot;https://github.com/kulshekhar/ts-jest/issues/3992&quot; data-og-url=&quot;https://github.com/kulshekhar/ts-jest/issues/3992&quot; data-og-image=&quot;https://scrap.kakaocdn.net/dn/bYgcbL/hyRspuo2S5/ixeWOnv6ZxkjgotSWTmaE1/img.png?width=1200&amp;amp;height=600&amp;amp;face=0_0_1200_600&quot;&gt;&lt;a href=&quot;https://github.com/kulshekhar/ts-jest/issues/3992&quot; target=&quot;_blank&quot; rel=&quot;noopener&quot; data-source-url=&quot;https://github.com/kulshekhar/ts-jest/issues/3992&quot;&gt;
&lt;div class=&quot;og-image&quot; style=&quot;background-image: url('https://scrap.kakaocdn.net/dn/bYgcbL/hyRspuo2S5/ixeWOnv6ZxkjgotSWTmaE1/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;[Bug]: npm install hang &amp;middot; Issue #3992 &amp;middot; kulshekhar/ts-jest&lt;/p&gt;
&lt;p class=&quot;og-desc&quot; data-ke-size=&quot;size16&quot;&gt;Version 27.1.4 Steps to reproduce init new project cd ~ mkdir ts-jest-hang-test cd ts-jest-hang-test npm init -y install ts-jest npm i -D ts-jest Expected behavior install successfully Actual behav...&lt;/p&gt;
&lt;p class=&quot;og-host&quot; data-ke-size=&quot;size16&quot;&gt;github.com&lt;/p&gt;
&lt;/div&gt;
&lt;/a&gt;&lt;/figure&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;h3 data-ke-size=&quot;size23&quot;&gt;해결책&lt;/h3&gt;
&lt;hr contenteditable=&quot;false&quot; data-ke-type=&quot;horizontalRule&quot; data-ke-style=&quot;style5&quot; /&gt;
&lt;ol style=&quot;list-style-type: decimal;&quot; data-ke-list-type=&quot;decimal&quot;&gt;
&lt;li&gt;kt가 아닌 다른 ISP의 망으로 개발을 한다.&lt;/li&gt;
&lt;li&gt;npm에 저장된 서버를 미러 서버로 바꿔준다.&lt;/li&gt;
&lt;/ol&gt;
&lt;pre id=&quot;code_1675254744010&quot; class=&quot;shell&quot; data-ke-language=&quot;shell&quot; data-ke-type=&quot;codeblock&quot;&gt;&lt;code&gt;# 미러 서버로 변경
npm config set registry https://registry.npmjs.cf/
# 기존 서버로 변경
npm config set registry https://registry.npmjs.org/&lt;/code&gt;&lt;/pre&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;h3 data-ke-size=&quot;size23&quot;&gt;참고자료&lt;/h3&gt;
&lt;hr contenteditable=&quot;false&quot; data-ke-type=&quot;horizontalRule&quot; data-ke-style=&quot;style5&quot; /&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;a href=&quot;https://velog.io/@librarian/ts-jest-%EC%84%A4%EC%B9%98-%EC%95%88%EB%90%98%EB%8A%94-%ED%98%84%EC%83%81&quot; target=&quot;_blank&quot; rel=&quot;noopener&quot;&gt;https://velog.io/@librarian/ts-jest-%EC%84%A4%EC%B9%98-%EC%95%88%EB%90%98%EB%8A%94-%ED%98%84%EC%83%81&lt;/a&gt;&lt;/p&gt;
&lt;figure id=&quot;og_1675254771697&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;ts-jest 설치 안되는 현상&quot; data-og-description=&quot;일단 이렇게 패키지 설치가 안될경우에 패키지의 문제인지 아닌지를 확인하기위해서 스택오버플로우에 검색을 해봤지만 외국인들은 설치가 잘만 되는것 같았다그럼 집에있는 네트워크가 문제&quot; data-og-host=&quot;velog.io&quot; data-og-source-url=&quot;https://velog.io/@librarian/ts-jest-%EC%84%A4%EC%B9%98-%EC%95%88%EB%90%98%EB%8A%94-%ED%98%84%EC%83%81&quot; data-og-url=&quot;https://velog.io/@librarian/ts-jest-설치-안되는-현상&quot; data-og-image=&quot;https://scrap.kakaocdn.net/dn/o9Xak/hyRsk7FXoW/LFHmZbu577snhOknZKsvmk/img.png?width=950&amp;amp;height=500&amp;amp;face=0_0_950_500&quot;&gt;&lt;a href=&quot;https://velog.io/@librarian/ts-jest-%EC%84%A4%EC%B9%98-%EC%95%88%EB%90%98%EB%8A%94-%ED%98%84%EC%83%81&quot; target=&quot;_blank&quot; rel=&quot;noopener&quot; data-source-url=&quot;https://velog.io/@librarian/ts-jest-%EC%84%A4%EC%B9%98-%EC%95%88%EB%90%98%EB%8A%94-%ED%98%84%EC%83%81&quot;&gt;
&lt;div class=&quot;og-image&quot; style=&quot;background-image: url('https://scrap.kakaocdn.net/dn/o9Xak/hyRsk7FXoW/LFHmZbu577snhOknZKsvmk/img.png?width=950&amp;amp;height=500&amp;amp;face=0_0_950_500');&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;ts-jest 설치 안되는 현상&lt;/p&gt;
&lt;p class=&quot;og-desc&quot; data-ke-size=&quot;size16&quot;&gt;일단 이렇게 패키지 설치가 안될경우에 패키지의 문제인지 아닌지를 확인하기위해서 스택오버플로우에 검색을 해봤지만 외국인들은 설치가 잘만 되는것 같았다그럼 집에있는 네트워크가 문제&lt;/p&gt;
&lt;p class=&quot;og-host&quot; data-ke-size=&quot;size16&quot;&gt;velog.io&lt;/p&gt;
&lt;/div&gt;
&lt;/a&gt;&lt;/figure&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;</description>
      <category>IT  이야기</category>
      <author>트리맨스</author>
      <guid isPermaLink="true">https://tre2man.tistory.com/345</guid>
      <comments>https://tre2man.tistory.com/345#entry345comment</comments>
      <pubDate>Wed, 1 Feb 2023 21:33:10 +0900</pubDate>
    </item>
    <item>
      <title>Bandit Level 6 해설</title>
      <link>https://tre2man.tistory.com/341</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;❗️모든 문제는 Mac 기준으로 풀이합니다❗️&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;Bandit Level 6 문제를 풀어 보자. 문제를 요약해 보자.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;h2 data-ke-size=&quot;size26&quot;&gt;문제&lt;/h2&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;/li&gt;
&lt;li&gt;1033 바이트의 크기&lt;/li&gt;
&lt;li&gt;실행 불가능한 파일이다.&lt;/li&gt;
&lt;/ul&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;h2 data-ke-size=&quot;size26&quot;&gt;해설&lt;/h2&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;find 명령어에 파일 사이즈를 제한할 수 있는 명령어가 있다. 이것으로 1차 분류&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;ls -al을 통해 실행 불가능한 권한을 가진 파일을 찾는다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;h2 data-ke-size=&quot;size26&quot;&gt;정답&lt;/h2&gt;
&lt;pre id=&quot;code_1671890926485&quot; class=&quot;shell&quot; data-ke-language=&quot;shell&quot; data-ke-type=&quot;codeblock&quot;&gt;&lt;code&gt;find ./* -size +1032c -size -1034c
ls -al&lt;/code&gt;&lt;/pre&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;h2 data-ke-size=&quot;size26&quot;&gt;참고자료&lt;/h2&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;span&gt;&lt;/span&gt;&lt;a href=&quot;https://coding-factory.tistory.com/804&quot;&gt;https://coding-factory.tistory.com/804&lt;/a&gt;&lt;/p&gt;
&lt;figure id=&quot;og_1671892335887&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;[Linux] 리눅스 find 명령어 사용법 (파일 찾기, 검색)&quot; data-og-description=&quot;find 명령어 리눅스의 find 명령어는 리눅스 파일 시스템에서 파일을 검색하는 데 사용되는 명령어입니다. 다양한 표현식을 사용하여 원하는 파일의 목록을 추출할 수 있습니다. 리눅스 find 사용&quot; data-og-host=&quot;coding-factory.tistory.com&quot; data-og-source-url=&quot;https://coding-factory.tistory.com/804&quot; data-og-url=&quot;https://coding-factory.tistory.com/804&quot; data-og-image=&quot;https://scrap.kakaocdn.net/dn/1YEmK/hyQ1eyoNl7/7y8oZnCGrwkHk7wSGtyizk/img.png?width=800&amp;amp;height=450&amp;amp;face=0_0_800_450,https://scrap.kakaocdn.net/dn/ctqV9k/hyQ1rdrLyU/3UECJOuMy99CzFkhu5YgeK/img.png?width=800&amp;amp;height=450&amp;amp;face=0_0_800_450,https://scrap.kakaocdn.net/dn/vRurw/hyQ1mJYTJQ/Kf7RXHvfL41PTuMZjkuVBK/img.jpg?width=428&amp;amp;height=428&amp;amp;face=0_0_428_428&quot;&gt;&lt;a href=&quot;https://coding-factory.tistory.com/804&quot; target=&quot;_blank&quot; rel=&quot;noopener&quot; data-source-url=&quot;https://coding-factory.tistory.com/804&quot;&gt;
&lt;div class=&quot;og-image&quot; style=&quot;background-image: url('https://scrap.kakaocdn.net/dn/1YEmK/hyQ1eyoNl7/7y8oZnCGrwkHk7wSGtyizk/img.png?width=800&amp;amp;height=450&amp;amp;face=0_0_800_450,https://scrap.kakaocdn.net/dn/ctqV9k/hyQ1rdrLyU/3UECJOuMy99CzFkhu5YgeK/img.png?width=800&amp;amp;height=450&amp;amp;face=0_0_800_450,https://scrap.kakaocdn.net/dn/vRurw/hyQ1mJYTJQ/Kf7RXHvfL41PTuMZjkuVBK/img.jpg?width=428&amp;amp;height=428&amp;amp;face=0_0_428_428');&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;[Linux] 리눅스 find 명령어 사용법 (파일 찾기, 검색)&lt;/p&gt;
&lt;p class=&quot;og-desc&quot; data-ke-size=&quot;size16&quot;&gt;find 명령어 리눅스의 find 명령어는 리눅스 파일 시스템에서 파일을 검색하는 데 사용되는 명령어입니다. 다양한 표현식을 사용하여 원하는 파일의 목록을 추출할 수 있습니다. 리눅스 find 사용&lt;/p&gt;
&lt;p class=&quot;og-host&quot; data-ke-size=&quot;size16&quot;&gt;coding-factory.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;</description>
      <category>프로그래밍 언어/Shell</category>
      <author>트리맨스</author>
      <guid isPermaLink="true">https://tre2man.tistory.com/341</guid>
      <comments>https://tre2man.tistory.com/341#entry341comment</comments>
      <pubDate>Mon, 2 Jan 2023 11:32:28 +0900</pubDate>
    </item>
    <item>
      <title>2022년 연말 개발 회고록</title>
      <link>https://tre2man.tistory.com/343</link>
      <description>&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;h3 data-ke-size=&quot;size23&quot;&gt;2022년 회고&lt;/h3&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;이전 회고를 쓴지 벌써 1년이 지났다. 개발자로 들어선지 1년이 넘어가고 이것저것 많이 쓰게 당했던 기억들이 있다. 올해 무엇을 했는지 생각해보며 셀프처형을 해보자.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;h3 data-ke-size=&quot;size23&quot;&gt;실무 경험의 장벽&lt;/h3&gt;
&lt;hr contenteditable=&quot;false&quot; data-ke-type=&quot;horizontalRule&quot; data-ke-style=&quot;style5&quot; /&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;스타트업에서 계속 백앤드 개발자로 일하는 동안 다양한 문제를 경험했고, 이를 해결하는 것을 계속 반복했다. 어차피 일을 하게 되는것은 문제를 계속 해결하는 것의 반복이겠지만, 개발자라는 직업 특성상 조금 더 심했던 것 같다. 우당탕탕 스타트업 이라는 말이 딱인 것 같다. 여튼 문제를 해결하기까지는 힘들었으나 문제를 해결하고 나니, 본인과 팀의 지식으로 남게 되어 결과적으로는 좋게 되었다. 아쉬운 것은 너무 맨땅에 헤딩만 한 것 같아서, 이번에도 역시 돌아 간다는 생각이 많이 들었다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;h3 data-ke-size=&quot;size23&quot;&gt;우매함의 봉우리&lt;/h3&gt;
&lt;hr contenteditable=&quot;false&quot; data-ke-type=&quot;horizontalRule&quot; data-ke-style=&quot;style5&quot; /&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&quot;더닝 크루거&quot; 라는 말이 있다. 간단히 말하면, 빈 수레가 요란하다는 것이다. 능력이 부족한 사람들이 자신의 능력을 과대평가하고, 능력이 뛰어난 사람들은 자신의 능력을 과소평가 한다는 말이 있다.&lt;/p&gt;
&lt;p&gt;&lt;figure class=&quot;imageblock alignCenter&quot; data-ke-mobileStyle=&quot;widthOrigin&quot; data-filename=&quot;123354.png&quot; data-origin-width=&quot;721&quot; data-origin-height=&quot;600&quot;&gt;&lt;span data-url=&quot;https://blog.kakaocdn.net/dn/NiqjM/btrU15ZWczo/fKjE2pEPZckwy7OU9lyOOk/img.png&quot; data-phocus=&quot;https://blog.kakaocdn.net/dn/NiqjM/btrU15ZWczo/fKjE2pEPZckwy7OU9lyOOk/img.png&quot; data-alt=&quot;https://commons.wikimedia.org/wiki/File:Dunning-Kruger-Effect-en.png&quot;&gt;&lt;img src=&quot;https://blog.kakaocdn.net/dn/NiqjM/btrU15ZWczo/fKjE2pEPZckwy7OU9lyOOk/img.png&quot; srcset=&quot;https://img1.daumcdn.net/thumb/R1280x0/?scode=mtistory2&amp;fname=https%3A%2F%2Fblog.kakaocdn.net%2Fdn%2FNiqjM%2FbtrU15ZWczo%2FfKjE2pEPZckwy7OU9lyOOk%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;534&quot; height=&quot;444&quot; data-filename=&quot;123354.png&quot; data-origin-width=&quot;721&quot; data-origin-height=&quot;600&quot;/&gt;&lt;/span&gt;&lt;figcaption&gt;https://commons.wikimedia.org/wiki/File:Dunning-Kruger-Effect-en.png&lt;/figcaption&gt;
&lt;/figure&gt;
&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;올해 초에 아마도 위쪽 그래프에서 왼쪽의 솟아오른 봉우리 어딘가에 있던 것 같다. 하지만 계속 문제를 경험할수록, 점점 심해로 빠지는 것이다. 심해로 빠진 이후로는 슬럼프가 급격히 찾아왔다. 다행히도 시간이 지나며 그 슬럼프는 사라졌지만, 나의 부족함을 확실하게 알게 되는 계기가 되었다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;h3 data-ke-size=&quot;size23&quot;&gt;이직 준비&lt;/h3&gt;
&lt;hr contenteditable=&quot;false&quot; data-ke-type=&quot;horizontalRule&quot; data-ke-style=&quot;style5&quot; /&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;이직을 준비하면서, 확실히 개발을 보는 눈이 넓어졌다고 생각이 된다. 처음에는 몇몇 회사의 코딩테스트를 지원했는데, 생각보다 할만하다는 생각이 들어서 이직을 준비하게 되었다. 하지만 계속 준비할수록 탈락 통보는 쌓여갔고, 점점 지쳐갔다. 하지만 여기서도 얻어가는 것이 많았다. 타 회사에서 면접 또는 코딩테스트를 할 때 중요하게 보는 것들, 회사의 가치관, 인재상 등등 여러가지 항목에서 원하는 것들이 다르고, 그것을 이루기 위해서 어떠한 노력을 해야 하는지 알았다.&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;뭐라고 정의하기는 어렵지만, 최근에는 원하는 인재상은 공통적으로 &lt;b&gt;문제 해결을 하는 방법&lt;/b&gt;에 초점을 맞춘 것 같았다.&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;h3 data-ke-size=&quot;size23&quot;&gt;결론&lt;/h3&gt;
&lt;hr contenteditable=&quot;false&quot; data-ke-type=&quot;horizontalRule&quot; data-ke-style=&quot;style5&quot; /&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;올해는 개발자로서 실무를 해보고, 새로운 기술에 도전하면서 문제를 해결하는 일들의 연속이였다. 정리를 할려고 해도 똑같은 얘기의 반복이라서 사실 정리할 것도 별로 없는 것 같다. 아니면 오히려 작년에 너무 다이나믹하게 지내서 상대적으로 생각할 게 별로 없을 것 같기도 하다.&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;내년에는 조금 더 넓은 시야를 가지고 계속 성장을 하는 해가 되었으면 좋겠다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;</description>
      <category>잡다한 이야기</category>
      <author>트리맨스</author>
      <guid isPermaLink="true">https://tre2man.tistory.com/343</guid>
      <comments>https://tre2man.tistory.com/343#entry343comment</comments>
      <pubDate>Sat, 31 Dec 2022 22:08:18 +0900</pubDate>
    </item>
    <item>
      <title>Programmers 합승 택시 요금</title>
      <link>https://tre2man.tistory.com/342</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;a href=&quot;https://school.programmers.co.kr/learn/courses/30/lessons/72413&quot; target=&quot;_blank&quot; rel=&quot;noopener&quot;&gt;https://school.programmers.co.kr/learn/courses/30/lessons/72413&lt;/a&gt;&lt;/p&gt;
&lt;figure id=&quot;og_1672463288088&quot; contenteditable=&quot;false&quot; data-ke-type=&quot;opengraph&quot; data-ke-align=&quot;alignCenter&quot; data-og-type=&quot;website&quot; data-og-title=&quot;프로그래머스&quot; data-og-description=&quot;코드 중심의 개발자 채용. 스택 기반의 포지션 매칭. 프로그래머스의 개발자 맞춤형 프로필을 등록하고, 나와 기술 궁합이 잘 맞는 기업들을 매칭 받으세요.&quot; data-og-host=&quot;programmers.co.kr&quot; data-og-source-url=&quot;https://school.programmers.co.kr/learn/courses/30/lessons/72413&quot; data-og-url=&quot;https://programmers.co.kr/&quot; data-og-image=&quot;https://scrap.kakaocdn.net/dn/QdCs1/hyQ6WxxzXf/r22tpb5fNYKoFeChtSkWR1/img.png?width=1200&amp;amp;height=630&amp;amp;face=0_0_1200_630,https://scrap.kakaocdn.net/dn/xNo6d/hyQ6Ofco8x/6HkiKDKGyjKSfVEI6bAdqK/img.png?width=1200&amp;amp;height=630&amp;amp;face=0_0_1200_630&quot;&gt;&lt;a href=&quot;https://school.programmers.co.kr/learn/courses/30/lessons/72413&quot; target=&quot;_blank&quot; rel=&quot;noopener&quot; data-source-url=&quot;https://school.programmers.co.kr/learn/courses/30/lessons/72413&quot;&gt;
&lt;div class=&quot;og-image&quot; style=&quot;background-image: url('https://scrap.kakaocdn.net/dn/QdCs1/hyQ6WxxzXf/r22tpb5fNYKoFeChtSkWR1/img.png?width=1200&amp;amp;height=630&amp;amp;face=0_0_1200_630,https://scrap.kakaocdn.net/dn/xNo6d/hyQ6Ofco8x/6HkiKDKGyjKSfVEI6bAdqK/img.png?width=1200&amp;amp;height=630&amp;amp;face=0_0_1200_630');&quot;&gt;&amp;nbsp;&lt;/div&gt;
&lt;div class=&quot;og-text&quot;&gt;
&lt;p class=&quot;og-title&quot; data-ke-size=&quot;size16&quot;&gt;프로그래머스&lt;/p&gt;
&lt;p class=&quot;og-desc&quot; data-ke-size=&quot;size16&quot;&gt;코드 중심의 개발자 채용. 스택 기반의 포지션 매칭. 프로그래머스의 개발자 맞춤형 프로필을 등록하고, 나와 기술 궁합이 잘 맞는 기업들을 매칭 받으세요.&lt;/p&gt;
&lt;p class=&quot;og-host&quot; data-ke-size=&quot;size16&quot;&gt;programmers.co.kr&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;h3 data-ke-size=&quot;size23&quot;&gt;플로이드-와샬&lt;/h3&gt;
&lt;hr contenteditable=&quot;false&quot; data-ke-type=&quot;horizontalRule&quot; data-ke-style=&quot;style5&quot; /&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;임의의 양의 가중치가 있는 graph의 노드 s에서 e까지 가는 길에 m이라는 경유지가 있다고 할 때, 해당 경로의 최단거리를 구하는 알고리즘이다. 다익스트라 알고리즘은 노드 s에서 e까지 가는 길의 최소값만 구하는 알고리즘이다. 다익스트라는 모든 경로를 하나씩 확인하면서 최소값에 대해서 계속 탐색하는 알고리즘이라면, 플로이드-와샬은 시작 노드와 마지막 노드의 최소 거리만을 사용하여 결과를 낸다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;플로이드-와샬의 시간복잡도는 o(n^3) 이므로 계산이 오래 걸리는 편이다. 문제를 풀이할 때 주어진 조건을 확인하여 시간복잡도를 잘 확인해야 한다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;h3 data-ke-size=&quot;size23&quot;&gt;구현&lt;/h3&gt;
&lt;hr contenteditable=&quot;false&quot; data-ke-type=&quot;horizontalRule&quot; data-ke-style=&quot;style5&quot; /&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;플로이드-와샬의 구현은 생각보다 쉽다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;예를 들어 s부터 e까지 가는 최단거리를 d[s][e] 라고 하자. 둘이 직결되어 있으면 d[s][e]에 바로 입력하고, 직결되어 있지 않으면 Infinity 값을 입력하고, 본인에 대한 가중치는 0으로 입력한다. 그 이후 d[s][e] &amp;gt; d[s][m] + d[m][e] 인 경우를 만족한다면, d[s][e] = d[s][m] + d[m][e] 를 실행한다. 직결이 되어 있지 않는 노드의 기본값은 Infinity이지만, 해당 경로가 m을 경유해서 연결이 가능하다면 최소값으로 치환한다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;이 문제는 모든 노드간 최소 거리를 알면 d[s][m] + d[m][a] + d[m][b] 의 최소 거리를 쉽게 구할 수 있다. m에 모든 노드를 대입하여 최소값을 찾으면 된다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;pre id=&quot;code_1672464335406&quot; class=&quot;javascript&quot; data-ke-language=&quot;javascript&quot; data-ke-type=&quot;codeblock&quot;&gt;&lt;code&gt;/**
 *
 * @param {number} n
 * @param {number} s
 * @param {number} a
 * @param {number} b
 * @param {number[][]} fares
 * @returns {number}
 */
function solution(n, s, a, b, fares) {
  // 전체 노드에 대한 그래프, graph[a][b] 는 a에서 b까지 가는 최단경로라고 생각하자.
  const graph = Array.from({ length: n + 1 }, () =&amp;gt;
    Array.from({ length: n + 1 }).fill(Infinity)
  );
  fares.forEach(([a, b, c]) =&amp;gt; {
    // 그래프 제작
    graph[a][b] = c;
    graph[b][a] = c;
  });
  // 본인에 대한 최단경로는 0
  for (let i = 0; i &amp;lt;= n; i++) {
    graph[i][i] = 0;
  }
  // k는 경유, i는 시작, j는 도착
  for (let k = 1; k &amp;lt;= n; k++) {
    for (let i = 1; i &amp;lt;= n; i++) {
      for (let j = 1; j &amp;lt;= n; j++) {
        if (graph[i][j] &amp;gt; graph[i][k] + graph[k][j])
          graph[i][j] = graph[i][k] + graph[k][j];
      }
    }
  }
  let answer = Infinity;
  for (let i = 1; i &amp;lt;= n; i++) {
    // 최종적으로 우리의 목표는 s-m + m-a + m-b의 최솟값을 구하는 것이다.
    const shortest = graph[s][i] + graph[i][a] + graph[i][b];
    answer = Math.min(answer, shortest);
  }
  return answer;
}&lt;/code&gt;&lt;/pre&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;</description>
      <category>개발 지식/알고리즘</category>
      <author>트리맨스</author>
      <guid isPermaLink="true">https://tre2man.tistory.com/342</guid>
      <comments>https://tre2man.tistory.com/342#entry342comment</comments>
      <pubDate>Sat, 31 Dec 2022 14:27:12 +0900</pubDate>
    </item>
    <item>
      <title>Bandit Level 5 해설</title>
      <link>https://tre2man.tistory.com/340</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;❗️모든 문제는 Mac 기준으로 풀이합니다❗️&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;Bandit Level 5 문제를 풀어 보자. 문제를 요약해 보자.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;h2 data-ke-size=&quot;size26&quot;&gt;문제&lt;/h2&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;h2 data-ke-size=&quot;size26&quot;&gt;해설&lt;/h2&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;h2 data-ke-size=&quot;size26&quot;&gt;정답&lt;/h2&gt;
&lt;pre id=&quot;code_1671890926485&quot; class=&quot;shell&quot; data-ke-language=&quot;shell&quot; data-ke-type=&quot;codeblock&quot;&gt;&lt;code&gt;file ./-file*
cat ./-file07&lt;/code&gt;&lt;/pre&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;h2 data-ke-size=&quot;size26&quot;&gt;참고자료&lt;/h2&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;a href=&quot;https://linux.die.net/man/1/file&quot; target=&quot;_blank&quot; rel=&quot;noopener&quot;&gt;https://linux.die.net/man/1/file&lt;/a&gt;&lt;/p&gt;
&lt;figure id=&quot;og_1671892093548&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;file(1): determine file type - Linux man page&quot; data-og-description=&quot;file(1) - Linux man page Name file - determine file type Synopsis file [-bchikLNnprsvz0] [--apple] [--mime-encoding] [--mime-type] [-e testname] [-F separator] [-f namefile] [-m magicfiles] file ... file -C [-m magicfiles] file [--help] Description This ma&quot; data-og-host=&quot;linux.die.net&quot; data-og-source-url=&quot;https://linux.die.net/man/1/file&quot; data-og-url=&quot;https://linux.die.net/man/1/file&quot; data-og-image=&quot;&quot;&gt;&lt;a href=&quot;https://linux.die.net/man/1/file&quot; target=&quot;_blank&quot; rel=&quot;noopener&quot; data-source-url=&quot;https://linux.die.net/man/1/file&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;file(1): determine file type - Linux man page&lt;/p&gt;
&lt;p class=&quot;og-desc&quot; data-ke-size=&quot;size16&quot;&gt;file(1) - Linux man page Name file - determine file type Synopsis file [-bchikLNnprsvz0] [--apple] [--mime-encoding] [--mime-type] [-e testname] [-F separator] [-f namefile] [-m magicfiles] file ... file -C [-m magicfiles] file [--help] Description This ma&lt;/p&gt;
&lt;p class=&quot;og-host&quot; data-ke-size=&quot;size16&quot;&gt;linux.die.net&lt;/p&gt;
&lt;/div&gt;
&lt;/a&gt;&lt;/figure&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;</description>
      <category>프로그래밍 언어/Shell</category>
      <author>트리맨스</author>
      <guid isPermaLink="true">https://tre2man.tistory.com/340</guid>
      <comments>https://tre2man.tistory.com/340#entry340comment</comments>
      <pubDate>Sat, 31 Dec 2022 13:29:26 +0900</pubDate>
    </item>
    <item>
      <title>Bandit Level 4 해설</title>
      <link>https://tre2man.tistory.com/339</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;❗️모든 문제는 Mac 기준으로 풀이합니다❗️&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;Bandit Level 4 문제를 풀어 보자. 문제를 요약해 보자.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;h2 data-ke-size=&quot;size26&quot;&gt;문제&lt;/h2&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;h2 data-ke-size=&quot;size26&quot;&gt;해설&lt;/h2&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;ls -al로 찾으면 된다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;h2 data-ke-size=&quot;size26&quot;&gt;정답&lt;/h2&gt;
&lt;pre id=&quot;code_1671890926485&quot; class=&quot;shell&quot; data-ke-language=&quot;shell&quot; data-ke-type=&quot;codeblock&quot;&gt;&lt;code&gt;cd .inhere
ls -al
cat .hidden&lt;/code&gt;&lt;/pre&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;h2 data-ke-size=&quot;size26&quot;&gt;참고자료&lt;/h2&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;없음&lt;/p&gt;</description>
      <category>프로그래밍 언어/Shell</category>
      <author>트리맨스</author>
      <guid isPermaLink="true">https://tre2man.tistory.com/339</guid>
      <comments>https://tre2man.tistory.com/339#entry339comment</comments>
      <pubDate>Thu, 29 Dec 2022 15:25:55 +0900</pubDate>
    </item>
    <item>
      <title>Bandit Level 3 해설</title>
      <link>https://tre2man.tistory.com/338</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;❗️모든 문제는 Mac 기준으로 풀이합니다❗️&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;Bandit Level 3 문제를 풀어 보자. 문제를 요약해 보자.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;h2 data-ke-size=&quot;size26&quot;&gt;문제&lt;/h2&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;h2 data-ke-size=&quot;size26&quot;&gt;해설&lt;/h2&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;큰따옴표로 묶어준다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;h2 data-ke-size=&quot;size26&quot;&gt;정답&lt;/h2&gt;
&lt;pre id=&quot;code_1671890926485&quot; class=&quot;shell&quot; data-ke-language=&quot;shell&quot; data-ke-type=&quot;codeblock&quot;&gt;&lt;code&gt;cat &quot;spaces in this filename&quot;&lt;/code&gt;&lt;/pre&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;h2 data-ke-size=&quot;size26&quot;&gt;참고자료&lt;/h2&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;a href=&quot;https://appuals.com/how-to-handle-passing-filenames-with-spaces-in-bash/&quot; target=&quot;_blank&quot; rel=&quot;noopener&quot;&gt;https://appuals.com/how-to-handle-passing-filenames-with-spaces-in-bash/&lt;/a&gt;&lt;/p&gt;
&lt;figure id=&quot;og_1671891826550&quot; contenteditable=&quot;false&quot; data-ke-type=&quot;opengraph&quot; data-ke-align=&quot;alignCenter&quot; data-og-type=&quot;article&quot; data-og-title=&quot;How to Handle Passing Filenames with Spaces in Bash&quot; data-og-description=&quot;Linux has a default shell Bash (aka Bourne again shell) to execute commands for the system. Most programmers prefer bash over cmd because of the&quot; data-og-host=&quot;appuals.com&quot; data-og-source-url=&quot;https://appuals.com/how-to-handle-passing-filenames-with-spaces-in-bash/&quot; data-og-url=&quot;https://appuals.com/how-to-handle-passing-filenames-with-spaces-in-bash/&quot; data-og-image=&quot;https://scrap.kakaocdn.net/dn/iEnTs/hyQ1kk4zwk/6Anad4Aq3wKNPJPScVXYn1/img.png?width=627&amp;amp;height=310&amp;amp;face=0_0_627_310,https://scrap.kakaocdn.net/dn/k5rRi/hyQ1hPqJFc/TCBxcPKJPNcXQuqsjA1bx0/img.png?width=733&amp;amp;height=340&amp;amp;face=0_0_733_340,https://scrap.kakaocdn.net/dn/b60slW/hyQZQTqFhS/Fq2d8NJKWl4DwcS6cjF8gk/img.png?width=734&amp;amp;height=309&amp;amp;face=0_0_734_309&quot;&gt;&lt;a href=&quot;https://appuals.com/how-to-handle-passing-filenames-with-spaces-in-bash/&quot; target=&quot;_blank&quot; rel=&quot;noopener&quot; data-source-url=&quot;https://appuals.com/how-to-handle-passing-filenames-with-spaces-in-bash/&quot;&gt;
&lt;div class=&quot;og-image&quot; style=&quot;background-image: url('https://scrap.kakaocdn.net/dn/iEnTs/hyQ1kk4zwk/6Anad4Aq3wKNPJPScVXYn1/img.png?width=627&amp;amp;height=310&amp;amp;face=0_0_627_310,https://scrap.kakaocdn.net/dn/k5rRi/hyQ1hPqJFc/TCBxcPKJPNcXQuqsjA1bx0/img.png?width=733&amp;amp;height=340&amp;amp;face=0_0_733_340,https://scrap.kakaocdn.net/dn/b60slW/hyQZQTqFhS/Fq2d8NJKWl4DwcS6cjF8gk/img.png?width=734&amp;amp;height=309&amp;amp;face=0_0_734_309');&quot;&gt;&amp;nbsp;&lt;/div&gt;
&lt;div class=&quot;og-text&quot;&gt;
&lt;p class=&quot;og-title&quot; data-ke-size=&quot;size16&quot;&gt;How to Handle Passing Filenames with Spaces in Bash&lt;/p&gt;
&lt;p class=&quot;og-desc&quot; data-ke-size=&quot;size16&quot;&gt;Linux has a default shell Bash (aka Bourne again shell) to execute commands for the system. Most programmers prefer bash over cmd because of the&lt;/p&gt;
&lt;p class=&quot;og-host&quot; data-ke-size=&quot;size16&quot;&gt;appuals.com&lt;/p&gt;
&lt;/div&gt;
&lt;/a&gt;&lt;/figure&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;</description>
      <category>프로그래밍 언어/Shell</category>
      <author>트리맨스</author>
      <guid isPermaLink="true">https://tre2man.tistory.com/338</guid>
      <comments>https://tre2man.tistory.com/338#entry338comment</comments>
      <pubDate>Wed, 28 Dec 2022 10:24:15 +0900</pubDate>
    </item>
    <item>
      <title>Bandit Level 2 해설</title>
      <link>https://tre2man.tistory.com/337</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;❗️모든 문제는 Mac 기준으로 풀이합니다❗️&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;Bandit Level 2 문제를 풀어 보자. 문제를 요약해 보자.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;h2 data-ke-size=&quot;size26&quot;&gt;문제&lt;/h2&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;파일 이름은 &quot;-&quot; 이다. 해당 파일안에 암호가 있다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;h2 data-ke-size=&quot;size26&quot;&gt;해설&lt;/h2&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;대시는 기본 파일 입출력에서 dev/stdin 또는 dev/stdout으로 인식할 수도 있다. 해당 파일을 열기 위해서는 전체 위치까지 지정해 주어야 한다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;h2 data-ke-size=&quot;size26&quot;&gt;정답&lt;/h2&gt;
&lt;pre id=&quot;code_1671890926485&quot; class=&quot;shell&quot; data-ke-language=&quot;shell&quot; data-ke-type=&quot;codeblock&quot;&gt;&lt;code&gt;cat ./-
# 또는 
cat &amp;lt; -&lt;/code&gt;&lt;/pre&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;h2 data-ke-size=&quot;size26&quot;&gt;참고자료&lt;/h2&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;span&gt;&lt;/span&gt;&lt;a href=&quot;https://stackoverflow.com/questions/42187323/how-to-open-a-dashed-filename-using-terminal&quot;&gt;https://stackoverflow.com/questions/42187323/how-to-open-a-dashed-filename-using-terminal&lt;/a&gt;&lt;/p&gt;
&lt;figure id=&quot;og_1671891610250&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;How to open a &amp;quot;-&amp;quot; dashed filename using terminal?&quot; data-og-description=&quot;I tried gedit, nano, vi, leafpad and other text editors , it won't open, I tried cat and other file looking commands, and I ensure you it's a file not a directory!&quot; data-og-host=&quot;stackoverflow.com&quot; data-og-source-url=&quot;https://stackoverflow.com/questions/42187323/how-to-open-a-dashed-filename-using-terminal&quot; data-og-url=&quot;https://stackoverflow.com/questions/42187323/how-to-open-a-dashed-filename-using-terminal&quot; data-og-image=&quot;https://scrap.kakaocdn.net/dn/cgWgE9/hyQZHvqPff/RocDIwadUYYhPT2JjIkWnk/img.png?width=316&amp;amp;height=316&amp;amp;face=0_0_316_316&quot;&gt;&lt;a href=&quot;https://stackoverflow.com/questions/42187323/how-to-open-a-dashed-filename-using-terminal&quot; target=&quot;_blank&quot; rel=&quot;noopener&quot; data-source-url=&quot;https://stackoverflow.com/questions/42187323/how-to-open-a-dashed-filename-using-terminal&quot;&gt;
&lt;div class=&quot;og-image&quot; style=&quot;background-image: url('https://scrap.kakaocdn.net/dn/cgWgE9/hyQZHvqPff/RocDIwadUYYhPT2JjIkWnk/img.png?width=316&amp;amp;height=316&amp;amp;face=0_0_316_316');&quot;&gt;&amp;nbsp;&lt;/div&gt;
&lt;div class=&quot;og-text&quot;&gt;
&lt;p class=&quot;og-title&quot; data-ke-size=&quot;size16&quot;&gt;How to open a &quot;-&quot; dashed filename using terminal?&lt;/p&gt;
&lt;p class=&quot;og-desc&quot; data-ke-size=&quot;size16&quot;&gt;I tried gedit, nano, vi, leafpad and other text editors , it won't open, I tried cat and other file looking commands, and I ensure you it's a file not a directory!&lt;/p&gt;
&lt;p class=&quot;og-host&quot; data-ke-size=&quot;size16&quot;&gt;stackoverflow.com&lt;/p&gt;
&lt;/div&gt;
&lt;/a&gt;&lt;/figure&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;span&gt;&lt;/span&gt;&lt;a href=&quot;https://tldp.org/LDP/abs/html/special-chars.html&quot;&gt;https://tldp.org/LDP/abs/html/special-chars.html&lt;/a&gt;&lt;/p&gt;
&lt;figure id=&quot;og_1671891617032&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;Special Characters&quot; data-og-description=&quot;(cd /source/directory &amp;amp;&amp;amp; tar cf - . ) | (cd /dest/directory &amp;amp;&amp;amp; tar xpvf -) # Move entire file tree from one directory to another # [courtesy Alan Cox , with a minor change] # 1) cd /source/directory # Source directory, where the files to be moved are. # 2)&quot; data-og-host=&quot;tldp.org&quot; data-og-source-url=&quot;https://tldp.org/LDP/abs/html/special-chars.html&quot; data-og-url=&quot;https://tldp.org/LDP/abs/html/special-chars.html&quot; data-og-image=&quot;&quot;&gt;&lt;a href=&quot;https://tldp.org/LDP/abs/html/special-chars.html&quot; target=&quot;_blank&quot; rel=&quot;noopener&quot; data-source-url=&quot;https://tldp.org/LDP/abs/html/special-chars.html&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;Special Characters&lt;/p&gt;
&lt;p class=&quot;og-desc&quot; data-ke-size=&quot;size16&quot;&gt;(cd /source/directory &amp;amp;&amp;amp; tar cf - . ) | (cd /dest/directory &amp;amp;&amp;amp; tar xpvf -) # Move entire file tree from one directory to another # [courtesy Alan Cox , with a minor change] # 1) cd /source/directory # Source directory, where the files to be moved are. # 2)&lt;/p&gt;
&lt;p class=&quot;og-host&quot; data-ke-size=&quot;size16&quot;&gt;tldp.org&lt;/p&gt;
&lt;/div&gt;
&lt;/a&gt;&lt;/figure&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;</description>
      <category>프로그래밍 언어/Shell</category>
      <author>트리맨스</author>
      <guid isPermaLink="true">https://tre2man.tistory.com/337</guid>
      <comments>https://tre2man.tistory.com/337#entry337comment</comments>
      <pubDate>Tue, 27 Dec 2022 10:20:21 +0900</pubDate>
    </item>
    <item>
      <title>Bandit Level 1 해설</title>
      <link>https://tre2man.tistory.com/336</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;❗️모든 문제는 Mac 기준으로 풀이합니다❗️&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;Bandit Level 1 문제를 풀어 보자. 문제를 요약해 보자.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;h2 data-ke-size=&quot;size26&quot;&gt;문제&lt;/h2&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;readme 파일 안에 암호가 있다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;h2 data-ke-size=&quot;size26&quot;&gt;해설&lt;/h2&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;cat 명령어 또는 vi, vim 등의 도구들을 사용하여 파일안의 암호를 알 수 있다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;h2 data-ke-size=&quot;size26&quot;&gt;정답&lt;/h2&gt;
&lt;pre id=&quot;code_1671890926485&quot; class=&quot;shell&quot; data-ke-language=&quot;shell&quot; data-ke-type=&quot;codeblock&quot;&gt;&lt;code&gt;cat readme&lt;/code&gt;&lt;/pre&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;h2 data-ke-size=&quot;size26&quot;&gt;참고자료&lt;/h2&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;없음&lt;/p&gt;</description>
      <category>프로그래밍 언어/Shell</category>
      <author>트리맨스</author>
      <guid isPermaLink="true">https://tre2man.tistory.com/336</guid>
      <comments>https://tre2man.tistory.com/336#entry336comment</comments>
      <pubDate>Mon, 26 Dec 2022 09:18:11 +0900</pubDate>
    </item>
    <item>
      <title>Bandit Level 0 해설</title>
      <link>https://tre2man.tistory.com/335</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;❗️모든 문제는 Mac 기준으로 풀이합니다❗️&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;Bandit Level 0 문제를 풀어 보자. 문제를 요약해 보자.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;h2 data-ke-size=&quot;size26&quot;&gt;문제&lt;/h2&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;해당 호스트에 ssh 로그인 하세요. 포트는 2200번 입니다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;h2 data-ke-size=&quot;size26&quot;&gt;해설&lt;/h2&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;-p 옵션을 사용하면 포트를 지정할 수 있다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;h2 data-ke-size=&quot;size26&quot;&gt;정답&lt;/h2&gt;
&lt;pre id=&quot;code_1671890942148&quot; class=&quot;shell&quot; data-ke-language=&quot;shell&quot; data-ke-type=&quot;codeblock&quot;&gt;&lt;code&gt;ssh bandit0@bandit.labs.overthewire.org -p 2220&lt;/code&gt;&lt;/pre&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;h2 data-ke-size=&quot;size26&quot;&gt;참고자료&lt;/h2&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;a href=&quot;https://linux.die.net/man/1/ssh&quot; target=&quot;_blank&quot; rel=&quot;noopener&quot;&gt;https://linux.die.net/man/1/ssh&lt;/a&gt;&lt;/p&gt;
&lt;figure id=&quot;og_1671891035101&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;ssh(1): OpenSSH SSH client - Linux man page&quot; data-og-description=&quot;ssh(1) - Linux man page Name ssh - OpenSSH SSH client (remote login program) Synopsis ssh [-1246AaCfgKkMNnqsTtVvXxYy] [-b bind_address] [-c cipher_spec] [ -D[ bind_address:]port] [-e escape_char] [-F configfile][-i identity_file] [ -L [ bind_address:]port:&quot; data-og-host=&quot;linux.die.net&quot; data-og-source-url=&quot;https://linux.die.net/man/1/ssh&quot; data-og-url=&quot;https://linux.die.net/man/1/ssh&quot; data-og-image=&quot;&quot;&gt;&lt;a href=&quot;https://linux.die.net/man/1/ssh&quot; target=&quot;_blank&quot; rel=&quot;noopener&quot; data-source-url=&quot;https://linux.die.net/man/1/ssh&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;ssh(1): OpenSSH SSH client - Linux man page&lt;/p&gt;
&lt;p class=&quot;og-desc&quot; data-ke-size=&quot;size16&quot;&gt;ssh(1) - Linux man page Name ssh - OpenSSH SSH client (remote login program) Synopsis ssh [-1246AaCfgKkMNnqsTtVvXxYy] [-b bind_address] [-c cipher_spec] [ -D[ bind_address:]port] [-e escape_char] [-F configfile][-i identity_file] [ -L [ bind_address:]port:&lt;/p&gt;
&lt;p class=&quot;og-host&quot; data-ke-size=&quot;size16&quot;&gt;linux.die.net&lt;/p&gt;
&lt;/div&gt;
&lt;/a&gt;&lt;/figure&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;</description>
      <category>프로그래밍 언어/Shell</category>
      <author>트리맨스</author>
      <guid isPermaLink="true">https://tre2man.tistory.com/335</guid>
      <comments>https://tre2man.tistory.com/335#entry335comment</comments>
      <pubDate>Sun, 25 Dec 2022 09:10:38 +0900</pubDate>
    </item>
    <item>
      <title>LeetCode 790. Domino and Tromino Tiling</title>
      <link>https://tre2man.tistory.com/333</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;a href=&quot;https://leetcode.com/problems/domino-and-tromino-tiling/&quot;&gt;https://leetcode.com/problems/domino-and-tromino-tiling/&lt;/a&gt;&lt;/p&gt;
&lt;figure id=&quot;og_1671884489445&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;Domino and Tromino Tiling - LeetCode&quot; data-og-description=&quot;Domino and Tromino Tiling - You have two types of tiles: a 2 x 1 domino shape and a tromino shape. You may rotate these shapes. [https://assets.leetcode.com/uploads/2021/07/15/lc-domino.jpg] Given an integer n, return the number of ways to tile an 2 x n bo&quot; data-og-host=&quot;leetcode.com&quot; data-og-source-url=&quot;https://leetcode.com/problems/domino-and-tromino-tiling/&quot; data-og-url=&quot;https://leetcode.com/problems/domino-and-tromino-tiling/description&quot; data-og-image=&quot;https://scrap.kakaocdn.net/dn/cg3Ggh/hyQZCOm0K0/Eer2Ni0z49SXlPmv4tMEB1/img.png?width=500&amp;amp;height=260&amp;amp;face=0_0_500_260,https://scrap.kakaocdn.net/dn/deBcjN/hyQZSDFY6j/9Bje9xShL6angXaaPEMukK/img.jpg?width=803&amp;amp;height=363&amp;amp;face=0_0_803_363,https://scrap.kakaocdn.net/dn/bEZjGu/hyQ1gXcp0a/maIvKr0H1KXFjqvQA7dl61/img.png?width=924&amp;amp;height=222&amp;amp;face=0_0_924_222&quot;&gt;&lt;a href=&quot;https://leetcode.com/problems/domino-and-tromino-tiling/&quot; target=&quot;_blank&quot; rel=&quot;noopener&quot; data-source-url=&quot;https://leetcode.com/problems/domino-and-tromino-tiling/&quot;&gt;
&lt;div class=&quot;og-image&quot; style=&quot;background-image: url('https://scrap.kakaocdn.net/dn/cg3Ggh/hyQZCOm0K0/Eer2Ni0z49SXlPmv4tMEB1/img.png?width=500&amp;amp;height=260&amp;amp;face=0_0_500_260,https://scrap.kakaocdn.net/dn/deBcjN/hyQZSDFY6j/9Bje9xShL6angXaaPEMukK/img.jpg?width=803&amp;amp;height=363&amp;amp;face=0_0_803_363,https://scrap.kakaocdn.net/dn/bEZjGu/hyQ1gXcp0a/maIvKr0H1KXFjqvQA7dl61/img.png?width=924&amp;amp;height=222&amp;amp;face=0_0_924_222');&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;Domino and Tromino Tiling - LeetCode&lt;/p&gt;
&lt;p class=&quot;og-desc&quot; data-ke-size=&quot;size16&quot;&gt;Domino and Tromino Tiling - You have two types of tiles: a 2 x 1 domino shape and a tromino shape. You may rotate these shapes. [https://assets.leetcode.com/uploads/2021/07/15/lc-domino.jpg] Given an integer n, return the number of ways to tile an 2 x n bo&lt;/p&gt;
&lt;p class=&quot;og-host&quot; data-ke-size=&quot;size16&quot;&gt;leetcode.com&lt;/p&gt;
&lt;/div&gt;
&lt;/a&gt;&lt;/figure&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;무슨 문제인지 보아하니, DP를 사용하는 문제 같다. 백준의 2xn 타일링에서 조금 더 꼬아 놓은 듯 하다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;먼저 2xn타일링 문제를 간단히 살펴보자.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;a href=&quot;https://www.acmicpc.net/problem/11726&quot; target=&quot;_blank&quot; rel=&quot;noopener&quot;&gt;https://www.acmicpc.net/problem/11726&lt;/a&gt;&lt;/p&gt;
&lt;figure id=&quot;og_1671884636099&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;11726번: 2&amp;times;n 타일링&quot; data-og-description=&quot;2&amp;times;n 크기의 직사각형을 1&amp;times;2, 2&amp;times;1 타일로 채우는 방법의 수를 구하는 프로그램을 작성하시오. 아래 그림은 2&amp;times;5 크기의 직사각형을 채운 한 가지 방법의 예이다.&quot; data-og-host=&quot;www.acmicpc.net&quot; data-og-source-url=&quot;https://www.acmicpc.net/problem/11726&quot; data-og-url=&quot;https://www.acmicpc.net/problem/11726&quot; data-og-image=&quot;https://scrap.kakaocdn.net/dn/sXruy/hyQ1ljWomk/HhfjFPAuUoVgdJSWnwzhQ0/img.png?width=2834&amp;amp;height=1480&amp;amp;face=0_0_2834_1480,https://scrap.kakaocdn.net/dn/bqO06E/hyQZRxZqXY/pLvONCK3ep3fpGzoCifoL1/img.png?width=1381&amp;amp;height=554&amp;amp;face=0_0_1381_554&quot;&gt;&lt;a href=&quot;https://www.acmicpc.net/problem/11726&quot; target=&quot;_blank&quot; rel=&quot;noopener&quot; data-source-url=&quot;https://www.acmicpc.net/problem/11726&quot;&gt;
&lt;div class=&quot;og-image&quot; style=&quot;background-image: url('https://scrap.kakaocdn.net/dn/sXruy/hyQ1ljWomk/HhfjFPAuUoVgdJSWnwzhQ0/img.png?width=2834&amp;amp;height=1480&amp;amp;face=0_0_2834_1480,https://scrap.kakaocdn.net/dn/bqO06E/hyQZRxZqXY/pLvONCK3ep3fpGzoCifoL1/img.png?width=1381&amp;amp;height=554&amp;amp;face=0_0_1381_554');&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;11726번: 2&amp;times;n 타일링&lt;/p&gt;
&lt;p class=&quot;og-desc&quot; data-ke-size=&quot;size16&quot;&gt;2&amp;times;n 크기의 직사각형을 1&amp;times;2, 2&amp;times;1 타일로 채우는 방법의 수를 구하는 프로그램을 작성하시오. 아래 그림은 2&amp;times;5 크기의 직사각형을 채운 한 가지 방법의 예이다.&lt;/p&gt;
&lt;p class=&quot;og-host&quot; data-ke-size=&quot;size16&quot;&gt;www.acmicpc.net&lt;/p&gt;
&lt;/div&gt;
&lt;/a&gt;&lt;/figure&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;이 문제는 DP를 사용하는데, 이전 차수의 타일 상태에서 값을 가져와 푼다.&lt;/p&gt;
&lt;p&gt;&lt;figure class=&quot;imageblock alignCenter&quot; data-ke-mobileStyle=&quot;widthOrigin&quot; data-filename=&quot;IMG_CCE229A12122-1.jpeg&quot; data-origin-width=&quot;1486&quot; data-origin-height=&quot;1000&quot;&gt;&lt;span data-url=&quot;https://blog.kakaocdn.net/dn/cp1wFv/btrUuIko1t9/JkE5miQ9B5r2FFqFmLX84k/img.jpg&quot; data-phocus=&quot;https://blog.kakaocdn.net/dn/cp1wFv/btrUuIko1t9/JkE5miQ9B5r2FFqFmLX84k/img.jpg&quot;&gt;&lt;img src=&quot;https://blog.kakaocdn.net/dn/cp1wFv/btrUuIko1t9/JkE5miQ9B5r2FFqFmLX84k/img.jpg&quot; srcset=&quot;https://img1.daumcdn.net/thumb/R1280x0/?scode=mtistory2&amp;fname=https%3A%2F%2Fblog.kakaocdn.net%2Fdn%2Fcp1wFv%2FbtrUuIko1t9%2FJkE5miQ9B5r2FFqFmLX84k%2Fimg.jpg&quot; onerror=&quot;this.onerror=null; this.src='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png'; this.srcset='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png';&quot; loading=&quot;lazy&quot; width=&quot;550&quot; height=&quot;370&quot; data-filename=&quot;IMG_CCE229A12122-1.jpeg&quot; data-origin-width=&quot;1486&quot; data-origin-height=&quot;1000&quot;/&gt;&lt;/span&gt;&lt;/figure&gt;
&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;위의 그림에서 차수는 N이 된다. N=1인 경우와 N=2인 경우에는 상태가 고정이 되어 있다. N=3인 경우를 보면 색상이 다르게 칠해져 있다.&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;검은색 블록을 보았을 때, 4개가 모자란 경우와 2개가 모자란 경우로 나누어서 계산을 한다. 즉 &lt;b&gt;이전 상태에서 블록을 추가해서 현재 상태를 완성할 수 있는 조건을 찾는다!&lt;/b&gt; N=2일 경우의 수에서 맨 왼쪽에 블록 1개만 추가하는 경우와 N=1일 경우에 블록 2개를 붙이는 경우의 수가 있을 것이다. 이를 중복되지 않게 처리하게 되면 dp(n) = dp(n-1) + dp(n-2)의 점화식이 나오게 되고, O(n)의 시간으로 풀 수가 있다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;하지만 지금 문제에서는 경우의 수를 약간 꼬아 놨다. 그래서 블록의 모양을 보고 경우의 수를 나누어 주어야 한다. 이전 상태를 불러올 수 있게 하는 것이 중요하다.&lt;/p&gt;
&lt;p&gt;&lt;figure class=&quot;imageblock alignCenter&quot; data-ke-mobileStyle=&quot;widthOrigin&quot; data-filename=&quot;IMG_239162A78431-1.jpeg&quot; data-origin-width=&quot;2386&quot; data-origin-height=&quot;1000&quot;&gt;&lt;span data-url=&quot;https://blog.kakaocdn.net/dn/4SrNh/btrUuxiT6Hh/aecwo1wJXzppItzc6KD8L1/img.jpg&quot; data-phocus=&quot;https://blog.kakaocdn.net/dn/4SrNh/btrUuxiT6Hh/aecwo1wJXzppItzc6KD8L1/img.jpg&quot;&gt;&lt;img src=&quot;https://blog.kakaocdn.net/dn/4SrNh/btrUuxiT6Hh/aecwo1wJXzppItzc6KD8L1/img.jpg&quot; srcset=&quot;https://img1.daumcdn.net/thumb/R1280x0/?scode=mtistory2&amp;fname=https%3A%2F%2Fblog.kakaocdn.net%2Fdn%2F4SrNh%2FbtrUuxiT6Hh%2Faecwo1wJXzppItzc6KD8L1%2Fimg.jpg&quot; onerror=&quot;this.onerror=null; this.src='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png'; this.srcset='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png';&quot; loading=&quot;lazy&quot; width=&quot;585&quot; height=&quot;245&quot; data-filename=&quot;IMG_239162A78431-1.jpeg&quot; data-origin-width=&quot;2386&quot; data-origin-height=&quot;1000&quot;/&gt;&lt;/span&gt;&lt;/figure&gt;
&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;결과적으로 나올 수 있는 경우의 수는 3가지가 있고, 이전 상태에 대해서는 위와 같은 경우의 수를 모두 더하면 직사각형이 될 수 있다. 여기서 알 수 있는 것은, 2번과 3번의 경우의 수는 항상 같다! 왜냐하면 대칭의 모양을 하고 있고, 실제로 계산을 해 보면 같은 수가 나온다. 그래서 계산을 줄여서 3xn 배열이 아닌 2xn 배열로 선언해서 풀이가 가능하다. 초기값만 잘 지정해 두면 나머지는 O(n)으로 풀이가 가능하다.&lt;/p&gt;
&lt;pre id=&quot;code_1671885729431&quot; data-ke-language=&quot;typescript&quot; data-ke-type=&quot;codeblock&quot;&gt;&lt;code&gt;var numTilings = function (n) {
  const MOD = 10 ** 9 + 7;
  if (n &amp;lt;= 2) {
    return n;
  }

  const answer = Array.from({ length: n + 1 }, () =&amp;gt; Array(2).fill(0));
  answer[1][0] = 1;
  answer[2][0] = answer[2][1] = 2;
  for (let i = 3; i &amp;lt;= n; i++) {
    answer[i][0] =
      (answer[i - 1][0] + answer[i - 2][0] + answer[i - 1][1]) % MOD;
    answer[i][1] = (2 * answer[i - 2][0] + answer[i - 1][1]) % MOD;
  }
  return answer[n][0];
};&lt;/code&gt;&lt;/pre&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;</description>
      <category>개발 지식/알고리즘</category>
      <author>트리맨스</author>
      <guid isPermaLink="true">https://tre2man.tistory.com/333</guid>
      <comments>https://tre2man.tistory.com/333#entry333comment</comments>
      <pubDate>Sat, 24 Dec 2022 21:43:07 +0900</pubDate>
    </item>
    <item>
      <title>Active Record , Data Mapper 패턴에 대하여</title>
      <link>https://tre2man.tistory.com/332</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;여기서는 서버 APP에서 DB의 데이터에 엑세스하는 접근 방식에 대해서 설명하고 있다. 주로 2가지의 패턴이 보이게 되는데, 이에 대한 차이점을 정리해 볼려고 한다.&lt;/p&gt;
&lt;h3 data-ke-size=&quot;size23&quot;&gt;Active Record Pattern&lt;/h3&gt;
&lt;hr contenteditable=&quot;false&quot; data-ke-type=&quot;horizontalRule&quot; data-ke-style=&quot;style5&quot; /&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;모델 내에서 모든 쿼리(접근자) 메서드를 정의하고 모델 메서드를 통해서 객체의 CRUD를 한다.&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;아래의 예시를 보면 User라는 엔티티에 직접 findByName이라는 쿼리 메서드를 지정하고 있다.&amp;nbsp;&lt;/p&gt;
&lt;pre id=&quot;code_1670656927399&quot; class=&quot;typescript&quot; data-ke-language=&quot;typescript&quot; data-ke-type=&quot;codeblock&quot;&gt;&lt;code&gt;import { BaseEntity, Entity, PrimaryGeneratedColumn, Column } from &quot;typeorm&quot;

@Entity()
export class User extends BaseEntity {
    @PrimaryGeneratedColumn()
    id: number

    @Column()
    firstName: string

    @Column()
    lastName: string

    @Column()
    isActive: boolean

    static findByName(firstName: string, lastName: string) {
        return this.createQueryBuilder(&quot;user&quot;)
            .where(&quot;user.firstName = :firstName&quot;, { firstName })
            .andWhere(&quot;user.lastName = :lastName&quot;, { lastName })
            .getMany()
    }
}&lt;/code&gt;&lt;/pre&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;h3 data-ke-size=&quot;size23&quot;&gt;Data Mapper Pattern&lt;/h3&gt;
&lt;hr contenteditable=&quot;false&quot; data-ke-type=&quot;horizontalRule&quot; data-ke-style=&quot;style5&quot; /&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;전형적인 서버 애플리케이션에서 데이터는 컨트롤러 -&amp;gt; 서비스 -&amp;gt; 리포지토리 -&amp;gt; DB의 계층으로 나뉘어 있다. 여기서 리포지토리는 DB와 프로그램 내 데이터 표현 계층을 연결하는 데이터 엑세스 계층이다. 이 패턴은 여러 서버 애플리케이션에서 정석처럼 사용되고 있으며, 단일 책임 원칙에 위배되지도 않으면서 테스트도 비교적 쉽다는 장점이 있다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;사용 예시는 아래와 같다.&lt;/p&gt;
&lt;pre id=&quot;code_1670657288322&quot; class=&quot;typescript&quot; data-ke-language=&quot;typescript&quot; data-ke-type=&quot;codeblock&quot;&gt;&lt;code&gt;const userRepository = dataSource.getRepository(User)

// example how to save DM entity
const user = new User()
user.firstName = &quot;Timber&quot;
user.lastName = &quot;Saw&quot;
user.isActive = true
await userRepository.save(user)

// example how to remove DM entity
await userRepository.remove(user)

// example how to load DM entities
const users = await userRepository.find({ skip: 2, take: 5 })
const newUsers = await userRepository.findBy({ isActive: true })
const timber = await userRepository.findOneBy({
    firstName: &quot;Timber&quot;,
    lastName: &quot;Saw&quot;,
})&lt;/code&gt;&lt;/pre&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;h3 data-ke-size=&quot;size23&quot;&gt;차이점&lt;/h3&gt;
&lt;hr contenteditable=&quot;false&quot; data-ke-type=&quot;horizontalRule&quot; data-ke-style=&quot;style5&quot; /&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;Active Record는 직관적이고 비교적 빠르게 앱을 제작할 수 있다. 그래서 소규모 앱에 자주 쓰인다. 반대로 Data Mapper는 Model과 역할을 분리하여 사용하므로, 비교적 확장성이 좋고 큰 앱에서 주로 사용한다. 하지만 빠르게 앱을 제작하기에는 약간의 제한이 있다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;h3 data-ke-size=&quot;size23&quot;&gt;참고자료&lt;/h3&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;a href=&quot;https://orkhan.gitbook.io/typeorm/docs/active-record-data-mapper&quot; target=&quot;_blank&quot; rel=&quot;noopener&quot;&gt;https://orkhan.gitbook.io/typeorm/docs/active-record-data-mapper&lt;/a&gt;&lt;/p&gt;
&lt;figure id=&quot;og_1670657301399&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;Active Record vs Data Mapper - typeorm&quot; data-og-description=&quot;Using the Data Mapper approach, you define all your query methods in separate classes called &amp;quot;repositories&amp;quot;, and you save, remove, and load objects using repositories. In data mapper your entities are very dumb - they just define their properties and may h&quot; data-og-host=&quot;orkhan.gitbook.io&quot; data-og-source-url=&quot;https://orkhan.gitbook.io/typeorm/docs/active-record-data-mapper&quot; data-og-url=&quot;https://orkhan.gitbook.io/typeorm/docs/active-record-data-mapper&quot; data-og-image=&quot;&quot;&gt;&lt;a href=&quot;https://orkhan.gitbook.io/typeorm/docs/active-record-data-mapper&quot; target=&quot;_blank&quot; rel=&quot;noopener&quot; data-source-url=&quot;https://orkhan.gitbook.io/typeorm/docs/active-record-data-mapper&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;Active Record vs Data Mapper - typeorm&lt;/p&gt;
&lt;p class=&quot;og-desc&quot; data-ke-size=&quot;size16&quot;&gt;Using the Data Mapper approach, you define all your query methods in separate classes called &quot;repositories&quot;, and you save, remove, and load objects using repositories. In data mapper your entities are very dumb - they just define their properties and may h&lt;/p&gt;
&lt;p class=&quot;og-host&quot; data-ke-size=&quot;size16&quot;&gt;orkhan.gitbook.io&lt;/p&gt;
&lt;/div&gt;
&lt;/a&gt;&lt;/figure&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;a href=&quot;https://en.wikipedia.org/wiki/Active_record_pattern&quot; target=&quot;_blank&quot; rel=&quot;noopener&quot;&gt;https://en.wikipedia.org/wiki/Active_record_pattern&lt;/a&gt;&lt;/p&gt;
&lt;figure id=&quot;og_1670657336913&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;Active record pattern - Wikipedia&quot; data-og-description=&quot;In software engineering, the active record pattern is an architectural pattern. It is found in software that stores in-memory object data in relational databases. It was named by Martin Fowler in his 2003 book Patterns of Enterprise Application Architectur&quot; data-og-host=&quot;en.wikipedia.org&quot; data-og-source-url=&quot;https://en.wikipedia.org/wiki/Active_record_pattern&quot; data-og-url=&quot;https://en.wikipedia.org/wiki/Active_record_pattern&quot; data-og-image=&quot;&quot;&gt;&lt;a href=&quot;https://en.wikipedia.org/wiki/Active_record_pattern&quot; target=&quot;_blank&quot; rel=&quot;noopener&quot; data-source-url=&quot;https://en.wikipedia.org/wiki/Active_record_pattern&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;Active record pattern - Wikipedia&lt;/p&gt;
&lt;p class=&quot;og-desc&quot; data-ke-size=&quot;size16&quot;&gt;In software engineering, the active record pattern is an architectural pattern. It is found in software that stores in-memory object data in relational databases. It was named by Martin Fowler in his 2003 book Patterns of Enterprise Application Architectur&lt;/p&gt;
&lt;p class=&quot;og-host&quot; data-ke-size=&quot;size16&quot;&gt;en.wikipedia.org&lt;/p&gt;
&lt;/div&gt;
&lt;/a&gt;&lt;/figure&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;a href=&quot;https://en.wikipedia.org/wiki/Data_mapper_pattern&quot; target=&quot;_blank&quot; rel=&quot;noopener&quot;&gt;https://en.wikipedia.org/wiki/Data_mapper_pattern&lt;/a&gt;&lt;/p&gt;
&lt;figure id=&quot;og_1670657340860&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;Data mapper pattern - Wikipedia&quot; data-og-description=&quot;In software engineering, the data mapper pattern is an architectural pattern. It was named by Martin Fowler in his 2003 book Patterns of Enterprise Application Architecture.[1] The interface of an object conforming to this pattern would include functions s&quot; data-og-host=&quot;en.wikipedia.org&quot; data-og-source-url=&quot;https://en.wikipedia.org/wiki/Data_mapper_pattern&quot; data-og-url=&quot;https://en.wikipedia.org/wiki/Data_mapper_pattern&quot; data-og-image=&quot;&quot;&gt;&lt;a href=&quot;https://en.wikipedia.org/wiki/Data_mapper_pattern&quot; target=&quot;_blank&quot; rel=&quot;noopener&quot; data-source-url=&quot;https://en.wikipedia.org/wiki/Data_mapper_pattern&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;Data mapper pattern - Wikipedia&lt;/p&gt;
&lt;p class=&quot;og-desc&quot; data-ke-size=&quot;size16&quot;&gt;In software engineering, the data mapper pattern is an architectural pattern. It was named by Martin Fowler in his 2003 book Patterns of Enterprise Application Architecture.[1] The interface of an object conforming to this pattern would include functions s&lt;/p&gt;
&lt;p class=&quot;og-host&quot; data-ke-size=&quot;size16&quot;&gt;en.wikipedia.org&lt;/p&gt;
&lt;/div&gt;
&lt;/a&gt;&lt;/figure&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;</description>
      <category>프레임워크/NestJS</category>
      <author>트리맨스</author>
      <guid isPermaLink="true">https://tre2man.tistory.com/332</guid>
      <comments>https://tre2man.tistory.com/332#entry332comment</comments>
      <pubDate>Sat, 10 Dec 2022 16:48:05 +0900</pubDate>
    </item>
    <item>
      <title>[NestJS] JWT 인증과 Role 적용하기</title>
      <link>https://tre2man.tistory.com/331</link>
      <description>&lt;p data-ke-size=&quot;size16&quot; style=&quot;text-align: left;&quot;&gt; &lt;br&gt;JWT인증과 Role인증을 동시에 적용하는 로직을 만드는 연습을 해 보았다. JWT인증에 대해서 정리한 글은 아래의 링크에 나와 있다.&lt;br&gt;&lt;a href=&quot;https://tre2man.tistory.com/321&quot; target=&quot;_blank&quot;&gt;&lt;span&gt;https://tre2man.tistory.com/321&lt;/span&gt;&lt;/a&gt;&lt;/p&gt;
&lt;figure data-ke-type=&quot;opengraph&quot; data-og-title=&quot;로그인 인증 부수기&quot; data-ke-align=&quot;alignCenter&quot; data-og-description=&quot;최근들어 유저의 인증 로직 관련해서 보완해야 할 점이 보여서 수정 중, 이참에 로그인 인증 관련해서 한번 정리해 보고자 글을 작성한다. 배경 지식 먼저 인증과 인가에 대해서 구분을 하자. 인&quot; data-og-host=&quot;tre2man.tistory.com&quot; data-og-source-url=&quot;https://tre2man.tistory.com/321&quot; data-og-image=&quot;https://scrap.kakaocdn.net/dn/bv6bz5/hyQQDeP7Ey/7eKBfHWYDRkwzBkVGJZFZk/img.png?width=603&amp;amp;height=128&amp;amp;face=0_0_603_128,https://scrap.kakaocdn.net/dn/iVICO/hyQQvgM4hm/l2k8SUoiuzpLTTvFRXZGkk/img.png?width=603&amp;amp;height=128&amp;amp;face=0_0_603_128&quot; data-og-url=&quot;https://tre2man.tistory.com/321&quot;&gt;
 &lt;a href=&quot;https://tre2man.tistory.com/321&quot; target=&quot;_blank&quot; data-source-url=&quot;https://tre2man.tistory.com/321&quot;&gt;
  &lt;div class=&quot;og-image&quot; style=&quot;background-image: url('https://scrap.kakaocdn.net/dn/bv6bz5/hyQQDeP7Ey/7eKBfHWYDRkwzBkVGJZFZk/img.png?width=603&amp;amp;height=128&amp;amp;face=0_0_603_128,https://scrap.kakaocdn.net/dn/iVICO/hyQQvgM4hm/l2k8SUoiuzpLTTvFRXZGkk/img.png?width=603&amp;amp;height=128&amp;amp;face=0_0_603_128')&quot;&gt; 
  &lt;/div&gt;
  &lt;div class=&quot;og-text&quot;&gt;
   &lt;p class=&quot;og-title&quot;&gt;로그인 인증 부수기&lt;/p&gt;
   &lt;p class=&quot;og-desc&quot;&gt;최근들어 유저의 인증 로직 관련해서 보완해야 할 점이 보여서 수정 중, 이참에 로그인 인증 관련해서 한번 정리해 보고자 글을 작성한다. 배경 지식 먼저 인증과 인가에 대해서 구분을 하자. 인&lt;/p&gt;
   &lt;p class=&quot;og-host&quot;&gt;tre2man.tistory.com&lt;/p&gt;
  &lt;/div&gt;&lt;/a&gt;
&lt;/figure&gt;
&lt;p data-ke-size=&quot;size16&quot; style=&quot;text-align: left;&quot;&gt; &lt;br&gt;JWT인증을 하기 전에 공식 문서를 잘 읽어 보자. NestJS는 공식문서가 잘 되어 있어서 꼼꼼하게 읽어 보는것이 좋다.&lt;br&gt;&lt;a href=&quot;https://docs.nestjs.com/security/authentication&quot; target=&quot;_blank&quot;&gt;&lt;span&gt;https://docs.nestjs.com/security/authentication&lt;/span&gt;&lt;/a&gt;&lt;/p&gt;
&lt;figure data-ke-type=&quot;opengraph&quot; data-og-title=&quot;Documentation | NestJS - A progressive Node.js framework&quot; data-ke-align=&quot;alignCenter&quot; data-og-description=&quot;Nest is a framework for building efficient, scalable Node.js server-side applications. It uses progressive JavaScript, is built with TypeScript and combines elements of OOP (Object Oriented Progamming), FP (Functional Programming), and FRP (Functional Reac&quot; data-og-host=&quot;docs.nestjs.com&quot; data-og-source-url=&quot;https://docs.nestjs.com/security/authentication&quot; data-og-image=&quot;https://scrap.kakaocdn.net/dn/bqNI6k/hyQQydvlUY/5kBQL3OP5oY5QBEuMTrSQ1/img.png?width=820&amp;amp;height=429&amp;amp;face=0_0_820_429&quot; data-og-url=&quot;https://docs.nestjs.com&quot;&gt;
 &lt;a href=&quot;https://docs.nestjs.com&quot; target=&quot;_blank&quot; data-source-url=&quot;https://docs.nestjs.com/security/authentication&quot;&gt;
  &lt;div class=&quot;og-image&quot; style=&quot;background-image: url('https://scrap.kakaocdn.net/dn/bqNI6k/hyQQydvlUY/5kBQL3OP5oY5QBEuMTrSQ1/img.png?width=820&amp;amp;height=429&amp;amp;face=0_0_820_429')&quot;&gt; 
  &lt;/div&gt;
  &lt;div class=&quot;og-text&quot;&gt;
   &lt;p class=&quot;og-title&quot;&gt;Documentation | NestJS - A progressive Node.js framework&lt;/p&gt;
   &lt;p class=&quot;og-desc&quot;&gt;Nest is a framework for building efficient, scalable Node.js server-side applications. It uses progressive JavaScript, is built with TypeScript and combines elements of OOP (Object Oriented Progamming), FP (Functional Programming), and FRP (Functional Reac&lt;/p&gt;
   &lt;p class=&quot;og-host&quot;&gt;docs.nestjs.com&lt;/p&gt;
  &lt;/div&gt;&lt;/a&gt;
&lt;/figure&gt;
&lt;p data-ke-size=&quot;size16&quot; style=&quot;text-align: left;&quot;&gt; &lt;br&gt;먼저 JWT인증을 할 때의 플로우를 간단히 정리해 보았다.&lt;/p&gt;
&lt;ol style=&quot;list-style-type: decimal;&quot; data-ke-list-type=&quot;decimal&quot;&gt;
 &lt;li&gt;회원가입 및 로그인을 할 때 JWT 토큰을 반환한다.&lt;/li&gt;
 &lt;li&gt;가드가 적용된 컨트롤러에 요청을 보낼 때, 유효한 JWT토큰 확인과 Role가드가 있다면 Role까지 확인한다.&lt;/li&gt;
 &lt;li&gt;유효한 토큰과 권한이 아니면 401에러, 맞으면 정상적으로 로직을 실행한다.&lt;/li&gt;
&lt;/ol&gt;
&lt;p data-ke-size=&quot;size16&quot; style=&quot;text-align: left;&quot;&gt; &lt;br&gt;인증 관련한 라이브러리는 passport를 사용하고, jwt를 적용할려고 하면 추가 라이브러리가 필요하다. 여기에다가 몇개의 패키지를 더 설치해야 하는데, 이에 대한 내용은 공식문서에 자세히 설명이 되어 있으므로 따로 적지는 않겠다.&lt;br&gt; &lt;br&gt;먼저 유저를 인증할 validate 로직을 제작한다. 주로 Auth와 관련된 서비스에 추가하면 가독성이 좋고, 입력된 데이터를 통해서 해당 유저가 존재하는지 확인하면 된다. 아래는 그냥 예시이다.&lt;/p&gt;
&lt;pre data-ke-type=&quot;codeblock&quot; class=&quot;html&quot; data-ke-language=&quot;html&quot;&gt;&lt;code&gt;&amp;nbsp;&amp;nbsp;async validate(input: JwtPayload): Promise&amp;lt;User | null&amp;gt; {
&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;const user = await this.userService.findOneUser(input);
&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;if (!user) return null;
&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;return user;
&amp;nbsp;&amp;nbsp;}&lt;/code&gt;&lt;/pre&gt;
&lt;p data-ke-size=&quot;size16&quot; style=&quot;text-align: left;&quot;&gt; &lt;br&gt;여기서 주의할 것은 실제 사용할 때는 &lt;b&gt;암호는 단방향 해시로 암호화가 되어 있어야 하는 것이다.&lt;/b&gt; 이러한 점을 염두해서 로직을 작성해야 한다. 다음으로는 JwtStrategy 객체를 새로 지정한다. 이것으로 토큰이 유효한지 판별할 수 있다.&lt;/p&gt;
&lt;pre data-ke-type=&quot;codeblock&quot; class=&quot;html&quot; data-ke-language=&quot;html&quot;&gt;&lt;code&gt;// jwt-strategy.ts

@Injectable()
export class JwtStrategy extends PassportStrategy(Strategy) {
&amp;nbsp;&amp;nbsp;constructor(private readonly authService: AuthService) {
&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;super({
&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;jwtFromRequest: ExtractJwt.fromAuthHeaderAsBearerToken(),
&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;ignoreExpiration: false,
&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;secretOrKey: process.env.JWT_TOKEN,
&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;});
&amp;nbsp;&amp;nbsp;}

&amp;nbsp;&amp;nbsp;async validate(payload: JwtPayload) {
&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;const user = await this.authService.validate(payload);
&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;if (!user) throw new HttpException('Unauthorized', 401);
&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;return user;
&amp;nbsp;&amp;nbsp;}
}&lt;/code&gt;&lt;/pre&gt;
&lt;p data-ke-size=&quot;size16&quot; style=&quot;text-align: left;&quot;&gt; &lt;br&gt;다음으로는 Jwt 가드를 정의할 것이다. AuthGuard('jwt')를 상속받는 객체를 생성한다.&lt;/p&gt;
&lt;pre data-ke-type=&quot;codeblock&quot; class=&quot;html&quot; data-ke-language=&quot;html&quot;&gt;&lt;code&gt;// auth-guard.ts

@Injectable()
export class JwtAuthGuard extends AuthGuard('jwt') {}&lt;/code&gt;&lt;/pre&gt;
&lt;p data-ke-size=&quot;size16&quot; style=&quot;text-align: left;&quot;&gt; &lt;br&gt;여담이지만, GraphQL을 사용할 때는 request를 중간에서 한번 정제할 필요가 있다. 이를 위해서 NestJS에서 예시를 제공해 주고 있다. 만약 GraphQL을 사용한다면 ExecutionContext말고 GqlExcutionContext를 사용해야 한다.&lt;/p&gt;
&lt;pre data-ke-type=&quot;codeblock&quot; class=&quot;html&quot; data-ke-language=&quot;html&quot;&gt;&lt;code&gt;// graphql-guard

@Injectable()
export class GqlAuthGuard extends AuthGuard('jwt') {
&amp;nbsp;&amp;nbsp;getRequest(context: ExecutionContext) {
&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;const ctx = GqlExecutionContext.create(context);
&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;return ctx.getContext().req;
&amp;nbsp;&amp;nbsp;}
}&lt;/code&gt;&lt;/pre&gt;
&lt;p data-ke-size=&quot;size16&quot; style=&quot;text-align: left;&quot;&gt; &lt;br&gt;jwt 인증 로직이 완성되었으면은 role을 지정한다. enum으로 제작하며, role-guard를 사용 가능하게 만드는 SetMetadata를 사용하여 데코레이터까지 생성해 준다.&lt;/p&gt;
&lt;pre data-ke-type=&quot;codeblock&quot; class=&quot;html&quot; data-ke-language=&quot;html&quot;&gt;&lt;code&gt;// role-list.ts

export const ROLE_KEY = 'roles';
export enum Role {
&amp;nbsp;&amp;nbsp;admin = 'admin',
&amp;nbsp;&amp;nbsp;user = 'user',
}
export const Roles = (...roles: (keyof typeof Role)[]) =&amp;gt;
&amp;nbsp;&amp;nbsp;SetMetadata(ROLE_KEY, roles);&lt;/code&gt;&lt;/pre&gt;
&lt;p data-ke-size=&quot;size16&quot; style=&quot;text-align: left;&quot;&gt; &lt;br&gt;위에 있는 Roles 데코레이터를 사용하게 되면, 해당 데코레이터의 입력 변수에 들어가는 role만 요청 가능하게 하는 가드를 새로 만들어 주어야 한다. 여기서는 reflector로 지정한 메타데이터를 불러올 수 있다. 그래서 해당 로직에서는 canActivate 메서드 안의 reflector는 앞에서 지정한 ROLE_KEY에 해당하는 메타데이터(@Role 사용할 때 입력했던 역할들)를 불러오게 되고, 이를 user의 role과 비교하여 권한 제공 여부를 결정하게 된다.&lt;/p&gt;
&lt;pre data-ke-type=&quot;codeblock&quot; class=&quot;html&quot; data-ke-language=&quot;html&quot;&gt;&lt;code&gt;// role-guard.ts

@Injectable()
export class RoleGuard implements CanActivate {
&amp;nbsp;&amp;nbsp;constructor(private reflector: Reflector) {}

&amp;nbsp;&amp;nbsp;canActivate(context: ExecutionContext) {
&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;const roles = this.reflector.get&amp;lt;(keyof typeof Role)[]&amp;gt;(
&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;ROLE_KEY,
&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;context.getHandler(),
&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;);
&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;const user: User = context.switchToHttp().getRequest().user;
&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;if (!roles.includes(user.role))
&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;throw new HttpException('Unauthorized', 401);
&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;return true;
&amp;nbsp;&amp;nbsp;}
}&lt;/code&gt;&lt;/pre&gt;
&lt;p data-ke-size=&quot;size16&quot; style=&quot;text-align: left;&quot;&gt; &lt;br&gt;위와 같이 로직을 다 작성했으면, 모듈에 입력 후 정상 작동을 확인해보자. 모듈에 입력할 때 async 모듈로 삽입한 이유는, configService를 사용할 때 dotenv파일에서 설정값을 불러오는 과정이 async하기 때문에 아래와 같이 작성했다. 처음부터 전역으로 적용하고 싶으면 dotenv-cli를 사용하면 된다.&lt;/p&gt;
&lt;pre data-ke-type=&quot;codeblock&quot; class=&quot;html&quot; data-ke-language=&quot;html&quot;&gt;&lt;code&gt;// auth.module.ts

@Module({
&amp;nbsp;&amp;nbsp;imports: [
&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;UserModule,
&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;PassportModule.register({
&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;defaultStrategy: 'jwt',
&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;}),
&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;JwtModule.registerAsync({
&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;inject: [ConfigService],
&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;useFactory: (configService: ConfigService) =&amp;gt; ({
&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;secret: configService.get&amp;lt;string&amp;gt;('JWT_TOKEN'),
&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;signOptions: { expiresIn: '10m' },
&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;controllers: [AuthController],
&amp;nbsp;&amp;nbsp;providers: [userProvider, UserService, AuthService, JwtStrategy],
&amp;nbsp;&amp;nbsp;exports: [AuthService],
})
export class AuthModule {}&lt;/code&gt;&lt;/pre&gt;
&lt;p data-ke-size=&quot;size16&quot; style=&quot;text-align: left;&quot;&gt; &lt;br&gt;컨트롤러에 적용할 때는 다음과 같이 한다. 예시로 든 아래의 로직은 요청하는 토큰의 유저의 권한이 admin일 경우에만 작동을 한다는 것이다.&lt;/p&gt;
&lt;pre data-ke-type=&quot;codeblock&quot; class=&quot;html&quot; data-ke-language=&quot;html&quot;&gt;&lt;code&gt;@Controller('user')
export class UserController {
&amp;nbsp;&amp;nbsp;constructor(private readonly userService: UserService) {}

&amp;nbsp;&amp;nbsp;@Get()
&amp;nbsp;&amp;nbsp;@UseGuards(JwtAuthGuard, RoleGuard)
&amp;nbsp;&amp;nbsp;@Roles('admin')
&amp;nbsp;&amp;nbsp;async findOneUser(@Query() input: FindOneUserDto) {
&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;return await this.userService.findOneUser(input);
&amp;nbsp;&amp;nbsp;}
}&lt;/code&gt;&lt;/pre&gt;
&lt;p data-ke-size=&quot;size16&quot; style=&quot;text-align: left;&quot;&gt; &lt;br&gt;아래의 사진은 Postman으로 요청을 보냈을 때 나오는 응답값이다. 해당 요청들을 보내기 위해서 위의 코드는 Query String을 던지지만, 잠시 없애 두었다. 여튼 user권한이 있는 jwt 토큰을 던졌을 때는 허가되지 않은 요청을 반환하게 된다.&lt;/p&gt;&lt;figure class=&quot;imageblock alignCenter&quot; data-ke-mobileStyle=&quot;widthOrigin&quot; data-origin-width=&quot;1402&quot; data-origin-height=&quot;688&quot;&gt;&lt;span data-url=&quot;https://blog.kakaocdn.net/dn/njnvu/btrTk1RM3aG/90LmdfxdAkHwCg7B0cRGE0/img.png&quot; data-phocus=&quot;https://blog.kakaocdn.net/dn/njnvu/btrTk1RM3aG/90LmdfxdAkHwCg7B0cRGE0/img.png&quot;&gt;&lt;img src=&quot;https://blog.kakaocdn.net/dn/njnvu/btrTk1RM3aG/90LmdfxdAkHwCg7B0cRGE0/img.png&quot; srcset=&quot;https://img1.daumcdn.net/thumb/R1280x0/?scode=mtistory2&amp;fname=https%3A%2F%2Fblog.kakaocdn.net%2Fdn%2Fnjnvu%2FbtrTk1RM3aG%2F90LmdfxdAkHwCg7B0cRGE0%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;1402&quot; height=&quot;688&quot; data-origin-width=&quot;1402&quot; data-origin-height=&quot;688&quot;/&gt;&lt;/span&gt;&lt;/figure&gt;

&lt;p data-ke-size=&quot;size16&quot; style=&quot;text-align: left;&quot;&gt; &lt;br&gt;아래의 사진은 admin 권한이 있는 토큰을 던졌을 때 나오는 응답값이다. 권한 문제 없이 요청값이 잘 들어오는 것을 볼 수 있다.&lt;/p&gt;&lt;figure class=&quot;imageblock alignCenter&quot; data-ke-mobileStyle=&quot;widthOrigin&quot; data-origin-width=&quot;1388&quot; data-origin-height=&quot;1008&quot;&gt;&lt;span data-url=&quot;https://blog.kakaocdn.net/dn/bjzeHh/btrTgqeUXOn/1mJq8msHTrhocLplfL8ikK/img.png&quot; data-phocus=&quot;https://blog.kakaocdn.net/dn/bjzeHh/btrTgqeUXOn/1mJq8msHTrhocLplfL8ikK/img.png&quot;&gt;&lt;img src=&quot;https://blog.kakaocdn.net/dn/bjzeHh/btrTgqeUXOn/1mJq8msHTrhocLplfL8ikK/img.png&quot; srcset=&quot;https://img1.daumcdn.net/thumb/R1280x0/?scode=mtistory2&amp;fname=https%3A%2F%2Fblog.kakaocdn.net%2Fdn%2FbjzeHh%2FbtrTgqeUXOn%2F1mJq8msHTrhocLplfL8ikK%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;1388&quot; height=&quot;1008&quot; data-origin-width=&quot;1388&quot; data-origin-height=&quot;1008&quot;/&gt;&lt;/span&gt;&lt;/figure&gt;

&lt;p data-ke-size=&quot;size16&quot; style=&quot;text-align: left;&quot;&gt; &lt;br&gt; &lt;/p&gt;</description>
      <category>프레임워크/NestJS</category>
      <author>트리맨스</author>
      <guid isPermaLink="true">https://tre2man.tistory.com/331</guid>
      <comments>https://tre2man.tistory.com/331#entry331comment</comments>
      <pubDate>Sat, 10 Dec 2022 16:00:34 +0900</pubDate>
    </item>
    <item>
      <title>template database &amp;quot;template1&amp;quot; does not exist 에러 해결하기</title>
      <link>https://tre2man.tistory.com/330</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;실수로 postgres의 기본 db를 지워버렸다. 그 이후에 새 db를 만들려고 하니 자꾸 아래와 같은 오류가 나왔다.&lt;/p&gt;
&lt;pre class=&quot;pgsql&quot;&gt;&lt;code&gt;template database &quot;template1&quot; does not exist&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;알고보니 postgresql은 db를 수동으로 새로 만들때 템플릿이 있어야 하는 것 같았다. 그래서 기존의 template0, template1 db를 다시 살려야 하는 상황에 왔다. 구글에 검색하니 템플릿 데이터베이스를 따로 만들수 있다고 한다. 아래 명령어를 치니 템플릿이 새로 생성되었다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;pre id=&quot;code_1669034320600&quot; class=&quot;sql&quot; data-ke-language=&quot;sql&quot; data-ke-type=&quot;codeblock&quot;&gt;&lt;code&gt;create database template0 TEMPLATE postgres;
update pg_database set datistemplate=true  where datname='template0';&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;확인하니 기본 템플릿이 0과 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;template1&lt;/p&gt;
&lt;ul style=&quot;list-style-type: disc;&quot; data-ke-list-type=&quot;disc&quot;&gt;
&lt;li&gt;기본값으로 create database 하면 이놈을 복사한다.&lt;/li&gt;
&lt;li&gt;template1에 개체를 추가하면 다음에 create database할때 개체가 기본값으로 추가됨 (Perl 포함)&lt;/li&gt;
&lt;li&gt;인코딩, locale 정보 포함가능&lt;/li&gt;
&lt;/ul&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;template0&lt;/p&gt;
&lt;ul style=&quot;list-style-type: disc;&quot; data-ke-list-type=&quot;disc&quot;&gt;
&lt;li&gt;template1과 동일한 데이터&lt;/li&gt;
&lt;li&gt;pg_database.datistemplate = false값이다. 즉, db클러스터 초기화 이후 template0을 변경할 수 없다. 슈퍼유저 또는 db 소유자만 복제 가능&lt;/li&gt;
&lt;li&gt;인코딩, locale 정보가 없을수 있다.&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;결론은 postgres를 로컬 및 인스턴스에서 직접 설치해서 사용할 경우, 기본적으로 생기는 template0(1) 은 ALTER는 자유롭게 해도 되나 특별한 이유가 없으면 DROP하지 않는 것이 좋을 것 같다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;h3 data-ke-size=&quot;size23&quot;&gt;참고자료&lt;/h3&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;a href=&quot;https://stackoverflow.com/questions/27992560/template0-and-template1-database-dropped-accidently&quot; target=&quot;_blank&quot; rel=&quot;noopener&quot;&gt;https://stackoverflow.com/questions/27992560/template0-and-template1-database-dropped-accidently&lt;/a&gt;&lt;/p&gt;
&lt;figure id=&quot;og_1669034332757&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;template0 and template1 database dropped accidently&quot; data-og-description=&quot;I did not know that template0 and template1 database templates are required in order to create empty databases. I deleted them in order to clear up postgres. Now I'm not able to create any new data...&quot; data-og-host=&quot;stackoverflow.com&quot; data-og-source-url=&quot;https://stackoverflow.com/questions/27992560/template0-and-template1-database-dropped-accidently&quot; data-og-url=&quot;https://stackoverflow.com/questions/27992560/template0-and-template1-database-dropped-accidently&quot; data-og-image=&quot;https://scrap.kakaocdn.net/dn/ba24SR/hyQDuJTFbJ/LjtSjvPK3u7wz376uaAsk0/img.png?width=316&amp;amp;height=316&amp;amp;face=0_0_316_316&quot;&gt;&lt;a href=&quot;https://stackoverflow.com/questions/27992560/template0-and-template1-database-dropped-accidently&quot; target=&quot;_blank&quot; rel=&quot;noopener&quot; data-source-url=&quot;https://stackoverflow.com/questions/27992560/template0-and-template1-database-dropped-accidently&quot;&gt;
&lt;div class=&quot;og-image&quot; style=&quot;background-image: url('https://scrap.kakaocdn.net/dn/ba24SR/hyQDuJTFbJ/LjtSjvPK3u7wz376uaAsk0/img.png?width=316&amp;amp;height=316&amp;amp;face=0_0_316_316');&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;template0 and template1 database dropped accidently&lt;/p&gt;
&lt;p class=&quot;og-desc&quot; data-ke-size=&quot;size16&quot;&gt;I did not know that template0 and template1 database templates are required in order to create empty databases. I deleted them in order to clear up postgres. Now I'm not able to create any new data...&lt;/p&gt;
&lt;p class=&quot;og-host&quot; data-ke-size=&quot;size16&quot;&gt;stackoverflow.com&lt;/p&gt;
&lt;/div&gt;
&lt;/a&gt;&lt;/figure&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;a href=&quot;https://www.postgresql.org/docs/current/manage-ag-templatedbs.html&quot; target=&quot;_blank&quot; rel=&quot;noopener&quot;&gt;https://www.postgresql.org/docs/current/manage-ag-templatedbs.html&lt;/a&gt;&lt;/p&gt;
&lt;figure id=&quot;og_1669034341782&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;23.3.&amp;nbsp;Template Databases&quot; data-og-description=&quot;23.3.&amp;nbsp;Template Databases CREATE DATABASE actually works by copying an existing database. By default, it copies the standard system database named &amp;hellip;&quot; data-og-host=&quot;www.postgresql.org&quot; data-og-source-url=&quot;https://www.postgresql.org/docs/current/manage-ag-templatedbs.html&quot; data-og-url=&quot;https://www.postgresql.org/docs/15/manage-ag-templatedbs.html&quot; data-og-image=&quot;https://scrap.kakaocdn.net/dn/MTtQc/hyQDrTYoaa/hRnTu2OgSKVPwUCumRGzSk/img.png?width=540&amp;amp;height=557&amp;amp;face=0_0_540_557&quot;&gt;&lt;a href=&quot;https://www.postgresql.org/docs/current/manage-ag-templatedbs.html&quot; target=&quot;_blank&quot; rel=&quot;noopener&quot; data-source-url=&quot;https://www.postgresql.org/docs/current/manage-ag-templatedbs.html&quot;&gt;
&lt;div class=&quot;og-image&quot; style=&quot;background-image: url('https://scrap.kakaocdn.net/dn/MTtQc/hyQDrTYoaa/hRnTu2OgSKVPwUCumRGzSk/img.png?width=540&amp;amp;height=557&amp;amp;face=0_0_540_557');&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;23.3.&amp;nbsp;Template Databases&lt;/p&gt;
&lt;p class=&quot;og-desc&quot; data-ke-size=&quot;size16&quot;&gt;23.3.&amp;nbsp;Template Databases CREATE DATABASE actually works by copying an existing database. By default, it copies the standard system database named &amp;hellip;&lt;/p&gt;
&lt;p class=&quot;og-host&quot; data-ke-size=&quot;size16&quot;&gt;www.postgresql.org&lt;/p&gt;
&lt;/div&gt;
&lt;/a&gt;&lt;/figure&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;</description>
      <category>서버 인프라/DB</category>
      <author>트리맨스</author>
      <guid isPermaLink="true">https://tre2man.tistory.com/330</guid>
      <comments>https://tre2man.tistory.com/330#entry330comment</comments>
      <pubDate>Mon, 21 Nov 2022 21:45:07 +0900</pubDate>
    </item>
    <item>
      <title>웹사이트 접속 시에 무슨 일이 벌어지는가?</title>
      <link>https://tre2man.tistory.com/329</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;&amp;nbsp;&lt;/p&gt;
&lt;h3 data-ke-size=&quot;size23&quot;&gt;용어정리&lt;/h3&gt;
&lt;hr contenteditable=&quot;false&quot; data-ke-type=&quot;horizontalRule&quot; data-ke-style=&quot;style5&quot; /&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;DNS : 도메인 네임 (시스템) 서버는 URL들의 이름과 IP주소를 저장하고 있는 DB이다. 아이피 주소와 URL을 매핑시켜준다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;TCP/IP : 데이터가 웹을 이동하는 방법을 나타내는 통신 규격이다.&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;&amp;nbsp;&lt;/p&gt;
&lt;h3 data-ke-size=&quot;size23&quot;&gt;브라우저가 해당 도메인주소와 대응하는 IP주소를 확인&lt;/h3&gt;
&lt;hr contenteditable=&quot;false&quot; data-ke-type=&quot;horizontalRule&quot; data-ke-style=&quot;style5&quot; /&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;먼저 해야할 것은 나의 IP주소, DNS서버의 IP주소를 찾아야 한다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;위 작업을 하기 위해서는 LAN 밖으로 나가는 Gateway Router를 찾아야 한다. 이것의 IP를 얻기 위해서는 라우팅 테이블이 필요한데, 이것은 DHCP가 자동으로 관리하거나 수동으로 관리한다. DHCP가 관리할 경우에 라우팅 테이블을 얻기 위해서 DHCP 브로드캐스트를 보내고, DHCP에서 Gateway Router의 IP, 나의 IP, DNS서버의 IP를 얻는다.&amp;nbsp;이후에는 Gateway Router의 Mac 주소가 필요한데, ARP통신을 이용해서 Gateway Router의 Mac 주소를 얻는다. 이제서야 패킷이 LAN밖으로 나갈 수 있다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;다음으로 할 일은 도메인이 가르키는 IP주소를 얻어야 한다. 이 IP주소를 통해서 해당 웹사이트를 호스팅하고 있는 서버 컴퓨터에 접근이 가능하다. 구글의 IP주소를 알아보고 싶으면 nslookup &lt;a href=&quot;http://www.google.com을&quot;&gt;www.google.com을&lt;/a&gt; 입력하면 IP주소가 나오게 된다. 구글 사용자는 매우 많아서 서버 IP주소는 여러개가 있고, nslookup은 내가 접근 가능한 IP주소를 보여준다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;브라우저에 웹사이트 이름을 치면 4개의 캐시에서 IP 주소를 확인한다.&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;OS 캐시 확인 (systemcall을 활용)&lt;/li&gt;
&lt;li&gt;router 캐시 확인 (DNS 기록을 캐싱하고있는 router와 통신)&lt;/li&gt;
&lt;li&gt;ISP 캐시 확인 (ISP는 DNS서버를 구축하고 있으므로 혹시 모르니 확인)&lt;/li&gt;
&lt;/ol&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;여기서 캐시를 찾으면 다음 단계로 넘어가고, 캐시를 찾지 못하면 DNS query를 날려서 아이피 주소를 찾으려고 한다. 여기서 ISP의 DNS서버를 DNS cursor라고 부르고, 여러 DNS 서버를 오가면서 계속 검색한다. 도메인 이름의 구조에 기반해서 검색하는데, 도메인 구조는 아래의 사진과 같다.&lt;/p&gt;
&lt;p&gt;&lt;figure class=&quot;imageblock alignCenter&quot; data-ke-mobileStyle=&quot;widthOrigin&quot; data-filename=&quot;domail.png&quot; data-origin-width=&quot;964&quot; data-origin-height=&quot;480&quot;&gt;&lt;span data-url=&quot;https://blog.kakaocdn.net/dn/ctBm51/btrRBd8jMQK/up8hDuTq0gEXlb4nVeRjRk/img.png&quot; data-phocus=&quot;https://blog.kakaocdn.net/dn/ctBm51/btrRBd8jMQK/up8hDuTq0gEXlb4nVeRjRk/img.png&quot;&gt;&lt;img src=&quot;https://blog.kakaocdn.net/dn/ctBm51/btrRBd8jMQK/up8hDuTq0gEXlb4nVeRjRk/img.png&quot; srcset=&quot;https://img1.daumcdn.net/thumb/R1280x0/?scode=mtistory2&amp;fname=https%3A%2F%2Fblog.kakaocdn.net%2Fdn%2FctBm51%2FbtrRBd8jMQK%2Fup8hDuTq0gEXlb4nVeRjRk%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;377&quot; data-filename=&quot;domail.png&quot; data-origin-width=&quot;964&quot; data-origin-height=&quot;480&quot;/&gt;&lt;/span&gt;&lt;/figure&gt;
&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;각 레벨별로 name서버가 있으며, 여기서 DNS lookup 프로세스에서 쿼리가 진행된다. 검색 순서는 DNS cursor가 루트 네임 서버에 연락하면, 그림에서 보듯이 위에서 아래로 진행되며 IP주소를 찾게 되면 DNS recursor로 보내게 된다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;갑자기 궁금해졌는데, DNS서버에서 IP를 따와야지 사이트 로딩이 시작되는거라 DNS서버는 응답속도가 상당히 중요할 것 같은 생각이 들어서 &lt;a href=&quot;https://www.dnsperf.com/#!dns-providers,Asia,resolver_simulation&quot; target=&quot;_blank&quot; rel=&quot;noopener&quot;&gt;속도 비교하는 사이트&lt;/a&gt;에 가서 비교를 해 보았다.&lt;/p&gt;
&lt;p&gt;&lt;figure class=&quot;imageblock alignCenter&quot; data-ke-mobileStyle=&quot;widthOrigin&quot; data-filename=&quot;스크린샷 2022-11-19 22.47.06.png&quot; data-origin-width=&quot;859&quot; data-origin-height=&quot;1175&quot;&gt;&lt;span data-url=&quot;https://blog.kakaocdn.net/dn/dcrJPp/btrRAih6pNR/s5GuspsxDKn5qhvC6Yh6QK/img.png&quot; data-phocus=&quot;https://blog.kakaocdn.net/dn/dcrJPp/btrRAih6pNR/s5GuspsxDKn5qhvC6Yh6QK/img.png&quot;&gt;&lt;img src=&quot;https://blog.kakaocdn.net/dn/dcrJPp/btrRAih6pNR/s5GuspsxDKn5qhvC6Yh6QK/img.png&quot; srcset=&quot;https://img1.daumcdn.net/thumb/R1280x0/?scode=mtistory2&amp;fname=https%3A%2F%2Fblog.kakaocdn.net%2Fdn%2FdcrJPp%2FbtrRAih6pNR%2Fs5GuspsxDKn5qhvC6Yh6QK%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;572&quot; height=&quot;782&quot; data-filename=&quot;스크린샷 2022-11-19 22.47.06.png&quot; data-origin-width=&quot;859&quot; data-origin-height=&quot;1175&quot;/&gt;&lt;/span&gt;&lt;/figure&gt;
&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;Asia 지역에서는Cloudflare가 제일 빠르다고 나온다. 그런데 여기는 무료 네임서버에 SSL인증서도 준다. 만약 네임서버의 응답 속도를 빠르게 하고 싶다면, Cloudflare로 네임서버를 옮기는 것도 좋아 보인다. 하지만 난 AWS 사용자라 그냥 Route53 씀. 솔직히 그게 편하다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;h3 data-ke-size=&quot;size23&quot;&gt;브라우저와 서버 TCP connection&lt;/h3&gt;
&lt;hr contenteditable=&quot;false&quot; data-ke-type=&quot;horizontalRule&quot; data-ke-style=&quot;style5&quot; /&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;드디어 IP주소를 받았다. 이제 가상 회선을 수립해야 한다. 일반적으로 HTTP요청의 경우에는 TCP를 사용하므로, TCP connection을 사용해서 연결한다. TCP/TP three-way handshake 프로세스를 통해서 커넥션이 이루어진다. SYN과 ACK 메시지를 이용한다.&lt;/p&gt;
&lt;ol style=&quot;list-style-type: decimal;&quot; data-ke-list-type=&quot;decimal&quot;&gt;
&lt;li&gt;클라이언트에서 SYN(n)패킷을 서버에 보내고 커넥션 요청&lt;/li&gt;
&lt;li&gt;서버가 커넥션 가능한 포트가 있다면 ACK(n+1) + SYN(m)패킷으로 응답&lt;/li&gt;
&lt;li&gt;클라이언트는 SYN/ACK패킷을 받고 서버에세 ACK(m+1)패킷을 보냄&lt;/li&gt;
&lt;/ol&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;TCP 커넥션이 완료되었다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;h3 data-ke-size=&quot;size23&quot;&gt;브라우저가 웹 서버에 HTTP 요청&lt;/h3&gt;
&lt;hr contenteditable=&quot;false&quot; data-ke-type=&quot;horizontalRule&quot; data-ke-style=&quot;style5&quot; /&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;TCP 연결 이후 데이터를 전송하면 된다. 브라우저는 GET 요청을 통해 웹페이지를 요구한다. 이 때 GET 요청 안의 헤더, 쿠키, 데이터 등을 담아서 요청한다. 이거는 postman이나 axios, fetch 등의 도구를 사용해봤다면 한번쯤 들어봤을 법 하다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;h3 data-ke-size=&quot;size23&quot;&gt;서버에서 요청 받은 후 요청 반환&lt;/h3&gt;
&lt;hr contenteditable=&quot;false&quot; data-ke-type=&quot;horizontalRule&quot; data-ke-style=&quot;style5&quot; /&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;서버에서는 브라우저가 요청한 정보들을 기반으로 그에 맞는 응답값을 보낸다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;서버 애플리케이션은 request handler한테 요청을 전달한 후 response를 생성하게 한다. response는 요청한 웹페이지, status-code, content-encoding, caching-control, 캐시 등의 정보들을 종합해서 반환한다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;h3 data-ke-size=&quot;size23&quot;&gt;브라우저에서 HTML 컨텐츠 보여주기&lt;/h3&gt;
&lt;hr contenteditable=&quot;false&quot; data-ke-type=&quot;horizontalRule&quot; data-ke-style=&quot;style5&quot; /&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;브라우저에서는 웹 사이트의 컨텐츠들을 알맞게 조합해서 사용자가 사용할 수 있는 웹사이트를 보여준다. 여기서는 HTML, CSS, JS 등의 컨텐츠들이 코드에 맞게 보여지게 된다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;</description>
      <category>개발 지식/웹</category>
      <author>트리맨스</author>
      <guid isPermaLink="true">https://tre2man.tistory.com/329</guid>
      <comments>https://tre2man.tistory.com/329#entry329comment</comments>
      <pubDate>Sat, 19 Nov 2022 21:15:36 +0900</pubDate>
    </item>
    <item>
      <title>대학생은 무슨 노트북을 사용해야 할까?</title>
      <link>https://tre2man.tistory.com/328</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;얼마전에 다나와에서 나온 설문조사를 보았다. 다나와에서 만 14세 이상을 대상으로 노트북 브랜드 선호도를 조사한 결과가 있었다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;a href=&quot;https://dpg.danawa.com/news/view?boardSeq=64&amp;amp;listSeq=5189216&quot;&gt;https://dpg.danawa.com/news/view?boardSeq=64&amp;amp;listSeq=5189216&lt;/a&gt;&amp;nbsp;&lt;/p&gt;
&lt;figure id=&quot;og_1668785662273&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;10대 절반이 '맥북' 노트북 선호... 30대는 삼성전자 노트북&quot; data-og-description=&quot;다나와가 오픈서베이에 의뢰해 만 14세 이상 300명(10대부터 50대 각 연령대 별 60명)을 대상으로 노트북 브랜드에 대한 선호도를 조사한 결과 연령대 별로 브랜드 선호 차이가 큰 것으로 나타났다.&quot; data-og-host=&quot;dpg.danawa.com&quot; data-og-source-url=&quot;https://dpg.danawa.com/news/view?boardSeq=64&amp;amp;listSeq=5189216&quot; data-og-url=&quot;https://dpg.danawa.com/news/view?boardSeq=64&amp;amp;listSeq=5189216&quot; data-og-image=&quot;https://scrap.kakaocdn.net/dn/brVPsx/hyQDpfCZaB/pVer44YWy2hZAafzqJNbdK/img.jpg?width=140&amp;amp;height=140&amp;amp;face=0_0_140_140&quot;&gt;&lt;a href=&quot;https://dpg.danawa.com/news/view?boardSeq=64&amp;amp;listSeq=5189216&quot; target=&quot;_blank&quot; rel=&quot;noopener&quot; data-source-url=&quot;https://dpg.danawa.com/news/view?boardSeq=64&amp;amp;listSeq=5189216&quot;&gt;
&lt;div class=&quot;og-image&quot; style=&quot;background-image: url('https://scrap.kakaocdn.net/dn/brVPsx/hyQDpfCZaB/pVer44YWy2hZAafzqJNbdK/img.jpg?width=140&amp;amp;height=140&amp;amp;face=0_0_140_140');&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;10대 절반이 '맥북' 노트북 선호... 30대는 삼성전자 노트북&lt;/p&gt;
&lt;p class=&quot;og-desc&quot; data-ke-size=&quot;size16&quot;&gt;다나와가 오픈서베이에 의뢰해 만 14세 이상 300명(10대부터 50대 각 연령대 별 60명)을 대상으로 노트북 브랜드에 대한 선호도를 조사한 결과 연령대 별로 브랜드 선호 차이가 큰 것으로 나타났다.&lt;/p&gt;
&lt;p class=&quot;og-host&quot; data-ke-size=&quot;size16&quot;&gt;dpg.danawa.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;결과를 간단히 말하면, 10대 학생들의 맥북 선호도가 50% 정도로 상당히 높은 비율에 속해 있었다. 다나와 사이트 특성상 전자기기에 관심이 많은 사람들이 주로 사용한다는 점과 표본이 300명 정도로 매우 적다는 것을 생각해도 맥북의 선호도는 상당히 높은 것으로 나와있다. 이것을 처음 보고 든 생각은 애플이 브랜딩 하나만큼은 정말 잘 한다는 생각이 들었다. 같은 노트북인데다가 사용 용도도 극단적으로 다른 것도 아닌데, 이렇게 선호도가 다른 것이 참 신기했다.&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;여튼 내가 하고싶은 얘기는 저 설문지에서 대답한 10대 학생들이 해당 선호도를 그대로 대학 입학때까지 들고가게 된다면 저들 중 50%는 맥북을 살 것일라는 결론에 도달할 수도 있다. 하지만 맥북을 사면 생각하던 것들이 모두 다 되는것은 아니다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;h3 data-ke-size=&quot;size23&quot;&gt;경험담&lt;/h3&gt;
&lt;hr contenteditable=&quot;false&quot; data-ke-type=&quot;horizontalRule&quot; data-ke-style=&quot;style5&quot; /&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;일단 결론부터 말하면은, 입학 후 해당 학과에서 어떤 프로그램을 주로 쓰는지를 잘 알아둬야 한다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;위의 결론에 대한 이유로 첫번째는, 학과에 따라서 사용하는 프로그램이 다르고, 이에 따라서 맥에서는 실행되지 않는 프로그램이 있을 수 있다. 예를 들어 나같은 경우에는 전자공학부를 나왔다. 여기서 사용하는 프로그램중 하나가 orCad라고 회로설계를 하는 프로그램이 있다. 해당 프로그램은 맥에서는 실행되지 않으므로 부트캠프로 윈도우 설치 후 실행해야 한다. 하지만 M1맥 이후부터는 부트캠프도 지원하지 않으므로 사실상 사용을 못 한다고 봐야 한다. 노트북을 산 이유 중 하나가 전공과 관련된 프로그램을 실행해야 하는데, 해당 프로그램도 돌리지 못한다면 아무 의미가 없다고 생각한다.&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;두번째 이유는, 한글이 거의 안 된다. 만약 과에서 한글 프로그램 작성을 잘 안한다고 하면 상관이 없긴 한데, 대학교 특성상 한글 프로그램을 사용한 문서들을 많이 사용하기 때문에 학교와 연관된 문서를 작성할 때는 한글이 거의 필수적이라고 보면 된다. 맥 전용 한글 프로그램도 있긴 한데, 해당 프로그램은 2014년에 출시된 이후 업데이트 버전이 계속 없다가, 최근에 구독 버전으로 맥용 한글을 출시한 바 있다. 하지만 이것도 사용하기는 쉽지 않아 보인다. 대부분 윈도우 한글을 쓰다 보니 오류가 나도 해결책을 찾기 힘들고, 맥용 한글 프로그램을 학교에서 무료로 지원해주는 학교도 잘 없기 때문이다.&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;마지막으로는 지원되는 게임이 거의 없다고 보면 된다. 일단 롤은 맥을 지원하고 플레이 경험도 나쁘지 않다. 하지만 그 이외의 게임을 한다고 하면, 포기하는 것이 좋다. 게임하고 싶으면 근처의 PC방을 가야 한다. 이거는 게임을 하지 않는 사람이라면은, 노트북을 고르는 데 중요한 선택지가 되지는 않을 것 같다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;만약 특수한 목적이 있으면은 해당 목적에 맞는 노트북을 구입하는 것이 좋다. 예를들어 IOS 개발을 해야 한다하면, 맥북 이외의 노트북에서는 개발을 할 수 없기 때문에 무조건 맥북을 사야 한다. 하지만 윈도우에서만 실행되는 피스파이스를 무조건 써야 하는 환경이면, 윈도우 노트북을 사야 할 것이다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;h3 data-ke-size=&quot;size23&quot;&gt;나의 노트북 일대기&lt;/h3&gt;
&lt;hr contenteditable=&quot;false&quot; data-ke-type=&quot;horizontalRule&quot; data-ke-style=&quot;style5&quot; /&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;내 노트북 일대기는 다음과 같다. 1학년때 입학할때는 전자과 + 간단한 게임 때문에 무난한 엘지 노트북을 샀다. 16년도 당시에는 아직 CD를 사용하는 책이 드물게 있어서 일부러 CD롬을 지원하는 노트북으로 샀다. 윈도우 노트북이였으니 당연히 웬만한 게임은 잘 돌아가고, 피스파이스나 OrCAD같은 프로그램도 잘 돌아갔다.&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;&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;</description>
      <category>잡다한 이야기</category>
      <author>트리맨스</author>
      <guid isPermaLink="true">https://tre2man.tistory.com/328</guid>
      <comments>https://tre2man.tistory.com/328#entry328comment</comments>
      <pubDate>Sat, 19 Nov 2022 13:58:04 +0900</pubDate>
    </item>
    <item>
      <title>슬랙 점심 추천기 만들기</title>
      <link>https://tre2man.tistory.com/327</link>
      <description>&lt;p style=&quot;text-align: left;&quot; data-ke-size=&quot;size16&quot;&gt;&lt;br /&gt;직장에 있을 때 가장 고민을 많이 했던 시간은 역시 점심 먹을 때다. 매번 똑같은 회사생활에서 점심은 색다른 이벤트이며, 점심을 잘 먹였느냐에 따라서 오후 컨디션이 결정될 때가 있다. 여튼 점심을 먹기 위해 고민하는 시간이 길어질수록 점심시간은 짧아졌기에, 이런 현상을 어느정도 줄여줄 수 있는 점심 추천기를 만들어 보기로 했다.&lt;/p&gt;
&lt;p style=&quot;text-align: left;&quot; data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;h3 style=&quot;text-align: left;&quot; data-ke-size=&quot;size23&quot;&gt;목표&lt;/h3&gt;
&lt;hr data-ke-type=&quot;horizontalRule&quot; data-ke-style=&quot;style5&quot; /&gt;
&lt;ol style=&quot;list-style-type: decimal;&quot; data-ke-list-type=&quot;decimal&quot;&gt;
&lt;li&gt;슬랙과 연동하여 점심시간 10분전에 추천을 받았으면 좋겠다.&lt;/li&gt;
&lt;li&gt;식당 추가 및 삭제 가능&lt;/li&gt;
&lt;li&gt;혹시 모르니 즉시 추천 가능&lt;/li&gt;
&lt;/ol&gt;
&lt;p style=&quot;text-align: left;&quot; data-ke-size=&quot;size16&quot;&gt;해당 조건들을 가지고 점심을 추천할 수 있는(사내 직원들만 이용 가능한) 간단한 서비스를 만들어 보기로 했다.&lt;/p&gt;
&lt;p style=&quot;text-align: left;&quot; data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;h3 style=&quot;text-align: left;&quot; data-ke-size=&quot;size23&quot;&gt;구상&lt;/h3&gt;
&lt;hr data-ke-type=&quot;horizontalRule&quot; data-ke-style=&quot;style5&quot; /&gt;
&lt;p style=&quot;text-align: left;&quot; data-ke-size=&quot;size16&quot;&gt;먼저 비용이 적게 들고 빨리 제작할 수 있어야 한다. 이것은 예전에 만들었던 람다를 사용해서 만들어 보기로 했다.&lt;br /&gt;하지만 람다를 사용하게 되면 함수를 콜드 스타트로 호출할 때 마다 로컬에 있는 정보가 사라지게 된다. 이를 해결하기 위해서 온디맨드 다이나모DB와 연동하여 데이터를 게속 저장할 수 있게 하면서도 비용도 매우 적게 들게 하였다.&lt;br /&gt;그외 다양한 서버리스 프레임워크의 사용법과 배포 방법은 아래 사이트에서 자세하게 나와있다.&lt;br /&gt;&lt;a href=&quot;https://tre2man.tistory.com/299&quot; target=&quot;_blank&quot; rel=&quot;noopener&quot;&gt;&lt;span&gt;https://tre2man.tistory.com/299&lt;/span&gt;&lt;/a&gt;&lt;/p&gt;
&lt;figure data-ke-type=&quot;opengraph&quot; data-og-title=&quot;Typescript를 이용한 어플리케이션 Lambda에 배포하기&quot; data-ke-align=&quot;alignCenter&quot; data-og-description=&quot;요즘 lambda가 재밌어 보여서 계속 알아보는 중이다. 람다 (서버리스) 애플리케이션에 대한 설명은 다음 사이트에 잘 나와있다. https://www.redhat.com/ko/topics/cloud-native-apps/what-is-serverless 서버리스란? &quot; data-og-host=&quot;tre2man.tistory.com&quot; data-og-source-url=&quot;https://tre2man.tistory.com/299&quot; data-og-image=&quot;https://scrap.kakaocdn.net/dn/sdOSq/hyQzjnlqzo/nMbaKjy6I2tJRKK3jkN39k/img.png?width=800&amp;amp;height=800&amp;amp;face=0_0_800_800,https://scrap.kakaocdn.net/dn/brEwD7/hyQzmdhTdz/mZNcqHIikLZQxOfGoeasK1/img.png?width=800&amp;amp;height=800&amp;amp;face=0_0_800_800&quot; data-og-url=&quot;https://tre2man.tistory.com/299&quot;&gt;&lt;a href=&quot;https://tre2man.tistory.com/299&quot; target=&quot;_blank&quot; rel=&quot;noopener&quot; data-source-url=&quot;https://tre2man.tistory.com/299&quot;&gt;
&lt;div class=&quot;og-image&quot; style=&quot;background-image: url('https://scrap.kakaocdn.net/dn/sdOSq/hyQzjnlqzo/nMbaKjy6I2tJRKK3jkN39k/img.png?width=800&amp;amp;height=800&amp;amp;face=0_0_800_800,https://scrap.kakaocdn.net/dn/brEwD7/hyQzmdhTdz/mZNcqHIikLZQxOfGoeasK1/img.png?width=800&amp;amp;height=800&amp;amp;face=0_0_800_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;Typescript를 이용한 어플리케이션 Lambda에 배포하기&lt;/p&gt;
&lt;p class=&quot;og-desc&quot; data-ke-size=&quot;size16&quot;&gt;요즘 lambda가 재밌어 보여서 계속 알아보는 중이다. 람다 (서버리스) 애플리케이션에 대한 설명은 다음 사이트에 잘 나와있다. https://www.redhat.com/ko/topics/cloud-native-apps/what-is-serverless 서버리스란?&lt;/p&gt;
&lt;p class=&quot;og-host&quot; data-ke-size=&quot;size16&quot;&gt;tre2man.tistory.com&lt;/p&gt;
&lt;/div&gt;
&lt;/a&gt;&lt;/figure&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;&amp;nbsp;&lt;/p&gt;
&lt;h3 style=&quot;text-align: left;&quot; data-ke-size=&quot;size23&quot;&gt;다이나모 DB 연동&lt;/h3&gt;
&lt;hr data-ke-type=&quot;horizontalRule&quot; data-ke-style=&quot;style5&quot; /&gt;
&lt;p style=&quot;text-align: left;&quot; data-ke-size=&quot;size16&quot;&gt;AWS는 nodejs용 라이브러리를 제공하고 있다. AWS 계정의 키 파일을 만든 후, 간단한 세팅을 통해서 dynamoDB와 연동하자.&lt;br /&gt;먼저 AWS 콘솔에서 다이나모 DB를 제작한다. 이 때 생성하려는 다이나모DB는 온디맨드 모드로 제작하면 비용이 거의 나오지 않는다.&lt;br /&gt;&lt;br /&gt;이후 NodeJS 환경에서 다음과 같이 코드를 작성하면 AWS의 설정 및 다이나모DB 객체를 불러올 수 있다.&lt;/p&gt;
&lt;pre class=&quot;html xml&quot; data-ke-type=&quot;codeblock&quot; data-ke-language=&quot;html&quot;&gt;&lt;code&gt;import AWS, { Credentials } from &quot;aws-sdk&quot;;

AWS.config.update({
&amp;nbsp;&amp;nbsp;region: &quot;ap-northeast-2&quot;,
&amp;nbsp;&amp;nbsp;credentials: new Credentials({
&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;accessKeyId: process.env.MY_AWS_ACCESS_KEY_ID as string,
&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;secretAccessKey: process.env.MY_AWS_SECRET_ACCESS_KEY as string,
&amp;nbsp;&amp;nbsp;}),
});

const dynamo = new AWS.DynamoDB.DocumentClient();&lt;/code&gt;&lt;/pre&gt;
&lt;p style=&quot;text-align: left;&quot; data-ke-size=&quot;size16&quot;&gt;&lt;br /&gt;가장 최신 버전에서는 credential에 들어가는 원소가 객체였다. 도큐먼트에도 나오지 않았다. 항상 타입을 잘 확인하고 사용을 해야 할 것 같다. &lt;br /&gt;&lt;br /&gt;다이나모DB는 NoSQL기반의 DB이고, 서버리스 기반이다. 그래서 key-value값으로 이루어진 거대한 JSON파일이라고 생각하면 편하다. 테이블을 제작할 때 칼럼을 미리 만들어 두지 않아도 알아서 잘 검색하니, 바로 쓰면 된다. 예시로 데이터를 조회하는 코드만 하나 예시로 올려 둔다. 나머지 작업에 대해서는 공식 문서에 자세히 적혀 있었다.&lt;/p&gt;
&lt;pre class=&quot;html xml&quot; data-ke-type=&quot;codeblock&quot; data-ke-language=&quot;html&quot;&gt;&lt;code&gt;/**
 * 데이터 조회
 * @param name 탐색할 key
 */
export const readData = async (name?: string): Promise&amp;lt;MenuInfo[]&amp;gt; =&amp;gt; {
&amp;nbsp;&amp;nbsp;try {
&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;if (!!name) {
&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;return new Promise((resolve) =&amp;gt; {
&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;dynamo.get(
&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;&amp;nbsp;TableName: &quot;lunch-list&quot;,
&amp;nbsp;&amp;nbsp;&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;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;name,
&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;&amp;nbsp;},
&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;(err, data) =&amp;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 (!!data.Item) {
&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;resolve([data.Item] as MenuInfo[]);
&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;&amp;nbsp;&amp;nbsp;&amp;nbsp;resolve([]);
&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;&amp;nbsp;&amp;nbsp;&amp;nbsp;});
&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;} else {
&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;return new Promise((resolve) =&amp;gt; {
&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;dynamo.scan(
&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;&amp;nbsp;TableName: &quot;lunch-list&quot;,
&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;(err, data) =&amp;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;resolve(data.Items as MenuInfo[]);
&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;&amp;nbsp;&amp;nbsp;&amp;nbsp;});
&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;}
&amp;nbsp;&amp;nbsp;} catch (e) {
&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;return [];
&amp;nbsp;&amp;nbsp;}
};&lt;/code&gt;&lt;/pre&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;&amp;nbsp;&lt;/p&gt;
&lt;h3 style=&quot;text-align: left;&quot; data-ke-size=&quot;size23&quot;&gt;크론 동작&lt;/h3&gt;
&lt;hr data-ke-type=&quot;horizontalRule&quot; data-ke-style=&quot;style5&quot; /&gt;
&lt;p style=&quot;text-align: left;&quot; data-ke-size=&quot;size16&quot;&gt;현재 프로그램은 서버리스 프레임워크를 기반으로 제작하고, 배포 환경도 람다인지라 자체적인 크론 트리거를 제작하여 배포할 수 있다. 해당 동작은 serverless.yml안에 있으며, 트리거의 timezone은 서울로 해야지 한국 지역에서 cron작업이 제시간에 잘 동작했다.&lt;br /&gt;아래에 있는 코드는 월~금 11:50시 마다 크론 동작을 수행한다. 점심 10분전에 밥을 추천하는 로직을 실행한다.&lt;/p&gt;
&lt;pre class=&quot;shell&quot; data-ke-type=&quot;codeblock&quot; data-ke-language=&quot;shell&quot;&gt;&lt;code&gt;service: lunch-recommendation

frameworkVersion: &quot;3&quot;

plugins:
&amp;nbsp;&amp;nbsp;- serverless-plugin-typescript
&amp;nbsp;&amp;nbsp;- serverless-offline
&amp;nbsp;&amp;nbsp;- serverless-local-schedule
useDotenv: true

provider:
&amp;nbsp;&amp;nbsp;name: aws
&amp;nbsp;&amp;nbsp;runtime: nodejs16.x
&amp;nbsp;&amp;nbsp;region: ap-northeast-2
&amp;nbsp;&amp;nbsp;environment:
&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;MY_AWS_ACCESS_KEY_ID: ${env:AWS_ACCESS_KEY_ID}
&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;MY_AWS_SECRET_ACCESS_KEY: ${env:AWS_SECRET_ACCESS_KEY}

functions:
&amp;nbsp;&amp;nbsp;recommend-cron:
&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;handler: src/recommend-cron.main
&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;events:
&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;- schedule: 
&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;rate : cron(50 11 ? * TUE-SAT *)
&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;timezone: Asia/Seoul&lt;/code&gt;&lt;/pre&gt;
&lt;p style=&quot;text-align: left;&quot; data-ke-size=&quot;size16&quot;&gt;&lt;br /&gt;serverless 기반의 서버를 다 작성했으면, 여러 config값들을 설정한 다음 serverless offline 명령어로 배포를 시작한다. 이 때 생성되는 API 주소는 다음에 나올 슬랙과 연동할 때 필요한 주소다. 나중에 API Gateway에 가도 볼 수 있다.&lt;/p&gt;
&lt;p style=&quot;text-align: left;&quot; data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;h3 style=&quot;text-align: left;&quot; data-ke-size=&quot;size23&quot;&gt;슬랙과 연동&lt;/h3&gt;
&lt;hr data-ke-type=&quot;horizontalRule&quot; data-ke-style=&quot;style5&quot; /&gt;
&lt;p style=&quot;text-align: left;&quot; data-ke-size=&quot;size16&quot;&gt;슬랙에는 '슬래시 커맨드' 라는 기능이 있다. 슬랙 채널에서 '/' + '메시지' + '옵션' 을 대화창에 날리면, 메시지가 특정한 명령어로 인식되어 동작을 수행한다. 이러한 기능을 통해서 메뉴 추가/삭제/추천 기능을 만들어 보려고 한다.&lt;br /&gt;&lt;br /&gt;생성된 앱의 설정에서 Slash Commands 메뉴를 선택한다. 이후 원하는 커맨드와 API 주소를 매칭하여 추가한다. 자세한 내용은 글의 하단에 있는 공식 문서에 자세히 나와 있다.&lt;/p&gt;
&lt;p&gt;&lt;figure class=&quot;imageblock alignCenter&quot; data-ke-mobileStyle=&quot;widthOrigin&quot; data-origin-width=&quot;1460&quot; data-origin-height=&quot;1090&quot;&gt;&lt;span data-url=&quot;https://blog.kakaocdn.net/dn/crTUZh/btrRcOm5vvL/JIyruX04TJV3PqCmyDVPQk/img.png&quot; data-phocus=&quot;https://blog.kakaocdn.net/dn/crTUZh/btrRcOm5vvL/JIyruX04TJV3PqCmyDVPQk/img.png&quot;&gt;&lt;img src=&quot;https://blog.kakaocdn.net/dn/crTUZh/btrRcOm5vvL/JIyruX04TJV3PqCmyDVPQk/img.png&quot; srcset=&quot;https://img1.daumcdn.net/thumb/R1280x0/?scode=mtistory2&amp;fname=https%3A%2F%2Fblog.kakaocdn.net%2Fdn%2FcrTUZh%2FbtrRcOm5vvL%2FJIyruX04TJV3PqCmyDVPQk%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;470&quot; data-origin-width=&quot;1460&quot; data-origin-height=&quot;1090&quot;/&gt;&lt;/span&gt;&lt;/figure&gt;
&lt;/p&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;&amp;nbsp;&lt;/p&gt;
&lt;h3 style=&quot;text-align: left;&quot; data-ke-size=&quot;size23&quot;&gt;자동배포&lt;/h3&gt;
&lt;hr data-ke-type=&quot;horizontalRule&quot; data-ke-style=&quot;style5&quot; /&gt;
&lt;p style=&quot;text-align: left;&quot; data-ke-size=&quot;size16&quot;&gt;배포까지 자동으로 하면 좋겠다는 생각이 들어서, github action을 통해서 간단히 배포하기로 했다. 배포 환경은 node 16.x 버전의 인스턴스를 기반으로 했고, 서버리스를 설치한 뒤 배포를 실행했다. aws 관련 보안키 파일만 secrets에 지정하면 바로 배포가 되었다.&lt;/p&gt;
&lt;pre class=&quot;shell&quot; data-ke-type=&quot;codeblock&quot; data-ke-language=&quot;shell&quot;&gt;&lt;code&gt;name: deploy kakaobob

on:
&amp;nbsp;&amp;nbsp;push:
&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;branches: [&quot;main&quot;]
&amp;nbsp;&amp;nbsp;pull_request:
&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;branches: [&quot;main&quot;]

env:
&amp;nbsp;&amp;nbsp;AWS_ACCESS_KEY_ID: ${{ secrets.AWS_ACCESS_KEY_ID }}
&amp;nbsp;&amp;nbsp;AWS_SECRET_ACCESS_KEY: ${{ secrets.AWS_SECRET_ACCESS_KEY }}

jobs:
&amp;nbsp;&amp;nbsp;build:
&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;runs-on: ubuntu-latest
&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;strategy:
&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;matrix:
&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;node-version: [16.x]
&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;steps:
&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;- uses: actions/checkout@v3
&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;- name: Use Node.js ${{ matrix.node-version }}
&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;uses: actions/setup-node@v3
&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;with:
&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;node-version: ${{ matrix.node-version }}
&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;cache: &quot;npm&quot;
&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;- name: Install dependencies
&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;run: npm ci
&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;- name: Install serverless
&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;run: npm install -g serverless
&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;- name: Deploy
&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;run: npm run deploy:ci&lt;/code&gt;&lt;/pre&gt;
&lt;p style=&quot;text-align: left;&quot; data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;h3 style=&quot;text-align: left;&quot; data-ke-size=&quot;size23&quot;&gt;결과 확인&lt;/h3&gt;
&lt;hr data-ke-type=&quot;horizontalRule&quot; data-ke-style=&quot;style5&quot; /&gt;
&lt;p style=&quot;text-align: left;&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;780&quot; data-origin-height=&quot;624&quot;&gt;&lt;span data-url=&quot;https://blog.kakaocdn.net/dn/eccxeV/btrQ2TaPCJ5/muG06hzFUwUpa6u142iEkk/img.png&quot; data-phocus=&quot;https://blog.kakaocdn.net/dn/eccxeV/btrQ2TaPCJ5/muG06hzFUwUpa6u142iEkk/img.png&quot;&gt;&lt;img src=&quot;https://blog.kakaocdn.net/dn/eccxeV/btrQ2TaPCJ5/muG06hzFUwUpa6u142iEkk/img.png&quot; srcset=&quot;https://img1.daumcdn.net/thumb/R1280x0/?scode=mtistory2&amp;fname=https%3A%2F%2Fblog.kakaocdn.net%2Fdn%2FeccxeV%2FbtrQ2TaPCJ5%2FmuG06hzFUwUpa6u142iEkk%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;515&quot; height=&quot;412&quot; data-origin-width=&quot;780&quot; data-origin-height=&quot;624&quot;/&gt;&lt;/span&gt;&lt;/figure&gt;
&lt;/p&gt;
&lt;p style=&quot;text-align: left;&quot; data-ke-size=&quot;size16&quot;&gt;전체 코드는 아래 링크되어 있다.&lt;/p&gt;
&lt;p style=&quot;text-align: left;&quot; data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;h3 style=&quot;text-align: left;&quot; data-ke-size=&quot;size23&quot;&gt;참고자료&lt;/h3&gt;
&lt;hr data-ke-type=&quot;horizontalRule&quot; data-ke-style=&quot;style5&quot; /&gt;
&lt;p style=&quot;text-align: left;&quot; data-ke-size=&quot;size16&quot;&gt;슬래시 커맨드&lt;br /&gt;&lt;a href=&quot;https://api.slack.com/interactivity/slash-commands#what_are_commands&quot; target=&quot;_blank&quot; rel=&quot;noopener&quot;&gt;&lt;span&gt;https://api.slack.com/interactivity/slash-commands#what_are_commands&lt;/span&gt;&lt;/a&gt;&lt;/p&gt;
&lt;figure data-ke-type=&quot;opengraph&quot; data-og-title=&quot;Enabling interactivity with Slash Commands | Slack&quot; data-ke-align=&quot;alignCenter&quot; data-og-description=&quot;What are Slash Commands? Slash Commands allow users to invoke your app by typing a string into the message composer box. A submitted Slash Command will cause a payload of data to be sent from Slack to the associated app. The app can then respond in whateve&quot; data-og-host=&quot;api.slack.com&quot; data-og-source-url=&quot;https://api.slack.com/interactivity/slash-commands#what_are_commands&quot; data-og-image=&quot;&quot; data-og-url=&quot;https://api.slack.com/interactivity/slash-commands#what_are_commands&quot;&gt;&lt;a href=&quot;https://api.slack.com/interactivity/slash-commands#what_are_commands&quot; target=&quot;_blank&quot; rel=&quot;noopener&quot; data-source-url=&quot;https://api.slack.com/interactivity/slash-commands#what_are_commands&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;Enabling interactivity with Slash Commands | Slack&lt;/p&gt;
&lt;p class=&quot;og-desc&quot; data-ke-size=&quot;size16&quot;&gt;What are Slash Commands? Slash Commands allow users to invoke your app by typing a string into the message composer box. A submitted Slash Command will cause a payload of data to be sent from Slack to the associated app. The app can then respond in whateve&lt;/p&gt;
&lt;p class=&quot;og-host&quot; data-ke-size=&quot;size16&quot;&gt;api.slack.com&lt;/p&gt;
&lt;/div&gt;
&lt;/a&gt;&lt;/figure&gt;
&lt;p style=&quot;text-align: left;&quot; data-ke-size=&quot;size16&quot;&gt;크론작업&lt;br /&gt;&lt;a href=&quot;https://www.serverless.com/examples/aws-node-scheduled-cron&quot; target=&quot;_blank&quot; rel=&quot;noopener&quot;&gt;&lt;span&gt;https://www.serverless.com/examples/aws-node-scheduled-cron&lt;/span&gt;&lt;/a&gt;&lt;/p&gt;
&lt;figure data-ke-type=&quot;opengraph&quot; data-og-title=&quot;AWS Node Scheduled Cron example in NodeJS&quot; data-ke-align=&quot;alignCenter&quot; data-og-description=&quot;This is an example of creating a function that runs as a cron job using the serverless 'schedule' event.&quot; data-og-host=&quot;www.serverless.com&quot; data-og-source-url=&quot;https://www.serverless.com/examples/aws-node-scheduled-cron&quot; data-og-image=&quot;https://scrap.kakaocdn.net/dn/XVgUu/hyQzel039J/8WLoE2T57VoPuYqnWBINY1/img.png?width=1200&amp;amp;height=630&amp;amp;face=0_0_1200_630&quot; data-og-url=&quot;https://www.serverless.com/examples/aws-node-scheduled-cron&quot;&gt;&lt;a href=&quot;https://www.serverless.com/examples/aws-node-scheduled-cron&quot; target=&quot;_blank&quot; rel=&quot;noopener&quot; data-source-url=&quot;https://www.serverless.com/examples/aws-node-scheduled-cron&quot;&gt;
&lt;div class=&quot;og-image&quot; style=&quot;background-image: url('https://scrap.kakaocdn.net/dn/XVgUu/hyQzel039J/8WLoE2T57VoPuYqnWBINY1/img.png?width=1200&amp;amp;height=630&amp;amp;face=0_0_1200_630');&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;AWS Node Scheduled Cron example in NodeJS&lt;/p&gt;
&lt;p class=&quot;og-desc&quot; data-ke-size=&quot;size16&quot;&gt;This is an example of creating a function that runs as a cron job using the serverless 'schedule' event.&lt;/p&gt;
&lt;p class=&quot;og-host&quot; data-ke-size=&quot;size16&quot;&gt;www.serverless.com&lt;/p&gt;
&lt;/div&gt;
&lt;/a&gt;&lt;/figure&gt;
&lt;p style=&quot;text-align: left;&quot; data-ke-size=&quot;size16&quot;&gt;전체코드&lt;br /&gt;&lt;a href=&quot;https://github.com/tre2man/lunch-recommend&quot; target=&quot;_blank&quot; rel=&quot;noopener&quot;&gt;&lt;span&gt;https://github.com/tre2man/lunch-recommend&lt;/span&gt;&lt;/a&gt;&lt;/p&gt;
&lt;figure data-ke-type=&quot;opengraph&quot; data-og-title=&quot;GitHub - tre2man/lunch-recommend: 슬랙 점심 추천기&quot; data-ke-align=&quot;alignCenter&quot; data-og-description=&quot;슬랙 점심 추천기. Contribute to tre2man/lunch-recommend development by creating an account on GitHub.&quot; data-og-host=&quot;github.com&quot; data-og-source-url=&quot;https://github.com/tre2man/lunch-recommend&quot; data-og-image=&quot;https://scrap.kakaocdn.net/dn/7Ekwv/hyQzq7PAPD/XOcDTr6TkmsWrAMKDFxJfK/img.png?width=1200&amp;amp;height=600&amp;amp;face=0_0_1200_600&quot; data-og-url=&quot;https://github.com/tre2man/lunch-recommend&quot;&gt;&lt;a href=&quot;https://github.com/tre2man/lunch-recommend&quot; target=&quot;_blank&quot; rel=&quot;noopener&quot; data-source-url=&quot;https://github.com/tre2man/lunch-recommend&quot;&gt;
&lt;div class=&quot;og-image&quot; style=&quot;background-image: url('https://scrap.kakaocdn.net/dn/7Ekwv/hyQzq7PAPD/XOcDTr6TkmsWrAMKDFxJfK/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 - tre2man/lunch-recommend: 슬랙 점심 추천기&lt;/p&gt;
&lt;p class=&quot;og-desc&quot; data-ke-size=&quot;size16&quot;&gt;슬랙 점심 추천기. Contribute to tre2man/lunch-recommend development by creating an account on GitHub.&lt;/p&gt;
&lt;p class=&quot;og-host&quot; data-ke-size=&quot;size16&quot;&gt;github.com&lt;/p&gt;
&lt;/div&gt;
&lt;/a&gt;&lt;/figure&gt;
&lt;p style=&quot;text-align: left;&quot; data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;</description>
      <category>메이킹/메이킹 프로젝트</category>
      <author>트리맨스</author>
      <guid isPermaLink="true">https://tre2man.tistory.com/327</guid>
      <comments>https://tre2man.tistory.com/327#entry327comment</comments>
      <pubDate>Mon, 14 Nov 2022 16:02:29 +0900</pubDate>
    </item>
    <item>
      <title>[NestJS] 데이터 요청과 응답 사이의 Request lifecycle</title>
      <link>https://tre2man.tistory.com/326</link>
      <description>&lt;p data-ke-size=&quot;size16&quot; style=&quot;text-align: left;&quot;&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;455&quot;&gt;&lt;span data-url=&quot;https://blog.kakaocdn.net/dn/T2GQL/btrQvOHKCz7/GzFbrhK5VNLQSXRQLCGf01/img.jpg&quot; data-phocus=&quot;https://blog.kakaocdn.net/dn/T2GQL/btrQvOHKCz7/GzFbrhK5VNLQSXRQLCGf01/img.jpg&quot;&gt;&lt;img src=&quot;https://blog.kakaocdn.net/dn/T2GQL/btrQvOHKCz7/GzFbrhK5VNLQSXRQLCGf01/img.jpg&quot; srcset=&quot;https://img1.daumcdn.net/thumb/R1280x0/?scode=mtistory2&amp;fname=https%3A%2F%2Fblog.kakaocdn.net%2Fdn%2FT2GQL%2FbtrQvOHKCz7%2FGzFbrhK5VNLQSXRQLCGf01%2Fimg.jpg&quot; onerror=&quot;this.onerror=null; this.src='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png'; this.srcset='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png';&quot; loading=&quot;lazy&quot; width=&quot;1280&quot; height=&quot;455&quot; data-origin-width=&quot;1280&quot; data-origin-height=&quot;455&quot;/&gt;&lt;/span&gt;&lt;/figure&gt;

&lt;p data-ke-size=&quot;size16&quot; style=&quot;text-align: left;&quot;&gt; &lt;br&gt;NestJS를 쓰면서 갑자기 느낀 게 있었다. 바로 NestJS 내부에서 데이터의 흐름이 어떻게 되는지 궁금했다. Service단 에서의 데이터 흐름뿐 아니라 클라이언트에서 데이터를 요청한 이후 데이터를 응답할 때 까지의 흐름을 알고 싶었다. 그래서 공식 문서를 보며 정리하기로 했다. 공식 문서는 Request lifecycle이라고 하는데, 왠지 Data lifecycle이라는 이름이 더 맞는다.&lt;br&gt;참고로 express는 use 메서드에 온갖 미들웨어를 넣어버리는데, 이 때 데이터 흐름을 개발자가 직접 제어해 주어야 한다. express는 자유도는 매우 높은 프레임워크이나, 견고하고 가독성 좋은 코드를 만들기 힘들다.&lt;br&gt; &lt;/p&gt;
&lt;h3 style=&quot;text-align: left;&quot; data-ke-size=&quot;size23&quot;&gt;데이터 흐름&lt;/h3&gt;
&lt;hr data-ke-type=&quot;horizontalRule&quot; data-ke-style=&quot;style5&quot;&gt;
&lt;p data-ke-size=&quot;size16&quot; style=&quot;text-align: left;&quot;&gt;1. 가드&lt;br&gt;미들웨어 다음으로 오는 가드는 데이터가 들어올 때 처음으로 데이터를 판별하는 곳이다. 가드는 등록된 사용자 판별, 사용자의 역할 판별 등의 역할을 맡는다. 말 그대로 문지기인 셈이다. 아래 코드에서 UseGuards 데코레이터를 사용하는 곳이다.&lt;/p&gt;
&lt;pre data-ke-type=&quot;codeblock&quot; class=&quot;html&quot; data-ke-language=&quot;html&quot;&gt;&lt;code&gt;@UseGuards(Guard1, Guard2)
@Controller('cats')
export class CatsController {
&amp;nbsp;&amp;nbsp;constructor(private catsService: CatsService) {}

&amp;nbsp;&amp;nbsp;@UseGuards(Guard3)
&amp;nbsp;&amp;nbsp;@Get()
&amp;nbsp;&amp;nbsp;getCats(): Cats[] {
&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;return this.catsService.getCats();
&amp;nbsp;&amp;nbsp;}
}&lt;/code&gt;&lt;/pre&gt;
&lt;p data-ke-size=&quot;size16&quot; style=&quot;text-align: left;&quot;&gt; &lt;br&gt;2. 인터셉터&lt;br&gt;공식 문서에서는 AOP (Aspect Oriented Programming)에서 영감을 받았다고 한다. 아래와 같은 기능을 수행할 수 있다.&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;기본 기능 동작의 확장&lt;/li&gt;
 &lt;li&gt;특정 조건에 따라서 기능 재정의 (예 : 캐싱)&lt;/li&gt;
&lt;/ul&gt;
&lt;p data-ke-size=&quot;size16&quot; style=&quot;text-align: left;&quot;&gt;공식 문서의 예시는 인터셉터를 사용하여 로깅하는 것으로 보여준다. 여기서 인터셉터는 응답 또는 반환 데이터를 중간에 조작할 수 있으며, callHandler에서는 rxjs의 Observable값을 반환한다. 해당 응답 스트림에서 추가 작업을 하는 것 같아 보이는데, 이걸 또 잘 다루려면  rxjs같은 stream 함수형 프로그래밍 라이브러리를 따로 공부해야 할 거 같다.&lt;br&gt; &lt;br&gt;3. 파이프&lt;br&gt;데이터 이동 시 중간에 데이터를 변환(문자열 -&amp;gt; 숫자) 하거나 유효성 검사를 할 수 있게 한다. NestJS에서는 바로 사용할 수 있는 파이프를 9개나 주지만! 실사용 하다 보면 매우 적다. 그래서 보통 class-validator 같은 라이브러리를 가져와서 유효성 검사를 한다.&lt;br&gt; &lt;br&gt;4. 실제 로직&lt;br&gt;이제서야 Controller를 지나서 Service 안에 있는 로직이 실행이 된다.&lt;br&gt; &lt;br&gt;5. 응답 인터셉터&lt;br&gt;인터셉터는 앞에서는 요청받는 데이터에 대해서 작업했다면, 이번에는 응답하는 데이터에 대해서 한번 더 거쳐간다. &lt;br&gt; &lt;br&gt;예외) 예외 필터&lt;br&gt;보통 에러를 던짐 (throw new InternalServerException 같이). 이거는 전역으로 적용이 되므로, 특정 단계로 볼 수 없을거 같아서 예와로 처리해 두었다.&lt;br&gt; &lt;/p&gt;
&lt;h3 style=&quot;text-align: left;&quot; data-ke-size=&quot;size23&quot;&gt;그림으로 정리&lt;/h3&gt;
&lt;hr data-ke-type=&quot;horizontalRule&quot; data-ke-style=&quot;style5&quot;&gt;
&lt;p data-ke-size=&quot;size16&quot; style=&quot;text-align: left;&quot;&gt;위의 내용을 토대로 흐름도를 그리면 다음과 같아 보인다.&lt;/p&gt;&lt;figure class=&quot;imageblock alignCenter&quot; data-ke-mobileStyle=&quot;widthOrigin&quot; data-origin-width=&quot;1885&quot; data-origin-height=&quot;1004&quot;&gt;&lt;span data-url=&quot;https://blog.kakaocdn.net/dn/m5zGG/btrQq5RZHJu/pCt2GFikA6GX5dZ834pNwK/img.png&quot; data-phocus=&quot;https://blog.kakaocdn.net/dn/m5zGG/btrQq5RZHJu/pCt2GFikA6GX5dZ834pNwK/img.png&quot;&gt;&lt;img src=&quot;https://blog.kakaocdn.net/dn/m5zGG/btrQq5RZHJu/pCt2GFikA6GX5dZ834pNwK/img.png&quot; srcset=&quot;https://img1.daumcdn.net/thumb/R1280x0/?scode=mtistory2&amp;fname=https%3A%2F%2Fblog.kakaocdn.net%2Fdn%2Fm5zGG%2FbtrQq5RZHJu%2FpCt2GFikA6GX5dZ834pNwK%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;1885&quot; height=&quot;1004&quot; data-origin-width=&quot;1885&quot; data-origin-height=&quot;1004&quot;/&gt;&lt;/span&gt;&lt;/figure&gt;

&lt;p data-ke-size=&quot;size16&quot; style=&quot;text-align: left;&quot;&gt; &lt;br&gt; &lt;/p&gt;
&lt;h3 style=&quot;text-align: left;&quot; data-ke-size=&quot;size23&quot;&gt;참고자료&lt;/h3&gt;
&lt;p data-ke-size=&quot;size16&quot; style=&quot;text-align: left;&quot;&gt;&lt;a href=&quot;https://docs.nestjs.com/faq/request-lifecycle&quot; target=&quot;_blank&quot;&gt;&lt;span&gt;https://docs.nestjs.com/faq/request-lifecycle&lt;/span&gt;&lt;/a&gt;&lt;/p&gt;
&lt;figure data-ke-type=&quot;opengraph&quot; data-og-title=&quot;Documentation | NestJS - A progressive Node.js framework&quot; data-ke-align=&quot;alignCenter&quot; data-og-description=&quot;Nest is a framework for building efficient, scalable Node.js server-side applications. It uses progressive JavaScript, is built with TypeScript and combines elements of OOP (Object Oriented Progamming), FP (Functional Programming), and FRP (Functional Reac&quot; data-og-host=&quot;docs.nestjs.com&quot; data-og-source-url=&quot;https://docs.nestjs.com/faq/request-lifecycle&quot; data-og-image=&quot;https://scrap.kakaocdn.net/dn/cFk4We/hyQtA3bHEB/6mn1Kx2gmqwL0s2QDx3UE0/img.png?width=820&amp;amp;height=429&amp;amp;face=0_0_820_429&quot; data-og-url=&quot;https://docs.nestjs.com&quot;&gt;
 &lt;a href=&quot;https://docs.nestjs.com&quot; target=&quot;_blank&quot; data-source-url=&quot;https://docs.nestjs.com/faq/request-lifecycle&quot;&gt;
  &lt;div class=&quot;og-image&quot; style=&quot;background-image: url('https://scrap.kakaocdn.net/dn/cFk4We/hyQtA3bHEB/6mn1Kx2gmqwL0s2QDx3UE0/img.png?width=820&amp;amp;height=429&amp;amp;face=0_0_820_429')&quot;&gt; 
  &lt;/div&gt;
  &lt;div class=&quot;og-text&quot;&gt;
   &lt;p class=&quot;og-title&quot;&gt;Documentation | NestJS - A progressive Node.js framework&lt;/p&gt;
   &lt;p class=&quot;og-desc&quot;&gt;Nest is a framework for building efficient, scalable Node.js server-side applications. It uses progressive JavaScript, is built with TypeScript and combines elements of OOP (Object Oriented Progamming), FP (Functional Programming), and FRP (Functional Reac&lt;/p&gt;
   &lt;p class=&quot;og-host&quot;&gt;docs.nestjs.com&lt;/p&gt;
  &lt;/div&gt;&lt;/a&gt;
&lt;/figure&gt;
&lt;p data-ke-size=&quot;size16&quot; style=&quot;text-align: left;&quot;&gt; &lt;br&gt; &lt;/p&gt;</description>
      <category>프레임워크/NestJS</category>
      <author>트리맨스</author>
      <guid isPermaLink="true">https://tre2man.tistory.com/326</guid>
      <comments>https://tre2man.tistory.com/326#entry326comment</comments>
      <pubDate>Sun, 6 Nov 2022 01:40:59 +0900</pubDate>
    </item>
    <item>
      <title>This is not the tsc command you are looking for 에러 해결하기</title>
      <link>https://tre2man.tistory.com/325</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;Express를 JS에서 TS로 마이그레이션을 하는 도중, tsc를 사용해 js로 컴파일 할려고 tsc 명령어를 치니 다음과 같은 에러 메시지가 나왔다.&lt;/p&gt;
&lt;p&gt;&lt;figure class=&quot;imageblock alignCenter&quot; data-ke-mobileStyle=&quot;widthOrigin&quot; data-filename=&quot;스크린샷 2022-11-05 21.25.30.png&quot; data-origin-width=&quot;707&quot; data-origin-height=&quot;170&quot;&gt;&lt;span data-url=&quot;https://blog.kakaocdn.net/dn/c3OdKL/btrQqkWksIb/aowZrTfb5DmusWqoob4uC1/img.png&quot; data-phocus=&quot;https://blog.kakaocdn.net/dn/c3OdKL/btrQqkWksIb/aowZrTfb5DmusWqoob4uC1/img.png&quot;&gt;&lt;img src=&quot;https://blog.kakaocdn.net/dn/c3OdKL/btrQqkWksIb/aowZrTfb5DmusWqoob4uC1/img.png&quot; srcset=&quot;https://img1.daumcdn.net/thumb/R1280x0/?scode=mtistory2&amp;fname=https%3A%2F%2Fblog.kakaocdn.net%2Fdn%2Fc3OdKL%2FbtrQqkWksIb%2FaowZrTfb5DmusWqoob4uC1%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;707&quot; height=&quot;170&quot; data-filename=&quot;스크린샷 2022-11-05 21.25.30.png&quot; data-origin-width=&quot;707&quot; data-origin-height=&quot;170&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;pre id=&quot;code_1667653511520&quot; class=&quot;shell&quot; data-ke-language=&quot;shell&quot; data-ke-type=&quot;codeblock&quot;&gt;&lt;code&gt;                                                                               
                This is not the tsc command you are looking for                
                                                                               

To get access to the TypeScript compiler, tsc, from the command line either:

- Use npm install typescript to first add TypeScript to your project before using npx
- Use yarn to avoid accidentally running code from un-installed packages&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;검색을 해보니, 해당 문제는 tsc 패키지와 typescript 패키지가 충돌해서 생기는 문제라고 한다. 그래서 tsc 패키지가 따로 설치되어 있으면, 해당 패키지를 삭제하면 된다.&lt;/p&gt;
&lt;pre id=&quot;code_1667653757387&quot; class=&quot;shell&quot; data-ke-language=&quot;shell&quot; data-ke-type=&quot;codeblock&quot;&gt;&lt;code&gt;npm uninstall tsc&lt;/code&gt;&lt;/pre&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;h3 data-ke-size=&quot;size23&quot;&gt;참고자료&lt;/h3&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;a href=&quot;https://stackoverflow.com/questions/67677320/this-is-not-the-tsc-command-you-are-looking-for&quot; target=&quot;_blank&quot; rel=&quot;noopener&quot;&gt;https://stackoverflow.com/questions/67677320/this-is-not-the-tsc-command-you-are-looking-for&lt;/a&gt;&lt;/p&gt;
&lt;figure id=&quot;og_1667653776127&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;This is not the tsc command you are looking for&quot; data-og-description=&quot;When I run tsc in the terminal (it does not matter where) I get returned: This is not the tsc command you are looking for I do not have TypeScript installed globally to my knowledge so what I am&quot; data-og-host=&quot;stackoverflow.com&quot; data-og-source-url=&quot;https://stackoverflow.com/questions/67677320/this-is-not-the-tsc-command-you-are-looking-for&quot; data-og-url=&quot;https://stackoverflow.com/questions/67677320/this-is-not-the-tsc-command-you-are-looking-for&quot; data-og-image=&quot;https://scrap.kakaocdn.net/dn/bF6jOp/hyQtwfjLtd/WITC2EuY5t8s4XcKWYkYN1/img.png?width=316&amp;amp;height=316&amp;amp;face=0_0_316_316,https://scrap.kakaocdn.net/dn/iODpT/hyQse1E5u1/zHgmaKYkNXtQxj2UmmKH90/img.png?width=818&amp;amp;height=200&amp;amp;face=0_0_818_200&quot;&gt;&lt;a href=&quot;https://stackoverflow.com/questions/67677320/this-is-not-the-tsc-command-you-are-looking-for&quot; target=&quot;_blank&quot; rel=&quot;noopener&quot; data-source-url=&quot;https://stackoverflow.com/questions/67677320/this-is-not-the-tsc-command-you-are-looking-for&quot;&gt;
&lt;div class=&quot;og-image&quot; style=&quot;background-image: url('https://scrap.kakaocdn.net/dn/bF6jOp/hyQtwfjLtd/WITC2EuY5t8s4XcKWYkYN1/img.png?width=316&amp;amp;height=316&amp;amp;face=0_0_316_316,https://scrap.kakaocdn.net/dn/iODpT/hyQse1E5u1/zHgmaKYkNXtQxj2UmmKH90/img.png?width=818&amp;amp;height=200&amp;amp;face=0_0_818_200');&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;This is not the tsc command you are looking for&lt;/p&gt;
&lt;p class=&quot;og-desc&quot; data-ke-size=&quot;size16&quot;&gt;When I run tsc in the terminal (it does not matter where) I get returned: This is not the tsc command you are looking for I do not have TypeScript installed globally to my knowledge so what I am&lt;/p&gt;
&lt;p class=&quot;og-host&quot; data-ke-size=&quot;size16&quot;&gt;stackoverflow.com&lt;/p&gt;
&lt;/div&gt;
&lt;/a&gt;&lt;/figure&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;</description>
      <category>프로그래밍 언어/JS TS</category>
      <author>트리맨스</author>
      <guid isPermaLink="true">https://tre2man.tistory.com/325</guid>
      <comments>https://tre2man.tistory.com/325#entry325comment</comments>
      <pubDate>Sat, 5 Nov 2022 22:09:42 +0900</pubDate>
    </item>
  </channel>
</rss>