<?xml version="1.0" encoding="UTF-8"?>
<rss version="2.0">
  <channel>
    <title>0과 1을 공부하다.</title>
    <link>https://developerjaypark.tistory.com/</link>
    <description>Jay Blog.
Github: https://github.com/GreatPark96</description>
    <language>ko</language>
    <pubDate>Fri, 29 May 2026 05:56:33 +0900</pubDate>
    <generator>TISTORY</generator>
    <ttl>100</ttl>
    <managingEditor>dev.parkjh</managingEditor>
    <image>
      <title>0과 1을 공부하다.</title>
      <url>https://tistory1.daumcdn.net/tistory/4949791/attach/0882beecc54641efbcc7cae66fd0dbc4</url>
      <link>https://developerjaypark.tistory.com</link>
    </image>
    <item>
      <title>[Tech] AI가 해주는 코드리뷰 - CodeRabbit</title>
      <link>https://developerjaypark.tistory.com/118</link>
      <description>&lt;p&gt;&lt;figure class=&quot;imageblock alignCenter&quot; data-ke-mobileStyle=&quot;widthOrigin&quot; data-filename=&quot;https___dev-to-uploads.s3.amazonaws.com_uploads_articles_zgwn2zagldf22rfxq3bp.webp&quot; data-origin-width=&quot;800&quot; data-origin-height=&quot;235&quot;&gt;&lt;span data-url=&quot;https://blog.kakaocdn.net/dn/cVumF1/btsNPCQRQDj/pMG2qg1dbnkl6eHeI1TLuK/img.webp&quot; data-phocus=&quot;https://blog.kakaocdn.net/dn/cVumF1/btsNPCQRQDj/pMG2qg1dbnkl6eHeI1TLuK/img.webp&quot;&gt;&lt;img src=&quot;https://blog.kakaocdn.net/dn/cVumF1/btsNPCQRQDj/pMG2qg1dbnkl6eHeI1TLuK/img.webp&quot; srcset=&quot;https://img1.daumcdn.net/thumb/R1280x0/?scode=mtistory2&amp;fname=https%3A%2F%2Fblog.kakaocdn.net%2Fdn%2FcVumF1%2FbtsNPCQRQDj%2FpMG2qg1dbnkl6eHeI1TLuK%2Fimg.webp&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;800&quot; height=&quot;235&quot; data-filename=&quot;https___dev-to-uploads.s3.amazonaws.com_uploads_articles_zgwn2zagldf22rfxq3bp.webp&quot; data-origin-width=&quot;800&quot; data-origin-height=&quot;235&quot;/&gt;&lt;/span&gt;&lt;/figure&gt;
&lt;/p&gt;
&lt;h2 data-ke-size=&quot;size26&quot;&gt;&lt;b&gt;Introduce&lt;/b&gt;&lt;/h2&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;최근에는 1인 프로젝트를 많이 진행하고 있다. 혼자 개발을 하다 보니 코드에서 놓치는 부분이 많다는 것을 실감하게 되었다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;여러 사람과 함께 프로젝트를 진행할 경우, &lt;b&gt;코드리뷰&lt;/b&gt;를 통해 서로의 코드를 교차 검증하고, 클린코드나 컨벤션 같은 사소한 수정은 물론 소프트웨어 결함을 일으킬 수 있는 로직까지 찾아내고 보완할 수 있다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;하지만 1인 개발 환경에서는 이러한 코드리뷰 과정을 거치기 어렵고, 여러 명이 함께 리뷰를 하더라도 시간이 많이 들고 리뷰의 정확도도 떨어질 수 있다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;이러한 한계를 보완하고자, &lt;b&gt;AI가 자동으로 코드리뷰&lt;/b&gt;를 수행하는 솔루션인 &lt;b&gt;CodeRabbit &lt;/b&gt;을 직접 경험해보고자 한다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;h3 data-ke-size=&quot;size23&quot;&gt;&lt;b&gt;특징&lt;/b&gt;&lt;/h3&gt;
&lt;ol style=&quot;list-style-type: decimal;&quot; data-ke-list-type=&quot;decimal&quot;&gt;
&lt;li&gt;&lt;b&gt;AI 기반 자동 코드리뷰&lt;/b&gt;
&lt;ul style=&quot;list-style-type: disc;&quot; data-ke-list-type=&quot;disc&quot;&gt;
&lt;li&gt;PR(Pull Request) 생성 시, AI가 자동으로 코드 변경 사항을 분석하고 리뷰 제공&lt;/li&gt;
&lt;/ul&gt;
&lt;/li&gt;
&lt;li&gt;&lt;b&gt;코드 요약 및 라인별 피드백&lt;/b&gt;
&lt;ul style=&quot;list-style-type: disc;&quot; data-ke-list-type=&quot;disc&quot;&gt;
&lt;li&gt;변경된 코드를 요약하고, 각 라인에 대해 스타일, 오류 가능성, 성능 개선 등을 지적&lt;/li&gt;
&lt;/ul&gt;
&lt;/li&gt;
&lt;li&gt;&lt;b&gt;다국어 지원&lt;/b&gt;
&lt;ul style=&quot;list-style-type: disc;&quot; data-ke-list-type=&quot;disc&quot;&gt;
&lt;li&gt;사용자의 언어에 따라 영어, 한국어 등으로 리뷰 제공 (초기 언어 감지 기반)&lt;/li&gt;
&lt;/ul&gt;
&lt;/li&gt;
&lt;li&gt;&lt;b&gt;시각적 이해 지원&lt;/b&gt;
&lt;ul style=&quot;list-style-type: disc;&quot; data-ke-list-type=&quot;disc&quot;&gt;
&lt;li&gt;시퀀스 다이어그램 등을 통해 코드 흐름을 시각화&lt;/li&gt;
&lt;/ul&gt;
&lt;/li&gt;
&lt;li&gt;&lt;b&gt;GitHub/GitLab 연동&lt;/b&gt;
&lt;ul style=&quot;list-style-type: disc;&quot; data-ke-list-type=&quot;disc&quot;&gt;
&lt;li&gt;주요 협업 플랫폼과 쉽게 통합 가능&lt;/li&gt;
&lt;/ul&gt;
&lt;/li&gt;
&lt;li&gt;&lt;b&gt;지속적 학습 및 커스터마이징&lt;/b&gt;
&lt;ul style=&quot;list-style-type: disc;&quot; data-ke-list-type=&quot;disc&quot;&gt;
&lt;li&gt;팀 코드 스타일에 맞춰 AI 리뷰 품질 향상 가능&lt;/li&gt;
&lt;/ul&gt;
&lt;/li&gt;
&lt;/ol&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;h3 data-ke-size=&quot;size23&quot;&gt;&lt;b&gt;가격&lt;/b&gt;&lt;/h3&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;가격은 아래와 같다. Free 플랜은 14일 동안만 무료로 사용 가능하며, Lite 플랜 기준으로는 인당 월 $12 금액이 청구된다.&lt;/p&gt;
&lt;p&gt;&lt;figure class=&quot;imageblock alignCenter&quot; data-ke-mobileStyle=&quot;widthOrigin&quot; data-filename=&quot;price.PNG&quot; data-origin-width=&quot;1535&quot; data-origin-height=&quot;789&quot;&gt;&lt;span data-url=&quot;https://blog.kakaocdn.net/dn/wSyvo/btsNOLnxpVQ/WRs1f7lkMmVaWNxRgtp7Xk/img.png&quot; data-phocus=&quot;https://blog.kakaocdn.net/dn/wSyvo/btsNOLnxpVQ/WRs1f7lkMmVaWNxRgtp7Xk/img.png&quot;&gt;&lt;img src=&quot;https://blog.kakaocdn.net/dn/wSyvo/btsNOLnxpVQ/WRs1f7lkMmVaWNxRgtp7Xk/img.png&quot; srcset=&quot;https://img1.daumcdn.net/thumb/R1280x0/?scode=mtistory2&amp;fname=https%3A%2F%2Fblog.kakaocdn.net%2Fdn%2FwSyvo%2FbtsNOLnxpVQ%2FWRs1f7lkMmVaWNxRgtp7Xk%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;1535&quot; height=&quot;789&quot; data-filename=&quot;price.PNG&quot; data-origin-width=&quot;1535&quot; data-origin-height=&quot;789&quot;/&gt;&lt;/span&gt;&lt;/figure&gt;
&lt;/p&gt;
&lt;hr contenteditable=&quot;false&quot; data-ke-type=&quot;horizontalRule&quot; data-ke-style=&quot;style1&quot; /&gt;
&lt;h2 data-ke-size=&quot;size26&quot;&gt;&lt;b&gt;Log-In&lt;/b&gt;&lt;/h2&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;b&gt;coderabbit &lt;/b&gt;(&lt;a href=&quot;https://www.coderabbit.ai&quot;&gt;https://www.coderabbit.ai&lt;/a&gt;) 홈페이지에서 &lt;b&gt;&amp;ldquo;Geta a free trial&amp;rdquo;&lt;/b&gt; 을 눌러 로그인을 진행한다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;로그인은 github, gitlab 과 같은 저장소 관리 시스템 계정으로 진행할 수 있다. 호스팅 방식에 따라 SaaS와 Self-Hosted 방식 중 선택하여 진행한다.&lt;/p&gt;
&lt;p&gt;&lt;figure class=&quot;imageblock alignCenter&quot; data-ke-mobileStyle=&quot;widthOrigin&quot; data-filename=&quot;login.PNG&quot; data-origin-width=&quot;1102&quot; data-origin-height=&quot;948&quot;&gt;&lt;span data-url=&quot;https://blog.kakaocdn.net/dn/EGfpC/btsNNUerkuW/z8CfP3wr3BIfddto0MVYKK/img.png&quot; data-phocus=&quot;https://blog.kakaocdn.net/dn/EGfpC/btsNNUerkuW/z8CfP3wr3BIfddto0MVYKK/img.png&quot;&gt;&lt;img src=&quot;https://blog.kakaocdn.net/dn/EGfpC/btsNNUerkuW/z8CfP3wr3BIfddto0MVYKK/img.png&quot; srcset=&quot;https://img1.daumcdn.net/thumb/R1280x0/?scode=mtistory2&amp;fname=https%3A%2F%2Fblog.kakaocdn.net%2Fdn%2FEGfpC%2FbtsNNUerkuW%2Fz8CfP3wr3BIfddto0MVYKK%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;1102&quot; height=&quot;948&quot; data-filename=&quot;login.PNG&quot; data-origin-width=&quot;1102&quot; data-origin-height=&quot;948&quot;/&gt;&lt;/span&gt;&lt;/figure&gt;
&lt;figure class=&quot;imageblock alignCenter&quot; data-ke-mobileStyle=&quot;widthOrigin&quot; data-filename=&quot;login1.PNG&quot; data-origin-width=&quot;879&quot; data-origin-height=&quot;867&quot;&gt;&lt;span data-url=&quot;https://blog.kakaocdn.net/dn/colWTw/btsNP65a704/ARvlvJkDJVHogGEjc4wbo0/img.png&quot; data-phocus=&quot;https://blog.kakaocdn.net/dn/colWTw/btsNP65a704/ARvlvJkDJVHogGEjc4wbo0/img.png&quot;&gt;&lt;img src=&quot;https://blog.kakaocdn.net/dn/colWTw/btsNP65a704/ARvlvJkDJVHogGEjc4wbo0/img.png&quot; srcset=&quot;https://img1.daumcdn.net/thumb/R1280x0/?scode=mtistory2&amp;fname=https%3A%2F%2Fblog.kakaocdn.net%2Fdn%2FcolWTw%2FbtsNP65a704%2FARvlvJkDJVHogGEjc4wbo0%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;879&quot; height=&quot;867&quot; data-filename=&quot;login1.PNG&quot; data-origin-width=&quot;879&quot; data-origin-height=&quot;867&quot;/&gt;&lt;/span&gt;&lt;/figure&gt;
&lt;figure class=&quot;imageblock alignCenter&quot; data-ke-mobileStyle=&quot;widthOrigin&quot; data-filename=&quot;login2.PNG&quot; data-origin-width=&quot;1244&quot; data-origin-height=&quot;753&quot;&gt;&lt;span data-url=&quot;https://blog.kakaocdn.net/dn/K9x0B/btsNQeWj2rr/P9Rdn4YXEcHVzT8JkGCVZk/img.png&quot; data-phocus=&quot;https://blog.kakaocdn.net/dn/K9x0B/btsNQeWj2rr/P9Rdn4YXEcHVzT8JkGCVZk/img.png&quot;&gt;&lt;img src=&quot;https://blog.kakaocdn.net/dn/K9x0B/btsNQeWj2rr/P9Rdn4YXEcHVzT8JkGCVZk/img.png&quot; srcset=&quot;https://img1.daumcdn.net/thumb/R1280x0/?scode=mtistory2&amp;fname=https%3A%2F%2Fblog.kakaocdn.net%2Fdn%2FK9x0B%2FbtsNQeWj2rr%2FP9Rdn4YXEcHVzT8JkGCVZk%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;1244&quot; height=&quot;753&quot; data-filename=&quot;login2.PNG&quot; data-origin-width=&quot;1244&quot; data-origin-height=&quot;753&quot;/&gt;&lt;/span&gt;&lt;/figure&gt;
&lt;figure class=&quot;imageblock alignCenter&quot; data-ke-mobileStyle=&quot;widthOrigin&quot; data-filename=&quot;login3.PNG&quot; data-origin-width=&quot;808&quot; data-origin-height=&quot;806&quot;&gt;&lt;span data-url=&quot;https://blog.kakaocdn.net/dn/TLRpb/btsNOauCJOc/nrdpWLXcsRi18tUMenm151/img.png&quot; data-phocus=&quot;https://blog.kakaocdn.net/dn/TLRpb/btsNOauCJOc/nrdpWLXcsRi18tUMenm151/img.png&quot;&gt;&lt;img src=&quot;https://blog.kakaocdn.net/dn/TLRpb/btsNOauCJOc/nrdpWLXcsRi18tUMenm151/img.png&quot; srcset=&quot;https://img1.daumcdn.net/thumb/R1280x0/?scode=mtistory2&amp;fname=https%3A%2F%2Fblog.kakaocdn.net%2Fdn%2FTLRpb%2FbtsNOauCJOc%2FnrdpWLXcsRi18tUMenm151%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;808&quot; height=&quot;806&quot; data-filename=&quot;login3.PNG&quot; data-origin-width=&quot;808&quot; data-origin-height=&quot;806&quot;/&gt;&lt;/span&gt;&lt;/figure&gt;
&lt;/p&gt;
&lt;hr contenteditable=&quot;false&quot; data-ke-type=&quot;horizontalRule&quot; data-ke-style=&quot;style1&quot; /&gt;
&lt;h2 data-ke-size=&quot;size26&quot;&gt;&lt;b&gt;Environment configuration&lt;/b&gt;&lt;/h2&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;로그인 후 기본적으로 설정해야 하는 항목은 많지 않다. Repositories 에서 자신이 리뷰 받고 싶은 저장소의 설정으로 들어간다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;b&gt;General - Review Language - Korean&lt;/b&gt; 선택하여 언어를 한국어로 설정한다.&lt;/p&gt;
&lt;p&gt;&lt;figure class=&quot;imageblock alignCenter&quot; data-ke-mobileStyle=&quot;widthOrigin&quot; data-filename=&quot;repositories.PNG&quot; data-origin-width=&quot;1893&quot; data-origin-height=&quot;948&quot;&gt;&lt;span data-url=&quot;https://blog.kakaocdn.net/dn/bRu2EW/btsNNWwAI0e/cNcOZFLySAKsHX4DWg2uX1/img.png&quot; data-phocus=&quot;https://blog.kakaocdn.net/dn/bRu2EW/btsNNWwAI0e/cNcOZFLySAKsHX4DWg2uX1/img.png&quot;&gt;&lt;img src=&quot;https://blog.kakaocdn.net/dn/bRu2EW/btsNNWwAI0e/cNcOZFLySAKsHX4DWg2uX1/img.png&quot; srcset=&quot;https://img1.daumcdn.net/thumb/R1280x0/?scode=mtistory2&amp;fname=https%3A%2F%2Fblog.kakaocdn.net%2Fdn%2FbRu2EW%2FbtsNNWwAI0e%2FcNcOZFLySAKsHX4DWg2uX1%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;1893&quot; height=&quot;948&quot; data-filename=&quot;repositories.PNG&quot; data-origin-width=&quot;1893&quot; data-origin-height=&quot;948&quot;/&gt;&lt;/span&gt;&lt;/figure&gt;
&lt;figure class=&quot;imageblock alignCenter&quot; data-ke-mobileStyle=&quot;widthOrigin&quot; data-filename=&quot;repositories1.PNG&quot; data-origin-width=&quot;1912&quot; data-origin-height=&quot;949&quot;&gt;&lt;span data-url=&quot;https://blog.kakaocdn.net/dn/bn6LgB/btsNP6qAvN9/aNXXkfEr4AwKzhrDv29mP0/img.png&quot; data-phocus=&quot;https://blog.kakaocdn.net/dn/bn6LgB/btsNP6qAvN9/aNXXkfEr4AwKzhrDv29mP0/img.png&quot;&gt;&lt;img src=&quot;https://blog.kakaocdn.net/dn/bn6LgB/btsNP6qAvN9/aNXXkfEr4AwKzhrDv29mP0/img.png&quot; srcset=&quot;https://img1.daumcdn.net/thumb/R1280x0/?scode=mtistory2&amp;fname=https%3A%2F%2Fblog.kakaocdn.net%2Fdn%2Fbn6LgB%2FbtsNP6qAvN9%2FaNXXkfEr4AwKzhrDv29mP0%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;1912&quot; height=&quot;949&quot; data-filename=&quot;repositories1.PNG&quot; data-origin-width=&quot;1912&quot; data-origin-height=&quot;949&quot;/&gt;&lt;/span&gt;&lt;/figure&gt;
&lt;/p&gt;
&lt;hr contenteditable=&quot;false&quot; data-ke-type=&quot;horizontalRule&quot; data-ke-style=&quot;style1&quot; /&gt;
&lt;h2 data-ke-size=&quot;size26&quot;&gt;&lt;b&gt;실습&lt;/b&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;p data-ke-size=&quot;size16&quot;&gt;커밋에 사용한 코드는 &lt;b&gt;프로그래머스의 - 완주하지 못한 선수 &lt;/b&gt;(&lt;a href=&quot;https://school.programmers.co.kr/learn/courses/30/lessons/42576&quot;&gt;https://school.programmers.co.kr/learn/courses/30/lessons/42576&lt;/a&gt;) 코딩 테스트 문제이다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;아래 코드 커밋 후 &lt;b&gt;PR(Pull Requests)&lt;/b&gt; 을 등록한다.&lt;/p&gt;
&lt;pre id=&quot;code_1746688933567&quot; class=&quot;python&quot; data-ke-language=&quot;python&quot; data-ke-type=&quot;codeblock&quot;&gt;&lt;code&gt;def solution(participant, completion):
    hashMap = {}
    for person in participant:
        hashMap[person] = hashMap.get(person, 0) + 1
    for person in completion:
        hashMap[person] -= 1
    for key, value in hashMap.items():
        if value &amp;gt; 0:
            return key
            return keyNew # 고의적으로 주입한 unreachable code&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&gt;&lt;figure class=&quot;imageblock alignCenter&quot; data-ke-mobileStyle=&quot;widthOrigin&quot; data-filename=&quot;review0-0.PNG&quot; data-origin-width=&quot;1917&quot; data-origin-height=&quot;628&quot;&gt;&lt;span data-url=&quot;https://blog.kakaocdn.net/dn/doixLa/btsNPbGjy5W/MGB1KYEWHreQ8H4FY4yEfK/img.png&quot; data-phocus=&quot;https://blog.kakaocdn.net/dn/doixLa/btsNPbGjy5W/MGB1KYEWHreQ8H4FY4yEfK/img.png&quot;&gt;&lt;img src=&quot;https://blog.kakaocdn.net/dn/doixLa/btsNPbGjy5W/MGB1KYEWHreQ8H4FY4yEfK/img.png&quot; srcset=&quot;https://img1.daumcdn.net/thumb/R1280x0/?scode=mtistory2&amp;fname=https%3A%2F%2Fblog.kakaocdn.net%2Fdn%2FdoixLa%2FbtsNPbGjy5W%2FMGB1KYEWHreQ8H4FY4yEfK%2Fimg.png&quot; onerror=&quot;this.onerror=null; this.src='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png'; this.srcset='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png';&quot; loading=&quot;lazy&quot; width=&quot;1917&quot; height=&quot;628&quot; data-filename=&quot;review0-0.PNG&quot; data-origin-width=&quot;1917&quot; data-origin-height=&quot;628&quot;/&gt;&lt;/span&gt;&lt;/figure&gt;
&lt;figure class=&quot;imageblock alignCenter&quot; data-ke-mobileStyle=&quot;widthOrigin&quot; data-filename=&quot;review0-1.PNG&quot; data-origin-width=&quot;1901&quot; data-origin-height=&quot;893&quot;&gt;&lt;span data-url=&quot;https://blog.kakaocdn.net/dn/bAeV44/btsNNSUZXNr/67abCKCczDmYjUHiL9ynt0/img.png&quot; data-phocus=&quot;https://blog.kakaocdn.net/dn/bAeV44/btsNNSUZXNr/67abCKCczDmYjUHiL9ynt0/img.png&quot;&gt;&lt;img src=&quot;https://blog.kakaocdn.net/dn/bAeV44/btsNNSUZXNr/67abCKCczDmYjUHiL9ynt0/img.png&quot; srcset=&quot;https://img1.daumcdn.net/thumb/R1280x0/?scode=mtistory2&amp;fname=https%3A%2F%2Fblog.kakaocdn.net%2Fdn%2FbAeV44%2FbtsNNSUZXNr%2F67abCKCczDmYjUHiL9ynt0%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;1901&quot; height=&quot;893&quot; data-filename=&quot;review0-1.PNG&quot; data-origin-width=&quot;1901&quot; data-origin-height=&quot;893&quot;/&gt;&lt;/span&gt;&lt;/figure&gt;
&lt;figure class=&quot;imageblock alignCenter&quot; data-ke-mobileStyle=&quot;widthOrigin&quot; data-filename=&quot;review0-2.PNG&quot; data-origin-width=&quot;1907&quot; data-origin-height=&quot;607&quot;&gt;&lt;span data-url=&quot;https://blog.kakaocdn.net/dn/k0cT3/btsNON6HXvJ/7Uq7R8km65JvMdnCOb3BwK/img.png&quot; data-phocus=&quot;https://blog.kakaocdn.net/dn/k0cT3/btsNON6HXvJ/7Uq7R8km65JvMdnCOb3BwK/img.png&quot;&gt;&lt;img src=&quot;https://blog.kakaocdn.net/dn/k0cT3/btsNON6HXvJ/7Uq7R8km65JvMdnCOb3BwK/img.png&quot; srcset=&quot;https://img1.daumcdn.net/thumb/R1280x0/?scode=mtistory2&amp;fname=https%3A%2F%2Fblog.kakaocdn.net%2Fdn%2Fk0cT3%2FbtsNON6HXvJ%2F7Uq7R8km65JvMdnCOb3BwK%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;1907&quot; height=&quot;607&quot; data-filename=&quot;review0-2.PNG&quot; data-origin-width=&quot;1907&quot; data-origin-height=&quot;607&quot;/&gt;&lt;/span&gt;&lt;/figure&gt;
&lt;figure class=&quot;imageblock alignCenter&quot; data-ke-mobileStyle=&quot;widthOrigin&quot; data-filename=&quot;review1.png&quot; data-origin-width=&quot;932&quot; data-origin-height=&quot;648&quot;&gt;&lt;span data-url=&quot;https://blog.kakaocdn.net/dn/ohWP2/btsNOLgOfs8/PW4F76qwHOmpkEKGo1IISK/img.png&quot; data-phocus=&quot;https://blog.kakaocdn.net/dn/ohWP2/btsNOLgOfs8/PW4F76qwHOmpkEKGo1IISK/img.png&quot;&gt;&lt;img src=&quot;https://blog.kakaocdn.net/dn/ohWP2/btsNOLgOfs8/PW4F76qwHOmpkEKGo1IISK/img.png&quot; srcset=&quot;https://img1.daumcdn.net/thumb/R1280x0/?scode=mtistory2&amp;fname=https%3A%2F%2Fblog.kakaocdn.net%2Fdn%2FohWP2%2FbtsNOLgOfs8%2FPW4F76qwHOmpkEKGo1IISK%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;932&quot; height=&quot;648&quot; data-filename=&quot;review1.png&quot; data-origin-width=&quot;932&quot; data-origin-height=&quot;648&quot;/&gt;&lt;/span&gt;&lt;/figure&gt;
&lt;figure class=&quot;imageblock alignCenter&quot; data-ke-mobileStyle=&quot;widthOrigin&quot; data-filename=&quot;review2.PNG&quot; data-origin-width=&quot;826&quot; data-origin-height=&quot;893&quot;&gt;&lt;span data-url=&quot;https://blog.kakaocdn.net/dn/dF2sO7/btsNOgHOu9f/BOpdWy9b2olb5LRoh5Eqc0/img.png&quot; data-phocus=&quot;https://blog.kakaocdn.net/dn/dF2sO7/btsNOgHOu9f/BOpdWy9b2olb5LRoh5Eqc0/img.png&quot;&gt;&lt;img src=&quot;https://blog.kakaocdn.net/dn/dF2sO7/btsNOgHOu9f/BOpdWy9b2olb5LRoh5Eqc0/img.png&quot; srcset=&quot;https://img1.daumcdn.net/thumb/R1280x0/?scode=mtistory2&amp;fname=https%3A%2F%2Fblog.kakaocdn.net%2Fdn%2FdF2sO7%2FbtsNOgHOu9f%2FBOpdWy9b2olb5LRoh5Eqc0%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;826&quot; height=&quot;893&quot; data-filename=&quot;review2.PNG&quot; data-origin-width=&quot;826&quot; data-origin-height=&quot;893&quot;/&gt;&lt;/span&gt;&lt;/figure&gt;
&lt;/p&gt;
&lt;hr contenteditable=&quot;false&quot; data-ke-type=&quot;horizontalRule&quot; data-ke-style=&quot;style1&quot; /&gt;
&lt;h2 data-ke-size=&quot;size26&quot;&gt;&lt;b&gt;총평&lt;/b&gt;&lt;/h2&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;AI 코드리뷰 솔루션은 마치 ChatGPT가 처음 등장했을 때처럼 신선한 충격을 주었다.&lt;/p&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;물론 다른 기술들과 마찬가지로 AI에 과도하게 의존하거나 맹신해서는 안 되지만, 코드 리뷰 과정에서 사람이 개입하기 전에 &lt;b&gt;AI가 먼저 1차 리뷰를 수행&lt;/b&gt;하고, 이후 사람이 이를 검토하는 방식은 여러 측면에서 효율성을 크게 높일 수 있다고 느꼈다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;무엇보다 기존 코드리뷰의 단점인 &lt;b&gt;시간 소요&lt;/b&gt;, &lt;b&gt;정확성 부족&lt;/b&gt;, &lt;b&gt;일관성 문제&lt;/b&gt;를 보완함으로써 &lt;b&gt;전반적인 코드 품질 향상&lt;/b&gt;을 기대할 수 있다는 점이 가장 인상 깊었다.&lt;/p&gt;
&lt;hr contenteditable=&quot;false&quot; data-ke-type=&quot;horizontalRule&quot; data-ke-style=&quot;style1&quot; /&gt;
&lt;h2 data-ke-size=&quot;size26&quot;&gt;&lt;b&gt;Ref&lt;/b&gt;&lt;/h2&gt;
&lt;ul style=&quot;list-style-type: disc;&quot; data-ke-list-type=&quot;disc&quot;&gt;
&lt;li&gt;&lt;a href=&quot;https://www.coderabbit.ai/&quot;&gt;https://www.coderabbit.ai/&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;&lt;a href=&quot;https://velog.io/@songju7920/Tech-CodeRabbit&quot;&gt;https://velog.io/@songju7920/Tech-CodeRabbit&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;&lt;a href=&quot;https://tech.inflab.com/20250303-introduce-coderabbit/&quot;&gt;https://tech.inflab.com/20250303-introduce-coderabbit/&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;&lt;a href=&quot;https://digitalbourgeois.tistory.com/939#google_vignette&quot;&gt;https://digitalbourgeois.tistory.com/939#google_vignette&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;&lt;a href=&quot;https://velog.io/@ljw4536/혼자-프로젝트-할-때-외로움을-해소해줄-AI-Code-reviewer-CodeRabbit-처음-맛보기&quot;&gt;https://velog.io/@ljw4536/혼자-프로젝트-할-때-외로움을-해소해줄-AI-Code-reviewer-CodeRabbit-처음-맛보기&lt;/a&gt;&lt;/li&gt;
&lt;/ul&gt;</description>
      <category>ETC/ETC</category>
      <category>AI</category>
      <category>ai code review</category>
      <category>Code Review</category>
      <category>coderabbit</category>
      <author>dev.parkjh</author>
      <guid isPermaLink="true">https://developerjaypark.tistory.com/118</guid>
      <comments>https://developerjaypark.tistory.com/118#entry118comment</comments>
      <pubDate>Thu, 8 May 2025 16:25:27 +0900</pubDate>
    </item>
    <item>
      <title>[Tech] 순수 프로그래밍 시간 측정(wakatime)</title>
      <link>https://developerjaypark.tistory.com/117</link>
      <description>&lt;p&gt;&lt;figure class=&quot;imageblock alignCenter&quot; data-ke-mobileStyle=&quot;widthOrigin&quot; data-filename=&quot;bb3a83b9-ee6e-4f9b-a1a7-64549c0a9e90.jpg&quot; data-origin-width=&quot;1008&quot; data-origin-height=&quot;720&quot;&gt;&lt;span data-url=&quot;https://blog.kakaocdn.net/dn/k6G9s/btsNNyngksS/z4PzwACShPzKQJ1hWHeAt1/img.jpg&quot; data-phocus=&quot;https://blog.kakaocdn.net/dn/k6G9s/btsNNyngksS/z4PzwACShPzKQJ1hWHeAt1/img.jpg&quot;&gt;&lt;img src=&quot;https://blog.kakaocdn.net/dn/k6G9s/btsNNyngksS/z4PzwACShPzKQJ1hWHeAt1/img.jpg&quot; srcset=&quot;https://img1.daumcdn.net/thumb/R1280x0/?scode=mtistory2&amp;fname=https%3A%2F%2Fblog.kakaocdn.net%2Fdn%2Fk6G9s%2FbtsNNyngksS%2Fz4PzwACShPzKQJ1hWHeAt1%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;1008&quot; height=&quot;720&quot; data-filename=&quot;bb3a83b9-ee6e-4f9b-a1a7-64549c0a9e90.jpg&quot; data-origin-width=&quot;1008&quot; data-origin-height=&quot;720&quot;/&gt;&lt;/span&gt;&lt;/figure&gt;
&lt;/p&gt;
&lt;h2 data-ke-size=&quot;size26&quot;&gt;&lt;b&gt;Introduce&lt;/b&gt;&lt;/h2&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;최근 프로그래밍 공부를 하면서 부쩍 이전보다 집중력이 떨어졌음을 느꼈다. 그래서 아이폰의 스크린 타임처럼 수치적으로 내가 얼마나 집중하고 있는지 보여 줄 수 있으면 어느정도 목표치를 갖고 집중할 수 있겠다고 느꼈다. 그래서 알아보던 중 순수 프로그래밍 시간을 측정하는 &lt;b&gt;wakatime&lt;/b&gt; 에 대해 알게 되었다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;b&gt;&amp;nbsp;wakatime&lt;/b&gt;은 개발자의 &lt;b&gt;코딩 시간을 자동으로 기록하고 시각화해주는 시간 추적 도구&lt;/b&gt; 이다. 동작 방식은 &lt;b&gt;실제로 키보드 입력이나 마우스 활동이 있는 시간만을 기록&lt;/b&gt; 한다. 지정한 시간만큼 동작이 없으면 세션 종료로 간주하고 다시 동작이 발생하면 새로운 세션이 시작된다. 설치 방법은 매우 간단하며, 개발자들이 주로 사용하는 다양한 통합개발환경에서 지원한다. 추적된 시간은 Dashboard 에서 시각화하여 확인 할 수 있다.&lt;/p&gt;
&lt;blockquote data-ke-style=&quot;style3&quot;&gt;⚠️ 프로젝트 이름은 기본적으로 &lt;b&gt;루트 폴더명&lt;/b&gt; 으로 자동 설정 된다.&lt;/blockquote&gt;
&lt;hr contenteditable=&quot;false&quot; data-ke-type=&quot;horizontalRule&quot; data-ke-style=&quot;style1&quot; /&gt;
&lt;h2 data-ke-size=&quot;size26&quot;&gt;&lt;b&gt;Sign Up (회원가입)&lt;/b&gt;&lt;/h2&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;wakatime (&lt;a href=&quot;https://wakatime.com&quot;&gt;https://wakatime.com&lt;/a&gt;) 사이트 접속 후 이메일 혹은 GitHub 연동 가입한다.&lt;/p&gt;
&lt;p&gt;&lt;figure class=&quot;imageblock alignCenter&quot; data-ke-mobileStyle=&quot;widthOrigin&quot; data-filename=&quot;sign-up.PNG&quot; data-origin-width=&quot;1920&quot; data-origin-height=&quot;950&quot;&gt;&lt;span data-url=&quot;https://blog.kakaocdn.net/dn/bUmqgH/btsNMz78Ms6/5gITCK6aSajoWAR1G1J5y0/img.png&quot; data-phocus=&quot;https://blog.kakaocdn.net/dn/bUmqgH/btsNMz78Ms6/5gITCK6aSajoWAR1G1J5y0/img.png&quot;&gt;&lt;img src=&quot;https://blog.kakaocdn.net/dn/bUmqgH/btsNMz78Ms6/5gITCK6aSajoWAR1G1J5y0/img.png&quot; srcset=&quot;https://img1.daumcdn.net/thumb/R1280x0/?scode=mtistory2&amp;fname=https%3A%2F%2Fblog.kakaocdn.net%2Fdn%2FbUmqgH%2FbtsNMz78Ms6%2F5gITCK6aSajoWAR1G1J5y0%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;1920&quot; height=&quot;950&quot; data-filename=&quot;sign-up.PNG&quot; data-origin-width=&quot;1920&quot; data-origin-height=&quot;950&quot;/&gt;&lt;/span&gt;&lt;/figure&gt;
&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;플랜을 선택한다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;현재는 무료 플랜으로 충분하다. 무료 플랜이라도 7일의 통계기간과 1개의 목표를 지정할 수 있다.&lt;/p&gt;
&lt;p&gt;&lt;figure class=&quot;imageblock alignCenter&quot; data-ke-mobileStyle=&quot;widthOrigin&quot; data-filename=&quot;plan.PNG&quot; data-origin-width=&quot;1238&quot; data-origin-height=&quot;926&quot;&gt;&lt;span data-url=&quot;https://blog.kakaocdn.net/dn/sfL5E/btsNNKHJ9mn/NUPZieiml9YQhHN1FhLsZk/img.png&quot; data-phocus=&quot;https://blog.kakaocdn.net/dn/sfL5E/btsNNKHJ9mn/NUPZieiml9YQhHN1FhLsZk/img.png&quot;&gt;&lt;img src=&quot;https://blog.kakaocdn.net/dn/sfL5E/btsNNKHJ9mn/NUPZieiml9YQhHN1FhLsZk/img.png&quot; srcset=&quot;https://img1.daumcdn.net/thumb/R1280x0/?scode=mtistory2&amp;fname=https%3A%2F%2Fblog.kakaocdn.net%2Fdn%2FsfL5E%2FbtsNNKHJ9mn%2FNUPZieiml9YQhHN1FhLsZk%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;1238&quot; height=&quot;926&quot; data-filename=&quot;plan.PNG&quot; data-origin-width=&quot;1238&quot; data-origin-height=&quot;926&quot;/&gt;&lt;/span&gt;&lt;/figure&gt;
&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;로그인 후 &lt;u&gt;&lt;b&gt;&lt;span style=&quot;color: #000000;&quot; data-token-index=&quot;1&quot;&gt;Setting - Account - API Key&lt;/span&gt;&lt;/b&gt;&lt;/u&gt; 으로 이동해서 자신의 &lt;u&gt;&lt;b&gt;API Key&lt;/b&gt;&lt;/u&gt; 를 획득한다.&lt;/p&gt;
&lt;p&gt;&lt;figure class=&quot;imageblock alignCenter&quot; data-ke-mobileStyle=&quot;widthOrigin&quot; data-filename=&quot;api-key.PNG&quot; data-origin-width=&quot;1896&quot; data-origin-height=&quot;201&quot;&gt;&lt;span data-url=&quot;https://blog.kakaocdn.net/dn/dXWFgU/btsNLn1UOru/Wh6PnjefCVC7uGuCHb1C8K/img.png&quot; data-phocus=&quot;https://blog.kakaocdn.net/dn/dXWFgU/btsNLn1UOru/Wh6PnjefCVC7uGuCHb1C8K/img.png&quot;&gt;&lt;img src=&quot;https://blog.kakaocdn.net/dn/dXWFgU/btsNLn1UOru/Wh6PnjefCVC7uGuCHb1C8K/img.png&quot; srcset=&quot;https://img1.daumcdn.net/thumb/R1280x0/?scode=mtistory2&amp;fname=https%3A%2F%2Fblog.kakaocdn.net%2Fdn%2FdXWFgU%2FbtsNLn1UOru%2FWh6PnjefCVC7uGuCHb1C8K%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;1896&quot; height=&quot;201&quot; data-filename=&quot;api-key.PNG&quot; data-origin-width=&quot;1896&quot; data-origin-height=&quot;201&quot;/&gt;&lt;/span&gt;&lt;/figure&gt;
&lt;/p&gt;
&lt;hr contenteditable=&quot;false&quot; data-ke-type=&quot;horizontalRule&quot; data-ke-style=&quot;style1&quot; /&gt;
&lt;h2 data-ke-size=&quot;size26&quot;&gt;&lt;b&gt;Implement - VSCode&lt;/b&gt;&lt;/h2&gt;
&lt;ul style=&quot;list-style-type: disc;&quot; data-ke-list-type=&quot;disc&quot;&gt;
&lt;li&gt;guide : &lt;a href=&quot;https://wakatime.com/vs-code&quot;&gt;https://wakatime.com/vs-code&lt;/a&gt;&lt;/li&gt;
&lt;/ul&gt;
&lt;figure id=&quot;og_1746595263032&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;WakaTime plugin for VS Code&quot; data-og-description=&quot;An open source VS Code extension for automated programming metrics.&quot; data-og-host=&quot;wakatime.com&quot; data-og-source-url=&quot;https://wakatime.com/vs-code&quot; data-og-url=&quot;https://wakatime.com/vs-code&quot; data-og-image=&quot;https://scrap.kakaocdn.net/dn/jJDnt/hyYRoNvla4/dNPI77tDCvG37T1huMpQTk/img.png?width=1602&amp;amp;height=840&amp;amp;face=0_0_1602_840,https://scrap.kakaocdn.net/dn/L9VvI/hyYMTBDni0/fPz1QJRHs7DxRePLJbbGx1/img.png?width=600&amp;amp;height=326&amp;amp;face=0_0_600_326&quot;&gt;&lt;a href=&quot;https://wakatime.com/vs-code&quot; target=&quot;_blank&quot; rel=&quot;noopener&quot; data-source-url=&quot;https://wakatime.com/vs-code&quot;&gt;
&lt;div class=&quot;og-image&quot; style=&quot;background-image: url('https://scrap.kakaocdn.net/dn/jJDnt/hyYRoNvla4/dNPI77tDCvG37T1huMpQTk/img.png?width=1602&amp;amp;height=840&amp;amp;face=0_0_1602_840,https://scrap.kakaocdn.net/dn/L9VvI/hyYMTBDni0/fPz1QJRHs7DxRePLJbbGx1/img.png?width=600&amp;amp;height=326&amp;amp;face=0_0_600_326');&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;WakaTime plugin for VS Code&lt;/p&gt;
&lt;p class=&quot;og-desc&quot; data-ke-size=&quot;size16&quot;&gt;An open source VS Code extension for automated programming metrics.&lt;/p&gt;
&lt;p class=&quot;og-host&quot; data-ke-size=&quot;size16&quot;&gt;wakatime.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;b&gt;VSCode - Extensions&lt;/b&gt; 에서 wakatime 검색 후 설치한다.&lt;/p&gt;
&lt;p&gt;&lt;figure class=&quot;imageblock alignCenter&quot; data-ke-mobileStyle=&quot;widthOrigin&quot; data-filename=&quot;1.PNG&quot; data-origin-width=&quot;1075&quot; data-origin-height=&quot;659&quot;&gt;&lt;span data-url=&quot;https://blog.kakaocdn.net/dn/pgwas/btsNLtgEQoc/aBkPbtvQCClS0TsNKYARHk/img.png&quot; data-phocus=&quot;https://blog.kakaocdn.net/dn/pgwas/btsNLtgEQoc/aBkPbtvQCClS0TsNKYARHk/img.png&quot;&gt;&lt;img src=&quot;https://blog.kakaocdn.net/dn/pgwas/btsNLtgEQoc/aBkPbtvQCClS0TsNKYARHk/img.png&quot; srcset=&quot;https://img1.daumcdn.net/thumb/R1280x0/?scode=mtistory2&amp;fname=https%3A%2F%2Fblog.kakaocdn.net%2Fdn%2Fpgwas%2FbtsNLtgEQoc%2FaBkPbtvQCClS0TsNKYARHk%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;1075&quot; height=&quot;659&quot; data-filename=&quot;1.PNG&quot; data-origin-width=&quot;1075&quot; data-origin-height=&quot;659&quot;/&gt;&lt;/span&gt;&lt;/figure&gt;
&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;F1 을 눌러 명령 팔레트를 활성화 하고 &lt;u&gt;&lt;b&gt;WakaTime: Api Key&lt;/b&gt;&lt;/u&gt; 검색 후 이전에 획득한 자신의 API Key 입력한다.&lt;/p&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 하단에서 WakaTime 세션 연결을 확인한다.&lt;/p&gt;
&lt;p&gt;&lt;figure class=&quot;imageblock alignCenter&quot; data-ke-mobileStyle=&quot;widthOrigin&quot; data-filename=&quot;waka.PNG&quot; data-origin-width=&quot;458&quot; data-origin-height=&quot;86&quot;&gt;&lt;span data-url=&quot;https://blog.kakaocdn.net/dn/cjYd6j/btsNL8C1dy9/YiQtyEhddjd9adwgKKTbMk/img.png&quot; data-phocus=&quot;https://blog.kakaocdn.net/dn/cjYd6j/btsNL8C1dy9/YiQtyEhddjd9adwgKKTbMk/img.png&quot;&gt;&lt;img src=&quot;https://blog.kakaocdn.net/dn/cjYd6j/btsNL8C1dy9/YiQtyEhddjd9adwgKKTbMk/img.png&quot; srcset=&quot;https://img1.daumcdn.net/thumb/R1280x0/?scode=mtistory2&amp;fname=https%3A%2F%2Fblog.kakaocdn.net%2Fdn%2FcjYd6j%2FbtsNL8C1dy9%2FYiQtyEhddjd9adwgKKTbMk%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;86&quot; data-filename=&quot;waka.PNG&quot; data-origin-width=&quot;458&quot; data-origin-height=&quot;86&quot;/&gt;&lt;/span&gt;&lt;/figure&gt;
&lt;/p&gt;
&lt;hr contenteditable=&quot;false&quot; data-ke-type=&quot;horizontalRule&quot; data-ke-style=&quot;style1&quot; /&gt;
&lt;h2 data-ke-size=&quot;size26&quot;&gt;&lt;b&gt;Implement - IntelliJ&lt;/b&gt;&lt;/h2&gt;
&lt;ul style=&quot;list-style-type: disc;&quot; data-ke-list-type=&quot;disc&quot;&gt;
&lt;li&gt;guide : &lt;a href=&quot;https://wakatime.com/intellij-idea&quot;&gt;https://wakatime.com/intellij-idea&lt;/a&gt;&lt;/li&gt;
&lt;/ul&gt;
&lt;figure id=&quot;og_1746595333116&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;WakaTime plugin for IntelliJ IDEA&quot; data-og-description=&quot;An open source IntelliJ IDEA extension for automated programming metrics.&quot; data-og-host=&quot;wakatime.com&quot; data-og-source-url=&quot;https://wakatime.com/intellij-idea&quot; data-og-url=&quot;https://wakatime.com/intellij-idea&quot; data-og-image=&quot;https://scrap.kakaocdn.net/dn/bmtoJT/hyYMWZpgnh/QkCJlu8PdHcmweB8ux4CC1/img.png?width=1602&amp;amp;height=840&amp;amp;face=0_0_1602_840&quot;&gt;&lt;a href=&quot;https://wakatime.com/intellij-idea&quot; target=&quot;_blank&quot; rel=&quot;noopener&quot; data-source-url=&quot;https://wakatime.com/intellij-idea&quot;&gt;
&lt;div class=&quot;og-image&quot; style=&quot;background-image: url('https://scrap.kakaocdn.net/dn/bmtoJT/hyYMWZpgnh/QkCJlu8PdHcmweB8ux4CC1/img.png?width=1602&amp;amp;height=840&amp;amp;face=0_0_1602_840');&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;WakaTime plugin for IntelliJ IDEA&lt;/p&gt;
&lt;p class=&quot;og-desc&quot; data-ke-size=&quot;size16&quot;&gt;An open source IntelliJ IDEA extension for automated programming metrics.&lt;/p&gt;
&lt;p class=&quot;og-host&quot; data-ke-size=&quot;size16&quot;&gt;wakatime.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;h2 data-ke-size=&quot;size26&quot;&gt;&amp;nbsp;&lt;/h2&gt;
&lt;hr contenteditable=&quot;false&quot; data-ke-type=&quot;horizontalRule&quot; data-ke-style=&quot;style1&quot; /&gt;
&lt;h2 data-ke-size=&quot;size26&quot;&gt;&lt;b&gt; Dashboard 구성&lt;/b&gt;&lt;/h2&gt;
&lt;p&gt;&lt;figure class=&quot;imageblock alignCenter&quot; data-ke-mobileStyle=&quot;widthOrigin&quot; data-filename=&quot;dashboard1.PNG&quot; data-origin-width=&quot;1703&quot; data-origin-height=&quot;717&quot;&gt;&lt;span data-url=&quot;https://blog.kakaocdn.net/dn/swZe0/btsNNRzWh9I/iF3MSQQzDVFL5bVKRnk3TK/img.png&quot; data-phocus=&quot;https://blog.kakaocdn.net/dn/swZe0/btsNNRzWh9I/iF3MSQQzDVFL5bVKRnk3TK/img.png&quot;&gt;&lt;img src=&quot;https://blog.kakaocdn.net/dn/swZe0/btsNNRzWh9I/iF3MSQQzDVFL5bVKRnk3TK/img.png&quot; srcset=&quot;https://img1.daumcdn.net/thumb/R1280x0/?scode=mtistory2&amp;fname=https%3A%2F%2Fblog.kakaocdn.net%2Fdn%2FswZe0%2FbtsNNRzWh9I%2FiF3MSQQzDVFL5bVKRnk3TK%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;1703&quot; height=&quot;717&quot; data-filename=&quot;dashboard1.PNG&quot; data-origin-width=&quot;1703&quot; data-origin-height=&quot;717&quot;/&gt;&lt;/span&gt;&lt;/figure&gt;
&lt;figure class=&quot;imageblock alignCenter&quot; data-ke-mobileStyle=&quot;widthOrigin&quot; data-filename=&quot;dashboard2.PNG&quot; data-origin-width=&quot;1704&quot; data-origin-height=&quot;726&quot;&gt;&lt;span data-url=&quot;https://blog.kakaocdn.net/dn/bnXt0B/btsNM9OMU9O/89LazIX9EUZdTXH22LM6zk/img.png&quot; data-phocus=&quot;https://blog.kakaocdn.net/dn/bnXt0B/btsNM9OMU9O/89LazIX9EUZdTXH22LM6zk/img.png&quot;&gt;&lt;img src=&quot;https://blog.kakaocdn.net/dn/bnXt0B/btsNM9OMU9O/89LazIX9EUZdTXH22LM6zk/img.png&quot; srcset=&quot;https://img1.daumcdn.net/thumb/R1280x0/?scode=mtistory2&amp;fname=https%3A%2F%2Fblog.kakaocdn.net%2Fdn%2FbnXt0B%2FbtsNM9OMU9O%2F89LazIX9EUZdTXH22LM6zk%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;1704&quot; height=&quot;726&quot; data-filename=&quot;dashboard2.PNG&quot; data-origin-width=&quot;1704&quot; data-origin-height=&quot;726&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;각 Dashboard 가 의미하는 바는 아래와 같다.&lt;/p&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;Summary (요약 화면)&lt;/b&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;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;/ul&gt;
&lt;/li&gt;
&lt;/ul&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;b&gt;Editors&lt;/b&gt;&lt;/p&gt;
&lt;ul style=&quot;list-style-type: disc;&quot; data-ke-list-type=&quot;disc&quot;&gt;
&lt;li&gt;VS Code, IntelliJ, PyCharm 등 사용한 개발 도구별 시간 분석&lt;/li&gt;
&lt;/ul&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;b&gt;Languages&lt;/b&gt;&lt;/p&gt;
&lt;ul style=&quot;list-style-type: disc;&quot; data-ke-list-type=&quot;disc&quot;&gt;
&lt;li&gt;Python, JavaScript 등 언어별 누적 시간과 비율 제공&lt;/li&gt;
&lt;li&gt;일/주/월별 추이 확인 가능&lt;/li&gt;
&lt;/ul&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;b&gt;Operating Systems&lt;/b&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;/ul&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;b&gt;Machines&lt;/b&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;/ul&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;b&gt;Today&lt;/b&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;/ul&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;b&gt;Weekdays&lt;/b&gt;&lt;/p&gt;
&lt;ul style=&quot;list-style-type: disc;&quot; data-ke-list-type=&quot;disc&quot;&gt;
&lt;li&gt;사용자의 활동을 유형별로 분류해서 보여주는 통계 지표 (Coding, Debugging, Building 등)&lt;/li&gt;
&lt;/ul&gt;
&lt;hr contenteditable=&quot;false&quot; data-ke-type=&quot;horizontalRule&quot; data-ke-style=&quot;style1&quot; /&gt;
&lt;h2 data-ke-size=&quot;size26&quot;&gt;&lt;b&gt;Ref&lt;/b&gt;&lt;/h2&gt;
&lt;ul style=&quot;list-style-type: disc;&quot; data-ke-list-type=&quot;disc&quot;&gt;
&lt;li&gt;&lt;a href=&quot;https://wakatime.com&quot;&gt;https://wakatime.com&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;&lt;a href=&quot;https://frontmulti.tistory.com/65&quot;&gt;https://frontmulti.tistory.com/65&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;&lt;a href=&quot;https://parkjh7764.tistory.com/entry/VSCode-WakaTime-순수-코딩-시간-추적-VSCode-익스텐션-Extension&quot;&gt;https://parkjh7764.tistory.com/entry/VSCode-WakaTime-순수-코딩-시간-추적-VSCode-익스텐션-Extension&lt;/a&gt;&lt;/li&gt;
&lt;/ul&gt;</description>
      <category>ETC/ETC</category>
      <category>Programming</category>
      <category>Wakatime</category>
      <category>work hour</category>
      <author>dev.parkjh</author>
      <guid isPermaLink="true">https://developerjaypark.tistory.com/117</guid>
      <comments>https://developerjaypark.tistory.com/117#entry117comment</comments>
      <pubDate>Wed, 7 May 2025 14:23:21 +0900</pubDate>
    </item>
    <item>
      <title>[Inflearn] 비전공자도 이해할 수 있는 Nginx 입문/실전</title>
      <link>https://developerjaypark.tistory.com/116</link>
      <description>&lt;p&gt;&lt;figure class=&quot;imageblock alignCenter&quot; data-ke-mobileStyle=&quot;widthOrigin&quot; data-filename=&quot;스크린샷 2025-05-06 오후 3.44.44.png&quot; data-origin-width=&quot;528&quot; data-origin-height=&quot;342&quot;&gt;&lt;span data-url=&quot;https://blog.kakaocdn.net/dn/1FrHN/btsNMze8L5j/ujJfWNhiPLGqPeqg8fWQkK/img.png&quot; data-phocus=&quot;https://blog.kakaocdn.net/dn/1FrHN/btsNMze8L5j/ujJfWNhiPLGqPeqg8fWQkK/img.png&quot;&gt;&lt;img src=&quot;https://blog.kakaocdn.net/dn/1FrHN/btsNMze8L5j/ujJfWNhiPLGqPeqg8fWQkK/img.png&quot; srcset=&quot;https://img1.daumcdn.net/thumb/R1280x0/?scode=mtistory2&amp;fname=https%3A%2F%2Fblog.kakaocdn.net%2Fdn%2F1FrHN%2FbtsNMze8L5j%2FujJfWNhiPLGqPeqg8fWQkK%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;528&quot; height=&quot;342&quot; data-filename=&quot;스크린샷 2025-05-06 오후 3.44.44.png&quot; data-origin-width=&quot;528&quot; data-origin-height=&quot;342&quot;/&gt;&lt;/span&gt;&lt;/figure&gt;
&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;ul style=&quot;list-style-type: disc;&quot; data-ke-list-type=&quot;disc&quot;&gt;
&lt;li&gt;&lt;b&gt;inflearn&lt;/b&gt;: &lt;a href=&quot;https://www.inflearn.com/course/비전공자도-이해-nginx-입문-실전/dashboard&quot;&gt;https://www.inflearn.com/course/비전공자도-이해-nginx-입문-실전/dashboard&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;&lt;b&gt;수업 자료&lt;/b&gt;: &lt;a href=&quot;https://jscode.notion.site/16011062ff0780aa87a3e879cb3ef482&quot;&gt;https://jscode.notion.site/16011062ff0780aa87a3e879cb3ef482&lt;/a&gt;&lt;/li&gt;
&lt;/ul&gt;
&lt;h2 data-ke-size=&quot;size26&quot;&gt;&lt;b&gt;Preview&lt;/b&gt;&lt;/h2&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;내가 Nginx 를 처음으로 접한 경험은 Docker Private Image Registry 를 구성하는 과정에서 HTTPS 인증서 적용을 위해 Nginx를 잠깐 경험해봤다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;실무에서는 주로 아파치(Apache) 를 웹서버로 사용해서 사용할 기회가 없었는데 예제를 통해 잠깐 경험해보니 충분히 공부할 가치가 높다고 생각했다. 백엔드 개발자 입장에서&lt;b&gt; &lt;/b&gt;&lt;u&gt;&lt;b&gt;웹서버 + 리버스 프록시 + 로드밸런서 기능&lt;/b&gt;&lt;/u&gt;까지 3 in 1 으로 쓸 수 있으니 충분히 가치가 있다고 느꼈다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;해당 인프런 강의를 수강하기 이전에 &lt;u&gt;&lt;b&gt;비전공자도 이해할 수 있는 DB 설계 입문/실전&lt;/b&gt;&lt;/u&gt;&amp;nbsp;을 수강한 경험이 있는데 쉽게 풀어서 설명해주는 강사님이 인상 깊어서 강사님의 강의로 선택하게 되었다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;앞선 경험을 통해 이미 Nginx 에 대해 간략하게 알고 있지만 약 3시간 정도 부담되지 않는 시간으로 Nginx의 기능에 대해 경험해보고자 한다.&lt;/p&gt;
&lt;hr contenteditable=&quot;false&quot; data-ke-type=&quot;horizontalRule&quot; data-ke-style=&quot;style1&quot; /&gt;
&lt;h2 data-ke-size=&quot;size26&quot;&gt;&lt;b&gt;Introduce&lt;/b&gt;&lt;/h2&gt;
&lt;h3 data-ke-size=&quot;size23&quot;&gt;&lt;b&gt;주석 공부법&lt;/b&gt;&lt;/h3&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;hr contenteditable=&quot;false&quot; data-ke-type=&quot;horizontalRule&quot; data-ke-style=&quot;style1&quot; /&gt;
&lt;h2 data-ke-size=&quot;size26&quot;&gt;&lt;b&gt;Nginx 개본 개념&lt;/b&gt;&lt;/h2&gt;
&lt;h3 data-ke-size=&quot;size23&quot;&gt;&lt;b&gt;Nginx는 왜 사용해야 할까 ?&lt;/b&gt;&lt;/h3&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;현업에서 Nginx를 안 쓰고 있는 기업을 찾기가 어렵다. 현업에서 많이 쓰고 있다는 것은 (백엔드)개발자로 취업을 하기 위해 &lt;u&gt;필수로 알아야 하는 기술&lt;/u&gt;임을 뜻한다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;Nginx 는 아래와 같이 다양한 기능을 가지고 있는 S/W 이다. 아래 기능들은 서버스를 운영할 때 필수적으로 사용하게 되는 기능들이다.&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;SSL 처리&lt;/li&gt;
&lt;li&gt;로드 밸런싱&lt;/li&gt;
&lt;li&gt;장애 대응&lt;/li&gt;
&lt;li&gt;캐싱&lt;/li&gt;
&lt;li&gt;보안 처리 (IP 차단, 요청 수 제한)&lt;/li&gt;
&lt;/ul&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;Nginx를 배운다는 것은 Nginx의 설정 파일을 내가 구성하고 싶은 대로 응용할 수 있게끔 배워야 한다. 그러려면 자주 쓰이는 Nginx의 문법과 기본적인 파일 구조를 익혀야 한다.&lt;/p&gt;
&lt;h3 data-ke-size=&quot;size23&quot;&gt;&lt;b&gt;Apache VS Nginx&lt;/b&gt;&lt;/h3&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;최근 Nginx 가 Apache 점유율을 뛰어 넘었다. Nginx를 많이 사용하는 가장 큰 이유는 Apache에 비해 훨씬 많은 트래픽을 처리할 수 있는 구조를 가지고 있다. (Nginx가 성능이 더 좋다.)&lt;/p&gt;
&lt;hr contenteditable=&quot;false&quot; data-ke-type=&quot;horizontalRule&quot; data-ke-style=&quot;style1&quot; /&gt;
&lt;h2 data-ke-size=&quot;size26&quot;&gt;&lt;b&gt;Nginx 설치 및 실행&lt;/b&gt;&lt;/h2&gt;
&lt;blockquote data-ke-style=&quot;style3&quot;&gt;⚠️ 필자는 앞서 Docker Container 을 통해 Nginx를 구성하였으나, 본 강의에는 AWS EC2 인스턴스에서 구성하여 진행하였다. 따라서 본 글에서도 동일하게 EC2 인스턴스 환경에서 실습을 진행한다.&lt;/blockquote&gt;
&lt;h3 data-ke-size=&quot;size23&quot;&gt;&lt;b&gt;Ubuntu 환경에서 Nginx 설치하기&lt;/b&gt;&lt;/h3&gt;
&lt;pre class=&quot;crystal&quot;&gt;&lt;code&gt;# apt에서 설치 가능한 패키지 리스트(최신 패키지, 버전 등)를 최신화시킨다.
# apt는 리눅스에서 사용되는 소프트웨어 패키지를 설치 및 관리할 수 있게 도와주는 툴이다.
# npm 또는 gradle과 같은 패키지 관리 도구와 비슷하다고 생각하면 된다. 
$ sudo apt update

# nginx 설치에 필요한 라이브러리 설치
$ sudo apt install curl gnupg2 ca-certificates lsb-release ubuntu-keyring

# nginx 공식 패키지를 안전하게 설치하기 위한 보안 조치이다. 자세한 코드 의미는 몰라도 된다. 
# 다만, curl, gpg, tee, |, &amp;gt;, /dev/null, echo가 무슨 기능을 하는 명령어인지는 정리해두자. 
$ curl &amp;lt;https://nginx.org/keys/nginx_signing.key&amp;gt; | gpg --dearmor \\
    | sudo tee /usr/share/keyrings/nginx-archive-keyring.gpg &amp;gt;/dev/null
$ gpg --dry-run --quiet --no-keyring --import --import-options import-show /usr/share/keyrings/nginx-archive-keyring.gpg
$ echo &quot;deb [signed-by=/usr/share/keyrings/nginx-archive-keyring.gpg] \\
&amp;lt;http://nginx.org/packages/ubuntu&amp;gt; `lsb_release -cs` nginx&quot; \\
    | sudo tee /etc/apt/sources.list.d/nginx.list

# nginx 설치
$ sudo apt update
$ sudo apt install nginx
&lt;/code&gt;&lt;/pre&gt;
&lt;h3 data-ke-size=&quot;size23&quot;&gt;&lt;b&gt;Nginx 실행하기&lt;/b&gt;&lt;/h3&gt;
&lt;pre class=&quot;elixir&quot;&gt;&lt;code&gt;# nginx 상태 확인
$ sudo systemctl status nginx

# nginx 시작
$ sudo systemctl start nginx

# nginx 상태 확인
$ sudo systemctl status nginx
&lt;/code&gt;&lt;/pre&gt;
&lt;ul style=&quot;list-style-type: disc;&quot; data-ke-list-type=&quot;disc&quot;&gt;
&lt;li&gt;실행 확인을 위하여 http://{EC2 IP 주소} 로 접속하여 본다.&lt;/li&gt;
&lt;/ul&gt;
&lt;h3 data-ke-size=&quot;size23&quot;&gt;&lt;b&gt;Nginx 로그 확인하기&lt;/b&gt;&lt;/h3&gt;
&lt;ul style=&quot;list-style-type: disc;&quot; data-ke-list-type=&quot;disc&quot;&gt;
&lt;li&gt;로그 파일 위치 : /var/log/nginx
&lt;ul style=&quot;list-style-type: disc;&quot; data-ke-list-type=&quot;disc&quot;&gt;
&lt;li&gt;access.log : Nginx 서버로 접근한 요청에 대한 정보&lt;/li&gt;
&lt;li&gt;error.log : 에러 메세지에 대한 내용&lt;/li&gt;
&lt;/ul&gt;
&lt;/li&gt;
&lt;/ul&gt;
&lt;hr contenteditable=&quot;false&quot; data-ke-type=&quot;horizontalRule&quot; data-ke-style=&quot;style1&quot; /&gt;
&lt;h2 data-ke-size=&quot;size26&quot;&gt;&lt;b&gt;Nginx를 활용해 웹 사이트 배포하기&lt;/b&gt;&lt;/h2&gt;
&lt;ul style=&quot;list-style-type: disc;&quot; data-ke-list-type=&quot;disc&quot;&gt;
&lt;li&gt;웹 서버(Web Server)는 사용자의 요청이 들어올 때마다 HTML, CSS, JS, 이미지와 같은 파일들을 제공하는 컴퓨터를 의미한다.&lt;/li&gt;
&lt;li&gt;Nginx 는 사용자의 요청이 들어올 때마다 파일(HTML, CSS, JS, 이미지 등)을 제공할 수 있는 기능을 가지고 있다.&lt;/li&gt;
&lt;li&gt;Nginx를 활용해 웹 사이트를 배포할 수 있다. 이걸 보고 &amp;lsquo;Nginx&amp;rsquo;가 웹 서버(Web Server) 역할을 한다&amp;rsquo;라고 한다.&lt;/li&gt;
&lt;/ul&gt;
&lt;h3 data-ke-size=&quot;size23&quot;&gt;&lt;b&gt;Nginx 기본 문법 해석&lt;/b&gt;&lt;/h3&gt;
&lt;ul style=&quot;list-style-type: disc;&quot; data-ke-list-type=&quot;disc&quot;&gt;
&lt;li&gt;&lt;b&gt;Nginx 설정 파일 위치&lt;/b&gt; : &lt;u&gt;&lt;b&gt;&lt;span style=&quot;color: #000000;&quot;&gt;/etc/nginx/nginx.conf&lt;/span&gt;&lt;/b&gt;&lt;/u&gt;
&lt;ul style=&quot;list-style-type: disc;&quot; data-ke-list-type=&quot;disc&quot;&gt;
&lt;li&gt;Nginx 에서 가장 근본이 되는 설정 파일 (루트 설정 파일)&lt;/li&gt;
&lt;li&gt;전역적으로 설정되어야 하는 내용(워커 프로세스 개수, 로그 저장 위치 등)&lt;/li&gt;
&lt;/ul&gt;
&lt;/li&gt;
&lt;li&gt;&lt;b&gt;기본 웹 서버(Web Server) 설정 파일&lt;/b&gt; :&amp;nbsp; &lt;u&gt;&lt;b&gt;&lt;span style=&quot;color: #000000;&quot;&gt;/etc/nginx/conf.d/default.conf&lt;/span&gt;&lt;/b&gt;&lt;/u&gt;&lt;/li&gt;
&lt;/ul&gt;
&lt;pre class=&quot;routeros&quot;&gt;&lt;code&gt;# server : '하나의 웹 사이트에 관련된 설정'을 관리하는 단위 ('server 블럭'이라고 부름)
server {
    # localhost:80으로 들어오는 요청을 이 server 블럭에서 처리하도록 설정
    # (server_name이 일치하는 server 블럭이 없는 경우 첫 번째 정의되어 있는 server 블럭을 기반으로 처리)
    # (아직은 정확히 몰라도 된다. 나중에 '멀티 도메인' 기능을 배우면 쉽게 이해할 수 있다.)
    listen       80;
    server_name  localhost;

    # / 으로 시작하는 모든 경로를 처리 (ex. /index.html)
    location / {
        # /jscode.html로 요청이 들어오면 /usr/share/nginx/html/jscode.html 파일로 응답
        root   /usr/share/nginx/html;
        
        # /로 요청이 들어오면 /usr/share/nginx/html/index.html로 응답
        # 만약 /usr/share/nginx/html/index.html이 없을 경우, /usr/share/nginx/html/index.htm으로 응답
        index  index.html index.htm;
    }

    # Nginx에서 500, 502, 503, 504의 상태 코드가 발생했을 때 /50x.html로 redirect
    error_page   500 502 503 504  /50x.html;
    
    # /50x.html과 완전히 일치하는 경로를 처리
    location = /50x.html {
        # /50x.html로 요청이 들어오면 /usr/share/nginx/html/50x.html 파일로 응답
        root   /usr/share/nginx/html;
    }
}
&lt;/code&gt;&lt;/pre&gt;
&lt;h3 data-ke-size=&quot;size23&quot;&gt;&lt;b&gt;Nginx 디버깅 절차&lt;/b&gt;&lt;/h3&gt;
&lt;pre class=&quot;maxima&quot;&gt;&lt;code&gt;# step 1. nginx 실행을 확인하라
$ sudo systemctl status nginx

# step 2. 문법을 점검하라
$ sudo nginx -t

# step 3. 로그를 확인하라
$ tail -f /var/log/nginx/access.log
$ tail -f /var/log/nginx/error.log

# step 4. 문제 해결 후 설정파일을 반영한다.
$ sudo nginx -s reload
&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;&lt;b&gt;[실습 생략]&lt;/b&gt;&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;style1&quot; /&gt;
&lt;h2 data-ke-size=&quot;size26&quot;&gt;&lt;b&gt;한 서버에서 다른 도메인을 가진 여러 웹 사이트 배포하기 (멀티도메인)&lt;/b&gt;&lt;/h2&gt;
&lt;p&gt;&lt;figure class=&quot;imageblock alignCenter&quot; data-ke-mobileStyle=&quot;widthOrigin&quot; data-filename=&quot;image 2.png&quot; data-origin-width=&quot;1410&quot; data-origin-height=&quot;706&quot;&gt;&lt;span data-url=&quot;https://blog.kakaocdn.net/dn/bpNSHy/btsNMvDQWMD/xMfi3ekuJUBL0m0y5TYvY1/img.png&quot; data-phocus=&quot;https://blog.kakaocdn.net/dn/bpNSHy/btsNMvDQWMD/xMfi3ekuJUBL0m0y5TYvY1/img.png&quot;&gt;&lt;img src=&quot;https://blog.kakaocdn.net/dn/bpNSHy/btsNMvDQWMD/xMfi3ekuJUBL0m0y5TYvY1/img.png&quot; srcset=&quot;https://img1.daumcdn.net/thumb/R1280x0/?scode=mtistory2&amp;fname=https%3A%2F%2Fblog.kakaocdn.net%2Fdn%2FbpNSHy%2FbtsNMvDQWMD%2FxMfi3ekuJUBL0m0y5TYvY1%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;1410&quot; height=&quot;706&quot; data-filename=&quot;image 2.png&quot; data-origin-width=&quot;1410&quot; data-origin-height=&quot;706&quot;/&gt;&lt;/span&gt;&lt;/figure&gt;
&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;u&gt;&lt;b&gt;/etc/nginx/conf.d/default.conf&lt;/b&gt;&lt;/u&gt; 에 두 개의 서버를 등록하여 멀티 도메인을 적용한다.&lt;/p&gt;
&lt;pre class=&quot;pgsql&quot;&gt;&lt;code&gt;server {
        listen 80;
        server_name react.manutd.co.kr;

        location / {
                root /usr/share/nginx/nginx-frontend-react/dist;
                index index.html;
        }

}

server {
        listen 80;
        server_name next.manutd.co.kr;

        location / {
                root /usr/share/nginx/nginx-frontend-next/out;
                index index.html;
        }
}
&lt;/code&gt;&lt;/pre&gt;
&lt;hr contenteditable=&quot;false&quot; data-ke-type=&quot;horizontalRule&quot; data-ke-style=&quot;style1&quot; /&gt;
&lt;h2 data-ke-size=&quot;size26&quot;&gt;&lt;b&gt;Nginx를 활용한 HTTPS 적용시키기&lt;/b&gt;&lt;/h2&gt;
&lt;h3 data-ke-size=&quot;size23&quot;&gt;&lt;b&gt;HTTPS 를 적용해야 하는 이유&lt;/b&gt;&lt;/h3&gt;
&lt;ul style=&quot;list-style-type: disc;&quot; data-ke-list-type=&quot;disc&quot;&gt;
&lt;li&gt;보안적인 이유 (HTTPS는 암호화 통신 프로토콜)&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;h3 data-ke-size=&quot;size23&quot;&gt;&lt;b&gt;Nginx, Certbot을 활용해 HTTPS 적용하기&lt;/b&gt;&lt;/h3&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;b&gt;Certbot 이란 ?&amp;nbsp;&lt;/b&gt; 웹 사이트에서 Let 's Encrypt 인증서 를 발급받고 갱신 및 관리하며 자동으로 사용하는 무료 오픈 소스 소프트웨어 도구&lt;/p&gt;
&lt;pre class=&quot;sql&quot;&gt;&lt;code&gt;# certbot 설치
$ sudo snap install --classic certbot
$ sudo ln -s /snap/bin/certbot /usr/bin/certbot

# **HTTPS 인증서 발급받기
# 반드시 도메인을 먼저 연결(레코드 등록)한 뒤에 위 명령어를 쳐야 정상 작동한다.** 
# 자동으로 default.conf 파일을 수정하여 반영한다.
$ sudo certbot --nginx -d &amp;lt;도메인 주소&amp;gt;
&lt;/code&gt;&lt;/pre&gt;
&lt;h2 data-ke-size=&quot;size26&quot;&gt;&amp;nbsp;&lt;/h2&gt;
&lt;h3 data-ke-size=&quot;size23&quot;&gt;&lt;b&gt;설정 파일 분리해서 관리하기 (include)&lt;/b&gt;&lt;/h3&gt;
&lt;ul style=&quot;list-style-type: disc;&quot; data-ke-list-type=&quot;disc&quot;&gt;
&lt;li&gt;default.conf 파일에서 다음과 같이 include 해서 파일을 분리할 수 있다.&lt;/li&gt;
&lt;/ul&gt;
&lt;pre class=&quot;stata&quot;&gt;&lt;code&gt;# default.conf
include conf.d/web/react.manutd.conf;
include conf.d/web/next.manutd.conf;
.
.
또는
.
.
include conf.d/web/*.conf;
&lt;/code&gt;&lt;/pre&gt;
&lt;hr contenteditable=&quot;false&quot; data-ke-type=&quot;horizontalRule&quot; data-ke-style=&quot;style1&quot; /&gt;
&lt;h2 data-ke-size=&quot;size26&quot;&gt;&lt;b&gt;리버스 프록시(Reverse Proxy)를 활용해 백엔드 서버 배포하기&lt;/b&gt;&lt;/h2&gt;
&lt;h3 data-ke-size=&quot;size23&quot;&gt;&lt;b&gt;용어&lt;/b&gt;&lt;/h3&gt;
&lt;ul style=&quot;list-style-type: disc;&quot; data-ke-list-type=&quot;disc&quot;&gt;
&lt;li&gt;&lt;b&gt;프록시(Proxy) :&lt;/b&gt; 중계(중간에서 연결해주는 것)의 의미&lt;/li&gt;
&lt;li&gt;&lt;b&gt;프록시 서버(Proxy Server) :&lt;/b&gt; 중간 역할을 해주는 서버&lt;/li&gt;
&lt;li&gt;&lt;b&gt;포워드 프록시(Foward Proxy) 서버&lt;/b&gt; : 보내려고 하는 요청을 관리 또는 보안 처리를 위한 용도 (ex. 사내 방화벽)&lt;/li&gt;
&lt;li&gt;&lt;b&gt;리버스 프록시(Reverse Proxy) :&lt;/b&gt; 들어오는 요청을 관리 또는 보안 처리 하기 위한 용도 (ex. HTTPS 처리, 요청 수 제한, 로드 밸런싱)&lt;/li&gt;
&lt;/ul&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;리버스 프록시는 들어오는 요청을 관리 또는 보안 처리를 하기 위해 사용한다. (악의적인 행위가 들어오는 것이 예상될 경우 활용한다)&lt;/p&gt;
&lt;h3 data-ke-size=&quot;size23&quot;&gt;&amp;nbsp;&lt;/h3&gt;
&lt;h3 data-ke-size=&quot;size23&quot;&gt;&lt;b&gt;Spring Boot 서버 배포하기&lt;/b&gt;&lt;/h3&gt;
&lt;ul style=&quot;list-style-type: disc;&quot; data-ke-list-type=&quot;disc&quot;&gt;
&lt;li&gt;&lt;b&gt;준비사항 : JDK 17 설치&lt;/b&gt;&lt;/li&gt;
&lt;/ul&gt;
&lt;pre class=&quot;sql&quot;&gt;&lt;code&gt;$ sudo apt update &amp;amp;&amp;amp; /
sudo apt install openjdk-17-jdk -y

# 설치 확인
$ java -version
&lt;/code&gt;&lt;/pre&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;ul style=&quot;list-style-type: disc;&quot; data-ke-list-type=&quot;disc&quot;&gt;
&lt;li&gt;&lt;b&gt;spring 빌드 및 실행&lt;/b&gt;&lt;/li&gt;
&lt;/ul&gt;
&lt;pre class=&quot;shell&quot;&gt;&lt;code&gt;$ ./gradlew clean build -x test
$ cd build/libs
$ nohup java -jar nginx-backend-springboot-0.0.1-SNAPSHOT.jar &amp;amp;

# 8080번 포트에서 실행되고 있는 프로세스 조회
$ lsof -i:8080
&lt;/code&gt;&lt;/pre&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;ul style=&quot;list-style-type: disc;&quot; data-ke-list-type=&quot;disc&quot;&gt;
&lt;li&gt;&lt;b&gt;conf 파일 작성&lt;/b&gt;&lt;/li&gt;
&lt;/ul&gt;
&lt;pre class=&quot;nginx&quot;&gt;&lt;code&gt;server {
				listen 80;
        server_name api.manutd.co.kr;
        
        # / 으로 시작하는 모든 경로를 처리
        location / {
				        # 들어온 요청을 전부 &amp;lt;http://localhost:8080&amp;gt;(Spring Boot 서버)로 전달
                proxy_pass &amp;lt;http://localhost:8080&amp;gt;;
        }       
}
&lt;/code&gt;&lt;/pre&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;ul style=&quot;list-style-type: disc;&quot; data-ke-list-type=&quot;disc&quot;&gt;
&lt;li&gt;&lt;b&gt;HTTPS 적용(DNS A 레코드에 사전 등록되어 있어야 한다.)&lt;/b&gt;&lt;/li&gt;
&lt;/ul&gt;
&lt;pre class=&quot;markdown&quot;&gt;&lt;code&gt;$ sudo certbot --nginx -d [api.manutd.co.kr](&amp;lt;http://api.manutd.co.kr&amp;gt;) 
&lt;/code&gt;&lt;/pre&gt;
&lt;h3 data-ke-size=&quot;size23&quot;&gt;&amp;nbsp;&lt;/h3&gt;
&lt;h3 data-ke-size=&quot;size23&quot;&gt;&lt;b&gt;IP당 요청 수 제한하기&lt;/b&gt;&lt;/h3&gt;
&lt;ul style=&quot;list-style-type: disc;&quot; data-ke-list-type=&quot;disc&quot;&gt;
&lt;li&gt;&lt;b&gt;api.conf 파일 수정&lt;/b&gt;&lt;/li&gt;
&lt;/ul&gt;
&lt;pre class=&quot;properties&quot;&gt;&lt;code&gt;# limit_req_zone : 요청 수를 제한하기 위한 메모리 공간(zone)과 요청 속도(rate)를 정의
# $binary_remote_addr : 요청 수를 제한하는 기준을 클라이언트의 IP로 설정
# zone=mylimit:10m : 메모리 공간(zone)의 이름을 mylimit이라고 지정, 
                     메모리 공간의 크기를 10MB 제한 (약 16만개의 IP 주소를 관리할 수 있음)
# rate=3r/s : 1초에 최대 3개의 요청만 허용
limit_req_zone $binary_remote_addr zone=mylimit:10m rate=3r/s;

server {
				# limit_req_zone에서 정의한 mylimit이라는 조건을 이 server 블럭에 적용
        **limit_req zone=mylimit;**
        # 요청이 제한됐을 때 429(Too Many Requests) 상태 코드를 반환
        **limit_req_status 429;**
        
        server_name api.manutd.co.kr;

        location / {
                proxy_pass &amp;lt;http://localhost:8080&amp;gt;;
        }

.
.
.
(생략)
.
# 설정 파일 재로드
$ sudo nginx -s reload
&lt;/code&gt;&lt;/pre&gt;
&lt;hr contenteditable=&quot;false&quot; data-ke-type=&quot;horizontalRule&quot; data-ke-style=&quot;style1&quot; /&gt;
&lt;h2 data-ke-size=&quot;size26&quot;&gt;&lt;b&gt;Nginx를 로드 밸런서(Load Balancer)로 사용하기&lt;/b&gt;&lt;/h2&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;b&gt;로드 밸런서&lt;/b&gt; : 사용자의 요청을 여러 대의 서버로 골고루 전달하기 위한 장치 (트래픽 부하 분산)&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;ul style=&quot;list-style-type: disc;&quot; data-ke-list-type=&quot;disc&quot;&gt;
&lt;li&gt;&lt;b&gt;8081 포트를 사용하는 spring boot server 추가&lt;/b&gt;&lt;/li&gt;
&lt;/ul&gt;
&lt;pre class=&quot;angelscript&quot;&gt;&lt;code&gt;$ cd ~/nginx-backend-springboot/build/libs
$ nohup java -jar nginx-backend-springboot-0.0.1-SNAPSHOT.jar --server.port=8081 &amp;amp;
$ lsof -i:8081
&lt;/code&gt;&lt;/pre&gt;
&lt;pre class=&quot;properties&quot;&gt;&lt;code&gt;limit_req_zone $binary_remote_addr zone=mylimit:10m rate=3r/s;

# 로드 밸런싱 대상 서버들을 upstream이라는 그룹으로 묶음
# upstream 그룹의 이름은 backend라고 지정
**upstream backend {
        server localhost:8080;
        server localhost:8081;
}**

server {
				# limit_req_zone에서 정의한 mylimit이라는 조건을 이 server 블럭에 적용
        **limit_req zone=mylimit;**
        # 요청이 제한됐을 때 429(Too Many Requests) 상태 코드를 반환
        **limit_req_status 429;**
        
        server_name api.manutd.co.kr;

        # upstream 그룹에서 지정한 서버들로 요청이 분산됨
        location / {
                proxy_pass **&amp;lt;http://backend&amp;gt;;**
        }

.
.
.
(생략)
.
# 설정 파일 재로드
$ sudo nginx -s reload
&lt;/code&gt;&lt;/pre&gt;
&lt;hr contenteditable=&quot;false&quot; data-ke-type=&quot;horizontalRule&quot; data-ke-style=&quot;style1&quot; /&gt;
&lt;h2 data-ke-size=&quot;size26&quot;&gt;&lt;b&gt;Review&lt;/b&gt;&lt;/h2&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;짧은 시간동안 Nginx 를 익히기에 아주 좋은 강의였다. 다만 해당 강의의 특징은 강의명에서도 알 수 있듯이 비전공자도 이해할 수 있는 수준에서 강의를 진행한다는 것이다. 그 말은 즉, 전공자 또는 실무자 입장에서는 다소 강의 내용이 쉬울 수 있다는 것이다. 만약 본인이 &lt;u&gt;&lt;b&gt;비전공자 입장&lt;/b&gt;&lt;/u&gt;에서 새롭게 배우는 위치라면 해당 강사님의 강의를 적극 추천하지만 &lt;u&gt;&lt;b&gt;전공자 또는 실무라&lt;/b&gt;&lt;/u&gt;자면 예제 문서를 보고 잠깐 따라해보는 것이 좀 더 효율적이라고 느꼈다.&lt;/p&gt;</description>
      <category>Back-End</category>
      <category>load balancer</category>
      <category>Nginx</category>
      <category>Revers Proxy</category>
      <category>web server</category>
      <author>dev.parkjh</author>
      <guid isPermaLink="true">https://developerjaypark.tistory.com/116</guid>
      <comments>https://developerjaypark.tistory.com/116#entry116comment</comments>
      <pubDate>Tue, 6 May 2025 16:00:49 +0900</pubDate>
    </item>
    <item>
      <title>[Inflearn] 비전공자도 이해할 수 있는 DB 설계 입문/실전</title>
      <link>https://developerjaypark.tistory.com/115</link>
      <description>&lt;p&gt;&lt;figure class=&quot;imageblock alignCenter&quot; data-ke-mobileStyle=&quot;widthOrigin&quot; data-filename=&quot;스크린샷 2025-05-04 오후 3.18.46.png&quot; data-origin-width=&quot;532&quot; data-origin-height=&quot;342&quot;&gt;&lt;span data-url=&quot;https://blog.kakaocdn.net/dn/BSUjC/btsNKsnMyUQ/qvzP1ObZtWcBq86rGhgaqk/img.png&quot; data-phocus=&quot;https://blog.kakaocdn.net/dn/BSUjC/btsNKsnMyUQ/qvzP1ObZtWcBq86rGhgaqk/img.png&quot;&gt;&lt;img src=&quot;https://blog.kakaocdn.net/dn/BSUjC/btsNKsnMyUQ/qvzP1ObZtWcBq86rGhgaqk/img.png&quot; srcset=&quot;https://img1.daumcdn.net/thumb/R1280x0/?scode=mtistory2&amp;fname=https%3A%2F%2Fblog.kakaocdn.net%2Fdn%2FBSUjC%2FbtsNKsnMyUQ%2FqvzP1ObZtWcBq86rGhgaqk%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;532&quot; height=&quot;342&quot; data-filename=&quot;스크린샷 2025-05-04 오후 3.18.46.png&quot; data-origin-width=&quot;532&quot; data-origin-height=&quot;342&quot;/&gt;&lt;/span&gt;&lt;/figure&gt;
&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;ul style=&quot;list-style-type: disc;&quot; data-ke-list-type=&quot;disc&quot;&gt;
&lt;li&gt;&lt;b&gt;inflearn&lt;/b&gt; : &lt;a href=&quot;https://www.inflearn.com/course/비전공자-db-설계-입문/dashboard&quot;&gt;https://www.inflearn.com/course/비전공자-db-설계-입문/dashboard&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;&lt;b&gt;수업 자료&lt;/b&gt;: &lt;a href=&quot;https://jscode.notion.site/d551b000ffac42879d8630cd7edd618f&quot;&gt;https://jscode.notion.site/d551b000ffac42879d8630cd7edd618f&lt;/a&gt;&lt;/li&gt;
&lt;/ul&gt;
&lt;h2 data-ke-size=&quot;size26&quot;&gt;&lt;b&gt;Preview&lt;/b&gt;&lt;/h2&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;회사에서 무작정 DB 설계를 시작했고 학부시절 그리고 자격증 취득 과정에서 배웠던 개념은 머릿속에서 떠난지 오래다. 이미 어느정도 정규화 하는 절차에 대해서 알고 있었지만 개념적인 부분을 공부해서 조금 다듬을 필요가 있었다. 강의시간이 너무 길지도 않고 단순 이론에 치중하지도 않고 실무 위주의 강의를 찾던 중 해당 강의를 찾게 되어 학습하게 되었다.&lt;/p&gt;
&lt;hr contenteditable=&quot;false&quot; data-ke-type=&quot;horizontalRule&quot; data-ke-style=&quot;style1&quot; /&gt;
&lt;h2 data-ke-size=&quot;size26&quot;&gt;&lt;b&gt;Introduce&lt;/b&gt;&lt;/h2&gt;
&lt;h3 data-ke-size=&quot;size23&quot;&gt;&lt;b&gt;공부는 어렵게 해야 한다.&lt;/b&gt;&lt;/h3&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 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;ldquo;블로그에 업로드&amp;rdquo; 하는 것이다.&lt;/p&gt;
&lt;h3 data-ke-size=&quot;size23&quot;&gt;&amp;nbsp;&lt;/h3&gt;
&lt;h3 data-ke-size=&quot;size23&quot;&gt;&lt;b&gt;파레토 법칙(Pareto principle)&lt;/b&gt;&lt;/h3&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;파레토 법칙이란 현업에서 자주 사용되는 개념인 20%만 익혀도 80%의 효율을 낸다.&lt;/li&gt;
&lt;li&gt;현업에서 사용하는 기술을 우선적으로 공부하고 현업에 돌입하여 필요한 내용만 추가적으로 공부를 하는 것이 효율적이다.&lt;/li&gt;
&lt;/ul&gt;
&lt;h3 data-ke-size=&quot;size23&quot;&gt;&amp;nbsp;&lt;/h3&gt;
&lt;h3 data-ke-size=&quot;size23&quot;&gt;&lt;b&gt;First Word 법칙&lt;/b&gt;&lt;/h3&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;hr contenteditable=&quot;false&quot; data-ke-type=&quot;horizontalRule&quot; data-ke-style=&quot;style1&quot; /&gt;
&lt;h2 data-ke-size=&quot;size26&quot;&gt;&lt;b&gt;필수 개념&lt;/b&gt;&lt;/h2&gt;
&lt;h3 data-ke-size=&quot;size23&quot;&gt;&lt;b&gt;데이터베이스 모델링(Database Modeling) 이란?&lt;/b&gt;&lt;/h3&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;h3 data-ke-size=&quot;size23&quot;&gt;&amp;nbsp;&lt;/h3&gt;
&lt;h3 data-ke-size=&quot;size23&quot;&gt;&lt;b&gt;관계형 데이터베이스 (RDBMS)의 기본 구성&lt;/b&gt;&lt;/h3&gt;
&lt;ul style=&quot;list-style-type: disc;&quot; data-ke-list-type=&quot;disc&quot;&gt;
&lt;li&gt;테이블(Table), 컬럼(Column), 로우(Row)의 구성 요소가 있다.&lt;/li&gt;
&lt;/ul&gt;
&lt;h3 data-ke-size=&quot;size23&quot;&gt;&amp;nbsp;&lt;/h3&gt;
&lt;h3 data-ke-size=&quot;size23&quot;&gt;&lt;b&gt;PK(기본키), FK(외래키)&lt;/b&gt;&lt;/h3&gt;
&lt;ul style=&quot;list-style-type: disc;&quot; data-ke-list-type=&quot;disc&quot;&gt;
&lt;li&gt;&lt;b&gt;PK(Primary Key, 기본키)&lt;/b&gt;
&lt;ul style=&quot;list-style-type: disc;&quot; data-ke-list-type=&quot;disc&quot;&gt;
&lt;li&gt;특정 데이터를 식별하기 위한 값&lt;/li&gt;
&lt;li&gt;중복되어서는 안 되며, 비어있어서도 안 된다.&lt;/li&gt;
&lt;li&gt;PK는 값이 변경되는 것을 권장하지 않는다. 따라서 현업에서는 주로 Auto Increment(숫자가 1씩 증가하는 방식)또는 UUID(랜덤값)로 설정을 많이 한다.&lt;/li&gt;
&lt;/ul&gt;
&lt;/li&gt;
&lt;li&gt;&lt;b&gt;FK(Foreign Key, 외래키)&lt;/b&gt;
&lt;ul style=&quot;list-style-type: disc;&quot; data-ke-list-type=&quot;disc&quot;&gt;
&lt;li&gt;다른 테이블의 데이터와 관계를 연결하기 위한 키&lt;/li&gt;
&lt;li&gt;FK는 PK를 저장하는 것이 일반적이다.&lt;/li&gt;
&lt;li&gt;FK는 적절하지 않은 FK를 넣었을 때 에러를 발생시킨다. (참조 무결성)&lt;/li&gt;
&lt;/ul&gt;
&lt;/li&gt;
&lt;/ul&gt;
&lt;h3 data-ke-size=&quot;size23&quot;&gt;&amp;nbsp;&lt;/h3&gt;
&lt;h3 data-ke-size=&quot;size23&quot;&gt;&lt;b&gt;데이터베이스 네이밍 규칙&lt;/b&gt;&lt;/h3&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;snake_case를 사용한다.&lt;/li&gt;
&lt;li&gt;축약어를 사용하지 않는다.&lt;/li&gt;
&lt;li&gt;SQL 쿼리를 작성할 때는 예약어만 대문자로 표현하라 (SELECT, FROM, AND, OR 등등)&lt;/li&gt;
&lt;li&gt;테이블명을 지을 때는 복수형을 사용한다. (user&amp;rarr;users, post&amp;rarr;posts)(선택사항)&lt;/li&gt;
&lt;/ul&gt;
&lt;hr contenteditable=&quot;false&quot; data-ke-type=&quot;horizontalRule&quot; data-ke-style=&quot;style1&quot; /&gt;
&lt;h2 data-ke-size=&quot;size26&quot;&gt;&lt;b&gt;DB 설계 핵심 원칙 및 전체 과정&lt;/b&gt;&lt;/h2&gt;
&lt;h3 data-ke-size=&quot;size23&quot;&gt;&lt;b&gt;아래 사항은 현재 시점에서 꼭 공부할 필요는 없다.&lt;/b&gt;&lt;/h3&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;li&gt;제1정규형 ~ 5정규형, BCNF 정규형 / 부분적 함수 종속, 이행적 함수 종속&lt;/li&gt;
&lt;/ul&gt;
&lt;h3 data-ke-size=&quot;size23&quot;&gt;&amp;nbsp;&lt;/h3&gt;
&lt;h3 data-ke-size=&quot;size23&quot;&gt;&lt;b&gt;DB 설계 시 꼭 기억해야 할 핵심 원칙 1가지&lt;/b&gt;&lt;/h3&gt;
&lt;ul style=&quot;list-style-type: disc;&quot; data-ke-list-type=&quot;disc&quot;&gt;
&lt;li&gt;데이터베이스를 설계할 때의 핵심은 &amp;ldquo;중복 없애기&amp;rdquo; 이다.&lt;/li&gt;
&lt;li&gt;저장된 데이터들 중에서 모순되는 상황이 생긴 현상을 이상현상(Anomaly)라고 한다.&lt;/li&gt;
&lt;li&gt;중복 없애기를 위한 DB 설계 방법이 정규화(Normalization) 이다.&lt;/li&gt;
&lt;/ul&gt;
&lt;h3 data-ke-size=&quot;size23&quot;&gt;&amp;nbsp;&lt;/h3&gt;
&lt;h3 data-ke-size=&quot;size23&quot;&gt;&lt;b&gt;정규화(Normalization)&lt;/b&gt;&lt;/h3&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;DB를 설계하면서 중복을 없애는 과정이 정규화이다.&lt;/li&gt;
&lt;li&gt;제1정규형, 제2정규형, 제3정규형 등 모든 정규형은 중복을 없애게 해주는 방법이다.&lt;/li&gt;
&lt;li&gt;현업에서는 1,2,3 정규형까지만 주로 적용한다.&lt;/li&gt;
&lt;/ul&gt;
&lt;h3 data-ke-size=&quot;size23&quot;&gt;&amp;nbsp;&lt;/h3&gt;
&lt;h3 data-ke-size=&quot;size23&quot;&gt;&lt;b&gt;DB 설계 전체 과정&lt;/b&gt;&lt;/h3&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;데이터베이스를 설계하기 전에 어떤 데이터를 저장해야 하는 지 파악해야 한다. 그러려면 대략적인 UI 디자인이 나와 있어야 한다. 또는 요구사항 정의서가 나와 있어야 한다.&lt;/li&gt;
&lt;li&gt;그룹핑해서 분류하기ex) 아이디, 패스워드, 이름, 이메일 &amp;rarr; 사용자&lt;/li&gt;
&lt;li&gt;ex) 게시글 제목, 게시글 내용, 작성자 &amp;rarr; 게시글&lt;/li&gt;
&lt;li&gt;저장해야 하는 데이터를 묶어서 그룹핑할 수 있는 상위 개념을 찾는다.&lt;/li&gt;
&lt;li&gt;6가지 규칙 적용시키면서 테이블 분리해나가기&lt;/li&gt;
&lt;/ol&gt;
&lt;h3 data-ke-size=&quot;size23&quot;&gt;&amp;nbsp;&lt;/h3&gt;
&lt;h3 data-ke-size=&quot;size23&quot;&gt;&lt;b&gt;저장할 데이터 파악하기&lt;/b&gt;&lt;/h3&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;h3 data-ke-size=&quot;size23&quot;&gt;&amp;nbsp;&lt;/h3&gt;
&lt;h3 data-ke-size=&quot;size23&quot;&gt;&lt;b&gt;그룹핑해서 분류하기&lt;/b&gt;&lt;/h3&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;이렇게 그룹핑 된 하나의 그룹을 보고 데이터베이스에서는 엔티티(Entity)라고 한다.&lt;/li&gt;
&lt;li&gt;엔티티(Entity)가 데이터베이스의 테이블(Table)이라고 생각해도 된다.&lt;/li&gt;
&lt;/ul&gt;
&lt;h3 data-ke-size=&quot;size23&quot;&gt;&amp;nbsp;&lt;/h3&gt;
&lt;h3 data-ke-size=&quot;size23&quot;&gt;&lt;b&gt;테이블로 나타내기&lt;/b&gt;&lt;/h3&gt;
&lt;ul style=&quot;list-style-type: disc;&quot; data-ke-list-type=&quot;disc&quot;&gt;
&lt;li&gt;&lt;b&gt;users (사용자)&lt;/b&gt;&lt;/li&gt;
&lt;/ul&gt;
&lt;table style=&quot;border-collapse: collapse; width: 100%;&quot; border=&quot;1&quot; data-ke-align=&quot;alignLeft&quot; data-ke-style=&quot;style8&quot;&gt;
&lt;tbody&gt;
&lt;tr&gt;
&lt;td style=&quot;width: 25%;&quot;&gt;&lt;b&gt;id&lt;/b&gt;&lt;/td&gt;
&lt;td style=&quot;width: 25%;&quot;&gt;&lt;b&gt;email&lt;/b&gt;&lt;/td&gt;
&lt;td style=&quot;width: 25%;&quot;&gt;&lt;b&gt;password&lt;/b&gt;&lt;/td&gt;
&lt;td style=&quot;width: 25%;&quot;&gt;&lt;b&gt;name&lt;/b&gt;&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td style=&quot;width: 25%;&quot;&gt;1&lt;/td&gt;
&lt;td style=&quot;width: 25%;&quot;&gt;test@mail.com&lt;/td&gt;
&lt;td style=&quot;width: 25%;&quot;&gt;qwer1234!&lt;/td&gt;
&lt;td style=&quot;width: 25%;&quot;&gt;jay&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td style=&quot;width: 25%;&quot;&gt;2&lt;/td&gt;
&lt;td style=&quot;width: 25%;&quot;&gt;test1@mail.com&lt;/td&gt;
&lt;td style=&quot;width: 25%;&quot;&gt;qwer1234!&lt;/td&gt;
&lt;td style=&quot;width: 25%;&quot;&gt;kim&lt;/td&gt;
&lt;/tr&gt;
&lt;/tbody&gt;
&lt;/table&gt;
&lt;hr contenteditable=&quot;false&quot; data-ke-type=&quot;horizontalRule&quot; data-ke-style=&quot;style1&quot; /&gt;
&lt;h2 data-ke-size=&quot;size26&quot;&gt;&lt;b&gt;DB 설계 규칙 6가지&lt;/b&gt;&lt;/h2&gt;
&lt;h3 data-ke-size=&quot;size23&quot;&gt;&lt;b&gt;[규칙 1] 한 칸에는 한 가지 정보만 들어가도록 만들어라&lt;/b&gt;&lt;/h3&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;DB 테이블을 설계 할 때 &amp;lsquo;한 칸에는 한 가지 정보만 들어가야 한다.&amp;rsquo;라는 규칙을 지켜야 한다. 규칙을 지키지 않은 사례는 아래와 같다.&lt;/p&gt;
&lt;ul style=&quot;list-style-type: disc;&quot; data-ke-list-type=&quot;disc&quot;&gt;
&lt;li&gt;&lt;b&gt;users(사용자)&lt;/b&gt;&lt;/li&gt;
&lt;/ul&gt;
&lt;table style=&quot;border-collapse: collapse; width: 100%; height: 51px;&quot; border=&quot;1&quot; data-ke-align=&quot;alignLeft&quot; data-ke-style=&quot;style8&quot;&gt;
&lt;tbody&gt;
&lt;tr style=&quot;height: 17px;&quot;&gt;
&lt;td style=&quot;width: 33.3333%; height: 17px;&quot;&gt;&lt;b&gt;id(PK)&lt;/b&gt;&lt;/td&gt;
&lt;td style=&quot;width: 33.3333%; height: 17px;&quot;&gt;&lt;b&gt;name&lt;/b&gt;&lt;/td&gt;
&lt;td style=&quot;width: 33.3333%; height: 17px;&quot;&gt;&lt;b&gt;email&lt;/b&gt;&lt;/td&gt;
&lt;/tr&gt;
&lt;tr style=&quot;height: 17px;&quot;&gt;
&lt;td style=&quot;width: 33.3333%; height: 17px;&quot;&gt;1&lt;/td&gt;
&lt;td style=&quot;width: 33.3333%; height: 17px;&quot;&gt;parkjh&lt;/td&gt;
&lt;td style=&quot;width: 33.3333%; height: 17px;&quot;&gt;parkjh@gmail.com, jhpark@gmail.com&lt;/td&gt;
&lt;/tr&gt;
&lt;tr style=&quot;height: 17px;&quot;&gt;
&lt;td style=&quot;width: 33.3333%; height: 17px;&quot;&gt;2&lt;/td&gt;
&lt;td style=&quot;width: 33.3333%; height: 17px;&quot;&gt;parkmj&lt;/td&gt;
&lt;td style=&quot;width: 33.3333%; height: 17px;&quot;&gt;parkmj@gmail.com, mjpark@gmail.com&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;데이터를 콤마(,) 로 구분하는 것이 좋지 않은 선택인 이유&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 data-ke-size=&quot;size16&quot;&gt;한 칸에 2개 이상의 정보가 들어가 있다면 ? &amp;rarr; 테이블을 분리한다.&lt;/p&gt;
&lt;ul style=&quot;list-style-type: disc;&quot; data-ke-list-type=&quot;disc&quot;&gt;
&lt;li&gt;&lt;b&gt;users(사용자)&lt;/b&gt;&lt;/li&gt;
&lt;/ul&gt;
&lt;table style=&quot;border-collapse: collapse; width: 100%;&quot; border=&quot;1&quot; data-ke-align=&quot;alignLeft&quot; data-ke-style=&quot;style8&quot;&gt;
&lt;tbody&gt;
&lt;tr&gt;
&lt;td style=&quot;width: 50%;&quot;&gt;&lt;b&gt;id(PK)&lt;/b&gt;&lt;/td&gt;
&lt;td style=&quot;width: 50%;&quot;&gt;&lt;b&gt;name&lt;/b&gt;&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td style=&quot;width: 50%;&quot;&gt;1&lt;/td&gt;
&lt;td style=&quot;width: 50%;&quot;&gt;parkjh&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td style=&quot;width: 50%;&quot;&gt;2&lt;/td&gt;
&lt;td style=&quot;width: 50%;&quot;&gt;parkmj&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;ul style=&quot;list-style-type: disc;&quot; data-ke-list-type=&quot;disc&quot;&gt;
&lt;li&gt;&lt;b&gt;emails(이메일)&lt;/b&gt;&lt;/li&gt;
&lt;/ul&gt;
&lt;table style=&quot;border-collapse: collapse; width: 100%;&quot; border=&quot;1&quot; data-ke-align=&quot;alignLeft&quot; data-ke-style=&quot;style8&quot;&gt;
&lt;tbody&gt;
&lt;tr&gt;
&lt;td style=&quot;width: 33.3333%;&quot;&gt;&lt;b&gt;id(PK)&lt;/b&gt;&lt;/td&gt;
&lt;td style=&quot;width: 33.3333%;&quot;&gt;&lt;b&gt;email&lt;/b&gt;&lt;/td&gt;
&lt;td style=&quot;width: 33.3333%;&quot;&gt;&lt;b&gt;user_id(FK)&lt;/b&gt;&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td style=&quot;width: 33.3333%;&quot;&gt;1&lt;/td&gt;
&lt;td style=&quot;width: 33.3333%;&quot;&gt;parkjh@gmail.com&lt;/td&gt;
&lt;td style=&quot;width: 33.3333%;&quot;&gt;1&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td style=&quot;width: 33.3333%;&quot;&gt;2&lt;/td&gt;
&lt;td style=&quot;width: 33.3333%;&quot;&gt;jhpark@gmail.com&lt;/td&gt;
&lt;td style=&quot;width: 33.3333%;&quot;&gt;1&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td style=&quot;width: 33.3333%;&quot;&gt;3&lt;/td&gt;
&lt;td style=&quot;width: 33.3333%;&quot;&gt;parkmj@gmail.com&lt;/td&gt;
&lt;td style=&quot;width: 33.3333%;&quot;&gt;2&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td style=&quot;width: 33.3333%;&quot;&gt;4&lt;/td&gt;
&lt;td style=&quot;width: 33.3333%;&quot;&gt;mjpark@gmail.com&lt;/td&gt;
&lt;td style=&quot;width: 33.3333%;&quot;&gt;2&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;이 과정을 보고 데이터베이스 이론에서는 &lt;b&gt;제1정규형&lt;/b&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;b&gt;요약&lt;/b&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;한 칸에 두 가지 이상의 정보가 들어가있을 땐 테이블을 분리해서 FK를 활용하면 된다.&lt;/li&gt;
&lt;li&gt;특정 테이블에 FK를 도입했을 때 규칙 1이 안 지켜진다면, 다른 테이블로 FK를 옮겨보자.&lt;/li&gt;
&lt;li&gt;&amp;lsquo;한 가지 정보&amp;rsquo;의 기준은 절대적이지 않다. 따라서 서비스에 맞게 판단해야 한다.&lt;/li&gt;
&lt;/ul&gt;
&lt;h3 data-ke-size=&quot;size23&quot;&gt;&amp;nbsp;&lt;/h3&gt;
&lt;h3 data-ke-size=&quot;size23&quot;&gt;&lt;b&gt;[규칙 2] 어떤 테이블에 FK를 넣어도 &amp;lsquo;규칙 1&amp;rsquo;을 지키지 못할 때는 중간 테이블을 하나 더 만들어라&lt;/b&gt;&lt;/h3&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;아래 테이블의 규칙 1과 같이 적용하기에는 중복이 명확하게 제거되지 않는다.&lt;/p&gt;
&lt;ul style=&quot;list-style-type: disc;&quot; data-ke-list-type=&quot;disc&quot;&gt;
&lt;li&gt;&lt;b&gt;students (학생)&lt;/b&gt;&lt;/li&gt;
&lt;/ul&gt;
&lt;table style=&quot;border-collapse: collapse; width: 100%;&quot; border=&quot;1&quot; data-ke-align=&quot;alignLeft&quot; data-ke-style=&quot;style8&quot;&gt;
&lt;tbody&gt;
&lt;tr&gt;
&lt;td style=&quot;width: 33.3333%;&quot;&gt;&lt;b&gt;id(PK)&lt;/b&gt;&lt;/td&gt;
&lt;td style=&quot;width: 33.3333%;&quot;&gt;&lt;b&gt;name&lt;/b&gt;&lt;/td&gt;
&lt;td style=&quot;width: 33.3333%;&quot;&gt;&lt;b&gt;courses&lt;/b&gt;&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td style=&quot;width: 33.3333%;&quot;&gt;1&lt;/td&gt;
&lt;td style=&quot;width: 33.3333%;&quot;&gt;김철수&lt;/td&gt;
&lt;td style=&quot;width: 33.3333%;&quot;&gt;수학, 과학&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td style=&quot;width: 33.3333%;&quot;&gt;2&lt;/td&gt;
&lt;td style=&quot;width: 33.3333%;&quot;&gt;박민지&lt;/td&gt;
&lt;td style=&quot;width: 33.3333%;&quot;&gt;국어, 수학&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td style=&quot;width: 33.3333%;&quot;&gt;3&lt;/td&gt;
&lt;td style=&quot;width: 33.3333%;&quot;&gt;조혜진&lt;/td&gt;
&lt;td style=&quot;width: 33.3333%;&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;ul style=&quot;list-style-type: disc;&quot; data-ke-list-type=&quot;disc&quot;&gt;
&lt;li&gt;&lt;b&gt;courses (수강과목)&lt;/b&gt;&lt;/li&gt;
&lt;/ul&gt;
&lt;table style=&quot;border-collapse: collapse; width: 100%;&quot; border=&quot;1&quot; data-ke-align=&quot;alignLeft&quot; data-ke-style=&quot;style8&quot;&gt;
&lt;tbody&gt;
&lt;tr&gt;
&lt;td style=&quot;width: 33.3333%;&quot;&gt;&lt;b&gt;id(PK)&lt;/b&gt;&lt;/td&gt;
&lt;td style=&quot;width: 33.3333%;&quot;&gt;&lt;b&gt;name&lt;/b&gt;&lt;/td&gt;
&lt;td style=&quot;width: 33.3333%;&quot;&gt;&lt;b&gt;student_id(FK)&lt;/b&gt;&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td style=&quot;width: 33.3333%;&quot;&gt;1&lt;/td&gt;
&lt;td style=&quot;width: 33.3333%;&quot;&gt;수학&lt;/td&gt;
&lt;td style=&quot;width: 33.3333%;&quot;&gt;1,2&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td style=&quot;width: 33.3333%;&quot;&gt;2&lt;/td&gt;
&lt;td style=&quot;width: 33.3333%;&quot;&gt;과학&lt;/td&gt;
&lt;td style=&quot;width: 33.3333%;&quot;&gt;1,3&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td style=&quot;width: 33.3333%;&quot;&gt;3&lt;/td&gt;
&lt;td style=&quot;width: 33.3333%;&quot;&gt;국어&lt;/td&gt;
&lt;td style=&quot;width: 33.3333%;&quot;&gt;2,3&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;아래와 같이 테이블을 하나 더 추가해서 개선할 수 있다.&lt;/p&gt;
&lt;ul style=&quot;list-style-type: disc;&quot; data-ke-list-type=&quot;disc&quot;&gt;
&lt;li&gt;&lt;b&gt;students (학생)&lt;/b&gt;&lt;/li&gt;
&lt;/ul&gt;
&lt;table style=&quot;border-collapse: collapse; width: 100%;&quot; border=&quot;1&quot; data-ke-align=&quot;alignLeft&quot; data-ke-style=&quot;style8&quot;&gt;
&lt;tbody&gt;
&lt;tr&gt;
&lt;td style=&quot;width: 50%;&quot;&gt;&lt;b&gt;id(PK)&lt;/b&gt;&lt;/td&gt;
&lt;td style=&quot;width: 50%;&quot;&gt;&lt;b&gt;name&lt;/b&gt;&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td style=&quot;width: 50%;&quot;&gt;1&lt;/td&gt;
&lt;td style=&quot;width: 50%;&quot;&gt;김철수&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td style=&quot;width: 50%;&quot;&gt;2&lt;/td&gt;
&lt;td style=&quot;width: 50%;&quot;&gt;박민지&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td style=&quot;width: 50%;&quot;&gt;3&lt;/td&gt;
&lt;td style=&quot;width: 50%;&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;ul style=&quot;list-style-type: disc;&quot; data-ke-list-type=&quot;disc&quot;&gt;
&lt;li&gt;&lt;b&gt;courses (수강과목)&lt;/b&gt;&lt;/li&gt;
&lt;/ul&gt;
&lt;table style=&quot;border-collapse: collapse; width: 100%;&quot; border=&quot;1&quot; data-ke-align=&quot;alignLeft&quot; data-ke-style=&quot;style8&quot;&gt;
&lt;tbody&gt;
&lt;tr&gt;
&lt;td style=&quot;width: 50%;&quot;&gt;&lt;b&gt;id(PK)&lt;/b&gt;&lt;/td&gt;
&lt;td style=&quot;width: 50%;&quot;&gt;&lt;b&gt;name&lt;/b&gt;&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td style=&quot;width: 50%;&quot;&gt;1&lt;/td&gt;
&lt;td style=&quot;width: 50%;&quot;&gt;수학&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td style=&quot;width: 50%;&quot;&gt;2&lt;/td&gt;
&lt;td style=&quot;width: 50%;&quot;&gt;과학&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td style=&quot;width: 50%;&quot;&gt;3&lt;/td&gt;
&lt;td style=&quot;width: 50%;&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;ul style=&quot;list-style-type: disc;&quot; data-ke-list-type=&quot;disc&quot;&gt;
&lt;li&gt;&lt;b&gt;students_courses &amp;rarr; 테이블명을 동사를 생각 하면 좀 더 가독성 생긴다 (course_registrations)&lt;/b&gt;&lt;/li&gt;
&lt;/ul&gt;
&lt;table style=&quot;border-collapse: collapse; width: 100%;&quot; border=&quot;1&quot; data-ke-align=&quot;alignLeft&quot; data-ke-style=&quot;style8&quot;&gt;
&lt;tbody&gt;
&lt;tr&gt;
&lt;td style=&quot;width: 33.3333%;&quot;&gt;&lt;b&gt;id(PK)&lt;/b&gt;&lt;/td&gt;
&lt;td style=&quot;width: 33.3333%;&quot;&gt;&lt;b&gt;student_id(FK)&lt;/b&gt;&lt;/td&gt;
&lt;td style=&quot;width: 33.3333%;&quot;&gt;&lt;b&gt;course_id(FK)&lt;/b&gt;&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td style=&quot;width: 33.3333%;&quot;&gt;1&lt;/td&gt;
&lt;td style=&quot;width: 33.3333%;&quot;&gt;1&lt;/td&gt;
&lt;td style=&quot;width: 33.3333%;&quot;&gt;1&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td style=&quot;width: 33.3333%;&quot;&gt;2&lt;/td&gt;
&lt;td style=&quot;width: 33.3333%;&quot;&gt;1&lt;/td&gt;
&lt;td style=&quot;width: 33.3333%;&quot;&gt;2&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td style=&quot;width: 33.3333%;&quot;&gt;3&lt;/td&gt;
&lt;td style=&quot;width: 33.3333%;&quot;&gt;2&lt;/td&gt;
&lt;td style=&quot;width: 33.3333%;&quot;&gt;1&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td style=&quot;width: 33.3333%;&quot;&gt;4&lt;/td&gt;
&lt;td style=&quot;width: 33.3333%;&quot;&gt;2&lt;/td&gt;
&lt;td style=&quot;width: 33.3333%;&quot;&gt;3&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td style=&quot;width: 33.3333%;&quot;&gt;5&lt;/td&gt;
&lt;td style=&quot;width: 33.3333%;&quot;&gt;3&lt;/td&gt;
&lt;td style=&quot;width: 33.3333%;&quot;&gt;2&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td style=&quot;width: 33.3333%;&quot;&gt;6&lt;/td&gt;
&lt;td style=&quot;width: 33.3333%;&quot;&gt;3&lt;/td&gt;
&lt;td style=&quot;width: 33.3333%;&quot;&gt;3&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;h3 data-ke-size=&quot;size23&quot;&gt;&amp;nbsp;&lt;/h3&gt;
&lt;h3 data-ke-size=&quot;size23&quot;&gt;&lt;b&gt;[규칙 3] 헷갈릴 땐 관계(1:1, 1:N, N:M) 를 파악해봐라&lt;/b&gt;&lt;/h3&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;규칙 1과 규칙 2가 헷갈릴 땐 엔티티 간의 관계를 파악한다.&lt;/p&gt;
&lt;h4 data-ke-size=&quot;size20&quot;&gt;&lt;b&gt;엔티티 관계 파악 방법&lt;/b&gt;&lt;/h4&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;b&gt;엔티티 간에 어울리는 동사 찾기&lt;/b&gt;&lt;/p&gt;
&lt;ol style=&quot;list-style-type: decimal;&quot; data-ke-list-type=&quot;decimal&quot;&gt;
&lt;li&gt;A(주어)가 B를 ___. (A가 주어)&lt;/li&gt;
&lt;li&gt;B(주어)가 A에 의해 ___. (B가 주어)&lt;/li&gt;
&lt;/ol&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;b&gt;1번 과정에서 찾은 동사를 활용해 적절한 단어 (하나의 or 여러개의) 찾기&lt;/b&gt;&lt;/p&gt;
&lt;ol style=&quot;list-style-type: decimal;&quot; data-ke-list-type=&quot;decimal&quot;&gt;
&lt;li&gt;하나의 A는 (하나의 or 여러 개의) B를 ___. (A의 관점)&lt;/li&gt;
&lt;li&gt;하나의 B는 (하나의 or 여러 개의) A에 의해 ___ (B의 관점)&lt;/li&gt;
&lt;/ol&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;** 문장 처음에 시작하는 &amp;lsquo;하나의&amp;rsquo; 라는 말을 반드시 붙어야 헷갈리지 않는다.&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;b&gt;관계 판단하기&lt;/b&gt;&lt;/p&gt;
&lt;ol style=&quot;list-style-type: decimal;&quot; data-ke-list-type=&quot;decimal&quot;&gt;
&lt;li&gt;A, B의 관점 전부 다 &amp;lsquo;하나&amp;rsquo;만 가진다면 &amp;rarr; A:B=1:1&lt;/li&gt;
&lt;li&gt;A의 관점에서는 &amp;lsquo;여러개&amp;rsquo;의 B를 가지고, B의 관점에서는 &amp;lsquo;하나&amp;rsquo;의 A를 가진다면 &amp;rarr; A:B=1:N&lt;/li&gt;
&lt;li&gt;A의 관점에서는 &amp;lsquo;하나&amp;rsquo;의 B를 가지고, B의 관점에서는 &amp;lsquo;여러개&amp;rsquo;의 A를 가진다면 &amp;rarr; A:B=N:1&lt;/li&gt;
&lt;li&gt;A, B 관점 전부 다 &amp;lsquo;여러개&amp;rsquo;를 가진다면 &amp;rarr; A:B=N:M&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;&lt;b&gt;users(사용자)&lt;/b&gt;&lt;/li&gt;
&lt;/ul&gt;
&lt;table style=&quot;border-collapse: collapse; width: 100%;&quot; border=&quot;1&quot; data-ke-align=&quot;alignLeft&quot; data-ke-style=&quot;style8&quot;&gt;
&lt;tbody&gt;
&lt;tr&gt;
&lt;td style=&quot;width: 50%;&quot;&gt;&lt;b&gt;id(PK)&lt;/b&gt;&lt;/td&gt;
&lt;td style=&quot;width: 50%;&quot;&gt;&lt;b&gt;name&lt;/b&gt;&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td style=&quot;width: 50%;&quot;&gt;1&lt;/td&gt;
&lt;td style=&quot;width: 50%;&quot;&gt;parkjh&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td style=&quot;width: 50%;&quot;&gt;2&lt;/td&gt;
&lt;td style=&quot;width: 50%;&quot;&gt;kimsy&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;ul style=&quot;list-style-type: disc;&quot; data-ke-list-type=&quot;disc&quot;&gt;
&lt;li&gt;&lt;b&gt;emails(이메일)&lt;/b&gt;&lt;/li&gt;
&lt;/ul&gt;
&lt;table style=&quot;border-collapse: collapse; width: 100%;&quot; border=&quot;1&quot; data-ke-align=&quot;alignLeft&quot; data-ke-style=&quot;style8&quot;&gt;
&lt;tbody&gt;
&lt;tr&gt;
&lt;td style=&quot;width: 33.3333%;&quot;&gt;&lt;b&gt;id(PK)&lt;/b&gt;&lt;/td&gt;
&lt;td style=&quot;width: 33.3333%;&quot;&gt;&lt;b&gt;email&lt;/b&gt;&lt;/td&gt;
&lt;td style=&quot;width: 33.3333%;&quot;&gt;&lt;b&gt;user_id(FK)&lt;/b&gt;&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td style=&quot;width: 33.3333%;&quot;&gt;1&lt;/td&gt;
&lt;td style=&quot;width: 33.3333%;&quot;&gt;parkjh@gmail.com&lt;/td&gt;
&lt;td style=&quot;width: 33.3333%;&quot;&gt;1&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td style=&quot;width: 33.3333%;&quot;&gt;2&lt;/td&gt;
&lt;td style=&quot;width: 33.3333%;&quot;&gt;jhpark@gmail.com&lt;/td&gt;
&lt;td style=&quot;width: 33.3333%;&quot;&gt;1&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td style=&quot;width: 33.3333%;&quot;&gt;3&lt;/td&gt;
&lt;td style=&quot;width: 33.3333%;&quot;&gt;kimsy@gmail.com&lt;/td&gt;
&lt;td style=&quot;width: 33.3333%;&quot;&gt;2&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td style=&quot;width: 33.3333%;&quot;&gt;4&lt;/td&gt;
&lt;td style=&quot;width: 33.3333%;&quot;&gt;sykim@gmail.com&lt;/td&gt;
&lt;td style=&quot;width: 33.3333%;&quot;&gt;2&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;ol style=&quot;list-style-type: decimal;&quot; data-ke-list-type=&quot;decimal&quot;&gt;
&lt;li&gt;&lt;b&gt;엔티티 간에 어울리는 동사 찾기&lt;/b&gt;
&lt;ul style=&quot;list-style-type: disc;&quot; data-ke-list-type=&quot;disc&quot;&gt;
&lt;li&gt;사용자(A)가 이메일(B)을 소유한다.&lt;/li&gt;
&lt;li&gt;이메일(B)은 사용자(A)에 의해 소유되어 진다.&lt;/li&gt;
&lt;/ul&gt;
&lt;/li&gt;
&lt;li&gt;&lt;b&gt;1번 과정에서 찾은 동사를 활용해 적절한 단어(하나의 or 여러개의) 찾기&lt;/b&gt;
&lt;ul style=&quot;list-style-type: disc;&quot; data-ke-list-type=&quot;disc&quot;&gt;
&lt;li&gt;하나의 사용자(A)는 &amp;lsquo;여러 개&amp;rsquo;의 이메일(B)을 소유한다.&lt;/li&gt;
&lt;li&gt;하나의 이메일(B)은 &amp;lsquo;하나&amp;rsquo;의 사용자(A)에 의해 소유되어진다.&lt;/li&gt;
&lt;/ul&gt;
&lt;/li&gt;
&lt;li&gt;&lt;b&gt;관계 판단하기&lt;/b&gt;
&lt;ul style=&quot;list-style-type: disc;&quot; data-ke-list-type=&quot;disc&quot;&gt;
&lt;li&gt;&lt;b&gt;사용자 : 이메일 = 1 : N&lt;/b&gt;&lt;/li&gt;
&lt;/ul&gt;
&lt;/li&gt;
&lt;/ol&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;b&gt;학생, 수강 과목 예제&lt;/b&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;하나의 수강 과목이 여러 명의 학생에 의해 들어진다.&lt;/li&gt;
&lt;li&gt;학생 : 수강과목 = N : M&lt;/li&gt;
&lt;/ul&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;b&gt;사용자, 프로필 예제&lt;/b&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;하나의 프로필은 하나의 사용자에 의해 등록되어진다.&lt;/li&gt;
&lt;li&gt;사용자 : 프로필 = 1 : 1&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;b&gt;1:1 관계의 특징&lt;/b&gt;&lt;/p&gt;
&lt;ul style=&quot;list-style-type: disc;&quot; data-ke-list-type=&quot;disc&quot;&gt;
&lt;li&gt;아무 테이블에 FK를 넣어도 된다.&lt;/li&gt;
&lt;li&gt;합쳐도 되는 지 고려해봐야 한다. (어지간하면 1:1 관계로 분리하지 않는 것을 추천한다.)&lt;/li&gt;
&lt;/ul&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;b&gt;1:N 관계의 특징&lt;/b&gt;&lt;/p&gt;
&lt;ul style=&quot;list-style-type: disc;&quot; data-ke-list-type=&quot;disc&quot;&gt;
&lt;li&gt;N 쪽의 테이블에 FK가 들어가야 한다.&lt;/li&gt;
&lt;/ul&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;b&gt;N:M 관계의 특징&lt;/b&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;중간 테이블에 두 테이블의 FK가 들어가야 한다.&lt;/li&gt;
&lt;li&gt;N:M 관계에서 중간 테이블을 추가해 1:N 관계로 바꿔 표현하게 된다.&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;b&gt;[규칙 4] 데이터 중복이 발생하는 컬럼이 있는 지 확인해라&lt;/b&gt;&lt;/h3&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;아래 테이블은 작성자에서 중복이 발생함을 확인한다. 사용자 테이블을 하나 만들어 중복을 제거한다.&lt;/li&gt;
&lt;/ul&gt;
&lt;table style=&quot;border-collapse: collapse; width: 100%; height: 68px;&quot; border=&quot;1&quot; data-ke-align=&quot;alignLeft&quot; data-ke-style=&quot;style8&quot;&gt;
&lt;tbody&gt;
&lt;tr style=&quot;height: 17px;&quot;&gt;
&lt;td style=&quot;width: 25%; height: 17px;&quot;&gt;&lt;b&gt;id(PK)&lt;/b&gt;&lt;/td&gt;
&lt;td style=&quot;width: 25%; height: 17px;&quot;&gt;&lt;b&gt;subject&lt;/b&gt;&lt;/td&gt;
&lt;td style=&quot;width: 25%; height: 17px;&quot;&gt;&lt;b&gt;contents&lt;/b&gt;&lt;/td&gt;
&lt;td style=&quot;width: 25%; height: 17px;&quot;&gt;&lt;b&gt;register&lt;/b&gt;&lt;/td&gt;
&lt;/tr&gt;
&lt;tr style=&quot;height: 17px;&quot;&gt;
&lt;td style=&quot;width: 25%; height: 17px;&quot;&gt;1&lt;/td&gt;
&lt;td style=&quot;width: 25%; height: 17px;&quot;&gt;test1&lt;/td&gt;
&lt;td style=&quot;width: 25%; height: 17px;&quot;&gt;hello1&lt;/td&gt;
&lt;td style=&quot;width: 25%; height: 17px;&quot;&gt;parkjh&lt;/td&gt;
&lt;/tr&gt;
&lt;tr style=&quot;height: 17px;&quot;&gt;
&lt;td style=&quot;width: 25%; height: 17px;&quot;&gt;2&lt;/td&gt;
&lt;td style=&quot;width: 25%; height: 17px;&quot;&gt;test2&lt;/td&gt;
&lt;td style=&quot;width: 25%; height: 17px;&quot;&gt;hello1&lt;/td&gt;
&lt;td style=&quot;width: 25%; height: 17px;&quot;&gt;parkjh&lt;/td&gt;
&lt;/tr&gt;
&lt;tr style=&quot;height: 17px;&quot;&gt;
&lt;td style=&quot;width: 25%; height: 17px;&quot;&gt;3&lt;/td&gt;
&lt;td style=&quot;width: 25%; height: 17px;&quot;&gt;test2&lt;/td&gt;
&lt;td style=&quot;width: 25%; height: 17px;&quot;&gt;hello2&lt;/td&gt;
&lt;td style=&quot;width: 25%; height: 17px;&quot;&gt;parkmj&lt;/td&gt;
&lt;/tr&gt;
&lt;/tbody&gt;
&lt;/table&gt;
&lt;h3 data-ke-size=&quot;size23&quot;&gt;&amp;nbsp;&lt;/h3&gt;
&lt;h3 data-ke-size=&quot;size23&quot;&gt;&lt;b&gt;[규칙 5] 가짜 중복과 진짜 중복을 구별해라&lt;/b&gt;&lt;/h3&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;가짜 중복과 진짜 중복을 판단하기 위해서는 &amp;ldquo;실제 서비스에서 A 데이터의 값을 수정하면, B 데이터의 값도 수정해야 하는지 판단&amp;rdquo;하면 된다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;아래 테이블은 제목, 내용, 작성자 모두 중복이 발생하였다. 하지만 진짜 중복은 작성자 컬럼에서만 발생하였다. 판단 기준에 따르면 작성자가 변경되면 모든 데이터가 변경되어야 한다. 하지만 제목과 내용은 작성자가 변경되어도 다른 데이터를 변경할 필요는 없다.&lt;/p&gt;
&lt;table style=&quot;border-collapse: collapse; width: 100%; height: 68px;&quot; border=&quot;1&quot; data-ke-style=&quot;style8&quot; data-ke-align=&quot;alignLeft&quot;&gt;
&lt;tbody&gt;
&lt;tr style=&quot;height: 17px;&quot;&gt;
&lt;td style=&quot;width: 25%; height: 17px;&quot;&gt;&lt;b&gt;id(PK)&lt;/b&gt;&lt;/td&gt;
&lt;td style=&quot;width: 25%; height: 17px;&quot;&gt;&lt;b&gt;subject&lt;/b&gt;&lt;/td&gt;
&lt;td style=&quot;width: 25%; height: 17px;&quot;&gt;&lt;b&gt;contents&lt;/b&gt;&lt;/td&gt;
&lt;td style=&quot;width: 25%; height: 17px;&quot;&gt;&lt;b&gt;register&lt;/b&gt;&lt;/td&gt;
&lt;/tr&gt;
&lt;tr style=&quot;height: 17px;&quot;&gt;
&lt;td style=&quot;width: 25%; height: 17px;&quot;&gt;1&lt;/td&gt;
&lt;td style=&quot;width: 25%; height: 17px;&quot;&gt;test&lt;/td&gt;
&lt;td style=&quot;width: 25%; height: 17px;&quot;&gt;hello&lt;/td&gt;
&lt;td style=&quot;width: 25%; height: 17px;&quot;&gt;parkjh&lt;/td&gt;
&lt;/tr&gt;
&lt;tr style=&quot;height: 17px;&quot;&gt;
&lt;td style=&quot;width: 25%; height: 17px;&quot;&gt;2&lt;/td&gt;
&lt;td style=&quot;width: 25%; height: 17px;&quot;&gt;test&lt;/td&gt;
&lt;td style=&quot;width: 25%; height: 17px;&quot;&gt;hello&lt;/td&gt;
&lt;td style=&quot;width: 25%; height: 17px;&quot;&gt;parkjh&lt;/td&gt;
&lt;/tr&gt;
&lt;tr style=&quot;height: 17px;&quot;&gt;
&lt;td style=&quot;width: 25%; height: 17px;&quot;&gt;3&lt;/td&gt;
&lt;td style=&quot;width: 25%; height: 17px;&quot;&gt;test&lt;/td&gt;
&lt;td style=&quot;width: 25%; height: 17px;&quot;&gt;hello&lt;/td&gt;
&lt;td style=&quot;width: 25%; height: 17px;&quot;&gt;parkjh&lt;/td&gt;
&lt;/tr&gt;
&lt;/tbody&gt;
&lt;/table&gt;
&lt;h3 data-ke-size=&quot;size23&quot;&gt;&amp;nbsp;&lt;/h3&gt;
&lt;h3 data-ke-size=&quot;size23&quot;&gt;&lt;b&gt;[규칙 6] 숨어있는 중복을 찾아라&lt;/b&gt;&lt;/h3&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;아래 테이블은 중복이 없는 것처럼 보이지만 만약 사용자가 게시글의 좋아요를 취소하기 위해 &lt;b&gt;likes 테이블의&lt;/b&gt; 해당 로우를 삭제한다면 좋아요 수도 변경되어야 한다. 하지만 만약 좋아요 수가 변경되지 않는다면 여기서 중복이 발생했을 때와 동일한 단점이 존재한다. 이처럼 숨어있는 중복은 주로 집계(합계, 평균, 최대값 등)의 값에서 많이 나타난다.&lt;/p&gt;
&lt;ul style=&quot;list-style-type: disc;&quot; data-ke-list-type=&quot;disc&quot;&gt;
&lt;li&gt;&lt;b&gt;posts (게시글)&lt;/b&gt;&lt;/li&gt;
&lt;/ul&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: 20%;&quot;&gt;id(PK)&lt;/td&gt;
&lt;td style=&quot;width: 20%;&quot;&gt;subject&lt;/td&gt;
&lt;td style=&quot;width: 20%;&quot;&gt;contents&lt;/td&gt;
&lt;td style=&quot;width: 20%;&quot;&gt;likes&lt;/td&gt;
&lt;td style=&quot;width: 20%;&quot;&gt;user_id(FK)&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td style=&quot;width: 20%;&quot;&gt;1&lt;/td&gt;
&lt;td style=&quot;width: 20%;&quot;&gt;제목1&lt;/td&gt;
&lt;td style=&quot;width: 20%;&quot;&gt;내용1&lt;/td&gt;
&lt;td style=&quot;width: 20%;&quot;&gt;2&lt;/td&gt;
&lt;td style=&quot;width: 20%;&quot;&gt;1&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;ul style=&quot;list-style-type: disc;&quot; data-ke-list-type=&quot;disc&quot;&gt;
&lt;li&gt;&lt;b&gt;users (사용자)&lt;/b&gt;&lt;/li&gt;
&lt;/ul&gt;
&lt;table style=&quot;border-collapse: collapse; width: 100%;&quot; border=&quot;1&quot; data-ke-align=&quot;alignLeft&quot; data-ke-style=&quot;style8&quot;&gt;
&lt;tbody&gt;
&lt;tr&gt;
&lt;td style=&quot;width: 50%;&quot;&gt;&lt;b&gt;id(PK)&lt;/b&gt;&lt;/td&gt;
&lt;td style=&quot;width: 50%;&quot;&gt;&lt;b&gt;name&lt;/b&gt;&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td style=&quot;width: 50%;&quot;&gt;1&lt;/td&gt;
&lt;td style=&quot;width: 50%;&quot;&gt;박지후&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td style=&quot;width: 50%;&quot;&gt;2&lt;/td&gt;
&lt;td style=&quot;width: 50%;&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;ul style=&quot;list-style-type: disc;&quot; data-ke-list-type=&quot;disc&quot;&gt;
&lt;li&gt;&lt;b&gt;likes (좋아요)&lt;/b&gt;&lt;/li&gt;
&lt;/ul&gt;
&lt;table style=&quot;border-collapse: collapse; width: 100%;&quot; border=&quot;1&quot; data-ke-align=&quot;alignLeft&quot; data-ke-style=&quot;style8&quot;&gt;
&lt;tbody&gt;
&lt;tr&gt;
&lt;td style=&quot;width: 33.3333%;&quot;&gt;&lt;b&gt;id(PK)&lt;/b&gt;&lt;/td&gt;
&lt;td style=&quot;width: 33.3333%;&quot;&gt;&lt;b&gt;user_id&lt;/b&gt;&lt;/td&gt;
&lt;td style=&quot;width: 33.3333%;&quot;&gt;&lt;b&gt;post_id&lt;/b&gt;&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td style=&quot;width: 33.3333%;&quot;&gt;1&lt;/td&gt;
&lt;td style=&quot;width: 33.3333%;&quot;&gt;1&lt;/td&gt;
&lt;td style=&quot;width: 33.3333%;&quot;&gt;1&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td style=&quot;width: 33.3333%;&quot;&gt;2&lt;/td&gt;
&lt;td style=&quot;width: 33.3333%;&quot;&gt;2&lt;/td&gt;
&lt;td style=&quot;width: 33.3333%;&quot;&gt;1&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;숨어있는 중복을 제거하기 위해서는 &amp;lsquo;좋아요 수&amp;rsquo; 컬럼을 제거하고 &amp;lsquo;&lt;b&gt;likes&lt;/b&gt;&amp;rsquo; 테이블을 활용해서 &amp;lsquo;좋아요 수&amp;rsquo;를 카운팅 하는 것이 좋다. 데이터 중복을 허용하면서 나름의 편리함을 가져가는 데이터 역정규화라는 것이 있다. 우선은 고려하지 않는다.&lt;/p&gt;
&lt;hr contenteditable=&quot;false&quot; data-ke-type=&quot;horizontalRule&quot; data-ke-style=&quot;style1&quot; /&gt;
&lt;h2 data-ke-size=&quot;size26&quot;&gt;&lt;b&gt;ERD(Entity Relationship Diagram)란 ?&lt;/b&gt;&lt;/h2&gt;
&lt;blockquote data-ke-style=&quot;style3&quot;&gt;&lt;b&gt;ERD(Entity Relationship Diagram)&amp;nbsp;= 엔티티(테이블) 간의 관계를 표현한 그래프&lt;/b&gt;&lt;/blockquote&gt;
&lt;ul style=&quot;list-style-type: disc;&quot; data-ke-list-type=&quot;disc&quot;&gt;
&lt;li&gt;많은 개발자들이 ERD으로 소통하기 때문에 ERD를 보고 해석할 수 있어야 한다.&lt;/li&gt;
&lt;li&gt;하지만 DB 설계를 공부하는 입장에서 현재 꼭 익힐 필요는 없다. 설계에 적응되고 활용하여도 늦지 않다.&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-filename=&quot;image.png&quot; data-origin-width=&quot;1024&quot; data-origin-height=&quot;494&quot;&gt;&lt;span data-url=&quot;https://blog.kakaocdn.net/dn/lyFA0/btsNKa117XQ/5ul3K8aNqKgeN02eFMCj1k/img.png&quot; data-phocus=&quot;https://blog.kakaocdn.net/dn/lyFA0/btsNKa117XQ/5ul3K8aNqKgeN02eFMCj1k/img.png&quot;&gt;&lt;img src=&quot;https://blog.kakaocdn.net/dn/lyFA0/btsNKa117XQ/5ul3K8aNqKgeN02eFMCj1k/img.png&quot; srcset=&quot;https://img1.daumcdn.net/thumb/R1280x0/?scode=mtistory2&amp;fname=https%3A%2F%2Fblog.kakaocdn.net%2Fdn%2FlyFA0%2FbtsNKa117XQ%2F5ul3K8aNqKgeN02eFMCj1k%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;1024&quot; height=&quot;494&quot; data-filename=&quot;image.png&quot; data-origin-width=&quot;1024&quot; data-origin-height=&quot;494&quot;/&gt;&lt;/span&gt;&lt;/figure&gt;
&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;ul style=&quot;list-style-type: disc;&quot; data-ke-list-type=&quot;disc&quot;&gt;
&lt;li&gt;&lt;b&gt;ERD 표기 방법&lt;/b&gt;&lt;/li&gt;
&lt;/ul&gt;
&lt;p&gt;&lt;figure class=&quot;imageblock alignCenter&quot; data-ke-mobileStyle=&quot;widthOrigin&quot; data-filename=&quot;img.gif&quot; data-origin-width=&quot;622&quot; data-origin-height=&quot;166&quot;&gt;&lt;span data-url=&quot;https://blog.kakaocdn.net/dn/bYEgrd/btsNJTTHROD/3qj6PZF50h7KwYPyAvnURk/img.gif&quot; data-phocus=&quot;https://blog.kakaocdn.net/dn/bYEgrd/btsNJTTHROD/3qj6PZF50h7KwYPyAvnURk/img.gif&quot;&gt;&lt;img src=&quot;https://blog.kakaocdn.net/dn/bYEgrd/btsNJTTHROD/3qj6PZF50h7KwYPyAvnURk/img.gif&quot; srcset=&quot;https://blog.kakaocdn.net/dn/bYEgrd/btsNJTTHROD/3qj6PZF50h7KwYPyAvnURk/img.gif&quot; onerror=&quot;this.onerror=null; this.src='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png'; this.srcset='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png';&quot; loading=&quot;lazy&quot; width=&quot;622&quot; height=&quot;166&quot; data-filename=&quot;img.gif&quot; data-origin-width=&quot;622&quot; data-origin-height=&quot;166&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;ul style=&quot;list-style-type: disc;&quot; data-ke-list-type=&quot;disc&quot;&gt;
&lt;li&gt;&lt;b&gt;Tools&lt;/b&gt;
&lt;ul style=&quot;list-style-type: circle;&quot; data-ke-list-type=&quot;circle&quot;&gt;
&lt;li&gt;&lt;a href=&quot;https://www.erdcloud.com&quot;&gt;https://www.erdcloud.com&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;&lt;a href=&quot;https://dbdiagram.io/d&quot;&gt;https://dbdiagram.io/d&lt;/a&gt;&lt;/li&gt;
&lt;/ul&gt;
&lt;/li&gt;
&lt;/ul&gt;
&lt;hr contenteditable=&quot;false&quot; data-ke-type=&quot;horizontalRule&quot; data-ke-style=&quot;style1&quot; /&gt;
&lt;h2 data-ke-size=&quot;size26&quot;&gt;&lt;b&gt;DB 설계 및 반영&lt;/b&gt;&lt;/h2&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;설계한 모델을 실제 DB에 반영하는 방법은 아래와 같다.&lt;/p&gt;
&lt;ol style=&quot;list-style-type: decimal;&quot; data-ke-list-type=&quot;decimal&quot;&gt;
&lt;li&gt;SQL문(DDL) 활용하기&lt;/li&gt;
&lt;li&gt;DB 관리툴 (MySQL Workbench, dbeaver) 활용하기&lt;/li&gt;
&lt;li&gt;ORM 활용하기(JPA, TypeORM, Sequelize 라이브러리 활용)&lt;/li&gt;
&lt;/ol&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;공부 순서는 &lt;b&gt;SQL &amp;rarr; DB 관리툴 &amp;rarr; ORM&lt;/b&gt; 을 활용하는 것이 좋다.&lt;/p&gt;
&lt;hr contenteditable=&quot;false&quot; data-ke-type=&quot;horizontalRule&quot; data-ke-style=&quot;style1&quot; /&gt;
&lt;h2 data-ke-size=&quot;size26&quot;&gt;&lt;b&gt;데이터 타입 (Data Type)&lt;/b&gt;&lt;/h2&gt;
&lt;ul style=&quot;list-style-type: disc;&quot; data-ke-list-type=&quot;disc&quot;&gt;
&lt;li&gt;정수를 저장해야 할 때 &lt;u&gt;&lt;b&gt;INT&lt;/b&gt;&lt;/u&gt; &amp;rarr; 계산에서 쓰는 값인지 아닌지 판별한다.&lt;/li&gt;
&lt;li&gt;10억이 넘어가는 정수 : &lt;u&gt;&lt;b&gt;BIGINT&lt;/b&gt;&lt;/u&gt;&lt;/li&gt;
&lt;li&gt;실수를 저장 : &lt;u&gt;&lt;b&gt;DECIMAL&lt;/b&gt;&lt;/u&gt;&lt;/li&gt;
&lt;li&gt;문자를 저장 : &lt;b&gt;&lt;u&gt;VARCHAR(글자수)&lt;/u&gt;&lt;/b&gt;&lt;/li&gt;
&lt;li&gt;6만이 넘어가는 문자를 저장 : &lt;u&gt;&lt;b&gt;LONGTEXT&lt;/b&gt;&lt;/u&gt;&lt;/li&gt;
&lt;li&gt;TimeZone을 고려하지 않고 날짜 데이터만 저장하면 되는 경우 (국내 서비스) : &lt;u&gt;&lt;b&gt;DATETIME&lt;/b&gt;&lt;/u&gt;&lt;/li&gt;
&lt;li&gt;TimeZone을 고려하면서 날짜 데이터를 저장해야 하는 경우 (해외 서비스) : &lt;u&gt;&lt;b&gt;TIMESTAMP&lt;/b&gt;&lt;/u&gt;&lt;/li&gt;
&lt;li&gt;True, False의 형태로 저장하고 싶은 경우: &lt;u&gt;&lt;b&gt;TINYINT(1)&lt;/b&gt;&lt;/u&gt;&lt;/li&gt;
&lt;li&gt;** 현업에서 잘 사용하지 않는 데이터 타입 : &lt;u&gt;&lt;b&gt;CHAR, FLOAT, DOUBLE, TEXT&lt;/b&gt;&lt;/u&gt;&lt;/li&gt;
&lt;/ul&gt;
&lt;hr contenteditable=&quot;false&quot; data-ke-type=&quot;horizontalRule&quot; data-ke-style=&quot;style1&quot; /&gt;
&lt;h2 data-ke-size=&quot;size26&quot;&gt;&lt;b&gt;역정규화&lt;/b&gt;&lt;/h2&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 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;b&gt;역정규화&lt;/b&gt;란 성능을 향상시키기 위해 정규화된 DB를 다시 중복을 허용하는 형태로 변경하는 과정을 뜻하며, 가급적이면 최후의 수단으로 사용하는 것이 좋다. 역정규화는 매우 제한적으로 사용해야 한다. 데이터 중복으로 인한 오류(이상현상)가 발생하지 않도록 많은 노력을 해야 한다.&lt;/p&gt;
&lt;hr contenteditable=&quot;false&quot; data-ke-type=&quot;horizontalRule&quot; data-ke-style=&quot;style1&quot; /&gt;
&lt;h2 data-ke-size=&quot;size26&quot;&gt;&lt;b&gt;REVIEW&lt;/b&gt;&lt;/h2&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;해당 강의를 처음 듣는 목적은 짧은 러닝 타임 강의를 통해 알고 있던 개념을 정리하고자 했다. 짧은 강의를 통해 알고 있던 개념을 이해하기 쉽게 정리할 수 있어서 좋았으나 좀 더 깊은 지식들을 습득하기에는 한계가 있었다. (1~5 정규화, 역정규화 등) 하지만 본 강의를 통해 후행학습 과제를 정할 수 있게 되었고 실무에서도 보다 자신있게 DB 설계를 할 수 있겠다는 자신감이 생겼다. 해당 강의에 대한 만족도가 높아서 앞으로 다른 개념들도 박재성 강사님을 통해 학습하고자 한다.&lt;/p&gt;</description>
      <category>Back-End</category>
      <category>Database</category>
      <category>정규화</category>
      <author>dev.parkjh</author>
      <guid isPermaLink="true">https://developerjaypark.tistory.com/115</guid>
      <comments>https://developerjaypark.tistory.com/115#entry115comment</comments>
      <pubDate>Sun, 4 May 2025 16:08:27 +0900</pubDate>
    </item>
    <item>
      <title>[Docker] Docker Private Image Registry</title>
      <link>https://developerjaypark.tistory.com/114</link>
      <description>&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;1141&quot;&gt;&lt;span data-url=&quot;https://blog.kakaocdn.net/dn/bbxLjo/btsNhOYkwnY/Iyo8Ew5x4ckuyi2gHOk1k1/img.png&quot; data-phocus=&quot;https://blog.kakaocdn.net/dn/bbxLjo/btsNhOYkwnY/Iyo8Ew5x4ckuyi2gHOk1k1/img.png&quot;&gt;&lt;img src=&quot;https://blog.kakaocdn.net/dn/bbxLjo/btsNhOYkwnY/Iyo8Ew5x4ckuyi2gHOk1k1/img.png&quot; srcset=&quot;https://img1.daumcdn.net/thumb/R1280x0/?scode=mtistory2&amp;fname=https%3A%2F%2Fblog.kakaocdn.net%2Fdn%2FbbxLjo%2FbtsNhOYkwnY%2FIyo8Ew5x4ckuyi2gHOk1k1%2Fimg.png&quot; onerror=&quot;this.onerror=null; this.src='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png'; this.srcset='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png';&quot; loading=&quot;lazy&quot; width=&quot;1280&quot; height=&quot;1141&quot; data-origin-width=&quot;1280&quot; data-origin-height=&quot;1141&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;h2 data-ke-size=&quot;size26&quot;&gt;&lt;b&gt;개요&lt;/b&gt;&lt;/h2&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;도커 이미지를 저장하는 대표적인 저장소로 도커 허브(&lt;a href=&quot;https://hub.docker.com/&quot;&gt;https://hub.docker.com/&lt;/a&gt;) 가 있다. 퍼블릭 저장소를 기본으로 제공하는 Docker Hub는 전 세계적으로 널리 사용되고 있지만, 기업 또는 개인이 자체적인 이미지 저장소로 활용하기 위해서는 Private 환경 구성이 필요한 경우가 많다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;Docker Hub는 무료 플랜 기준으로 Private 저장소를 1개만 제공하며, 추가 Private 저장소 사용이나 기업 단위 관리 기능은 유료 플랜 가입이 필요하다. 또한, 저장 용량이나 이미지 Pull 제한 등 사용 정책이 존재하기 때문에 비용이나 보안 요구 사항에 따라 별도의 Private Registry 구축을 고려하게 된다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;본 글에서는 Docker Image 를 Private 하게 관리하기 위해 &lt;b&gt;Self-Hosted, AWS ECR, NCP Container Registry&lt;/b&gt; 을 간단하게 사용해본다.&lt;/p&gt;
&lt;hr contenteditable=&quot;false&quot; data-ke-type=&quot;horizontalRule&quot; data-ke-style=&quot;style1&quot; /&gt;
&lt;h2 data-ke-size=&quot;size26&quot;&gt;&lt;b&gt;Test Image 생성&lt;/b&gt;&lt;/h2&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;각 Registry 테스트를 위하여 테스트용 이미지를 생성한다. 테스트에 사용하는 이미지는 &lt;b&gt;hashicorp/http-echo &lt;/b&gt;(&lt;a href=&quot;https://hub.docker.com/r/hashicorp/http-echo&quot;&gt;https://hub.docker.com/r/hashicorp/http-echo&lt;/a&gt;) 이미지이다. hashicorp/http-echo 는 인수를 입력받은 값을 출력하는 간단한 웹 서버이다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;아래의 Dockerfile을 하나 생성한다.&lt;/p&gt;
&lt;pre class=&quot;dockerfile&quot;&gt;&lt;code&gt;# Dockerfile

FROM hashicorp/http-echo

CMD [&quot;-listen=:8080&quot;, &quot;-text=hello world ver1.0&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;빌드 후 컨테이너를 실행한다.&lt;/p&gt;
&lt;pre class=&quot;angelscript&quot;&gt;&lt;code&gt;$ docker build -t my-echo:1.0 .
$ docker run -d --name my-echo-container-v1-0 -p 8080:8080 my-echo:1.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;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;a style=&quot;background-color: #e6f5ff; color: #0070d1;&quot; href=&quot;http://localhost:8080&quot;&gt;http://localhost:8080&lt;/a&gt;&lt;span&gt;&amp;nbsp;&lt;/span&gt;혹은 http://{HOST_IP}:8080 에 접속하여 정상적으로 적용 되었음을 확인한다.&lt;/p&gt;
&lt;p&gt;&lt;figure class=&quot;imageblock alignCenter&quot; data-ke-mobileStyle=&quot;widthOrigin&quot; data-filename=&quot;캡처.PNG&quot; data-origin-width=&quot;383&quot; data-origin-height=&quot;156&quot;&gt;&lt;span data-url=&quot;https://blog.kakaocdn.net/dn/c8zX0Y/btsNf9v8Ksm/e75oW9IzfJ6RHZfyqD7uNK/img.png&quot; data-phocus=&quot;https://blog.kakaocdn.net/dn/c8zX0Y/btsNf9v8Ksm/e75oW9IzfJ6RHZfyqD7uNK/img.png&quot;&gt;&lt;img src=&quot;https://blog.kakaocdn.net/dn/c8zX0Y/btsNf9v8Ksm/e75oW9IzfJ6RHZfyqD7uNK/img.png&quot; srcset=&quot;https://img1.daumcdn.net/thumb/R1280x0/?scode=mtistory2&amp;fname=https%3A%2F%2Fblog.kakaocdn.net%2Fdn%2Fc8zX0Y%2FbtsNf9v8Ksm%2Fe75oW9IzfJ6RHZfyqD7uNK%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;383&quot; height=&quot;156&quot; data-filename=&quot;캡처.PNG&quot; data-origin-width=&quot;383&quot; data-origin-height=&quot;156&quot;/&gt;&lt;/span&gt;&lt;/figure&gt;
&lt;/p&gt;
&lt;hr contenteditable=&quot;false&quot; data-ke-type=&quot;horizontalRule&quot; data-ke-style=&quot;style1&quot; /&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;b&gt;Self-Hosted&lt;/b&gt;&lt;/h2&gt;
&lt;h3 data-ke-size=&quot;size23&quot;&gt;&lt;b&gt;Registry 서비스 구성&lt;/b&gt;&lt;/h3&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;docker 에서 registry 를 구성할 수 있도록 Official Image(&lt;a href=&quot;https://hub.docker.com/_/registry&quot;&gt;https://hub.docker.com/_/registry&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;아래와 같이 run 명령어로 간단하게 실행 하거나 docker-compose.yml 을 작성하여 실행한다.&lt;/p&gt;
&lt;pre class=&quot;shell&quot; data-ke-language=&quot;shell&quot;&gt;&lt;code&gt;$ docker run -d --name docker-registry -p 5000:5000 registry:latest&lt;/code&gt;&lt;/pre&gt;
&lt;pre class=&quot;crystal&quot;&gt;&lt;code&gt;# docker-compose.yml
version: '3.8'
services:
        docker-registry:
                image: registry:latest
                container_name: docker-registry
                restart: on-failure
                ports:
                        - &quot;5000:5000&quot;
                volumes:
									      - ./registry-data:/var/lib/registry
&lt;/code&gt;&lt;/pre&gt;
&lt;pre class=&quot;elixir&quot;&gt;&lt;code&gt;$ docker compose up -d
&lt;/code&gt;&lt;/pre&gt;
&lt;p data-ke-size=&quot;size18&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;h3 data-ke-size=&quot;size23&quot;&gt;&lt;b&gt;Image build &amp;amp; push&lt;/b&gt;&lt;/h3&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;Docker Image를 빌드하고 푸시하는 가이드는 아래와 같다.&lt;/p&gt;
&lt;pre class=&quot;prolog&quot;&gt;&lt;code&gt;# image build
$ docker build -t {[IMAGE_NAME]:[IMAGE_TAG]} .

# tag (로컬에 있는 이미지를 업로드하기 위한 태그로 변경)
$ docker tag {[IMAGE_NAME]:[IMAGE_TAG]} {HOST_NAME}:5000/{[IMAGE_NAME]:[IMAGE_TAG]}

# push (로컬에 있는 이미지 업로드)
$ docker push {HOST_NAME}:5000/{[IMAGE_NAME]:[IMAGE_TAG]}

# 업로드 이미지 조회
$ curl -XGET localhost:5000/v2/_catalog
&lt;/code&gt;&lt;/pre&gt;
&lt;p data-ke-size=&quot;size18&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;h3 data-ke-size=&quot;size23&quot;&gt;&lt;b&gt;Image Pull&lt;/b&gt;&lt;/h3&gt;
&lt;pre class=&quot;dust&quot;&gt;&lt;code&gt;$ docker pull {HOST_NAME}:5000/{[IMAGE_NAME]:[IMAGE_TAG]}
&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;만약 위와 같은 절차를 통해 로컬이 아닌 다른 네트워크에서 push 를 시도한다면 &lt;b&gt;http: server gave HTTP response to HTTPS client&lt;/b&gt; 오류와 함께 push 되지 않을 것이다. 그 이유는 docker 는 기본적으로 HTTPS가 미적용된 레지스트리는 PUSH가 안된다. Sever에&amp;nbsp;**HTTPS 인증서(사설 포함)**를 발급&amp;nbsp;받거나,&amp;nbsp;Client에 &lt;b&gt;insecure-registries&lt;/b&gt; 설정해야 한다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size18&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;h3 data-ke-size=&quot;size23&quot;&gt;&lt;b&gt;insecure-registries 로 HTTP 허용&lt;/b&gt;&lt;/h3&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;pull/push 를 시도하는 클라이언트에서 아래 경로(centos 기준)에 해당 옵션을 추가하고 docker 재시작 한다.&lt;/p&gt;
&lt;pre class=&quot;prolog&quot;&gt;&lt;code&gt;$ vi /etc/docker/daemon.json
.
.
.

{
  &quot;insecure-registries&quot;: [
    &quot;{HOST_NAME}:5000&quot;
  ]
}

$ systemctl restart docker
&lt;/code&gt;&lt;/pre&gt;
&lt;p data-ke-size=&quot;size18&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;h3 data-ke-size=&quot;size23&quot;&gt;&lt;b&gt;SSL 인증서(사설) 생성&lt;/b&gt;&lt;/h3&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;Private Registry 를 HTTPS 적용을 위해 SSL 인증서를 생성하고 Nginx 을 통해 SSL 인증서와 계정 인증을 처리한 뒤, 내부 docker-registry로 요청을 프록시 하도록 구현한다.&lt;/p&gt;
&lt;h4 data-ke-size=&quot;size20&quot;&gt;ROOT 인증서(CA) 생성&lt;/h4&gt;
&lt;pre class=&quot;stata&quot;&gt;&lt;code&gt;$ mkdir certs &amp;amp; cd $_

# ca.key 생성
$ openssl genrsa -out ./certs/ca.key 2048

# ca.crt 생성
$ openssl req -x509 -new -key ./certs/ca.key -days 1000 -out ./certs/ca.crt
&lt;/code&gt;&lt;/pre&gt;
&lt;h4 data-ke-size=&quot;size20&quot;&gt;컨테이너 인증서 생성&lt;/h4&gt;
&lt;pre class=&quot;jboss-cli&quot;&gt;&lt;code&gt;# domain.key 생성
$ openssl genrsa -out ./certs/domain.key 2048

# domain.csr 생성
$ openssl req -new -key ./certs/domain.key -subj /CN={HOST_IP} -out ./certs/domain.csr

# SAN (Subject Alternative Name) 설정
$ echo subjectAltName=IP:{HOST_IP} &amp;gt; extfile.cnf

# 인증서 서명
$ openssl x509 -req -in ./certs/domain.csr -CA ./certs/ca.crt -CAkey ./certs/ca.key -CAcreateserial -out ./certs/domain.crt -days 10000 -extfile extfile.cnf
&lt;/code&gt;&lt;/pre&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;h3 data-ke-size=&quot;size23&quot;&gt;&lt;b&gt;Nginx 컨테이너 생성&lt;/b&gt;&lt;/h3&gt;
&lt;blockquote data-ke-style=&quot;style3&quot;&gt;&lt;b&gt;❓Nginx :가볍고 빠른 웹 서버 + 리버스 프록시 서버 + 로드밸런서&lt;/b&gt;&lt;/blockquote&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;h4 data-ke-size=&quot;size20&quot;&gt;계정 파일 생성(htpasswd)&lt;/h4&gt;
&lt;pre class=&quot;shell&quot;&gt;&lt;code&gt;# htpasswd 설치
$ yum install httpd-tools

# htpasswd 생성
$ mkdir auth &amp;amp;&amp;amp; cd $_
$ htpasswd -c htpasswd {USER_NAME}
&lt;/code&gt;&lt;/pre&gt;
&lt;h4 data-ke-size=&quot;size20&quot;&gt;nginx.conf 생성&lt;/h4&gt;
&lt;pre class=&quot;properties&quot;&gt;&lt;code&gt;$ vim nginx.conf
.
.
.
upstream docker-registry {
    server registry:5000;
}

server {
    listen 443 ssl;
    server_name {HOST_IP};

    ssl_certificate /etc/nginx/certs/domain.crt;
    ssl_certificate_key /etc/nginx/certs/domain.key;

    client_max_body_size 0;
    chunked_transfer_encoding on;

    location /v2/ {
        # 특정 docker client 차단
        if ($http_user_agent ~ &quot;^(docker/1\\.(3|4|5(?!\\.[0-9]-dev))|Go).*$&quot;) {
            return 404;
        }

        auth_basic &quot;registry.localhost&quot;;
        auth_basic_user_file /etc/nginx/auth/htpasswd;

        add_header 'Docker-Distribution-Api-Version' 'registry/2.0' always;

        proxy_pass &amp;lt;http://docker-registry&amp;gt;;
        proxy_set_header Host $http_host;
        proxy_set_header X-Real-IP $remote_addr;
        proxy_set_header X-Forwarded-For $proxy_add_x_forwarded_for;
        proxy_set_header X-Forwarded-Proto $scheme;
        proxy_read_timeout 900;
    }
}
&lt;/code&gt;&lt;/pre&gt;
&lt;h4 data-ke-size=&quot;size20&quot;&gt;nginx 컨테이너 실행&lt;/h4&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;아래와 같이 run 명령어로 간단하게 실행 하거나 앞서 작성한 docker-compose.yml 에 추가 작성하여 실행한다.&lt;/p&gt;
&lt;pre class=&quot;haml&quot;&gt;&lt;code&gt;$ docker run -d --name nginx -p 443:443 --link docker-registry:registry  \\
  -v $(pwd)/nginx.conf:/etc/nginx/conf.d/nginx.conf \\
  -v $(pwd)/certs/:/etc/nginx/certs \\
  -v $(pwd)/auth/:/etc/nginx/auth \\
  nginx:latest
&lt;/code&gt;&lt;/pre&gt;
&lt;pre class=&quot;haml&quot;&gt;&lt;code&gt;# docker-compose.yml
.
.
.
        nginx:
                image: nginx:latest
                container_name: nginx
                ports:
                        - &quot;443:443&quot;
                restart: always
                depends_on:
                        - docker-registry
                volumes:
                        - ./nginx.conf:/etc/nginx/conf.d/nginx.conf
                        - ./certs/:/etc/nginx/certs
                        - ./auth/:/etc/nginx/auth
&lt;/code&gt;&lt;/pre&gt;
&lt;p data-ke-size=&quot;size18&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;h3 data-ke-size=&quot;size23&quot;&gt;&lt;b&gt;인증서 등록 (클라이언트)&lt;/b&gt;&lt;/h3&gt;
&lt;pre class=&quot;angelscript&quot;&gt;&lt;code&gt;# 인증서 복사
$ sudo mkdir -p /etc/docker/certs.d/10.52.254.98/
$ sudo cp domain.crt /etc/docker/certs.d/10.52.254.98/ca.crt

# Docker service 재시작
$ sudo systemctl restart docker
&lt;/code&gt;&lt;/pre&gt;
&lt;p data-ke-size=&quot;size18&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;h3 data-ke-size=&quot;size23&quot;&gt;&lt;b&gt;Docker 로그인&lt;/b&gt;&lt;/h3&gt;
&lt;pre id=&quot;code_1744295077762&quot; class=&quot;shell&quot; data-ke-language=&quot;shell&quot; data-ke-type=&quot;codeblock&quot;&gt;&lt;code&gt;$ docker login {HOST_IP}&lt;/code&gt;&lt;/pre&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;hr contenteditable=&quot;false&quot; data-ke-type=&quot;horizontalRule&quot; data-ke-style=&quot;style1&quot; /&gt;
&lt;h2 data-ke-size=&quot;size26&quot;&gt;&lt;b&gt;Cloud - AWS&amp;nbsp; Elastic Container Registry (ECR)&lt;/b&gt;&lt;/h2&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;AWS 에서는 Elastic Container Registry (ECR)을 이용하여 private Registry를 관리할 수 있다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;서비스에 관한 자세한 설명은 AWS ECR (&lt;a href=&quot;https://aws.amazon.com/ko/ecr&quot;&gt;https://aws.amazon.com/ko/ecr&lt;/a&gt;) 문서를 참고한다.&lt;/p&gt;
&lt;h3 data-ke-size=&quot;size23&quot;&gt;&lt;b&gt;요금&lt;/b&gt;&lt;/h3&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;AWS의 ECR은 &lt;b&gt;저장소 스토리지 요금&lt;/b&gt;과 &lt;b&gt;데이터 전송 요금&lt;/b&gt;이 합산되어 청구된다. 각 청구되는 기준은 아래와 같다.&lt;/p&gt;
&lt;ul style=&quot;list-style-type: disc;&quot; data-ke-list-type=&quot;disc&quot;&gt;
&lt;li&gt;&lt;b&gt;저장소 스토리지 요금&lt;/b&gt;
&lt;ul style=&quot;list-style-type: disc;&quot; data-ke-list-type=&quot;disc&quot;&gt;
&lt;li&gt;프라이빗 리포지토리 (Private) : AWS 프리 티어 일부로 매월 500MB 1년간 무료 (초과시 월 0.10 USD / GB)&lt;/li&gt;
&lt;li&gt;퍼블릭 리포지토리 (Public) : 월 50GB 항상 무료 (초과시 월 0.10 USD / GB)&lt;/li&gt;
&lt;/ul&gt;
&lt;/li&gt;
&lt;li&gt;&lt;b&gt;데이터 전송 요금&lt;/b&gt;
&lt;ul style=&quot;list-style-type: disc;&quot; data-ke-list-type=&quot;disc&quot;&gt;
&lt;li&gt;프라이빗 리포지토리 (Private)
&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;데이터 송신 (다른 리전 혹은 인터넷) : (월 USD 0.126 / GB)&lt;/li&gt;
&lt;/ul&gt;
&lt;/li&gt;
&lt;li&gt;퍼블릭 리포지토리 (Public)
&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;데이터 송신 (다른 리전 혹은 인터넷) : 익명 사용자 월 500GB 무료, AWS 계정 사용자 월 5TB 무료 (초과시 월 0.09 USD / GB)&lt;/li&gt;
&lt;/ul&gt;
&lt;/li&gt;
&lt;/ul&gt;
&lt;/li&gt;
&lt;/ul&gt;
&lt;h3 data-ke-size=&quot;size23&quot;&gt;&lt;b&gt;Repository 생성&lt;/b&gt;&lt;/h3&gt;
&lt;p&gt;&lt;figure class=&quot;imageblock alignCenter&quot; data-ke-mobileStyle=&quot;widthOrigin&quot; data-filename=&quot;ecr.PNG&quot; data-origin-width=&quot;1813&quot; data-origin-height=&quot;830&quot;&gt;&lt;span data-url=&quot;https://blog.kakaocdn.net/dn/bFyfPZ/btsNhMsFPzj/z4O9VQuiHrEIMtaclZUZ70/img.png&quot; data-phocus=&quot;https://blog.kakaocdn.net/dn/bFyfPZ/btsNhMsFPzj/z4O9VQuiHrEIMtaclZUZ70/img.png&quot;&gt;&lt;img src=&quot;https://blog.kakaocdn.net/dn/bFyfPZ/btsNhMsFPzj/z4O9VQuiHrEIMtaclZUZ70/img.png&quot; srcset=&quot;https://img1.daumcdn.net/thumb/R1280x0/?scode=mtistory2&amp;fname=https%3A%2F%2Fblog.kakaocdn.net%2Fdn%2FbFyfPZ%2FbtsNhMsFPzj%2Fz4O9VQuiHrEIMtaclZUZ70%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;1813&quot; height=&quot;830&quot; data-filename=&quot;ecr.PNG&quot; data-origin-width=&quot;1813&quot; data-origin-height=&quot;830&quot;/&gt;&lt;/span&gt;&lt;/figure&gt;
&lt;/p&gt;
&lt;ul style=&quot;list-style-type: disc;&quot; data-ke-list-type=&quot;disc&quot;&gt;
&lt;li&gt;일반 설정 - 리포지토리 이름&lt;/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-filename=&quot;ecr2.PNG&quot; data-origin-width=&quot;1920&quot; data-origin-height=&quot;454&quot;&gt;&lt;span data-url=&quot;https://blog.kakaocdn.net/dn/c3OWGZ/btsNgo7JgAV/LK6xutgkiXe5knkmtyIcY1/img.png&quot; data-phocus=&quot;https://blog.kakaocdn.net/dn/c3OWGZ/btsNgo7JgAV/LK6xutgkiXe5knkmtyIcY1/img.png&quot;&gt;&lt;img src=&quot;https://blog.kakaocdn.net/dn/c3OWGZ/btsNgo7JgAV/LK6xutgkiXe5knkmtyIcY1/img.png&quot; srcset=&quot;https://img1.daumcdn.net/thumb/R1280x0/?scode=mtistory2&amp;fname=https%3A%2F%2Fblog.kakaocdn.net%2Fdn%2Fc3OWGZ%2FbtsNgo7JgAV%2FLK6xutgkiXe5knkmtyIcY1%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;1920&quot; height=&quot;454&quot; data-filename=&quot;ecr2.PNG&quot; data-origin-width=&quot;1920&quot; data-origin-height=&quot;454&quot;/&gt;&lt;/span&gt;&lt;/figure&gt;
&lt;/p&gt;
&lt;blockquote data-ke-style=&quot;style3&quot;&gt;❗️ AWS Repository 생성에 앞서 IAM 계정을 생성해야 하며, aws cli 설치 및 configure 을 지정해야 한다.&lt;/blockquote&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;h3 data-ke-size=&quot;size23&quot;&gt;&lt;b&gt;Image build &amp;amp; push&lt;/b&gt;&lt;/h3&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;ecr3.PNG&quot; data-origin-width=&quot;862&quot; data-origin-height=&quot;699&quot;&gt;&lt;span data-url=&quot;https://blog.kakaocdn.net/dn/PesLB/btsNgy9quFP/JHhQemPjyksTL2dk1xbuK1/img.png&quot; data-phocus=&quot;https://blog.kakaocdn.net/dn/PesLB/btsNgy9quFP/JHhQemPjyksTL2dk1xbuK1/img.png&quot;&gt;&lt;img src=&quot;https://blog.kakaocdn.net/dn/PesLB/btsNgy9quFP/JHhQemPjyksTL2dk1xbuK1/img.png&quot; srcset=&quot;https://img1.daumcdn.net/thumb/R1280x0/?scode=mtistory2&amp;fname=https%3A%2F%2Fblog.kakaocdn.net%2Fdn%2FPesLB%2FbtsNgy9quFP%2FJHhQemPjyksTL2dk1xbuK1%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;862&quot; height=&quot;699&quot; data-filename=&quot;ecr3.PNG&quot; data-origin-width=&quot;862&quot; data-origin-height=&quot;699&quot;/&gt;&lt;/span&gt;&lt;/figure&gt;
&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;커멘드 사용에 앞서 aws cli 설치 및 configure 을 지정한다.&lt;/p&gt;
&lt;pre class=&quot;routeros&quot;&gt;&lt;code&gt;# AWS CLI 설치
$ sudo yum install -y awscli

# 인증 정보 입력 (Access Key, Secret Key는 AWS 콘솔 &amp;rarr; IAM &amp;rarr; 사용자 &amp;rarr; 보안 자격 증명 에서 발급 가능)
$ aws configure
.
.
.
AWS Access Key ID [None]: {AWS Access Key}
AWS Secret Access Key [None]: {AWS Secret Key}
Default region name [None]: ap-northeast-2
Default output format [None]: json

&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 class=&quot;dsconfig&quot;&gt;&lt;code&gt;# docker login
$ aws ecr get-login-password --region ap-northeast-2 | docker login --username AWS --password-stdin {AWS_ID}.dkr.ecr.ap-northeast-2.amazonaws.com

# login check
$ cat ~/.docker/config.json

# image build
$ docker build -t {[IMAGE_NAME]:[IMAGE_TAG]} .

# tag (로컬에 있는 이미지를 ECR 업로드하기 위한 태그로 변경)
$ docker tag {[IMAGE_NAME]:[IMAGE_TAG]} {AWS_ID}.dkr.ecr.ap-northeast-2.amazonaws.com/{[AWS_REPOSITORIES_NAME]:[IMAGE_TAG]}

# push (로컬에 있는 이미지 업로드)
$ docker push {AWS_ID}.dkr.ecr.ap-northeast-2.amazonaws.com/{[AWS_REPOSITORIES_NAME]:[IMAGE_TAG]}
&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;AWS 콘솔에서 push 된 이미지를 확인한다.&lt;/p&gt;
&lt;p&gt;&lt;figure class=&quot;imageblock alignCenter&quot; data-ke-mobileStyle=&quot;widthOrigin&quot; data-filename=&quot;ECR4.PNG&quot; data-origin-width=&quot;1920&quot; data-origin-height=&quot;367&quot;&gt;&lt;span data-url=&quot;https://blog.kakaocdn.net/dn/b5rXxZ/btsNhZ6or02/aZvKKpVHFkBZoP76IGK760/img.png&quot; data-phocus=&quot;https://blog.kakaocdn.net/dn/b5rXxZ/btsNhZ6or02/aZvKKpVHFkBZoP76IGK760/img.png&quot;&gt;&lt;img src=&quot;https://blog.kakaocdn.net/dn/b5rXxZ/btsNhZ6or02/aZvKKpVHFkBZoP76IGK760/img.png&quot; srcset=&quot;https://img1.daumcdn.net/thumb/R1280x0/?scode=mtistory2&amp;fname=https%3A%2F%2Fblog.kakaocdn.net%2Fdn%2Fb5rXxZ%2FbtsNhZ6or02%2FaZvKKpVHFkBZoP76IGK760%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;1920&quot; height=&quot;367&quot; data-filename=&quot;ECR4.PNG&quot; data-origin-width=&quot;1920&quot; data-origin-height=&quot;367&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;h2 data-ke-size=&quot;size26&quot;&gt;Image Pull&lt;/h2&gt;
&lt;pre class=&quot;stylus&quot;&gt;&lt;code&gt;docker pull &amp;lt;AWS_ID&amp;gt;.dkr.ecr.ap-northeast-2.amazonaws.com/{[AWS_REPOSITORIES_NAME]:[IMAGE_TAG]}
&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;AWS의 ECR과 관련하여 좀 더 자세한 내용은 본 문서를 참고한다 (&lt;a href=&quot;https://docs.aws.amazon.com/AmazonECR/latest/userguide/getting-started-cli.html&quot;&gt;https://docs.aws.amazon.com/AmazonECR/latest/userguide/getting-started-cli.html&lt;/a&gt;)&lt;/p&gt;
&lt;hr contenteditable=&quot;false&quot; data-ke-type=&quot;horizontalRule&quot; data-ke-style=&quot;style1&quot; /&gt;
&lt;h2 data-ke-size=&quot;size26&quot;&gt;&lt;b&gt;Cloud - NCP Container Registry&lt;/b&gt;&lt;/h2&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;네이버 클라우드에서는 &amp;ldquo;Container Registry&amp;rdquo;를 이용하여 Private Registry 를 관리할 수 있다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;서비스에 관한 자세한 설명은 NCP Container Registry (&lt;a href=&quot;https://www.ncloud.com/product/containers/containerRegistry#overview&quot;&gt;https://www.ncloud.com/product/containers/containerRegistry#overview&lt;/a&gt;) 문서를 참고한다.&lt;/p&gt;
&lt;h3 data-ke-size=&quot;size23&quot;&gt;&lt;b&gt;요금&lt;/b&gt;&lt;/h3&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;네이버 클라우드의 Container Registry 서비스는 기본적으로 무료이다. 다만 Image 를 Object Storage 에 쌓기 때문에 별도의 Object Storage 비용이 청구된다.&lt;/p&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 환경에서 운영할 경우 아래의 금액이 청구된다.&lt;/p&gt;
&lt;p&gt;&lt;figure class=&quot;imageblock alignCenter&quot; data-ke-mobileStyle=&quot;widthOrigin&quot; data-filename=&quot;ncp5.PNG&quot; data-origin-width=&quot;976&quot; data-origin-height=&quot;396&quot;&gt;&lt;span data-url=&quot;https://blog.kakaocdn.net/dn/bOQrGA/btsNgos3nIo/X8LTviGQrZCUW3PQTeSuRK/img.png&quot; data-phocus=&quot;https://blog.kakaocdn.net/dn/bOQrGA/btsNgos3nIo/X8LTviGQrZCUW3PQTeSuRK/img.png&quot;&gt;&lt;img src=&quot;https://blog.kakaocdn.net/dn/bOQrGA/btsNgos3nIo/X8LTviGQrZCUW3PQTeSuRK/img.png&quot; srcset=&quot;https://img1.daumcdn.net/thumb/R1280x0/?scode=mtistory2&amp;fname=https%3A%2F%2Fblog.kakaocdn.net%2Fdn%2FbOQrGA%2FbtsNgos3nIo%2FX8LTviGQrZCUW3PQTeSuRK%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;976&quot; height=&quot;396&quot; data-filename=&quot;ncp5.PNG&quot; data-origin-width=&quot;976&quot; data-origin-height=&quot;396&quot;/&gt;&lt;/span&gt;&lt;/figure&gt;
&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;h3 data-ke-size=&quot;size23&quot;&gt;&lt;b&gt;Registry 생성&lt;/b&gt;&lt;/h3&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;레지스트리 이름과 버킷을 지정하여 새로운 Container Registry 를 생성한다.&lt;/p&gt;
&lt;p&gt;&lt;figure class=&quot;imageblock alignCenter&quot; data-ke-mobileStyle=&quot;widthOrigin&quot; data-filename=&quot;ncp1.PNG&quot; data-origin-width=&quot;1675&quot; data-origin-height=&quot;953&quot;&gt;&lt;span data-url=&quot;https://blog.kakaocdn.net/dn/DHt06/btsNgpFyeAA/9rgJuXcWvK3UyK8vW76csk/img.png&quot; data-phocus=&quot;https://blog.kakaocdn.net/dn/DHt06/btsNgpFyeAA/9rgJuXcWvK3UyK8vW76csk/img.png&quot;&gt;&lt;img src=&quot;https://blog.kakaocdn.net/dn/DHt06/btsNgpFyeAA/9rgJuXcWvK3UyK8vW76csk/img.png&quot; srcset=&quot;https://img1.daumcdn.net/thumb/R1280x0/?scode=mtistory2&amp;fname=https%3A%2F%2Fblog.kakaocdn.net%2Fdn%2FDHt06%2FbtsNgpFyeAA%2F9rgJuXcWvK3UyK8vW76csk%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;1675&quot; height=&quot;953&quot; data-filename=&quot;ncp1.PNG&quot; data-origin-width=&quot;1675&quot; data-origin-height=&quot;953&quot;/&gt;&lt;/span&gt;&lt;/figure&gt;
&lt;figure class=&quot;imageblock alignCenter&quot; data-ke-mobileStyle=&quot;widthOrigin&quot; data-filename=&quot;ncp2.PNG&quot; data-origin-width=&quot;1632&quot; data-origin-height=&quot;347&quot;&gt;&lt;span data-url=&quot;https://blog.kakaocdn.net/dn/8AiP0/btsNhm2iH3a/Pq8eFdpHHUFSndkuPtlZS0/img.png&quot; data-phocus=&quot;https://blog.kakaocdn.net/dn/8AiP0/btsNhm2iH3a/Pq8eFdpHHUFSndkuPtlZS0/img.png&quot;&gt;&lt;img src=&quot;https://blog.kakaocdn.net/dn/8AiP0/btsNhm2iH3a/Pq8eFdpHHUFSndkuPtlZS0/img.png&quot; srcset=&quot;https://img1.daumcdn.net/thumb/R1280x0/?scode=mtistory2&amp;fname=https%3A%2F%2Fblog.kakaocdn.net%2Fdn%2F8AiP0%2FbtsNhm2iH3a%2FPq8eFdpHHUFSndkuPtlZS0%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;1632&quot; height=&quot;347&quot; data-filename=&quot;ncp2.PNG&quot; data-origin-width=&quot;1632&quot; data-origin-height=&quot;347&quot;/&gt;&lt;/span&gt;&lt;/figure&gt;
&lt;/p&gt;
&lt;blockquote data-ke-style=&quot;style3&quot;&gt;❗️Container Registry 생성에 앞서 이미지 저장을 위해 사전에 Object Storage 버킷을 생성해야 한다.&lt;/blockquote&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;h3 data-ke-size=&quot;size23&quot;&gt;&lt;b&gt;Image build &amp;amp; push&lt;/b&gt;&lt;/h3&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;ncp3.PNG&quot; data-origin-width=&quot;702&quot; data-origin-height=&quot;790&quot;&gt;&lt;span data-url=&quot;https://blog.kakaocdn.net/dn/cpRT6b/btsNhL8nOFG/gC6I62Qb95G3PCAMDwZLgK/img.png&quot; data-phocus=&quot;https://blog.kakaocdn.net/dn/cpRT6b/btsNhL8nOFG/gC6I62Qb95G3PCAMDwZLgK/img.png&quot;&gt;&lt;img src=&quot;https://blog.kakaocdn.net/dn/cpRT6b/btsNhL8nOFG/gC6I62Qb95G3PCAMDwZLgK/img.png&quot; srcset=&quot;https://img1.daumcdn.net/thumb/R1280x0/?scode=mtistory2&amp;fname=https%3A%2F%2Fblog.kakaocdn.net%2Fdn%2FcpRT6b%2FbtsNhL8nOFG%2FgC6I62Qb95G3PCAMDwZLgK%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;702&quot; height=&quot;790&quot; data-filename=&quot;ncp3.PNG&quot; data-origin-width=&quot;702&quot; data-origin-height=&quot;790&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;pre class=&quot;prolog&quot;&gt;&lt;code&gt;# docker login
$ docker login {REGISTRIES_NAME}.kr.ncr.ntruss.com
.
.
.
Username: {Access Key ID}
Password:{Secret Key}

# login check
$ cat ~/.docker/config.json

# Image build
$ docker build -t {[IMAGE_NAME]:[IMAGE_TAG]} .

# tag (AWS ECR과 다른 점은 push 할 이미지명이 리포지토리 이름은 아니다.)
$ docker tag {[IMAGE_NAME]:[IMAGE_TAG]} {REGISTRIES_NAME}.kr.ncr.ntruss.com/{[IMAGE_NAME]:[IMAGE_TAG]}

# push
$ docker push {REGISTRIES_NAME}.kr.ncr.ntruss.com/{[IMAGE_NAME]:[IMAGE_TAG]}
&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;네이버 클라우드 콘솔에서 push 된 이미지를 확인한다.&lt;/p&gt;
&lt;p&gt;&lt;figure class=&quot;imageblock alignCenter&quot; data-ke-mobileStyle=&quot;widthOrigin&quot; data-filename=&quot;ncp4.PNG&quot; data-origin-width=&quot;1669&quot; data-origin-height=&quot;634&quot;&gt;&lt;span data-url=&quot;https://blog.kakaocdn.net/dn/cVp1qT/btsNf67lGbh/VtSFyqsWnMCODeJne6FxQK/img.png&quot; data-phocus=&quot;https://blog.kakaocdn.net/dn/cVp1qT/btsNf67lGbh/VtSFyqsWnMCODeJne6FxQK/img.png&quot;&gt;&lt;img src=&quot;https://blog.kakaocdn.net/dn/cVp1qT/btsNf67lGbh/VtSFyqsWnMCODeJne6FxQK/img.png&quot; srcset=&quot;https://img1.daumcdn.net/thumb/R1280x0/?scode=mtistory2&amp;fname=https%3A%2F%2Fblog.kakaocdn.net%2Fdn%2FcVp1qT%2FbtsNf67lGbh%2FVtSFyqsWnMCODeJne6FxQK%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;1669&quot; height=&quot;634&quot; data-filename=&quot;ncp4.PNG&quot; data-origin-width=&quot;1669&quot; data-origin-height=&quot;634&quot;/&gt;&lt;/span&gt;&lt;/figure&gt;
&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;h3 data-ke-size=&quot;size23&quot;&gt;&lt;b&gt;Image Pull&lt;/b&gt;&lt;/h3&gt;
&lt;pre class=&quot;dts&quot;&gt;&lt;code&gt;# pull
$ docker pull {REGISTRIES_NAME}.kr.ncr.ntruss.com/{[IMAGE_NAME]:[IMAGE_TAG]}
&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;NCP의 Container Registry 과 관련하여 좀 더 자세한 내용은 본 문서를 참고한다. (&lt;a href=&quot;https://guide.ncloud-docs.com/docs/containerregistry-overview&quot;&gt;https://guide.ncloud-docs.com/docs/containerregistry-overview&lt;/a&gt;)&lt;/p&gt;
&lt;hr contenteditable=&quot;false&quot; data-ke-type=&quot;horizontalRule&quot; data-ke-style=&quot;style1&quot; /&gt;
&lt;h2 data-ke-size=&quot;size26&quot;&gt;&lt;b&gt;Ref&lt;/b&gt;&lt;b&gt;&lt;/b&gt;&lt;/h2&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;b&gt;[Self-Hosted]&lt;/b&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;a href=&quot;https://cocococo.tistory.com/entry/Docker-Private-레지스트리-구축-및-사용-방법&quot;&gt;https://cocococo.tistory.com/entry/Docker-Private-레지스트리-구축-및-사용-방법&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;&lt;a href=&quot;https://devbksheen.tistory.com/entry/도커-사설-레지스트리Docker-Private-Registry에-이미지-배포하기&quot;&gt;https://devbksheen.tistory.com/entry/도커-사설-레지스트리Docker-Private-Registry에-이미지-배포하기&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;&lt;a href=&quot;https://velog.io/@lijahong/0부터-시작하는-Docker-공부-Private-Registry-구현&quot;&gt;https://velog.io/@lijahong/0부터-시작하는-Docker-공부-Private-Registry-구현&lt;/a&gt;&lt;/li&gt;
&lt;/ul&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;b&gt;[AWS]&lt;/b&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;a href=&quot;https://junuuu.tistory.com/862&quot;&gt;https://junuuu.tistory.com/862&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;&lt;a href=&quot;https://norgb.tistory.com/16&quot;&gt;https://norgb.tistory.com/16&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;&lt;a href=&quot;https://ks1171-park.tistory.com/107&quot;&gt;https://ks1171-park.tistory.com/107&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;&lt;a href=&quot;https://velog.io/@seoky1219/AWS-ECR&quot;&gt;https://velog.io/@seoky1219/AWS-ECR&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;&lt;a href=&quot;https://jhlee-developer.tistory.com/entry/AWS-AWS-CLI-설치-및-V2-업데이트&quot;&gt;https://jhlee-developer.tistory.com/entry/AWS-AWS-CLI-설치-및-V2-업데이트&lt;/a&gt;&lt;/li&gt;
&lt;/ul&gt;</description>
      <category>System/Server</category>
      <category>aws ecr</category>
      <category>docker</category>
      <category>docker images</category>
      <category>docker private register</category>
      <category>ncp container registry</category>
      <author>dev.parkjh</author>
      <guid isPermaLink="true">https://developerjaypark.tistory.com/114</guid>
      <comments>https://developerjaypark.tistory.com/114#entry114comment</comments>
      <pubDate>Thu, 10 Apr 2025 23:42:43 +0900</pubDate>
    </item>
    <item>
      <title>[nGrinder] API Performance Testing</title>
      <link>https://developerjaypark.tistory.com/113</link>
      <description>&lt;p&gt;&lt;figure class=&quot;imageblock alignCenter&quot; data-ke-mobileStyle=&quot;widthOrigin&quot; data-filename=&quot;image.png&quot; data-origin-width=&quot;480&quot; data-origin-height=&quot;300&quot;&gt;&lt;span data-url=&quot;https://blog.kakaocdn.net/dn/RsCJW/btsNbdqef5W/zDNsIaZo03IIkfT1ujiTZK/img.png&quot; data-phocus=&quot;https://blog.kakaocdn.net/dn/RsCJW/btsNbdqef5W/zDNsIaZo03IIkfT1ujiTZK/img.png&quot;&gt;&lt;img src=&quot;https://blog.kakaocdn.net/dn/RsCJW/btsNbdqef5W/zDNsIaZo03IIkfT1ujiTZK/img.png&quot; srcset=&quot;https://img1.daumcdn.net/thumb/R1280x0/?scode=mtistory2&amp;fname=https%3A%2F%2Fblog.kakaocdn.net%2Fdn%2FRsCJW%2FbtsNbdqef5W%2FzDNsIaZo03IIkfT1ujiTZK%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;480&quot; height=&quot;300&quot; data-filename=&quot;image.png&quot; data-origin-width=&quot;480&quot; data-origin-height=&quot;300&quot;/&gt;&lt;/span&gt;&lt;/figure&gt;
&lt;/p&gt;
&lt;h2 data-ke-size=&quot;size26&quot;&gt;&lt;b&gt;API Performance Testing&lt;/b&gt;&lt;/h2&gt;
&lt;ul style=&quot;list-style-type: disc;&quot; data-ke-list-type=&quot;disc&quot;&gt;
&lt;li&gt;API 성능 테스트(API Performance Testing)는 API가 높은 부하에서도 안정적으로 동작하는지 확인하는 과정이다.&lt;/li&gt;
&lt;li&gt;주요 목표는 응답 시간(Response Time), 처리량(Throughput), 동시 사용자 수(Concurrency), 그리고 리소스 사용률(CPU, 메모리 등)을 평가하는 것이다.&lt;/li&gt;
&lt;li&gt;주요 테스트 유형으로는 부하 테스트(Load Testing), 스트레스 테스트(Stress Testing), 스파이크 테스트(Spike Testing), 지속 테스트(Soak Testing) 가 있다.&lt;/li&gt;
&lt;/ul&gt;
&lt;hr contenteditable=&quot;false&quot; data-ke-type=&quot;horizontalRule&quot; data-ke-style=&quot;style6&quot; /&gt;
&lt;h2 data-ke-size=&quot;size26&quot;&gt;&lt;b&gt;nGrinder&lt;/b&gt;&lt;/h2&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;nGrinder는 네이버에서 제공하는 The Grinder 기반의 오픈소스 부하 테스트 도구이며, 분산 환경에서 대규모 부하 테스트를 쉽게 수행할 수 있도록 기능을 제공한다.&lt;/p&gt;
&lt;h3 data-ke-size=&quot;size23&quot;&gt;&lt;b&gt;구성요소&lt;/b&gt;&lt;/h3&gt;
&lt;ul style=&quot;list-style-type: disc;&quot; data-ke-list-type=&quot;disc&quot;&gt;
&lt;li&gt;&lt;b&gt;Controller&lt;/b&gt;
&lt;ul style=&quot;list-style-type: disc;&quot; data-ke-list-type=&quot;disc&quot;&gt;
&lt;li&gt;테스트를 관리하고 실행하는 중앙 역할&lt;/li&gt;
&lt;li&gt;테스트 스크립트 작성 및 실행 제어&lt;/li&gt;
&lt;li&gt;Agent에게 테스트 명령을 전달하고 결과 수집&lt;/li&gt;
&lt;/ul&gt;
&lt;/li&gt;
&lt;li&gt;&lt;b&gt;Agent&lt;/b&gt;
&lt;ul style=&quot;list-style-type: disc;&quot; data-ke-list-type=&quot;disc&quot;&gt;
&lt;li&gt;Controller의 명령을 받아 부하(Load)를 발생시키는 역할&lt;/li&gt;
&lt;li&gt;여러 Agent를 사용하여 분산 부하 테스트 가능&lt;/li&gt;
&lt;li&gt;테스트 실행 중 Target Server로 요청을 전송&lt;/li&gt;
&lt;/ul&gt;
&lt;/li&gt;
&lt;li&gt;&lt;b&gt;Target Server&lt;/b&gt;
&lt;ul style=&quot;list-style-type: disc;&quot; data-ke-list-type=&quot;disc&quot;&gt;
&lt;li&gt;테스트 대상 서버(API, 웹 애플리케이션 등)&lt;/li&gt;
&lt;li&gt;Agent에서 보내는 요청을 처리하고 성능을 평가&lt;/li&gt;
&lt;/ul&gt;
&lt;/li&gt;
&lt;li&gt;&lt;b&gt;User&lt;/b&gt;
&lt;ul style=&quot;list-style-type: disc;&quot; data-ke-list-type=&quot;disc&quot;&gt;
&lt;li&gt;nGrinder를 사용하는 테스트 관리자(테스터)&lt;/li&gt;
&lt;li&gt;Controller에서 테스트를 설정하고 결과를 분석&lt;/li&gt;
&lt;/ul&gt;
&lt;/li&gt;
&lt;/ul&gt;
&lt;h3 data-ke-size=&quot;size23&quot;&gt;&lt;b&gt;Architecture&lt;/b&gt;&lt;/h3&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;image.png&quot; data-origin-width=&quot;659&quot; data-origin-height=&quot;595&quot;&gt;&lt;span data-url=&quot;https://blog.kakaocdn.net/dn/diI0Ul/btsM97ENVub/3CDzIhcFQz88nI1HFDFfC1/img.png&quot; data-phocus=&quot;https://blog.kakaocdn.net/dn/diI0Ul/btsM97ENVub/3CDzIhcFQz88nI1HFDFfC1/img.png&quot;&gt;&lt;img src=&quot;https://blog.kakaocdn.net/dn/diI0Ul/btsM97ENVub/3CDzIhcFQz88nI1HFDFfC1/img.png&quot; srcset=&quot;https://img1.daumcdn.net/thumb/R1280x0/?scode=mtistory2&amp;fname=https%3A%2F%2Fblog.kakaocdn.net%2Fdn%2FdiI0Ul%2FbtsM97ENVub%2F3CDzIhcFQz88nI1HFDFfC1%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;659&quot; height=&quot;595&quot; data-filename=&quot;image.png&quot; data-origin-width=&quot;659&quot; data-origin-height=&quot;595&quot;/&gt;&lt;/span&gt;&lt;/figure&gt;
&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;사용자가 요청 &amp;rarr; Controller가 Agent와 Target Server 설정 &amp;rarr; Agent가 부하 테스트 실행 &amp;rarr; 성능 데이터 수집 및 분석 흐름으로 진행된다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;자세한 내용을 nGrinder Wiki 에서 확인 가능하다. (&lt;a href=&quot;https://github.com/naver/ngrinder/wiki/Architecture&quot;&gt;https://github.com/naver/ngrinder/wiki/Architecture&lt;/a&gt;)&lt;/p&gt;
&lt;hr contenteditable=&quot;false&quot; data-ke-type=&quot;horizontalRule&quot; data-ke-style=&quot;style6&quot; /&gt;
&lt;h2 data-ke-size=&quot;size26&quot;&gt;&lt;b&gt;설치 및 환경 구성 - nGrinder Docker&lt;/b&gt;&lt;/h2&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;nGrinder 를 설치 및 운영할 수 있는 방법은 Controller와 Agent 소스코드(&lt;a href=&quot;https://github.com/naver/ngrinder&quot;&gt;https://github.com/naver/ngrinder&lt;/a&gt;)를 내려 받아 직접 설치하는 방법과 Docker Image 를 내려받아 container 를 생성하여 운영하는 방법 두 가지가 있다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;본 글에서는 Controller, Agent Docker Image로 Container 를 생성하여 설치 및 운영하는 방법에 대해 기술한다. 아래 docker-compose.yml 파일은 하나의 Controller 와 하나의 Agent 를 정의하였다.&lt;/p&gt;
&lt;pre id=&quot;code_1743836381844&quot; class=&quot;bash&quot; data-ke-language=&quot;bash&quot; data-ke-type=&quot;codeblock&quot;&gt;&lt;code&gt;# docker-compose.yml
version: '3.8'
services:
        ngrinder_controller:
                image: ngrinder/controller
                container_name: ngrinder_controller
                restart: unless-stopped
                ports:
                        - &quot;80:80&quot;
                        - &quot;16001:16001&quot;
                        - &quot;12000-12009:12000-12009&quot;
                volumes:
                        - ~/ngrinder-controller:/opt/ngrinder-controller
        ngrinder_agent:
                image: ngrinder/agent
                container_name: ngrinder_agent_1
                restart: unless-stopped
                depends_on:
                        - ngrinder_controller
                links:
                        - &quot;ngrinder_controller:controller&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;Controller 와 Agent 에 설정된 옵션의 의미는 아래와 같다.&lt;/p&gt;
&lt;ul style=&quot;list-style-type: disc;&quot; data-ke-list-type=&quot;disc&quot;&gt;
&lt;li&gt;&lt;b&gt;controller - ports&lt;/b&gt;
&lt;ul style=&quot;list-style-type: disc;&quot; data-ke-list-type=&quot;disc&quot;&gt;
&lt;li&gt;80 : web service 포트&lt;/li&gt;
&lt;li&gt;9010 - 9019 : Agent들이 Controller 클러스터로 연결되는 포트&lt;/li&gt;
&lt;li&gt;16001 : 테스트를 하지 않는 유휴 상태의 Agent가 Controller에게 테스트 가능 메시지를 전달하는 포트&lt;/li&gt;
&lt;li&gt;12000-12009 : 테스트 실행 및 종료 등 컨트롤러 명령어와 에이전트별 테스트 실행 통계를 초별로 수집하는 포트&lt;/li&gt;
&lt;/ul&gt;
&lt;/li&gt;
&lt;li&gt;&lt;b&gt;agent - links&lt;/b&gt;
&lt;ul style=&quot;list-style-type: disc;&quot; data-ke-list-type=&quot;disc&quot;&gt;
&lt;li&gt;ngrinder_controller:controller : Controller Docker 컨테이너 간의 네트워크 연결&lt;/li&gt;
&lt;/ul&gt;
&lt;/li&gt;
&lt;li&gt;&lt;b&gt;controller - volumes&lt;/b&gt;
&lt;ul style=&quot;list-style-type: disc;&quot; data-ke-list-type=&quot;disc&quot;&gt;
&lt;li&gt;Docker 컨테이너 내부의 데이터를 유지(영속화, Persistent Storage)&lt;/li&gt;
&lt;/ul&gt;
&lt;/li&gt;
&lt;/ul&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;b&gt;$ docker compose up -d&lt;/b&gt; 을 통해 Controller 와 Agent 컨테이너를 실행한다.&lt;/p&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;http://{WEB_URL}:{PORT}&lt;/b&gt; 으로 접속하고 초기 ID / PW 는 admin / admin 이다.&lt;/p&gt;
&lt;p&gt;&lt;figure class=&quot;imageblock alignCenter&quot; data-ke-mobileStyle=&quot;widthOrigin&quot; data-filename=&quot;1234.PNG.png&quot; data-origin-width=&quot;1550&quot; data-origin-height=&quot;676&quot;&gt;&lt;span data-url=&quot;https://blog.kakaocdn.net/dn/vSedi/btsM98KqgSA/mEshOpqkeoW78OvBGnLLc0/img.png&quot; data-phocus=&quot;https://blog.kakaocdn.net/dn/vSedi/btsM98KqgSA/mEshOpqkeoW78OvBGnLLc0/img.png&quot;&gt;&lt;img src=&quot;https://blog.kakaocdn.net/dn/vSedi/btsM98KqgSA/mEshOpqkeoW78OvBGnLLc0/img.png&quot; srcset=&quot;https://img1.daumcdn.net/thumb/R1280x0/?scode=mtistory2&amp;fname=https%3A%2F%2Fblog.kakaocdn.net%2Fdn%2FvSedi%2FbtsM98KqgSA%2FmEshOpqkeoW78OvBGnLLc0%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;1550&quot; height=&quot;676&quot; data-filename=&quot;1234.PNG.png&quot; data-origin-width=&quot;1550&quot; data-origin-height=&quot;676&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;Agent Management 탭으로 이동해서 Agent 가 정상적으로 동작하고 있음을 확인한다.&lt;/p&gt;
&lt;p&gt;&lt;figure class=&quot;imageblock alignCenter&quot; data-ke-mobileStyle=&quot;widthOrigin&quot; data-filename=&quot;dsfsdf.PNG.png&quot; data-origin-width=&quot;1285&quot; data-origin-height=&quot;466&quot;&gt;&lt;span data-url=&quot;https://blog.kakaocdn.net/dn/dTFRMv/btsNay2VTs9/9Ta6xBMnW3LqGuk0h8qnt1/img.png&quot; data-phocus=&quot;https://blog.kakaocdn.net/dn/dTFRMv/btsNay2VTs9/9Ta6xBMnW3LqGuk0h8qnt1/img.png&quot;&gt;&lt;img src=&quot;https://blog.kakaocdn.net/dn/dTFRMv/btsNay2VTs9/9Ta6xBMnW3LqGuk0h8qnt1/img.png&quot; srcset=&quot;https://img1.daumcdn.net/thumb/R1280x0/?scode=mtistory2&amp;fname=https%3A%2F%2Fblog.kakaocdn.net%2Fdn%2FdTFRMv%2FbtsNay2VTs9%2F9Ta6xBMnW3LqGuk0h8qnt1%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;1285&quot; height=&quot;466&quot; data-filename=&quot;dsfsdf.PNG.png&quot; data-origin-width=&quot;1285&quot; data-origin-height=&quot;466&quot;/&gt;&lt;/span&gt;&lt;/figure&gt;
&lt;/p&gt;
&lt;hr contenteditable=&quot;false&quot; data-ke-type=&quot;horizontalRule&quot; data-ke-style=&quot;style6&quot; /&gt;
&lt;h2 data-ke-size=&quot;size26&quot;&gt;&lt;b&gt;Script 작성 - Groovy&lt;/b&gt;&lt;/h2&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;테스트에 사용할 스크립트를 작성한다. 스크립트는 Groovy, Jython으로 작성할 수 있다. Groovy는 자바와 유사하고 Jython은 파이썬과 유사하다. (본 글에서는 Groovy 기준으로 설명)&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;스크립트 유형 선택 후 파일을 생성하면 Sample Code가 작성되어 있다. 테스트할 API, Request Header, Request Body 등을 스크립트에서 설정할 수 있다.&lt;/p&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;테스트에 사용할 라이브러리와 데이터 파일을 각각 /lib, /resources 에 업로드한다. (/lib 에 추가한 파일은 자동 import)&lt;/p&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_1743836524168&quot; class=&quot;java&quot; data-ke-language=&quot;java&quot; data-ke-type=&quot;codeblock&quot;&gt;&lt;code&gt;import static net.grinder.script.Grinder.grinder
import static org.junit.Assert.*
import static org.hamcrest.Matchers.*
import net.grinder.script.GTest
import net.grinder.script.Grinder
import net.grinder.scriptengine.groovy.junit.GrinderRunner
import net.grinder.scriptengine.groovy.junit.annotation.BeforeProcess
import net.grinder.scriptengine.groovy.junit.annotation.BeforeThread
// import static net.grinder.util.GrinderUtils.* // You can use this if you're using nGrinder after 3.2.3
import org.junit.Before
import org.junit.BeforeClass
import org.junit.Test
import org.junit.runner.RunWith

import org.ngrinder.http.HTTPRequest
import org.ngrinder.http.HTTPRequestControl
import org.ngrinder.http.HTTPResponse
import org.ngrinder.http.cookie.Cookie
import org.ngrinder.http.cookie.CookieManager

/**
* A simple example using the HTTP plugin that shows the retrieval of a single page via HTTP.
*
* This script is automatically generated by ngrinder.
*
* @author admin
*/
@RunWith(GrinderRunner)
class TestRunner {

	public static GTest test
	public static HTTPRequest request
	public static Map&amp;lt;String, String&amp;gt; headers = [:]
	public static Map&amp;lt;String, Object&amp;gt; params = [:]
	public static List&amp;lt;Cookie&amp;gt; cookies = []

	@BeforeProcess
	public static void beforeProcess() {
		HTTPRequestControl.setConnectionTimeout(300000)
		test = new GTest(1, &quot;Test1&quot;)
		request = new HTTPRequest()
		grinder.logger.info(&quot;before process.&quot;)
	}

	@BeforeThread
	public void beforeThread() {
		test.record(this, &quot;test&quot;)
		grinder.statistics.delayReports = true
		grinder.logger.info(&quot;before thread.&quot;)
	}

	@Before
	public void before() {
		request.setHeaders(headers)
		CookieManager.addCookies(cookies)
		grinder.logger.info(&quot;before. init headers and cookies&quot;)
	}

	@Test
	public void test() {
		HTTPResponse response = request.GET(&quot;http://please_modify_this.com&quot;, params)

		if (response.statusCode == 301 || response.statusCode == 302) {
			grinder.logger.warn(&quot;Warning. The response may not be correct. The response code was {}.&quot;, response.statusCode)
		} else {
			assertThat(response.statusCode, is(200))
		}
	}
}&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;ul style=&quot;list-style-type: disc;&quot; data-ke-list-type=&quot;disc&quot;&gt;
&lt;li&gt;&lt;b&gt;@RunWith()&lt;/b&gt; : JUnit 테스트 실행기를 지정하는 역할&lt;/li&gt;
&lt;li&gt;&lt;b&gt;@BeforeProcess&lt;/b&gt; : 프로세스가 생성될때 실행해야 하는 동작 정의&lt;/li&gt;
&lt;li&gt;&lt;b&gt;@AfterProcess&lt;/b&gt; : 프로세스가 종료하기 직전에 실행해야 하는 동작 정의&lt;/li&gt;
&lt;li&gt;&lt;b&gt;@BeforeThread&lt;/b&gt; : 각 쓰레드가 실행된 전에 실행해야 하는 동작 정의&lt;/li&gt;
&lt;li&gt;&lt;b&gt;@AfterThread&lt;/b&gt; : 각 쓰레드가 종료하기 직전에 실행해야 하는 동작 정의&lt;/li&gt;
&lt;li&gt;&lt;b&gt;@Before&lt;/b&gt; : 모든 @Test 메소드가 실행되기 전에 실행해야 하는 동작 정의&lt;/li&gt;
&lt;li&gt;&lt;b&gt;@After&lt;/b&gt; : 모든 @Test 메소드가 종료된 이후 실행해야 하는 동작 정의&lt;/li&gt;
&lt;li&gt;&lt;b&gt;@Test&lt;/b&gt; : 테스트 동작 정의&lt;/li&gt;
&lt;/ul&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;image.png&quot; data-origin-width=&quot;746&quot; data-origin-height=&quot;366&quot;&gt;&lt;span data-url=&quot;https://blog.kakaocdn.net/dn/seDoS/btsNaJQFsp8/AjqLVrjqNDka87ONt9epPK/img.png&quot; data-phocus=&quot;https://blog.kakaocdn.net/dn/seDoS/btsNaJQFsp8/AjqLVrjqNDka87ONt9epPK/img.png&quot;&gt;&lt;img src=&quot;https://blog.kakaocdn.net/dn/seDoS/btsNaJQFsp8/AjqLVrjqNDka87ONt9epPK/img.png&quot; srcset=&quot;https://img1.daumcdn.net/thumb/R1280x0/?scode=mtistory2&amp;fname=https%3A%2F%2Fblog.kakaocdn.net%2Fdn%2FseDoS%2FbtsNaJQFsp8%2FAjqLVrjqNDka87ONt9epPK%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;746&quot; height=&quot;366&quot; data-filename=&quot;image.png&quot; data-origin-width=&quot;746&quot; data-origin-height=&quot;366&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;자세한 내용을 nGrinder Wiki 에서 확인 가능하다. (&lt;span&gt;&lt;/span&gt;&lt;a href=&quot;https://github.com/naver/ngrinder/wiki/Groovy-Script-Structure&quot;&gt;https://github.com/naver/ngrinder/wiki/Groovy-Script-Structure&lt;/a&gt;)&lt;/p&gt;
&lt;hr contenteditable=&quot;false&quot; data-ke-type=&quot;horizontalRule&quot; data-ke-style=&quot;style6&quot; /&gt;
&lt;h2 data-ke-size=&quot;size26&quot;&gt;테스트 생성 및 실행&lt;/h2&gt;
&lt;p&gt;&lt;figure class=&quot;imageblock alignCenter&quot; data-ke-mobileStyle=&quot;widthOrigin&quot; data-filename=&quot;스크린샷 2025-04-02 오후 3.18.40.png&quot; data-origin-width=&quot;1302&quot; data-origin-height=&quot;817&quot;&gt;&lt;span data-url=&quot;https://blog.kakaocdn.net/dn/cXXffH/btsNaMGyNBG/kNEVbiSPkOhdVDWK8MVKS1/img.png&quot; data-phocus=&quot;https://blog.kakaocdn.net/dn/cXXffH/btsNaMGyNBG/kNEVbiSPkOhdVDWK8MVKS1/img.png&quot;&gt;&lt;img src=&quot;https://blog.kakaocdn.net/dn/cXXffH/btsNaMGyNBG/kNEVbiSPkOhdVDWK8MVKS1/img.png&quot; srcset=&quot;https://img1.daumcdn.net/thumb/R1280x0/?scode=mtistory2&amp;fname=https%3A%2F%2Fblog.kakaocdn.net%2Fdn%2FcXXffH%2FbtsNaMGyNBG%2FkNEVbiSPkOhdVDWK8MVKS1%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;1302&quot; height=&quot;817&quot; data-filename=&quot;스크린샷 2025-04-02 오후 3.18.40.png&quot; data-origin-width=&quot;1302&quot; data-origin-height=&quot;817&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;&lt;b&gt;Agent&lt;/b&gt;&lt;/p&gt;
&lt;ul style=&quot;list-style-type: disc;&quot; data-ke-list-type=&quot;disc&quot;&gt;
&lt;li&gt;테스트를 실행할 가상 사용자(VUser)를 실행하는 물리적 또는 가상 서버&lt;/li&gt;
&lt;li&gt;nGrinder에서 부하를 발생시키는 역할을 하며, 여러 개의 Agent 사용 가능함.&lt;/li&gt;
&lt;/ul&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;b&gt;Vuser per agent&lt;/b&gt;&lt;/p&gt;
&lt;ul style=&quot;list-style-type: disc;&quot; data-ke-list-type=&quot;disc&quot;&gt;
&lt;li&gt;각 Agent에서 실행할 가상 사용자(Virtual User, VUser)의 수&lt;/li&gt;
&lt;li&gt;VUser는 실제 사용자를 시뮬레이션하여 요청을 보냄.&lt;/li&gt;
&lt;li&gt;VUser = Agent * (Processes * Threads)&lt;/li&gt;
&lt;li&gt;&lt;b&gt;Processes&lt;/b&gt;
&lt;ul style=&quot;list-style-type: disc;&quot; data-ke-list-type=&quot;disc&quot;&gt;
&lt;li&gt;Agent에서 실행할 프로세스 수&lt;/li&gt;
&lt;li&gt;한 개의 Agent 내에서 여러 개의 프로세스를 실행하여 성능 테스트를 병렬로 수행함.&lt;/li&gt;
&lt;/ul&gt;
&lt;/li&gt;
&lt;li&gt;&lt;b&gt;Threads&lt;/b&gt;
&lt;ul style=&quot;list-style-type: disc;&quot; data-ke-list-type=&quot;disc&quot;&gt;
&lt;li&gt;각 프로세스에서 실행할 스레드(쓰레드) 수&lt;/li&gt;
&lt;li&gt;하나의 프로세스 안에서 여러 개의 스레드를 실행하여 부하를 증가시킬 수 있음.&lt;/li&gt;
&lt;/ul&gt;
&lt;/li&gt;
&lt;/ul&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;b&gt;Script&lt;/b&gt;&lt;/p&gt;
&lt;ul style=&quot;list-style-type: disc;&quot; data-ke-list-type=&quot;disc&quot;&gt;
&lt;li&gt;성능 테스트를 수행할 테스트 스크립트(Groovy, Jython 등)&lt;/li&gt;
&lt;li&gt;실제 HTTP 요청을 보내거나, 데이터베이스에 쿼리를 실행하는 등의 테스트 로직을 정의함.&lt;/li&gt;
&lt;/ul&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;b&gt;Script Resources&lt;/b&gt;&lt;/p&gt;
&lt;ul style=&quot;list-style-type: disc;&quot; data-ke-list-type=&quot;disc&quot;&gt;
&lt;li&gt;테스트에 필요한 추가적인 파일(JAR, CSV, JSON 등)&lt;/li&gt;
&lt;/ul&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;b&gt;Target Host&lt;/b&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 또는 IP 주소&lt;/li&gt;
&lt;/ul&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;b&gt;Duration&lt;/b&gt;&lt;/p&gt;
&lt;ul style=&quot;list-style-type: disc;&quot; data-ke-list-type=&quot;disc&quot;&gt;
&lt;li&gt;테스트의 실행 시간, 5분으로 설정하면 5분 동안 지속적으로 요청 수행&lt;/li&gt;
&lt;/ul&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;b&gt;Run Count&lt;/b&gt;&lt;/p&gt;
&lt;ul style=&quot;list-style-type: disc;&quot; data-ke-list-type=&quot;disc&quot;&gt;
&lt;li&gt;특정 횟수만큼 테스트를 실행, Run Count = 1000이면 총 1000번의 요청 수행&lt;/li&gt;
&lt;/ul&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;b&gt;Enable Ramp-Up&lt;/b&gt;&lt;/p&gt;
&lt;ul style=&quot;list-style-type: disc;&quot; data-ke-list-type=&quot;disc&quot;&gt;
&lt;li&gt;VUser가 일정한 간격으로 증가하면서 실행됨.&lt;/li&gt;
&lt;li&gt;&lt;b&gt;Initial Count (초기 사용자 수)&lt;/b&gt;
&lt;ul style=&quot;list-style-type: disc;&quot; data-ke-list-type=&quot;disc&quot;&gt;
&lt;li&gt;테스트 시작 시 처음 실행될 VUser 수를 설정함.&lt;/li&gt;
&lt;li&gt;예: Initial Count = 10 &amp;rarr; 처음에 10명의 VUser가 실행됨.&lt;/li&gt;
&lt;/ul&gt;
&lt;/li&gt;
&lt;li&gt;&lt;b&gt;Initial Sleep Time (초기 대기 시간 / ms)&lt;/b&gt;
&lt;ul style=&quot;list-style-type: disc;&quot; data-ke-list-type=&quot;disc&quot;&gt;
&lt;li&gt;테스트가 시작된 후, VUser를 실행하기 전에 대기 시간(밀리초 단위)&lt;/li&gt;
&lt;li&gt;예: Initial Sleep Time = 5000ms &amp;rarr; 테스트 시작 후 5초 대기 후 VUser 실행&lt;/li&gt;
&lt;/ul&gt;
&lt;/li&gt;
&lt;li&gt;&lt;b&gt;Incremental Step (증가 단계)&lt;/b&gt;
&lt;ul style=&quot;list-style-type: disc;&quot; data-ke-list-type=&quot;disc&quot;&gt;
&lt;li&gt;일정한 간격(Interval)마다 몇 명의 VUser를 추가할 것인지 설정함.&lt;/li&gt;
&lt;li&gt;예: Incremental Step = 5 &amp;rarr; 매번 5명의 VUser가 추가됨.&lt;/li&gt;
&lt;/ul&gt;
&lt;/li&gt;
&lt;li&gt;&lt;b&gt;Interval (증가 간격, 초 단위)&lt;/b&gt;
&lt;ul style=&quot;list-style-type: disc;&quot; data-ke-list-type=&quot;disc&quot;&gt;
&lt;li&gt;VUser가 증가하는 시간 간격(초 단위)을 설정함.&lt;/li&gt;
&lt;li&gt;예: Interval = 10이면 10초마다 Incremental Step 만큼 VUser를 추가함.&lt;/li&gt;
&lt;/ul&gt;
&lt;/li&gt;
&lt;/ul&gt;
&lt;hr contenteditable=&quot;false&quot; data-ke-type=&quot;horizontalRule&quot; data-ke-style=&quot;style6&quot; /&gt;
&lt;h2 data-ke-size=&quot;size26&quot;&gt;&lt;b&gt;Performance Report&lt;/b&gt;&lt;/h2&gt;
&lt;p&gt;&lt;figure class=&quot;imageblock alignCenter&quot; data-ke-mobileStyle=&quot;widthOrigin&quot; data-filename=&quot;스크린샷 2025-04-02 오후 4.09.21.png&quot; data-origin-width=&quot;1294&quot; data-origin-height=&quot;860&quot;&gt;&lt;span data-url=&quot;https://blog.kakaocdn.net/dn/Deiff/btsNabNRIMJ/jB9taXUqCHRJHSkVKHvFs1/img.png&quot; data-phocus=&quot;https://blog.kakaocdn.net/dn/Deiff/btsNabNRIMJ/jB9taXUqCHRJHSkVKHvFs1/img.png&quot;&gt;&lt;img src=&quot;https://blog.kakaocdn.net/dn/Deiff/btsNabNRIMJ/jB9taXUqCHRJHSkVKHvFs1/img.png&quot; srcset=&quot;https://img1.daumcdn.net/thumb/R1280x0/?scode=mtistory2&amp;fname=https%3A%2F%2Fblog.kakaocdn.net%2Fdn%2FDeiff%2FbtsNabNRIMJ%2FjB9taXUqCHRJHSkVKHvFs1%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;1294&quot; height=&quot;860&quot; data-filename=&quot;스크린샷 2025-04-02 오후 4.09.21.png&quot; data-origin-width=&quot;1294&quot; data-origin-height=&quot;860&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;b&gt;Total Vusers&lt;/b&gt;&lt;/p&gt;
&lt;ul style=&quot;list-style-type: disc;&quot; data-ke-list-type=&quot;disc&quot;&gt;
&lt;li&gt;실행된 총 가상 사용자(VUser)의 수&lt;/li&gt;
&lt;li&gt;부하 테스트에서 최대 동시 실행된 사용자 수를 의&lt;/li&gt;
&lt;/ul&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;b&gt;TPS(Transactions Per Second, 초당 트랜잭션 수)&lt;/b&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;TPS가 높을수록 더 많은 요청을 처리할 수 있다는 의미 (성능이 좋다.)&lt;/li&gt;
&lt;li&gt;일반적으로 Executed Tests / Run time(초)&lt;/li&gt;
&lt;/ul&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;b&gt;Peak TPS (최대 TPS, 최고 초당 트랜잭션 수)&lt;/b&gt;&lt;/p&gt;
&lt;ul style=&quot;list-style-type: disc;&quot; data-ke-list-type=&quot;disc&quot;&gt;
&lt;li&gt;테스트 중에서 가장 높은 TPS(초당 요청 수)&lt;/li&gt;
&lt;li&gt;부하가 가장 많았을 때 서버가 처리할 수 있는 최대 성능&lt;/li&gt;
&lt;/ul&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;b&gt;Mean Test Time (평균 응답 시간, ms)&lt;/b&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;/ul&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;b&gt;Executed Tests(총 실행된 테스트 수)&lt;/b&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;모든 VUser가 수행한 테스트의 횟수&lt;/li&gt;
&lt;/ul&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;b&gt;Successful Tests(성공한 테스트 수)&lt;/b&gt;&lt;/p&gt;
&lt;ul style=&quot;list-style-type: disc;&quot; data-ke-list-type=&quot;disc&quot;&gt;
&lt;li&gt;총 요청(트랜잭션) 중에서 HTTP 200 등의 정상 응답을 받은 경우 성공으로 카운트&lt;/li&gt;
&lt;/ul&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;b&gt;Errors(에러 수)&lt;/b&gt;&lt;/p&gt;
&lt;ul style=&quot;list-style-type: disc;&quot; data-ke-list-type=&quot;disc&quot;&gt;
&lt;li&gt;HTTP 500(서버 오류) 또는 404(찾을 수 없음) 같은 에러 요청(실패한 트랜잭션) 수&lt;/li&gt;
&lt;/ul&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;b&gt;Run time (테스트 실행 시간, 초 단위)&lt;/b&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;/ul&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;b&gt;TPS Graph&lt;/b&gt;&lt;/p&gt;
&lt;ul style=&quot;list-style-type: disc;&quot; data-ke-list-type=&quot;disc&quot;&gt;
&lt;li&gt;X : 시간&lt;/li&gt;
&lt;li&gt;Y : TPS&lt;/li&gt;
&lt;/ul&gt;
&lt;hr contenteditable=&quot;false&quot; data-ke-type=&quot;horizontalRule&quot; data-ke-style=&quot;style6&quot; /&gt;
&lt;h2 data-ke-size=&quot;size26&quot;&gt;&lt;b&gt;Results analysis&lt;/b&gt;&lt;/h2&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;앞선 테스트를 통해 여러 API의 성능 데이터를 수집했다. TPS(초당 트랜잭션 수)와 MTT(평균 테스트 시간)를 통해 대략적인 성능을 짐작할 수 있지만, 이를 전체 API 대비 비교하여 분석하는 것이 중요하다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;API 성능을 평가하는 주요 지표는 다음과 같다:&lt;/p&gt;
&lt;ul style=&quot;list-style-type: disc;&quot; data-ke-list-type=&quot;disc&quot;&gt;
&lt;li&gt;&lt;b&gt;Throughput (TPS, 초당 트랜잭션 수)&lt;/b&gt;: 시스템이 처리할 수 있는 최대 요청량을 나타냄.&lt;/li&gt;
&lt;li&gt;&lt;b&gt;Latency (MTT, Mean Test Time)&lt;/b&gt;: 하나의 요청을 처리하는 데 걸리는 평균 시간.&lt;/li&gt;
&lt;/ul&gt;
&lt;h3 data-ke-size=&quot;size23&quot;&gt;Saturation Point 분석&lt;/h3&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;시스템의 &lt;b&gt;Saturation Point (포화점)&lt;/b&gt; 을 파악하기 위해 &lt;b&gt;Enable Ramp-Up&lt;/b&gt; 기능을 사용하여 &lt;b&gt;vUser를 점진적으로 증가&lt;/b&gt;시키며 테스트를 진행한다. 포화점이란, 요청이 증가하더라도 TPS가 특정 값에 수렴하는 지점을 의미하며, 이를 통해 &lt;b&gt;시스템의 임계점을 판단&lt;/b&gt;하고 향후 성능 개선 계획을 수립할 수 있다.&lt;/p&gt;
&lt;div data-ke-type=&quot;moreLess&quot; data-text-more=&quot;더보기&quot; data-text-less=&quot;닫기&quot;&gt;&lt;a class=&quot;btn-toggle-moreless&quot;&gt;더보기&lt;/a&gt;
&lt;div class=&quot;moreless-content&quot;&gt;
&lt;p data-ke-size=&quot;size18&quot;&gt;Saturation Point: 요청이 증가해도 TPS가 특정 값으로 수렴하는 지점.&lt;/p&gt;
&lt;/div&gt;
&lt;/div&gt;
&lt;h3 data-ke-size=&quot;size23&quot;&gt;TPS와 MTT를 활용한 성능 평가&lt;/h3&gt;
&lt;h4 data-ke-size=&quot;size20&quot;&gt;TPS가 낮고 MTT가 높은 경우&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;b&gt;병목(Bottleneck) 존재 가능성&lt;/b&gt;&lt;/li&gt;
&lt;li&gt;응답 시간이 길어지고 성능 저하 우려가 있음&lt;/li&gt;
&lt;/ul&gt;
&lt;h4 data-ke-size=&quot;size20&quot;&gt;TPS가 높고 MTT가 낮은 경우&lt;/h4&gt;
&lt;ul style=&quot;list-style-type: disc;&quot; data-ke-list-type=&quot;disc&quot;&gt;
&lt;li&gt;API가 빠르게 많은 요청을 처리 가능 &amp;rarr; &lt;b&gt;현재 시스템 성능 양호&lt;/b&gt;&lt;/li&gt;
&lt;li&gt;추가적인 최적화 없이도 현재 부하를 원활히 감당 가능&lt;/li&gt;
&lt;/ul&gt;
&lt;h3 data-ke-size=&quot;size23&quot;&gt;데이터 기반 성능 평가 (이상치 탐지)&lt;/h3&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;API의 TPS와 MTT 데이터를 활용하여 다음과 같은 통계 지표를 구하여 성능을 평가한다:&lt;/p&gt;
&lt;ul style=&quot;list-style-type: disc;&quot; data-ke-list-type=&quot;disc&quot;&gt;
&lt;li&gt;&lt;b&gt;중위값 (Median)&lt;/b&gt;: API 성능의 대표값&lt;/li&gt;
&lt;li&gt;&lt;b&gt;사분위수 (Q1, Q3)&lt;/b&gt;: 데이터의 분포를 분석하기 위한 기준&lt;/li&gt;
&lt;li&gt;&lt;b&gt;IQR (Interquartile Range)&lt;/b&gt;: Q3 - Q1로 계산되며, 데이터의 변동 범위를 나타냄&lt;/li&gt;
&lt;li&gt;&lt;b&gt;Lower Outlier (이상치 기준)&lt;/b&gt;: X &amp;lt; Q1 - 1.5 &amp;times; IQR&lt;/li&gt;
&lt;/ul&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;이러한 지표를 통해 &lt;b&gt;API 성능이 전반적으로 균일한지 평가하고&lt;/b&gt;, 특정 API가 비정상적으로 낮은 성능을 보이는지를 분석한다.&lt;/p&gt;
&lt;hr contenteditable=&quot;false&quot; data-ke-type=&quot;horizontalRule&quot; data-ke-style=&quot;style6&quot; /&gt;
&lt;h2 data-ke-size=&quot;size26&quot;&gt;&lt;b&gt;Future Challenges&lt;/b&gt;&lt;/h2&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;본 글을 통해 nGrinder 를 설치하고 테스트 스크립트를 통해 성능 테스트를 진행하였다. 하지만 현재 학습한 내용으로는 웹에 접속하여 수동으로 테스트를 진행해야 하는 번거로움이 존재한다. 향후 업무 효율성 및 성과를 증대 시키기 위해서는 스크립트 일괄 업로드, 테스트 원격 실행, 테스트 결과 원격지 발송 등 자동화를 구현해야 할 것이다. 자동화를 달성하기 위해 아래의 레퍼런스를 참고한다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;h3 data-ke-size=&quot;size23&quot;&gt;&lt;b&gt;스크립트 파일 대량 업로드&lt;/b&gt;&lt;/h3&gt;
&lt;ol style=&quot;list-style-type: decimal;&quot; data-ke-list-type=&quot;decimal&quot;&gt;
&lt;li&gt;최초 스크립트가 세팅된 docker image 를 생성하여 관리한다.&lt;/li&gt;
&lt;li&gt;svn 저장소를 이용하여 관리한다.&lt;/li&gt;
&lt;/ol&gt;
&lt;h3 data-ke-size=&quot;size23&quot;&gt;&lt;b&gt;테스트 원격 실행&lt;/b&gt;&lt;/h3&gt;
&lt;ul style=&quot;list-style-type: disc;&quot; data-ke-list-type=&quot;disc&quot;&gt;
&lt;li&gt;nGrinder 에서 제공하고 있는 REST API 를 이용하여 구현한다.&lt;/li&gt;
&lt;li&gt;Ref. &lt;a href=&quot;https://github.com/naver/ngrinder/wiki/REST-API-QuickStart&quot;&gt;https://github.com/naver/ngrinder/wiki/REST-API-QuickStart&lt;/a&gt;&lt;/li&gt;
&lt;/ul&gt;
&lt;h3 data-ke-size=&quot;size23&quot;&gt;&lt;b&gt;테스트 결과 원격지 발송&lt;/b&gt;&lt;/h3&gt;
&lt;ul style=&quot;list-style-type: disc;&quot; data-ke-list-type=&quot;disc&quot;&gt;
&lt;li&gt;nGrinder 에서 제공하고 있는 Webhook을 이용하여 구현한다.&lt;/li&gt;
&lt;/ul&gt;
&lt;hr contenteditable=&quot;false&quot; data-ke-type=&quot;horizontalRule&quot; data-ke-style=&quot;style6&quot; /&gt;
&lt;h2 data-ke-size=&quot;size26&quot;&gt;&lt;b&gt;Ref&lt;/b&gt;&lt;/h2&gt;
&lt;ul style=&quot;list-style-type: disc;&quot; data-ke-list-type=&quot;disc&quot;&gt;
&lt;li&gt;&lt;a href=&quot;https://developbear.tistory.com/123&quot;&gt;https://developbear.tistory.com/123&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;&lt;a href=&quot;https://brownbears.tistory.com/25&quot;&gt;https://brownbears.tistory.com/25&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;&lt;a href=&quot;https://notspoon.tistory.com/48&quot;&gt;https://notspoon.tistory.com/48&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;&lt;a href=&quot;https://developer-nyong.tistory.com/68&quot;&gt;https://developer-nyong.tistory.com/68&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;&lt;a href=&quot;https://2021-pick-git.github.io/nGrinder-basic/&quot;&gt;https://2021-pick-git.github.io/nGrinder-basic/&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;&lt;a href=&quot;https://github.com/naver/ngrinder/wiki&quot;&gt;https://github.com/naver/ngrinder/wiki&lt;/a&gt;&lt;/li&gt;
&lt;/ul&gt;</description>
      <category>Back-End/API</category>
      <category>API</category>
      <category>nGrinder</category>
      <category>performance testing</category>
      <author>dev.parkjh</author>
      <guid isPermaLink="true">https://developerjaypark.tistory.com/113</guid>
      <comments>https://developerjaypark.tistory.com/113#entry113comment</comments>
      <pubDate>Sat, 5 Apr 2025 16:11:37 +0900</pubDate>
    </item>
    <item>
      <title>[Kube] Kubernetes - Service / Volume (7)</title>
      <link>https://developerjaypark.tistory.com/112</link>
      <description>&lt;p&gt;&lt;figure class=&quot;imageblock alignCenter&quot; data-ke-mobileStyle=&quot;widthOrigin&quot; data-origin-width=&quot;1140&quot; data-origin-height=&quot;474&quot;&gt;&lt;span data-url=&quot;https://blog.kakaocdn.net/dn/uaoXp/btsIHDol8p8/bJ0xVjlZ08c5Q59AQynBk0/img.png&quot; data-phocus=&quot;https://blog.kakaocdn.net/dn/uaoXp/btsIHDol8p8/bJ0xVjlZ08c5Q59AQynBk0/img.png&quot;&gt;&lt;img src=&quot;https://blog.kakaocdn.net/dn/uaoXp/btsIHDol8p8/bJ0xVjlZ08c5Q59AQynBk0/img.png&quot; srcset=&quot;https://img1.daumcdn.net/thumb/R1280x0/?scode=mtistory2&amp;fname=https%3A%2F%2Fblog.kakaocdn.net%2Fdn%2FuaoXp%2FbtsIHDol8p8%2FbJ0xVjlZ08c5Q59AQynBk0%2Fimg.png&quot; onerror=&quot;this.onerror=null; this.src='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png'; this.srcset='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png';&quot; loading=&quot;lazy&quot; width=&quot;1140&quot; height=&quot;474&quot; data-origin-width=&quot;1140&quot; data-origin-height=&quot;474&quot;/&gt;&lt;/span&gt;&lt;/figure&gt;
&lt;/p&gt;
&lt;p style=&quot;text-align: center;&quot; data-ke-size=&quot;size16&quot;&gt;&lt;span style=&quot;background-color: #ffffff; color: #555555; text-align: center;&quot;&gt;본 게시글은 인프런&lt;span&gt;&amp;nbsp;&lt;/span&gt;&lt;/span&gt;&lt;b&gt;subicura&lt;/b&gt;&lt;span style=&quot;background-color: #ffffff; color: #555555; text-align: center;&quot;&gt;&lt;span&gt;&amp;nbsp;&lt;/span&gt;강사님의&lt;span&gt;&amp;nbsp;&lt;/span&gt;&lt;/span&gt;&lt;span style=&quot;background-color: #ffffff; color: #555555; text-align: center;&quot; data-token-index=&quot;1&quot;&gt;&lt;b&gt;초보를 위한 쿠버네티스 안내서&lt;/b&gt;&lt;span&gt;&amp;nbsp;&lt;/span&gt;강의 수강 후 작성한 내용입니다.&lt;/span&gt;&lt;/p&gt;
&lt;hr contenteditable=&quot;false&quot; data-ke-type=&quot;horizontalRule&quot; data-ke-style=&quot;style7&quot; /&gt;
&lt;h3 data-ke-size=&quot;size23&quot;&gt;&lt;b&gt;Service&lt;/b&gt;&lt;/h3&gt;
&lt;ul style=&quot;list-style-type: disc;&quot; data-ke-list-type=&quot;disc&quot;&gt;
&lt;li&gt;Pod은 자체 IP를 가지고 다른 Pod과 통신할 수 있지만, 쉽게 사라지고 생성되는 특징 때문에 직접 통신하는 방법은 권장하지 않는다.&lt;/li&gt;
&lt;li&gt;쿠버네티스는 Pod과 직접 통신하는 방법 대신, 별도의 고정된 IP를 가진 서비스를 만들고 그 서비스를 통해 Pod에 접근하는 방식을 사용한다.&lt;/li&gt;
&lt;li&gt;노출 범위에 따라 CluterIP, NodePort, LoadBalancer 타입으로 나눈다.&lt;/li&gt;
&lt;/ul&gt;
&lt;hr contenteditable=&quot;false&quot; data-ke-type=&quot;horizontalRule&quot; data-ke-style=&quot;style7&quot; /&gt;
&lt;h4 data-ke-size=&quot;size20&quot;&gt;&lt;b&gt;Service(ClusterIP)&lt;/b&gt;&lt;/h4&gt;
&lt;ul style=&quot;list-style-type: disc;&quot; data-ke-list-type=&quot;disc&quot;&gt;
&lt;li&gt;CluterIP는 클러스터 내부에서만 접근할 수 있다.&lt;/li&gt;
&lt;/ul&gt;
&lt;p&gt;&lt;figure class=&quot;imageblock alignCenter&quot; data-ke-mobileStyle=&quot;widthOrigin&quot; data-filename=&quot;1.png&quot; data-origin-width=&quot;1104&quot; data-origin-height=&quot;740&quot;&gt;&lt;span data-url=&quot;https://blog.kakaocdn.net/dn/bywdw6/btsIIoEeu49/YqD1lc0yMXvnbczPvQZE6k/img.png&quot; data-phocus=&quot;https://blog.kakaocdn.net/dn/bywdw6/btsIIoEeu49/YqD1lc0yMXvnbczPvQZE6k/img.png&quot;&gt;&lt;img src=&quot;https://blog.kakaocdn.net/dn/bywdw6/btsIIoEeu49/YqD1lc0yMXvnbczPvQZE6k/img.png&quot; srcset=&quot;https://img1.daumcdn.net/thumb/R1280x0/?scode=mtistory2&amp;fname=https%3A%2F%2Fblog.kakaocdn.net%2Fdn%2Fbywdw6%2FbtsIIoEeu49%2FYqD1lc0yMXvnbczPvQZE6k%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;1104&quot; height=&quot;740&quot; data-filename=&quot;1.png&quot; data-origin-width=&quot;1104&quot; data-origin-height=&quot;740&quot;/&gt;&lt;/span&gt;&lt;/figure&gt;
&lt;/p&gt;
&lt;pre class=&quot;yaml&quot;&gt;&lt;code&gt;apiVersion: apps/v1
kind: Deployment
metadata:
  name: redis
spec:
  selector:
    matchLabels:
      app: counter
      tier: db
  template:
    metadata:
      labels:
        app: counter
        tier: db
    spec:
      containers:
        - name: redis
          image: redis
          ports:
            - containerPort: 6379
              protocol: TCP

---
apiVersion: v1
kind: Service
metadata:
  name: redis
spec:
  ports:
    - port: 6379
      protocol: TCP
  selector:
    app: counter
    tier: db
&lt;/code&gt;&lt;/pre&gt;
&lt;p style=&quot;text-align: center;&quot; data-ke-size=&quot;size16&quot;&gt;&lt;i&gt;&lt;b&gt;구분자:&lt;/b&gt; 하나의 YAML파일에 여러 개의 리소스를 정의할 땐 &quot;---&quot;를 구분자로 사용한다.&lt;/i&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;b&gt;CluterIP 서비스 옵션&lt;/b&gt;&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&gt;spec.ports.port&lt;/td&gt;
&lt;td&gt;서비스가 생성할 Port&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;spec.ports.targetPort&lt;/td&gt;
&lt;td&gt;서비스가 접근할 Pod의 Port (기본: port랑 동일)&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;spec.selector&lt;/td&gt;
&lt;td&gt;서비스가 접근할 Pod의 label 조건&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;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;counter app Pod에서 redis Pod으로 접근이 되는지 테스트 해본다.&lt;/p&gt;
&lt;pre class=&quot;vala&quot;&gt;&lt;code&gt;$ kubectl apply -f counter-app.yml

# counter app에 접근
$ kubectl get po
$ kubectl exec -it counter-&amp;lt;xxxxx&amp;gt; -- sh

# curl localhost:3000
# curl localhost:3000
# telnet redis 6379
  dbsize
  KEYS *
  GET count
  quit
&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;Service를 통해 Pod과 성공적으로 연결됨을 확인한다.&lt;/p&gt;
&lt;pre class=&quot;elixir&quot;&gt;&lt;code&gt;$ kubectl get endpoints
$ kubectl get ep #줄여서

# redis Endpoint 확인
$ kubectl describe ep/redis
&lt;/code&gt;&lt;/pre&gt;
&lt;hr contenteditable=&quot;false&quot; data-ke-type=&quot;horizontalRule&quot; data-ke-style=&quot;style7&quot; /&gt;
&lt;h4 data-ke-size=&quot;size20&quot;&gt;&lt;b&gt;Service(NodePort)&amp;nbsp;&lt;/b&gt;&lt;b&gt;&lt;/b&gt;&lt;/h4&gt;
&lt;ul style=&quot;list-style-type: disc;&quot; data-ke-list-type=&quot;disc&quot;&gt;
&lt;li&gt;CluterIP는 클러스터 내부에서만 접근할 수 있다.&lt;/li&gt;
&lt;li&gt;클러스터 외부(노드)에서 접근할 수 있도록 NodePort 서비스를 사용한다.&lt;/li&gt;
&lt;/ul&gt;
&lt;p&gt;&lt;figure class=&quot;imageblock alignCenter&quot; data-ke-mobileStyle=&quot;widthOrigin&quot; data-filename=&quot;2.png&quot; data-origin-width=&quot;1004&quot; data-origin-height=&quot;806&quot;&gt;&lt;span data-url=&quot;https://blog.kakaocdn.net/dn/bemtQ8/btsIJ0a6xsa/0RXfMRi7DTYb0cBGaws171/img.png&quot; data-phocus=&quot;https://blog.kakaocdn.net/dn/bemtQ8/btsIJ0a6xsa/0RXfMRi7DTYb0cBGaws171/img.png&quot;&gt;&lt;img src=&quot;https://blog.kakaocdn.net/dn/bemtQ8/btsIJ0a6xsa/0RXfMRi7DTYb0cBGaws171/img.png&quot; srcset=&quot;https://img1.daumcdn.net/thumb/R1280x0/?scode=mtistory2&amp;fname=https%3A%2F%2Fblog.kakaocdn.net%2Fdn%2FbemtQ8%2FbtsIJ0a6xsa%2F0RXfMRi7DTYb0cBGaws171%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;1004&quot; height=&quot;806&quot; data-filename=&quot;2.png&quot; data-origin-width=&quot;1004&quot; data-origin-height=&quot;806&quot;/&gt;&lt;/span&gt;&lt;/figure&gt;
&lt;figure class=&quot;imageblock alignCenter&quot; data-ke-mobileStyle=&quot;widthOrigin&quot; data-filename=&quot;3.png&quot; data-origin-width=&quot;1520&quot; data-origin-height=&quot;814&quot;&gt;&lt;span data-url=&quot;https://blog.kakaocdn.net/dn/lKLm7/btsIJtEQgFn/2XNxkAGMH3u83DbKfu30a0/img.png&quot; data-phocus=&quot;https://blog.kakaocdn.net/dn/lKLm7/btsIJtEQgFn/2XNxkAGMH3u83DbKfu30a0/img.png&quot;&gt;&lt;img src=&quot;https://blog.kakaocdn.net/dn/lKLm7/btsIJtEQgFn/2XNxkAGMH3u83DbKfu30a0/img.png&quot; srcset=&quot;https://img1.daumcdn.net/thumb/R1280x0/?scode=mtistory2&amp;fname=https%3A%2F%2Fblog.kakaocdn.net%2Fdn%2FlKLm7%2FbtsIJtEQgFn%2F2XNxkAGMH3u83DbKfu30a0%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;1520&quot; height=&quot;814&quot; data-filename=&quot;3.png&quot; data-origin-width=&quot;1520&quot; data-origin-height=&quot;814&quot;/&gt;&lt;/span&gt;&lt;/figure&gt;
&lt;/p&gt;
&lt;pre class=&quot;yaml&quot;&gt;&lt;code&gt;apiVersion: v1
kind: Service
metadata:
  name: counter-np
spec:
  type: NodePort
  ports:
    - port: 3000
      protocol: TCP
      nodePort: 31000
  selector:
    app: counter
    tier: app
&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;b&gt;정의 설명&lt;/b&gt;&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&gt;spec.ports.nodePort&lt;/td&gt;
&lt;td&gt;노드에 오픈할 Port (미지정시 30000-32768 중에 자동 할당)&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;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;counter app을 해당 노드의 31000으로 오픈한다.&lt;/p&gt;
&lt;pre class=&quot;routeros&quot;&gt;&lt;code&gt;$ kubectl apply -f counter-nodeport.yml

# 서비스 상태 확인
kubectl get svc
&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;minikube ip로 테스트 클러스터의 노드 IP를 구하고 31000으로 접근한다.&lt;/p&gt;
&lt;pre class=&quot;angelscript&quot;&gt;&lt;code&gt; $ curl 192.168.64.4:31000 # 또는 
 &amp;lt;http://192.168.64.4:31000&amp;gt; # 브라우저에서 접근한다.
&lt;/code&gt;&lt;/pre&gt;
&lt;hr contenteditable=&quot;false&quot; data-ke-type=&quot;horizontalRule&quot; data-ke-style=&quot;style7&quot; /&gt;
&lt;h4 data-ke-size=&quot;size20&quot;&gt;&lt;b&gt;Service(LoadBalancer) 만들기&lt;/b&gt;&lt;/h4&gt;
&lt;ul style=&quot;list-style-type: disc;&quot; data-ke-list-type=&quot;disc&quot;&gt;
&lt;li&gt;NodePort의 단점은 노드가 사라졌을 때 자동으로 다른 노드를 통해 접근이 불가능하다는 점이다.&lt;/li&gt;
&lt;li&gt;예를 들어, 3개의 노드가 있다면 3개 중에 아무 노드로 접근해도 NodePort로 연결할 수 있지만 어떤 노드가 살아 있는지는 알 수가 없다.&lt;/li&gt;
&lt;li&gt;자동으로 살아 있는 노드에 접근하기 위해 모든 노드를 바라보는&amp;nbsp;Load Balancer가 필요하다.&lt;/li&gt;
&lt;li&gt;브라우저는 NodePort에 직접 요청을 보내는 것이 아니라 Load Balancer에 요청하고 Load Balancer가 알아서 살아 있는 노드에 접근하여 NodePort의 단점을 제거한다.&lt;/li&gt;
&lt;/ul&gt;
&lt;p&gt;&lt;figure class=&quot;imageblock alignCenter&quot; data-ke-mobileStyle=&quot;widthOrigin&quot; data-filename=&quot;4.png&quot; data-origin-width=&quot;2000&quot; data-origin-height=&quot;1352&quot;&gt;&lt;span data-url=&quot;https://blog.kakaocdn.net/dn/dL4JTl/btsIJyTFrqx/l9yEmARK4aL01I5qKfdvKK/img.png&quot; data-phocus=&quot;https://blog.kakaocdn.net/dn/dL4JTl/btsIJyTFrqx/l9yEmARK4aL01I5qKfdvKK/img.png&quot;&gt;&lt;img src=&quot;https://blog.kakaocdn.net/dn/dL4JTl/btsIJyTFrqx/l9yEmARK4aL01I5qKfdvKK/img.png&quot; srcset=&quot;https://img1.daumcdn.net/thumb/R1280x0/?scode=mtistory2&amp;fname=https%3A%2F%2Fblog.kakaocdn.net%2Fdn%2FdL4JTl%2FbtsIJyTFrqx%2Fl9yEmARK4aL01I5qKfdvKK%2Fimg.png&quot; onerror=&quot;this.onerror=null; this.src='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png'; this.srcset='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png';&quot; loading=&quot;lazy&quot; width=&quot;2000&quot; height=&quot;1352&quot; data-filename=&quot;4.png&quot; data-origin-width=&quot;2000&quot; data-origin-height=&quot;1352&quot;/&gt;&lt;/span&gt;&lt;/figure&gt;
&lt;/p&gt;
&lt;pre class=&quot;yaml&quot;&gt;&lt;code&gt;apiVersion: v1
kind: Service
metadata:
  name: counter-lb
spec:
  type: LoadBalancer
  ports:
    - port: 30000
      targetPort: 3000
      protocol: TCP
  selector:
    app: counter
    tier: app
&lt;/code&gt;&lt;/pre&gt;
&lt;hr contenteditable=&quot;false&quot; data-ke-type=&quot;horizontalRule&quot; data-ke-style=&quot;style7&quot; /&gt;
&lt;h4 data-ke-size=&quot;size20&quot;&gt;&lt;b&gt;ingress&lt;/b&gt;&lt;/h4&gt;
&lt;ul style=&quot;list-style-type: disc;&quot; data-ke-list-type=&quot;disc&quot;&gt;
&lt;li&gt;Ingress는 클러스터 외부에서 내부 서비스로 HTTP 및 HTTPS 경로를 통해 접근을 제어하는 방법이다.&lt;/li&gt;
&lt;li&gt;Ingress는 특정 URL 경로 또는 호스트 이름을 기반으로 트래픽을 라우팅하며, 부하 분산, SSL 종단, 이름 기반 가상 호스팅 등을 제공할 수 있다.&lt;/li&gt;
&lt;li&gt;Ingress를 사용하면 여러 서비스에 대한 접근을 하나의 IP 주소와 도메인 이름으로 관리할 수 있다. 이는 특히 여러 서비스를 운영하는 환경에서 매우 유용하다.&lt;/li&gt;
&lt;/ul&gt;
&lt;pre class=&quot;yaml&quot;&gt;&lt;code&gt;apiVersion: networking.k8s.io/v1
kind: Ingress
metadata:
  name: echo-v1
spec:
  rules:
    - host: v1.echo.192.168.64.5.sslip.io
      http:
        paths:
          - path: /
            pathType: Prefix
            backend:
              service:
                name: echo-v1
                port:
                  number: 3000

---
apiVersion: apps/v1
kind: Deployment
metadata:
  name: echo-v1
spec:
  replicas: 3
  selector:
    matchLabels:
      app: echo
      tier: app
      version: v1
  template:
    metadata:
      labels:
        app: echo
        tier: app
        version: v1
    spec:
      containers:
        - name: echo
          image: ghcr.io/subicura/echo:v1
          livenessProbe:
            httpGet:
              path: /
              port: 3000

---
apiVersion: v1
kind: Service
metadata:
  name: echo-v1
spec:
  ports:
    - port: 3000
      protocol: TCP
  selector:
    app: echo
    tier: app
    version: v1
&lt;/code&gt;&lt;/pre&gt;
&lt;hr contenteditable=&quot;false&quot; data-ke-type=&quot;horizontalRule&quot; data-ke-style=&quot;style7&quot; /&gt;
&lt;h4 data-ke-size=&quot;size20&quot;&gt;&lt;b&gt;Volume (local)&lt;/b&gt;&lt;/h4&gt;
&lt;ul style=&quot;list-style-type: disc;&quot; data-ke-list-type=&quot;disc&quot;&gt;
&lt;li&gt;지금까지 만들었던 컨테이너는 Pod을 제거하면 컨테이너 내부에 저장했던 데이터도 모두 사라진다.&lt;/li&gt;
&lt;li&gt;MySQL과 같은 데이터베이스는 데이터가 유실되지 않도록 반드시 별도의 저장소에 데이터를 저장하고 컨테이너를 새로 만들 때 이전 데이터를 가져와야 한다.&lt;/li&gt;
&lt;li&gt;쿠버네티스는 Volume을 이용하여 컨테이너의 디렉토리를 외부 저장소와 연결하고 다양한 플러그인을 지원하여 흔히 사용하는 대부분의 스토리지를 별도 설정없이 사용할 수 있다.&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;b&gt;empty-dir&lt;/b&gt;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;: 컨테이너 내부 공유&lt;/p&gt;
&lt;pre class=&quot;dts&quot;&gt;&lt;code&gt;apiVersion: v1
kind: Pod
metadata:
  name: sidecar
spec:
  containers:
    - name: app
      image: busybox
      args:
        - /bin/sh
        - -c
        - &amp;gt;
          while true;
          do
            echo &quot;$(date)\\n&quot; &amp;gt;&amp;gt; /var/log/example.log;
            sleep 1;
          done
      volumeMounts:
        - name: varlog
          mountPath: /var/log
    - name: sidecar
      image: busybox
      args: [/bin/sh, -c, &quot;tail -f /var/log/example.log&quot;]
      volumeMounts:
        - name: varlog
          mountPath: /var/log
  volumes:
    - name: varlog
      emptyDir: {}
&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;&lt;b&gt;hostpath&lt;/b&gt;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;: 호스트 폴더와 공유&lt;/p&gt;
&lt;pre class=&quot;less&quot;&gt;&lt;code&gt;apiVersion: v1
kind: Pod
metadata:
  name: host-log
spec:
  containers:
    - name: log
      image: busybox
      args: [&quot;/bin/sh&quot;, &quot;-c&quot;, &quot;sleep infinity&quot;]
      volumeMounts:
        - name: varlog
          mountPath: /host/var/log
  volumes:
    - name: varlog
      hostPath:
        path: /var/log
&lt;/code&gt;&lt;/pre&gt;
&lt;hr contenteditable=&quot;false&quot; data-ke-type=&quot;horizontalRule&quot; data-ke-style=&quot;style7&quot; /&gt;
&lt;h4 data-ke-size=&quot;size20&quot;&gt;&lt;b&gt;ConfigMap&lt;/b&gt;&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;nbsp;ConfigMap으로 설정을 관리한다.&lt;/li&gt;
&lt;/ul&gt;
&lt;pre class=&quot;less&quot;&gt;&lt;code&gt;global:
  scrape_interval: 15s

scrape_configs:
  - job_name: prometheus
    metrics_path: /prometheus/metrics
    static_configs:
      - targets:
          - localhost:9090
&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;먼저, ConfigMap을 만들고&amp;nbsp;--from-file&amp;nbsp;옵션을 이용하여 file을 설정으로 만든다.&lt;/p&gt;
&lt;pre class=&quot;routeros&quot;&gt;&lt;code&gt;# ConfitMap 생성 (configmap == cm)
$ kubectl create cm my-config --from-file=config-file.yml

# ConfitMap 조회
$kubectl get cm

# ConfigMap 내용 상세 조회
$ kubectl describe cm/my-config
&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;생성한 ConfigMap을&amp;nbsp;/etc/config&amp;nbsp;디렉토리에 연결한다.&lt;/p&gt;
&lt;pre class=&quot;less&quot;&gt;&lt;code&gt;apiVersion: v1
kind: Pod
metadata:
  name: alpine
spec:
  containers:
    - name: alpine
      image: alpine
      command: [&quot;sleep&quot;]
      args: [&quot;100000&quot;]
      volumeMounts:
        - name: config-vol
          mountPath: /etc/config
  volumes:
    - name: config-vol
      configMap:
        name: my-config
&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;volume을 연결하여 배포하고 확인한다.&lt;/p&gt;
&lt;pre class=&quot;routeros&quot;&gt;&lt;code&gt;$ kubectl apply -f alpine.yml

# 접속 후 설정 확인
$ kubectl exec -it alpine -- ls /etc/config
$ kubectl exec -it alpine -- cat /etc/config/config-file.yml
&lt;/code&gt;&lt;/pre&gt;
&lt;hr contenteditable=&quot;false&quot; data-ke-type=&quot;horizontalRule&quot; data-ke-style=&quot;style7&quot; /&gt;
&lt;h4 data-ke-size=&quot;size20&quot;&gt;&lt;b&gt;Secret&lt;/b&gt;&lt;/h4&gt;
&lt;ul style=&quot;list-style-type: disc;&quot; data-ke-list-type=&quot;disc&quot;&gt;
&lt;li&gt;쿠버네티스는 ConfigMap과 유사하지만, 보안 정보를 관리하기 위해 Secret을 별도로 제공한다.&lt;/li&gt;
&lt;li&gt;ConfigMap과 차이점은 데이터가&amp;nbsp;base64로 저장된다는 점이다.&lt;/li&gt;
&lt;/ul&gt;
&lt;pre class=&quot;routeros&quot;&gt;&lt;code&gt;# secret 생성
$ kubectl create secret generic db-user-pass --from-file=./username.txt --from-file=./password.txt

# secret 상세 조회
$ kubectl describe secret/db-user-pass

# -o yaml로 상세 조회
$ kubectl get secret/db-user-pass -o yaml

# 저장된 데이터 base64 decode
$ echo 'MXEydzNlNHI=' | base64 --decode
&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;설정한 Secret을 환경변수로 연결한다.&lt;/p&gt;
&lt;pre class=&quot;less&quot;&gt;&lt;code&gt;apiVersion: v1
kind: Pod
metadata:
  name: alpine-env
spec:
  containers:
    - name: alpine
      image: alpine
      command: [&quot;sleep&quot;]
      args: [&quot;100000&quot;]
      env:
        - name: DB_USERNAME
          valueFrom:
            secretKeyRef:
              name: db-user-pass
              key: username.txt
        - name: DB_PASSWORD
          valueFrom:
            secretKeyRef:
              name: db-user-pass
              key: password.txt
&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;배포 후 환경변수를 확인한다.&lt;/p&gt;
&lt;pre class=&quot;bash&quot; data-ke-language=&quot;bash&quot;&gt;&lt;code&gt;$ kubectl apply -f alpine-env.yml

# env 확인
$ kubectl exec -it alpine-env -- env&lt;/code&gt;&lt;/pre&gt;
&lt;hr contenteditable=&quot;false&quot; data-ke-type=&quot;horizontalRule&quot; data-ke-style=&quot;style7&quot; /&gt;
&lt;h4 data-ke-size=&quot;size20&quot;&gt;&lt;b&gt;참고사이트&lt;/b&gt;&lt;/h4&gt;
&lt;ul style=&quot;list-style-type: disc;&quot; data-ke-list-type=&quot;disc&quot;&gt;
&lt;li&gt;&lt;a href=&quot;https://subicura.com/k8s/guide/service.html&quot;&gt;https://subicura.com/k8s/guide/service.html&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;&lt;a href=&quot;https://subicura.com/k8s/guide/ingress.html#ingress-&quot;&gt;https://subicura.com/k8s/guide/ingress.html#ingress-&lt;/a&gt;&lt;a href=&quot;https://subicura.com/k8s/guide/ingress.html#ingress-%E1%84%86%E1%85%A1%E1%86%AB%E1%84%83%E1%85%B3%E1%86%AF%E1%84%80%E1%85%B5&quot;&gt;만들기&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;&lt;a href=&quot;https://subicura.com/k8s/guide/local-volume.html&quot;&gt;https://subicura.com/k8s/guide/local-volume.html&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;&lt;a href=&quot;https://subicura.com/k8s/guide/configmap.html&quot;&gt;https://subicura.com/k8s/guide/configmap.html&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;&lt;a href=&quot;https://subicura.com/k8s/guide/secret.html&quot;&gt;https://subicura.com/k8s/guide/secret.html&lt;/a&gt;&lt;/li&gt;
&lt;/ul&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;</description>
      <category>System/Server</category>
      <category>Configmap</category>
      <category>kubernetes</category>
      <category>secret</category>
      <category>service</category>
      <category>volume</category>
      <author>dev.parkjh</author>
      <guid isPermaLink="true">https://developerjaypark.tistory.com/112</guid>
      <comments>https://developerjaypark.tistory.com/112#entry112comment</comments>
      <pubDate>Tue, 23 Jul 2024 00:07:07 +0900</pubDate>
    </item>
    <item>
      <title>[Kube] Kubernetes - Deployment (6)</title>
      <link>https://developerjaypark.tistory.com/111</link>
      <description>&lt;p&gt;&lt;figure class=&quot;imageblock alignCenter&quot; data-ke-mobileStyle=&quot;widthOrigin&quot; data-origin-width=&quot;1140&quot; data-origin-height=&quot;474&quot;&gt;&lt;span data-url=&quot;https://blog.kakaocdn.net/dn/b6roCd/btsIJffJYuw/na7egFSoOZ9AvsvkKtHcm1/img.png&quot; data-phocus=&quot;https://blog.kakaocdn.net/dn/b6roCd/btsIJffJYuw/na7egFSoOZ9AvsvkKtHcm1/img.png&quot;&gt;&lt;img src=&quot;https://blog.kakaocdn.net/dn/b6roCd/btsIJffJYuw/na7egFSoOZ9AvsvkKtHcm1/img.png&quot; srcset=&quot;https://img1.daumcdn.net/thumb/R1280x0/?scode=mtistory2&amp;fname=https%3A%2F%2Fblog.kakaocdn.net%2Fdn%2Fb6roCd%2FbtsIJffJYuw%2Fna7egFSoOZ9AvsvkKtHcm1%2Fimg.png&quot; onerror=&quot;this.onerror=null; this.src='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png'; this.srcset='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png';&quot; loading=&quot;lazy&quot; width=&quot;1140&quot; height=&quot;474&quot; data-origin-width=&quot;1140&quot; data-origin-height=&quot;474&quot;/&gt;&lt;/span&gt;&lt;/figure&gt;
&lt;/p&gt;
&lt;p style=&quot;text-align: center;&quot; data-ke-size=&quot;size16&quot;&gt;&lt;span style=&quot;background-color: #ffffff; color: #555555; text-align: center;&quot;&gt;본 게시글은 인프런&lt;span&gt;&amp;nbsp;&lt;/span&gt;&lt;/span&gt;&lt;b&gt;subicura&lt;/b&gt;&lt;span style=&quot;background-color: #ffffff; color: #555555; text-align: center;&quot;&gt;&lt;span&gt;&amp;nbsp;&lt;/span&gt;강사님의&lt;span&gt;&amp;nbsp;&lt;/span&gt;&lt;/span&gt;&lt;span style=&quot;background-color: #ffffff; color: #555555; text-align: center;&quot; data-token-index=&quot;1&quot;&gt;&lt;b&gt;초보를 위한 쿠버네티스 안내서&lt;/b&gt;&lt;span&gt;&amp;nbsp;&lt;/span&gt;강의 수강 후 작성한 내용입니다.&lt;/span&gt;&lt;/p&gt;
&lt;hr contenteditable=&quot;false&quot; data-ke-type=&quot;horizontalRule&quot; data-ke-style=&quot;style7&quot; /&gt;
&lt;h4 data-ke-size=&quot;size20&quot;&gt;&lt;b&gt;Deployment (배포)&lt;/b&gt;&lt;/h4&gt;
&lt;ul style=&quot;list-style-type: disc;&quot; data-ke-list-type=&quot;disc&quot;&gt;
&lt;li&gt;Deployment는 쿠버네티스에서 가장 널리 사용되는 오브젝트이다.&lt;/li&gt;
&lt;li&gt;ReplicaSet을 이용하여 Pod을 업데이트하고 이력을 관리하여 롤백(Rollback)하거나 특정 버전(revision)으로 돌아갈 수 있다.&lt;/li&gt;
&lt;/ul&gt;
&lt;pre class=&quot;less&quot;&gt;&lt;code&gt;apiVersion: apps/v1
kind: Deployment
metadata:
  name: echo-deploy
spec:
  replicas: 4
  selector:
    matchLabels:
      app: echo
      tier: app
  template:
    metadata:
      labels:
        app: echo
        tier: app
    spec:
      containers:
        - name: echo
          image: ghcr.io/subicura/echo:v1
&lt;/code&gt;&lt;/pre&gt;
&lt;pre class=&quot;bash&quot; data-ke-language=&quot;bash&quot;&gt;&lt;code&gt;# Deployment 생성
kubectl apply -f echo-deployment.yml

# 리소스 확인
kubectl get po,rs,deploy

#---------
NAME                               READY   STATUS    RESTARTS   AGE
pod/echo-deploy-5f9566c4d9-7pmrp   1/1     Running   0          9s
pod/echo-deploy-5f9566c4d9-j97jb   1/1     Running   0          9s
pod/echo-deploy-5f9566c4d9-l47nd   1/1     Running   0          9s
pod/echo-deploy-5f9566c4d9-sxchs   1/1     Running   0          9s

NAME                                     DESIRED   CURRENT   READY   AGE
replicaset.apps/echo-deploy-5f9566c4d9   4         4         4       9s

NAME                          READY   UP-TO-DATE   AVAILABLE   AGE
deployment.apps/echo-deploy   4/4     4            4           9s
#---------&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;image: &lt;a href=&quot;http://ghcr.io/subicura/echo:v1&quot;&gt;ghcr.io/subicura/echo:v1&lt;/a&gt; 을 image: &lt;a href=&quot;http://ghcr.io/subicura/echo:v2&quot;&gt;ghcr.io/subicura/echo:v2&lt;/a&gt; 으로 변경 후 다시 배포&lt;/p&gt;
&lt;pre class=&quot;bash&quot; data-ke-language=&quot;bash&quot;&gt;&lt;code&gt;# Deployment 생성
kubectl apply -f echo-deployment.yml

# 리소스 확인
kubectl get po,rs,deploy

#---------
NAME                               READY   STATUS    RESTARTS   AGE
pod/echo-deploy-54c84dd47b-x44s6   1/1     Running   0          4s
pod/echo-deploy-54c84dd47b-xdx22   1/1     Running   0          6s
pod/echo-deploy-54c84dd47b-xf6sd   1/1     Running   0          6s
pod/echo-deploy-54c84dd47b-xpzx8   1/1     Running   0          4s

NAME                                     DESIRED   CURRENT   READY   AGE
replicaset.apps/echo-deploy-54c84dd47b   4         4         4       6s
replicaset.apps/echo-deploy-5f9566c4d9   0         0         0       99s

NAME                          READY   UP-TO-DATE   AVAILABLE   AGE
deployment.apps/echo-deploy   4/4     4            4           99s
#---------&lt;/code&gt;&lt;/pre&gt;
&lt;hr contenteditable=&quot;false&quot; data-ke-type=&quot;horizontalRule&quot; data-ke-style=&quot;style7&quot; /&gt;
&lt;h4 data-ke-size=&quot;size20&quot;&gt;&lt;b&gt;Deployment 버전 업데이트 절차&lt;/b&gt;&lt;/h4&gt;
&lt;ul style=&quot;list-style-type: disc;&quot; data-ke-list-type=&quot;disc&quot;&gt;
&lt;li&gt;Deployment 새로운 버전을 업데이트하기 위해 레플리카 셋을 이용한다.
&lt;ol style=&quot;list-style-type: decimal;&quot; data-ke-list-type=&quot;decimal&quot;&gt;
&lt;li&gt;버전을 업데이트하면 새로운 ReplicaSet을 생성하고 해당 ReplicaSet이 새로운 버전의 Pod을 생성한다.&lt;/li&gt;
&lt;li&gt;새로운 ReplicaSet을 0 -&amp;gt; 1개로 조정하고 정상적으로 Pod이 동작하면 기존 ReplicaSet을 4 -&amp;gt; 3개로 조정한다.&lt;/li&gt;
&lt;li&gt;새로운 ReplicaSet을 1 -&amp;gt; 2개로 조정하고 정상적으로 Pod이 동작하면 기존 ReplicaSet을 3 -&amp;gt; 2개로 조정한다.&lt;/li&gt;
&lt;li&gt;새로운 ReplicaSet을 2 -&amp;gt; 3개로 조정하고 정상적으로 Pod이 동작하면 기존 ReplicaSet을 2 -&amp;gt; 1개로 조정한다.&lt;/li&gt;
&lt;li&gt;최종적으로 새로운 ReplicaSet을 4개로 조정하고 정상적으로 Pod이 동작하면 기존 ReplicaSet을 0개로 조정한다&lt;/li&gt;
&lt;li&gt;업데이트 완료&lt;/li&gt;
&lt;/ol&gt;
&lt;/li&gt;
&lt;/ul&gt;
&lt;hr contenteditable=&quot;false&quot; data-ke-type=&quot;horizontalRule&quot; data-ke-style=&quot;style7&quot; /&gt;
&lt;h3 data-ke-size=&quot;size23&quot;&gt;&lt;b&gt;버전관리&lt;/b&gt;&lt;/h3&gt;
&lt;ul style=&quot;list-style-type: disc;&quot; data-ke-list-type=&quot;disc&quot;&gt;
&lt;li&gt;Deployment는 변경된 상태를 기록한다.&lt;/li&gt;
&lt;/ul&gt;
&lt;pre class=&quot;shell&quot;&gt;&lt;code&gt;# 히스토리 확인
$ kubectl rollout history deploy/echo-deploy

# revision 1 히스토리 상세 확인
$ kubectl rollout history deploy/echo-deploy --revision=1

# 바로 전으로 롤백
$ kubectl rollout undo deploy/echo-deploy

# 특정 버전으로 롤백
$ kubectl rollout undo deploy/echo-deploy --to-revision=2
&lt;/code&gt;&lt;/pre&gt;
&lt;hr contenteditable=&quot;false&quot; data-ke-type=&quot;horizontalRule&quot; data-ke-style=&quot;style7&quot; /&gt;
&lt;h3 data-ke-size=&quot;size23&quot;&gt;&lt;b&gt;배포 전략 설정&lt;/b&gt;&lt;/h3&gt;
&lt;ul style=&quot;list-style-type: disc;&quot; data-ke-list-type=&quot;disc&quot;&gt;
&lt;li&gt;Deployment 다양한 방식의 배포 전략이 있다.&lt;/li&gt;
&lt;li&gt;롤링업데이트(RollingUpdate)&amp;nbsp;방식을 사용할 때 동시에 업데이트하는 Pod의 개수를 변경한다.&lt;/li&gt;
&lt;/ul&gt;
&lt;pre class=&quot;yaml&quot;&gt;&lt;code&gt;apiVersion: apps/v1
kind: Deployment
metadata:
  name: echo-deploy-st
spec:
  replicas: 4
  selector:
    matchLabels:
      app: echo
      tier: app
  minReadySeconds: 5
  strategy:
    type: RollingUpdate
    rollingUpdate:
      maxSurge: 3
      maxUnavailable: 3 
  template:
    metadata:
      labels:
        app: echo
        tier: app
    spec:
      containers:
        - name: echo
          image: ghcr.io/subicura/echo:v1
          livenessProbe:
            httpGet:
              path: /
              port: 3000
&lt;/code&gt;&lt;/pre&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;hr contenteditable=&quot;false&quot; data-ke-type=&quot;horizontalRule&quot; data-ke-style=&quot;style7&quot; /&gt;
&lt;h4 data-ke-size=&quot;size20&quot;&gt;&lt;b&gt;참고 사이트&lt;/b&gt;&lt;/h4&gt;
&lt;ul style=&quot;list-style-type: disc;&quot; data-ke-list-type=&quot;disc&quot;&gt;
&lt;li&gt;&lt;a href=&quot;https://subicura.com/k8s/guide/deployment.html&quot;&gt;https://subicura.com/k8s/guide/deployment.html&lt;/a&gt;&lt;/li&gt;
&lt;/ul&gt;</description>
      <category>System/Server</category>
      <category>deployment</category>
      <category>kubernetes</category>
      <author>dev.parkjh</author>
      <guid isPermaLink="true">https://developerjaypark.tistory.com/111</guid>
      <comments>https://developerjaypark.tistory.com/111#entry111comment</comments>
      <pubDate>Mon, 22 Jul 2024 23:27:25 +0900</pubDate>
    </item>
    <item>
      <title>[Kube] Kubernetes - Pod  (5)</title>
      <link>https://developerjaypark.tistory.com/110</link>
      <description>&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p&gt;&lt;figure class=&quot;imageblock alignCenter&quot; data-ke-mobileStyle=&quot;widthOrigin&quot; data-origin-width=&quot;1140&quot; data-origin-height=&quot;474&quot;&gt;&lt;span data-url=&quot;https://blog.kakaocdn.net/dn/ddH7ao/btsIHglEz1b/EyHNOs7NNihee0kk4mcu71/img.png&quot; data-phocus=&quot;https://blog.kakaocdn.net/dn/ddH7ao/btsIHglEz1b/EyHNOs7NNihee0kk4mcu71/img.png&quot;&gt;&lt;img src=&quot;https://blog.kakaocdn.net/dn/ddH7ao/btsIHglEz1b/EyHNOs7NNihee0kk4mcu71/img.png&quot; srcset=&quot;https://img1.daumcdn.net/thumb/R1280x0/?scode=mtistory2&amp;fname=https%3A%2F%2Fblog.kakaocdn.net%2Fdn%2FddH7ao%2FbtsIHglEz1b%2FEyHNOs7NNihee0kk4mcu71%2Fimg.png&quot; onerror=&quot;this.onerror=null; this.src='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png'; this.srcset='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png';&quot; loading=&quot;lazy&quot; width=&quot;1140&quot; height=&quot;474&quot; data-origin-width=&quot;1140&quot; data-origin-height=&quot;474&quot;/&gt;&lt;/span&gt;&lt;/figure&gt;
&lt;/p&gt;
&lt;p style=&quot;text-align: center;&quot; data-ke-size=&quot;size16&quot;&gt;&lt;span style=&quot;background-color: #ffffff; color: #555555; text-align: center;&quot;&gt;본 게시글은 인프런&lt;span&gt;&amp;nbsp;&lt;/span&gt;&lt;/span&gt;&lt;b&gt;subicura&lt;/b&gt;&lt;span style=&quot;background-color: #ffffff; color: #555555; text-align: center;&quot;&gt;&lt;span&gt;&amp;nbsp;&lt;/span&gt;강사님의&lt;span&gt;&amp;nbsp;&lt;/span&gt;&lt;/span&gt;&lt;span style=&quot;background-color: #ffffff; color: #555555; text-align: center;&quot; data-token-index=&quot;1&quot;&gt;&lt;b&gt;초보를 위한 쿠버네티스 안내서&lt;/b&gt;&lt;span&gt;&amp;nbsp;&lt;/span&gt;강의 수강 후 작성한 내용입니다.&lt;/span&gt;&lt;/p&gt;
&lt;hr contenteditable=&quot;false&quot; data-ke-type=&quot;horizontalRule&quot; data-ke-style=&quot;style7&quot; /&gt;
&lt;h4 data-ke-size=&quot;size20&quot;&gt;&lt;b&gt;Pod 이란 ?&lt;/b&gt;&lt;/h4&gt;
&lt;ul style=&quot;list-style-type: disc;&quot; data-ke-list-type=&quot;disc&quot;&gt;
&lt;li&gt;Pod은 쿠버네티스에서 관리하는 가장 작은 배포 단위&lt;/li&gt;
&lt;li&gt;쿠버네티스와 도커의 차이점은 도커는 컨테이너를 만들지만 쿠버네티스는 컨테이너 대신 Pod을 만듬.&lt;/li&gt;
&lt;li&gt;Pod은 한개 또는 여러 개의 컨테이너를 포함함.&lt;/li&gt;
&lt;/ul&gt;
&lt;hr contenteditable=&quot;false&quot; data-ke-type=&quot;horizontalRule&quot; data-ke-style=&quot;style7&quot; /&gt;
&lt;h4 data-ke-size=&quot;size20&quot;&gt;&lt;b&gt;Pod 생성 맛보기&lt;/b&gt;&lt;/h4&gt;
&lt;ul style=&quot;list-style-type: disc;&quot; data-ke-list-type=&quot;disc&quot;&gt;
&lt;li&gt;저장소에 저장된 이미지로 Pod 만들기&lt;/li&gt;
&lt;/ul&gt;
&lt;pre class=&quot;bash&quot; data-ke-language=&quot;bash&quot;&gt;&lt;code&gt;# Pod 생성
$ kubectl run echo --image ghcr.io/subicura/echo:v1

# 생성된 Pod 보기
$ kubectl get pod/echo

# Pod의 상세내용 보기
$ kubectl describe pod/echo

# echo의 로그 보기
$ kubectl logs -f echo

# echo 컨테이너 접속
$ kubectl exec -it echo -- sh

# Pod 제거
$ kubectl delete pod/echo&lt;/code&gt;&lt;/pre&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;ul style=&quot;list-style-type: disc;&quot; data-ke-list-type=&quot;disc&quot;&gt;
&lt;li&gt;YAML로 Pod 생성하기&lt;/li&gt;
&lt;/ul&gt;
&lt;pre class=&quot;bash&quot; data-ke-language=&quot;bash&quot;&gt;&lt;code&gt;apiVersion: v1
kind: Pod
metadata:
  name: echo
  labels:
    app: echo
spec:
  containers:
    - name: app
      image: ghcr.io/subicura/echo:v1&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;b&gt;version,&amp;nbsp;kind,&amp;nbsp;metadata,&amp;nbsp;spec는 리소스를 정의할 때 반드시 필요한 요소이다.&lt;/b&gt;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;ul style=&quot;list-style-type: disc;&quot; data-ke-list-type=&quot;disc&quot;&gt;
&lt;li&gt;정의 설명 예&lt;/li&gt;
&lt;/ul&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&gt;version&lt;/td&gt;
&lt;td&gt;오브젝트 버전&lt;/td&gt;
&lt;td&gt;v1, app/v1, &lt;a href=&quot;http://networking.k8s.io/v1&quot;&gt;networking.k8s.io/v1&lt;/a&gt;, ...&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;kind&lt;/td&gt;
&lt;td&gt;종류&lt;/td&gt;
&lt;td&gt;Pod, ReplicaSet, Deployment, Service, ...&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;metadata&lt;/td&gt;
&lt;td&gt;메타데이터&lt;/td&gt;
&lt;td&gt;name과 label, annotation(주석)으로 구성&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;spec&lt;/td&gt;
&lt;td&gt;상세명세&lt;/td&gt;
&lt;td&gt;리소스 종류마다 다름&lt;/td&gt;
&lt;/tr&gt;
&lt;/tbody&gt;
&lt;/table&gt;
&lt;pre class=&quot;bash&quot; data-ke-language=&quot;bash&quot;&gt;&lt;code&gt;# Pod 생성
$ kubectl apply -f echo-pod.yml

# 생성된 Pod 보기
$ kubectl get pod/echo

# Pod의 상세내용 보기
$ kubectl describe pod/echo

# echo의 로그 보기
$ kubectl logs -f echo

# echo 컨테이너 접속
$ kubectl exec -it echo -- sh

# Pod 제거
$ kubectl delete pod/echo&lt;/code&gt;&lt;/pre&gt;
&lt;hr contenteditable=&quot;false&quot; data-ke-type=&quot;horizontalRule&quot; data-ke-style=&quot;style7&quot; /&gt;
&lt;h4 data-ke-size=&quot;size20&quot;&gt;&lt;b&gt;컨테이너 상태 모니터링&lt;/b&gt;&lt;b&gt;&lt;/b&gt;&lt;/h4&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;b&gt;livenessProbe&lt;/b&gt;&lt;/p&gt;
&lt;ul style=&quot;list-style-type: disc;&quot; data-ke-list-type=&quot;disc&quot;&gt;
&lt;li&gt;컨테이너가 정상적으로 동작하는지 체크하고 정상적으로 동작하지 않는다면&amp;nbsp;&lt;b&gt;컨테이너를 재시작&lt;/b&gt;하여 문제를 해결한다.&lt;/li&gt;
&lt;li&gt;정상이라는 것은 여러 가지 방식으로 체크할 수 있는데 아래 예시에서는&amp;nbsp;http get&amp;nbsp;요청을 보내 확인하는 방법을 사용한다.&lt;/li&gt;
&lt;/ul&gt;
&lt;pre class=&quot;yaml&quot;&gt;&lt;code&gt;apiVersion: v1
kind: Pod
metadata:
  name: echo-lp
  labels:
    app: echo
spec:
  containers:
    - name: app
      image: ghcr.io/subicura/echo:v1
      livenessProbe:
        httpGet:
          path: /not/exist # 고의적으로 존재하지 않는 path 기입
          port: 8080 # 고의적으로 존재하지 않는 포트 기입
        initialDelaySeconds: 5
        timeoutSeconds: 2 # Default 1
        periodSeconds: 5 # Defaults 10
        failureThreshold: 1 # Defaults 3
&lt;/code&gt;&lt;/pre&gt;
&lt;pre class=&quot;bash&quot; data-ke-language=&quot;bash&quot;&gt;&lt;code&gt;# Pod 생성
$ kubectl apply -f echo-pod.yml

# 생성된 Pod 보기
$ kubectl get pod/echo

# ---- 
# 정상적으로 응답하지 않기 때문에 CrashLoopBackOff 상태로 변경된다.
NAME      READY   STATUS             RESTARTS     AGE
echo-lp   0/1     **CrashLoopBackOff**   2 (8s ago)   23s
#-----

# Pod 제거
$ kubectl delete pod/echo&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;&lt;b&gt;readinessProbe&lt;/b&gt;&lt;/p&gt;
&lt;ul style=&quot;list-style-type: disc;&quot; data-ke-list-type=&quot;disc&quot;&gt;
&lt;li&gt;컨테이너가 준비되었는지 체크하고 정상적으로 준비되지 않았다면&amp;nbsp;&lt;b&gt;Pod으로 들어오는 요청을 제외한다&lt;/b&gt;.&lt;/li&gt;
&lt;li&gt;livenessProbe와 차이점은 문제가 있어도 Pod을 재시작하지 않고 요청만 제외한다는 점이다.&lt;/li&gt;
&lt;/ul&gt;
&lt;pre class=&quot;bash&quot; data-ke-language=&quot;bash&quot;&gt;&lt;code&gt;apiVersion: v1
kind: Pod
metadata:
  name: echo-rp
  labels:
    app: echo
spec:
  containers:
    - name: app
      image: ghcr.io/subicura/echo:v1
      readinessProbe:
        httpGet:
          path: /not/exist # 고의적으로 존재하지 않는 path 기입
          port: 8080 # 고의적으로 존재하지 않는 포트 기입
        initialDelaySeconds: 5
        timeoutSeconds: 2 # Default 1
        periodSeconds: 5 # Defaults 10
        failureThreshold: 1 # Defaults 3&lt;/code&gt;&lt;/pre&gt;
&lt;pre class=&quot;bash&quot; data-ke-language=&quot;bash&quot;&gt;&lt;code&gt;# Pod 생성
$ kubectl apply -f echo-pod.yml

# 생성된 Pod 보기
$ kubectl get pod/echo

# ---- 
# pod을 재시작하지 않고 제외한다. (READY 0)
NAME      READY   STATUS    RESTARTS   AGE
echo-rp   0/1     Running   0          16s
#-----

# Pod 제거
$ kubectl delete pod/echo&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;&lt;b&gt;livenessProbe + readinessProbe&lt;/b&gt;&lt;/p&gt;
&lt;ul style=&quot;list-style-type: disc;&quot; data-ke-list-type=&quot;disc&quot;&gt;
&lt;li&gt;보통&amp;nbsp;livenessProbe와&amp;nbsp;readinessProbe를 같이 적용한다.&lt;/li&gt;
&lt;li&gt;상세한 설정은 애플리케이션 환경에 따라 적절하게 조정한다.&lt;/li&gt;
&lt;/ul&gt;
&lt;pre class=&quot;yaml&quot;&gt;&lt;code&gt;apiVersion: v1
kind: Pod
metadata:
  name: echo-health
  labels:
    app: echo
spec:
  containers:
    - name: app
      image: ghcr.io/subicura/echo:v1
      livenessProbe:
        httpGet:
          path: /
          port: 3000
      readinessProbe:
        httpGet:
          path: /
          port: 3000
&lt;/code&gt;&lt;/pre&gt;
&lt;hr contenteditable=&quot;false&quot; data-ke-type=&quot;horizontalRule&quot; data-ke-style=&quot;style7&quot; /&gt;
&lt;h4 data-ke-size=&quot;size20&quot;&gt;&lt;b&gt;다중 컨테이너&lt;/b&gt;&lt;/h4&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;pod은 여러개의 컨테이너를 가질 수 있다.&lt;/p&gt;
&lt;pre class=&quot;less&quot;&gt;&lt;code&gt;apiVersion: v1
kind: Pod
metadata:
  name: counter
  labels:
    app: counter
spec:
  containers:
    - name: app # app 컨테이너
      image: ghcr.io/subicura/counter:latest
      env:
        - name: REDIS_HOST
          value: &quot;localhost&quot;
    - name: db # DB 컨테이너
      image: redis
&lt;/code&gt;&lt;/pre&gt;
&lt;pre class=&quot;bash&quot; data-ke-language=&quot;bash&quot;&gt;&lt;code&gt;# Pod 생성
$ kubectl apply -f echo-pod.yml

# Pod 목록 조회
$ kubectl get pod

# ------
# 2개의 컨테이너가 실행됨.
NAME      READY   STATUS    RESTARTS   AGE
counter   2/2     Running   0          27s
# ------

# Pod 로그 확인
$ kubectl logs counter app
$ kubectl logs counter db

# 컨테이너 접속
$ kubectl exec -it counter -c app -- sh

$ curl localhost:3000 # 1씩 redis에 기록하여 증가함

# redis에 접근하여 데이터 확인
$ telnet localhost 6379 
GET count
quit

# Pod 제거
$ kubectl delete -f echo-pod.yml&lt;/code&gt;&lt;/pre&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;hr contenteditable=&quot;false&quot; data-ke-type=&quot;horizontalRule&quot; data-ke-style=&quot;style7&quot; /&gt;
&lt;h4 data-ke-size=&quot;size20&quot;&gt;&lt;b&gt;ReplicaSet(복제셋)&lt;/b&gt;&lt;/h4&gt;
&lt;ul style=&quot;list-style-type: disc;&quot; data-ke-list-type=&quot;disc&quot;&gt;
&lt;li&gt;Pod을 단독으로 만들면 Pod이 어떤 문제(서버가 죽어서 Pod이 사라졌다던가)가 생겼을 때 자동으로 복구되지 않는다.&lt;/li&gt;
&lt;li&gt;이러한 Pod을 정해진 수만큼 복제하고 관리하는 것이 ReplicaSet 이다.&lt;/li&gt;
&lt;/ul&gt;
&lt;pre class=&quot;less&quot;&gt;&lt;code&gt;apiVersion: apps/v1
kind: ReplicaSet
metadata:
  name: echo-rs
spec:
  replicas: 1
  selector:
    matchLabels:
      app: echo
      tier: app
  template:
    metadata:
      labels:
        app: echo
        tier: app
    spec:
      containers:
        - name: echo
          image: ghcr.io/subicura/echo:v1
&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;ReplicaSet은&amp;nbsp;label을 체크해서&amp;nbsp;원하는 수의 Pod이 없으면&amp;nbsp;새로운 Pod을 생성한다. 이를 설정으로 표현하면 다음과 같다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;b&gt;정의 설명&lt;/b&gt;&lt;/p&gt;
&lt;table style=&quot;border-collapse: collapse; width: 100%; height: 70px;&quot; border=&quot;1&quot; data-ke-align=&quot;alignLeft&quot;&gt;
&lt;tbody&gt;
&lt;tr style=&quot;height: 20px;&quot;&gt;
&lt;td style=&quot;height: 20px;&quot;&gt;spec.selector&lt;/td&gt;
&lt;td style=&quot;height: 20px;&quot;&gt;label 체크 조건&lt;/td&gt;
&lt;/tr&gt;
&lt;tr style=&quot;height: 20px;&quot;&gt;
&lt;td style=&quot;height: 20px;&quot;&gt;spec.replicas&lt;/td&gt;
&lt;td style=&quot;height: 20px;&quot;&gt;원하는 Pod의 개수&lt;/td&gt;
&lt;/tr&gt;
&lt;tr style=&quot;height: 20px;&quot;&gt;
&lt;td style=&quot;height: 20px;&quot;&gt;spec.template&lt;/td&gt;
&lt;td style=&quot;height: 20px;&quot;&gt;생성할 Pod의 명세&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;위 예시 코드를 바탕으로 해석하면 app: echo, tier: app이라는 pod이 없으면 template에 정의된 내용을 생성한다.&lt;/p&gt;
&lt;pre class=&quot;vala&quot;&gt;&lt;code&gt;# ReplicaSet 생성
$ kubectl apply -f echo-rs.yml

# 리소스 확인
$ kubectl get pod,replicaset

# ------
# ReplicaSet과 Pod이 함께 생성됨.
NAME                READY   STATUS    RESTARTS   AGE
pod/echo-rs-hm8tb   1/1     Running   0          57s

NAME                      DESIRED   CURRENT   READY   AGE
replicaset.apps/echo-rs   1         1         1       57s
# ------- 

# 라벨 확인
$ kubectl get pod --show-labels
# --------
NAME            READY   STATUS    RESTARTS   AGE     LABELS
echo-rs-hm8tb   1/1     Running   0          7m25s   app=echo,tier=app
# ---------

# app의 라벨을 제거
$ kubectl label pod/echo-rs-hm8tm app-

$ kubectl get pod --show-labels # 새로운 pod이 생성된다.
&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;b&gt;스케일 아웃&lt;/b&gt;&lt;/p&gt;
&lt;ul style=&quot;list-style-type: disc;&quot; data-ke-list-type=&quot;disc&quot;&gt;
&lt;li&gt;replicas: 1 을 replicas: 4 으로 변경&lt;/li&gt;
&lt;li&gt;레플리카 셋은 label 단위로 확인하기 때문에 label이 겹치지 않게 정의 해야 한다.&lt;/li&gt;
&lt;/ul&gt;
&lt;pre class=&quot;less&quot;&gt;&lt;code&gt;apiVersion: apps/v1
kind: ReplicaSet
metadata:
  name: echo-rs
spec:
  replicas: 4
  selector:
    matchLabels:
      app: echo
      tier: app
  template:
    metadata:
      labels:
        app: echo
        tier: app
    spec:
      containers:
        - name: echo
          image: ghcr.io/subicura/echo:v1
&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;hr contenteditable=&quot;false&quot; data-ke-type=&quot;horizontalRule&quot; data-ke-style=&quot;style7&quot; /&gt;
&lt;h4 data-ke-size=&quot;size20&quot;&gt;&lt;b&gt;참고 사이트&lt;/b&gt;&lt;/h4&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;ul style=&quot;list-style-type: disc;&quot; data-ke-list-type=&quot;disc&quot;&gt;
&lt;li&gt;&lt;a href=&quot;https://subicura.com/k8s/guide/pod.html#&quot;&gt;https://subicura.com/k8s/guide/pod.html#&lt;/a&gt;&lt;a href=&quot;https://subicura.com/k8s/guide/pod.html#%E1%84%8F%E1%85%A5%E1%86%AB%E1%84%90%E1%85%A6%E1%84%8B%E1%85%B5%E1%84%82%E1%85%A5-%E1%84%89%E1%85%A1%E1%86%BC%E1%84%90%E1%85%A2-%E1%84%86%E1%85%A9%E1%84%82%E1%85%B5%E1%84%90%E1%85%A5%E1%84%85%E1%85%B5%E1%86%BC&quot;&gt;컨테이너-상태-모니터링&lt;/a&gt;&lt;/li&gt;
&lt;/ul&gt;
&lt;hr contenteditable=&quot;false&quot; data-ke-type=&quot;horizontalRule&quot; data-ke-style=&quot;style7&quot; /&gt;</description>
      <category>System/Server</category>
      <category>kubernetes</category>
      <category>pod</category>
      <author>dev.parkjh</author>
      <guid isPermaLink="true">https://developerjaypark.tistory.com/110</guid>
      <comments>https://developerjaypark.tistory.com/110#entry110comment</comments>
      <pubDate>Mon, 22 Jul 2024 00:44:31 +0900</pubDate>
    </item>
    <item>
      <title>[Kube] Kubernetes - kubectl 명령어 (4)</title>
      <link>https://developerjaypark.tistory.com/109</link>
      <description>&lt;p&gt;&lt;figure class=&quot;imageblock alignCenter&quot; data-ke-mobileStyle=&quot;widthOrigin&quot; data-origin-width=&quot;1140&quot; data-origin-height=&quot;474&quot;&gt;&lt;span data-url=&quot;https://blog.kakaocdn.net/dn/bFVHRo/btsID5ZiFf3/raPPj3UdWt20yE3vC0knf1/img.png&quot; data-phocus=&quot;https://blog.kakaocdn.net/dn/bFVHRo/btsID5ZiFf3/raPPj3UdWt20yE3vC0knf1/img.png&quot;&gt;&lt;img src=&quot;https://blog.kakaocdn.net/dn/bFVHRo/btsID5ZiFf3/raPPj3UdWt20yE3vC0knf1/img.png&quot; srcset=&quot;https://img1.daumcdn.net/thumb/R1280x0/?scode=mtistory2&amp;fname=https%3A%2F%2Fblog.kakaocdn.net%2Fdn%2FbFVHRo%2FbtsID5ZiFf3%2FraPPj3UdWt20yE3vC0knf1%2Fimg.png&quot; onerror=&quot;this.onerror=null; this.src='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png'; this.srcset='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png';&quot; loading=&quot;lazy&quot; width=&quot;1140&quot; height=&quot;474&quot; data-origin-width=&quot;1140&quot; data-origin-height=&quot;474&quot;/&gt;&lt;/span&gt;&lt;/figure&gt;
&lt;/p&gt;
&lt;p style=&quot;text-align: center;&quot; data-ke-size=&quot;size16&quot;&gt;&lt;span style=&quot;background-color: #ffffff; color: #555555; text-align: center;&quot;&gt;본 게시글은 인프런&lt;span&gt;&amp;nbsp;&lt;/span&gt;&lt;/span&gt;&lt;b&gt;subicura&lt;/b&gt;&lt;span style=&quot;background-color: #ffffff; color: #555555; text-align: center;&quot;&gt;&lt;span&gt;&amp;nbsp;&lt;/span&gt;강사님의&lt;span&gt;&amp;nbsp;&lt;/span&gt;&lt;/span&gt;&lt;span style=&quot;background-color: #ffffff; color: #555555; text-align: center;&quot; data-token-index=&quot;1&quot;&gt;&lt;b&gt;초보를 위한 쿠버네티스 안내서&lt;/b&gt;&lt;span&gt;&amp;nbsp;&lt;/span&gt;강의 수강 후 작성한 내용입니다.&lt;/span&gt;&lt;/p&gt;
&lt;p data-ke-size=&quot;size18&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;hr contenteditable=&quot;false&quot; data-ke-type=&quot;horizontalRule&quot; data-ke-style=&quot;style7&quot; /&gt;
&lt;p data-ke-size=&quot;size18&quot;&gt;&lt;b&gt;apply &lt;/b&gt;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;: 원하는 상태를 적용한다. 보통&amp;nbsp;-f&amp;nbsp;옵션으로 파일과 함께 사용한다.&lt;/p&gt;
&lt;pre id=&quot;code_1721316209578&quot; class=&quot;bash&quot; data-ke-language=&quot;bash&quot; data-ke-type=&quot;codeblock&quot;&gt;&lt;code&gt;$ kubectl apply -f [파일명 또는 URL]&lt;/code&gt;&lt;/pre&gt;
&lt;ul style=&quot;list-style-type: disc;&quot; data-ke-list-type=&quot;disc&quot;&gt;
&lt;li&gt;-o : 출력 형태 변경 (wide, yaml, json)&lt;/li&gt;
&lt;li&gt;&amp;mdash;show-labels: 레이블 확인&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;size18&quot;&gt;&lt;b&gt;get&lt;/b&gt;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;: 리소스 목록을 보여준다. get pod, service 와 같이 여러 TYPE 입력 가능하다.&lt;/p&gt;
&lt;pre id=&quot;code_1721316246040&quot; class=&quot;bash&quot; data-ke-language=&quot;bash&quot; data-ke-type=&quot;codeblock&quot;&gt;&lt;code&gt;$ kubectl get [TYPE]&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;size18&quot;&gt;&lt;b&gt;describe&lt;/b&gt;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;: 리소스의 상태를 자세하게 보여준다.&lt;/p&gt;
&lt;pre id=&quot;code_1721316261891&quot; class=&quot;bash&quot; data-ke-language=&quot;bash&quot; data-ke-type=&quot;codeblock&quot;&gt;&lt;code&gt;$ kubectl describe [TYPE]/[NAME] 또는 [TYPE][NAME]&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;size18&quot;&gt;&lt;b&gt;delete&lt;/b&gt;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;: 리소스를 제거한다.&lt;/p&gt;
&lt;pre id=&quot;code_1721316273092&quot; class=&quot;bash&quot; data-ke-language=&quot;bash&quot; data-ke-type=&quot;codeblock&quot;&gt;&lt;code&gt;$ kubectl delete [TYPE]/[NAME] 또는 [TYPE][NAME]&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;size18&quot;&gt;&lt;b&gt;logs&lt;/b&gt;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;: 컨테이너 로그 확인&lt;/p&gt;
&lt;pre id=&quot;code_1721316284872&quot; class=&quot;bash&quot; data-ke-language=&quot;bash&quot; data-ke-type=&quot;codeblock&quot;&gt;&lt;code&gt;$ kubectl logs [POD_NAME]&lt;/code&gt;&lt;/pre&gt;
&lt;ul style=&quot;list-style-type: disc;&quot; data-ke-list-type=&quot;disc&quot;&gt;
&lt;li&gt;-f : 실시간으로 확인&lt;/li&gt;
&lt;li&gt;-c : pod에 여러개의 컨테이너가 있는 경우 컨테이너 지정&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;size18&quot;&gt;&lt;b&gt;exec&lt;/b&gt;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;: 컨테이너에 명령어를 전달. 컨테이너에 접근할 때 주로 사용함.&lt;/p&gt;
&lt;pre id=&quot;code_1721316301740&quot; class=&quot;bash&quot; data-ke-language=&quot;bash&quot; data-ke-type=&quot;codeblock&quot;&gt;&lt;code&gt;$ kubectl exec [-it] [POD_NAME] &amp;mdash; [COMMAND]&lt;/code&gt;&lt;/pre&gt;
&lt;ul style=&quot;list-style-type: disc;&quot; data-ke-list-type=&quot;disc&quot;&gt;
&lt;li&gt;-c : pod에 여러개의 컨테이너가 있는 경우 컨테이너 지정&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;size18&quot;&gt;&lt;b&gt;config&lt;/b&gt;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;: kubectl 설정 관리. 관리하고 있는 쿠버네티스가 여러개일 때,&amp;nbsp; 현재 컨텍스트를 확인하고 특정 컨텍스트로 변경 할 때&lt;/p&gt;
&lt;pre id=&quot;code_1721316330792&quot; class=&quot;bash&quot; data-ke-language=&quot;bash&quot; data-ke-type=&quot;codeblock&quot;&gt;&lt;code&gt;$ kubectl config current-context
$ kubectl config use-context minikube&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;size18&quot;&gt;&lt;b&gt;기타&lt;/b&gt;&lt;/p&gt;
&lt;pre id=&quot;code_1721316345255&quot; class=&quot;bash&quot; data-ke-language=&quot;bash&quot; data-ke-type=&quot;codeblock&quot;&gt;&lt;code&gt;$ kubectl api-resources # 전체 오브젝트 종류 확인
$ kubectl explain pod # 특정 오브젝트 설명 보기&lt;/code&gt;&lt;/pre&gt;
&lt;hr contenteditable=&quot;false&quot; data-ke-type=&quot;horizontalRule&quot; data-ke-style=&quot;style7&quot; /&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;</description>
      <category>System/Server</category>
      <category>kubernetes</category>
      <author>dev.parkjh</author>
      <guid isPermaLink="true">https://developerjaypark.tistory.com/109</guid>
      <comments>https://developerjaypark.tistory.com/109#entry109comment</comments>
      <pubDate>Fri, 19 Jul 2024 00:25:56 +0900</pubDate>
    </item>
  </channel>
</rss>