<?xml version="1.0" encoding="UTF-8"?>
<rss version="2.0">
  <channel>
    <title>flowlog</title>
    <link>https://flowlog.tistory.com/</link>
    <description>개발, 엔지니어링, 클라우드 IT 정리
*배움에는 끝이 없다*</description>
    <language>ko</language>
    <pubDate>Tue, 30 Jun 2026 16:50:05 +0900</pubDate>
    <generator>TISTORY</generator>
    <ttl>100</ttl>
    <managingEditor>joon95</managingEditor>
    <image>
      <title>flowlog</title>
      <url>https://tistory1.daumcdn.net/tistory/5500116/attach/d9876e9dc1f5467e881992f0e0c06d49</url>
      <link>https://flowlog.tistory.com</link>
    </image>
    <item>
      <title>[Spring AI] 4편: MCP 서버 구동과 실행</title>
      <link>https://flowlog.tistory.com/121</link>
      <description>&lt;h2 data-ke-size=&quot;size26&quot;&gt;MCP 서버 구동 방식&lt;/h2&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;MCP 연동을 통해 제일 먼저 고민한건 MCP서버를 어디에 둘까?였다.&lt;/p&gt;
&lt;blockquote data-ke-style=&quot;style1&quot;&gt;&lt;span style=&quot;font-family: 'Noto Serif KR';&quot;&gt;stdio vs streamable-http&lt;/span&gt;&lt;/blockquote&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;먼저 stdio 방식은 해당프로세스가 자식프로세스로 mcp 서버를 관리하는 것으로 별도의 네트워크 이슈가 발생하지 않는 장점이 있지만, mcp 프로세스의 메모리점유율이 높아질 경우 해당 서버의 모든 프로세스에 영향이 올 수 있으므로 Enterprise 환경에서 맞지 않다. 그래서 별도로 MCP Server를 가지고 API로 서비스할 수 있는 기능을 활용해야한다. 바로 Streamable-http 방식인데 25년3월부터 해당 방식이 등장했다.(그전에는 sse 방식을 지원함)&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;b&gt;결론부터 말하자면 두가지 방식을 다 테스트 해봄~&lt;/b&gt;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;Streamable-http : &lt;span style=&quot;color: #333333; text-align: start;&quot;&gt;Google MCP&lt;/span&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;amp;조회&lt;/li&gt;
&lt;li&gt;추가로 gmail 을 여는 방법으로 아주 쉽게 기능 확장을 경험&lt;/li&gt;
&lt;/ul&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;stdio : Notion MCP&lt;/p&gt;
&lt;ul style=&quot;list-style-type: disc;&quot; data-ke-list-type=&quot;disc&quot;&gt;
&lt;li&gt;LLM, 다른 에이전트에서 정리된 데이터를 Notion 정리하기 위함&lt;/li&gt;
&lt;/ul&gt;
&lt;h2 data-ke-size=&quot;size26&quot;&gt;Google MCP Server&lt;/h2&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;Streamable-http 방식으로 구글MCP 를 올리는 방법에서 삽질을 많이 했다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;그냥 google-mcp 면 다 되는 줄 알고 .. ^^&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;github 에 올라온 많은 google-mcp 중에 streamable-http 을 지원하도록 FastAPI 로 개발된 repository를 발견하고 잘 올렸다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;figure id=&quot;og_1773990449469&quot; contenteditable=&quot;false&quot; data-ke-type=&quot;opengraph&quot; data-ke-align=&quot;alignCenter&quot; data-og-type=&quot;object&quot; data-og-title=&quot;GitHub - taylorwilsdon/google_workspace_mcp: Control Gmail, Google Calendar, Docs, Sheets, Slides, Chat, Forms, Tasks, Search &amp;amp; &quot; data-og-description=&quot;Control Gmail, Google Calendar, Docs, Sheets, Slides, Chat, Forms, Tasks, Search &amp;amp; Drive with AI - Comprehensive Google Workspace / G Suite MCP Server &amp;amp; CLI Tool - taylorwilsdon/google_work...&quot; data-og-host=&quot;github.com&quot; data-og-source-url=&quot;https://github.com/taylorwilsdon/google_workspace_mcp&quot; data-og-url=&quot;https://github.com/taylorwilsdon/google_workspace_mcp&quot; data-og-image=&quot;https://scrap.kakaocdn.net/dn/AWTJO/dJMb9aKEiqr/zeUafpAAwdJr2JmdgRFuCK/img.png?width=1280&amp;amp;height=640&amp;amp;face=0_0_1280_640,https://scrap.kakaocdn.net/dn/G0jMG/dJMb9frEDuM/1mhKCBbvKFIE61UEDjZcNk/img.png?width=1280&amp;amp;height=640&amp;amp;face=0_0_1280_640,https://scrap.kakaocdn.net/dn/zKumK/dJMb82MCgnk/qF5mcyFkqTjKkv9pIJXKq1/img.png?width=1684&amp;amp;height=2058&amp;amp;face=0_0_1684_2058&quot;&gt;&lt;a href=&quot;https://github.com/taylorwilsdon/google_workspace_mcp&quot; target=&quot;_blank&quot; rel=&quot;noopener&quot; data-source-url=&quot;https://github.com/taylorwilsdon/google_workspace_mcp&quot;&gt;
&lt;div class=&quot;og-image&quot; style=&quot;background-image: url('https://scrap.kakaocdn.net/dn/AWTJO/dJMb9aKEiqr/zeUafpAAwdJr2JmdgRFuCK/img.png?width=1280&amp;amp;height=640&amp;amp;face=0_0_1280_640,https://scrap.kakaocdn.net/dn/G0jMG/dJMb9frEDuM/1mhKCBbvKFIE61UEDjZcNk/img.png?width=1280&amp;amp;height=640&amp;amp;face=0_0_1280_640,https://scrap.kakaocdn.net/dn/zKumK/dJMb82MCgnk/qF5mcyFkqTjKkv9pIJXKq1/img.png?width=1684&amp;amp;height=2058&amp;amp;face=0_0_1684_2058');&quot;&gt;&amp;nbsp;&lt;/div&gt;
&lt;div class=&quot;og-text&quot;&gt;
&lt;p class=&quot;og-title&quot; data-ke-size=&quot;size16&quot;&gt;GitHub - taylorwilsdon/google_workspace_mcp: Control Gmail, Google Calendar, Docs, Sheets, Slides, Chat, Forms, Tasks, Search &amp;amp;&lt;/p&gt;
&lt;p class=&quot;og-desc&quot; data-ke-size=&quot;size16&quot;&gt;Control Gmail, Google Calendar, Docs, Sheets, Slides, Chat, Forms, Tasks, Search &amp;amp; Drive with AI - Comprehensive Google Workspace / G Suite MCP Server &amp;amp; CLI Tool - taylorwilsdon/google_work...&lt;/p&gt;
&lt;p class=&quot;og-host&quot; data-ke-size=&quot;size16&quot;&gt;github.com&lt;/p&gt;
&lt;/div&gt;
&lt;/a&gt;&lt;/figure&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;GCP 에서 프로젝트를 생성하고 client, client-sercret 을 발급하여 .env 파일에 잘 적는다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;그리고 내가 사용할 tools 를 선택하여 사용할 수 있다.&lt;/p&gt;
&lt;pre id=&quot;code_1773990514001&quot; class=&quot;java&quot; data-ke-language=&quot;java&quot; data-ke-type=&quot;codeblock&quot;&gt;&lt;code&gt;main.py --transport streamable-http --tools calendar&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;&lt;figure class=&quot;imageblock alignCenter&quot; data-ke-mobileStyle=&quot;widthOrigin&quot; data-origin-width=&quot;1102&quot; data-origin-height=&quot;818&quot;&gt;&lt;span data-url=&quot;https://blog.kakaocdn.net/dn/bS9xjx/dJMcaflC8RV/nZdkWpaNIelFCUm5MBIAU0/img.png&quot; data-phocus=&quot;https://blog.kakaocdn.net/dn/bS9xjx/dJMcaflC8RV/nZdkWpaNIelFCUm5MBIAU0/img.png&quot;&gt;&lt;img src=&quot;https://blog.kakaocdn.net/dn/bS9xjx/dJMcaflC8RV/nZdkWpaNIelFCUm5MBIAU0/img.png&quot; srcset=&quot;https://img1.daumcdn.net/thumb/R1280x0/?scode=mtistory2&amp;fname=https%3A%2F%2Fblog.kakaocdn.net%2Fdn%2FbS9xjx%2FdJMcaflC8RV%2FnZdkWpaNIelFCUm5MBIAU0%2Fimg.png&quot; onerror=&quot;this.onerror=null; this.src='//t1.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;818&quot; data-origin-width=&quot;1102&quot; data-origin-height=&quot;818&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;MCP-Inspector&lt;/h3&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;MCP 서버가 정상인지 체크하기 위해 위 프로그램을 실행&lt;/p&gt;
&lt;pre id=&quot;code_1773990598571&quot; class=&quot;java&quot; data-ke-language=&quot;java&quot; data-ke-type=&quot;codeblock&quot;&gt;&lt;code&gt;npx @modelcontextprotocol/inspector&lt;/code&gt;&lt;/pre&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;웹페이지가 하나 뜨는데 아래와 같이 입력하고 Connect 해보면 Tools에 LLM이 사용할 수 있는 목록이 나온다.&lt;/p&gt;
&lt;p&gt;&lt;figure class=&quot;imageblock alignCenter&quot; data-ke-mobileStyle=&quot;widthOrigin&quot; data-origin-width=&quot;1253&quot; data-origin-height=&quot;945&quot;&gt;&lt;span data-url=&quot;https://blog.kakaocdn.net/dn/yZzi6/dJMcaibBtGW/QfPIHxmoLvg4qcFkJVXrpk/img.png&quot; data-phocus=&quot;https://blog.kakaocdn.net/dn/yZzi6/dJMcaibBtGW/QfPIHxmoLvg4qcFkJVXrpk/img.png&quot;&gt;&lt;img src=&quot;https://blog.kakaocdn.net/dn/yZzi6/dJMcaibBtGW/QfPIHxmoLvg4qcFkJVXrpk/img.png&quot; srcset=&quot;https://img1.daumcdn.net/thumb/R1280x0/?scode=mtistory2&amp;fname=https%3A%2F%2Fblog.kakaocdn.net%2Fdn%2FyZzi6%2FdJMcaibBtGW%2FQfPIHxmoLvg4qcFkJVXrpk%2Fimg.png&quot; onerror=&quot;this.onerror=null; this.src='//t1.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;1253&quot; height=&quot;945&quot; data-origin-width=&quot;1253&quot; data-origin-height=&quot;945&quot;/&gt;&lt;/span&gt;&lt;/figure&gt;
&lt;/p&gt;
&lt;h3 data-ke-size=&quot;size23&quot;&gt;SpringAI코드&lt;/h3&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;MCP를 연결하려면 Transport 모듈을 사용해야하는데 별도의 패키지를 써야한다.&lt;/p&gt;
&lt;pre id=&quot;code_1773990686263&quot; class=&quot;java&quot; data-ke-language=&quot;java&quot; data-ke-type=&quot;codeblock&quot;&gt;&lt;code&gt;implementation 'org.springframework.ai:spring-ai-starter-mcp-client-webflux'
implementation 'io.modelcontextprotocol.sdk:mcp:1.1.0'&lt;/code&gt;&lt;/pre&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;pre id=&quot;code_1773990772329&quot; class=&quot;java&quot; data-ke-language=&quot;java&quot; data-ke-type=&quot;codeblock&quot;&gt;&lt;code&gt;spring:
  ai:
    mcp:
      client:
        enabled: true
        initialized: true
        type: ASYNC
        request-timeout: 2s
        toolcallback:
          enabled: true
        streamable-http:
          connections:
            google-calendar:
              url: http://localhost:8000
              endpoint: /mcp
  jackson:
    default-property-inclusion: non_null&lt;/code&gt;&lt;/pre&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;mcp client 는 yml autoconfiguration 이 정상적으로 잘 되었다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;근데 google 연동에서 default-user 를 계속 찾아다녀서 spring.jackson.default-property-inclusion: non_null 을 넣어주었다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;이제 ToolCallbackProvider 객체를 보면 google mcp tools 를 모두 볼 수 있게된다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;h3 data-ke-size=&quot;size23&quot;&gt;구글캘린더 결과&lt;/h3&gt;
&lt;p&gt;&lt;figure class=&quot;imageblock alignCenter&quot; data-ke-mobileStyle=&quot;widthOrigin&quot; data-origin-width=&quot;1821&quot; data-origin-height=&quot;877&quot;&gt;&lt;span data-url=&quot;https://blog.kakaocdn.net/dn/bCz1i2/dJMcacWIKk3/dpiZENnUqj4KkUy30qXDF0/img.png&quot; data-phocus=&quot;https://blog.kakaocdn.net/dn/bCz1i2/dJMcacWIKk3/dpiZENnUqj4KkUy30qXDF0/img.png&quot;&gt;&lt;img src=&quot;https://blog.kakaocdn.net/dn/bCz1i2/dJMcacWIKk3/dpiZENnUqj4KkUy30qXDF0/img.png&quot; srcset=&quot;https://img1.daumcdn.net/thumb/R1280x0/?scode=mtistory2&amp;fname=https%3A%2F%2Fblog.kakaocdn.net%2Fdn%2FbCz1i2%2FdJMcacWIKk3%2FdpiZENnUqj4KkUy30qXDF0%2Fimg.png&quot; onerror=&quot;this.onerror=null; this.src='//t1.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;1821&quot; height=&quot;877&quot; data-origin-width=&quot;1821&quot; data-origin-height=&quot;877&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;/h3&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;위에서 설명했듯이 이제 서비스만 [사용] 해주면 구글의 모든 서비스를 LLM 이 이용할 수 있다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;그래서 바로 Gmail 을 활성화 해보았다.&lt;/p&gt;
&lt;p&gt;&lt;figure class=&quot;imageblock alignCenter&quot; data-ke-mobileStyle=&quot;widthOrigin&quot; data-origin-width=&quot;568&quot; data-origin-height=&quot;318&quot;&gt;&lt;span data-url=&quot;https://blog.kakaocdn.net/dn/AWmyf/dJMcagx2ZON/jQl9bHoMsKUOGdT7yOCIA1/img.png&quot; data-phocus=&quot;https://blog.kakaocdn.net/dn/AWmyf/dJMcagx2ZON/jQl9bHoMsKUOGdT7yOCIA1/img.png&quot;&gt;&lt;img src=&quot;https://blog.kakaocdn.net/dn/AWmyf/dJMcagx2ZON/jQl9bHoMsKUOGdT7yOCIA1/img.png&quot; srcset=&quot;https://img1.daumcdn.net/thumb/R1280x0/?scode=mtistory2&amp;fname=https%3A%2F%2Fblog.kakaocdn.net%2Fdn%2FAWmyf%2FdJMcagx2ZON%2FjQl9bHoMsKUOGdT7yOCIA1%2Fimg.png&quot; onerror=&quot;this.onerror=null; this.src='//t1.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;568&quot; height=&quot;318&quot; data-origin-width=&quot;568&quot; data-origin-height=&quot;318&quot;/&gt;&lt;/span&gt;&lt;/figure&gt;
&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;그리고 메일함 내용 검색!!&lt;/p&gt;
&lt;p&gt;&lt;figure class=&quot;imageblock alignCenter&quot; data-ke-mobileStyle=&quot;widthOrigin&quot; data-origin-width=&quot;939&quot; data-origin-height=&quot;650&quot;&gt;&lt;span data-url=&quot;https://blog.kakaocdn.net/dn/bDZfSH/dJMcacCrNRB/JM0zlPK8DWsJgsqILwn9V1/img.png&quot; data-phocus=&quot;https://blog.kakaocdn.net/dn/bDZfSH/dJMcacCrNRB/JM0zlPK8DWsJgsqILwn9V1/img.png&quot;&gt;&lt;img src=&quot;https://blog.kakaocdn.net/dn/bDZfSH/dJMcacCrNRB/JM0zlPK8DWsJgsqILwn9V1/img.png&quot; srcset=&quot;https://img1.daumcdn.net/thumb/R1280x0/?scode=mtistory2&amp;fname=https%3A%2F%2Fblog.kakaocdn.net%2Fdn%2FbDZfSH%2FdJMcacCrNRB%2FJM0zlPK8DWsJgsqILwn9V1%2Fimg.png&quot; onerror=&quot;this.onerror=null; this.src='//t1.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;939&quot; height=&quot;650&quot; data-origin-width=&quot;939&quot; data-origin-height=&quot;650&quot;/&gt;&lt;/span&gt;&lt;/figure&gt;
&lt;/p&gt;
&lt;h2 data-ke-size=&quot;size26&quot;&gt;Notion MCP&lt;/h2&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;이제 MCP에 자신이 생겼다, stdio 모드로 해보자!!&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;spring mcp client stdio 에 적어주면 스프링 프로세스가 기동되면서 같이 실행된다.&lt;/p&gt;
&lt;pre id=&quot;code_1773991196402&quot; class=&quot;java&quot; data-ke-language=&quot;java&quot; data-ke-type=&quot;codeblock&quot;&gt;&lt;code&gt;spring:
  ai:
    mcp:
      client:
        enabled: true
        initialized: true
        type: ASYNC
        request-timeout: 5s
        toolcallback:
          enabled: true
        stdio:
          connections:
            notion:
              command: C:\\Program Files\\nodejs\\npx.cmd # windows 명령어 위치, linux는 npx 바로 써도됨
              args:
                - &quot;-y&quot;
                - &quot;@notionhq/notion-mcp-server&quot;
              env:
                NOTION_TOKEN: ${NOTION_API_KEY}
                # 핵심: npx가 내부적으로 node를 찾을 수 있도록 PATH를 직접 넘겨줍니다.
                PATH: &quot;C:\\Program Files\\nodejs\\;%PATH%&quot;&lt;/code&gt;&lt;/pre&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;window 에서 실행하느라 명령어 위치를 찾고 node 위치를 알려주기 위해 작업&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;h3 data-ke-size=&quot;size23&quot;&gt;접근토큰&lt;/h3&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;notion은 ntn 으로 시작하는 access-token을 받아야함&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;그래서 application 에서 callback url 을 받을 수 있도록 했음&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;- resources/templates/notion-auth-result.html&lt;/p&gt;
&lt;p&gt;&lt;figure class=&quot;imageblock alignCenter&quot; data-ke-mobileStyle=&quot;widthOrigin&quot; data-origin-width=&quot;517&quot; data-origin-height=&quot;326&quot;&gt;&lt;span data-url=&quot;https://blog.kakaocdn.net/dn/xtrXf/dJMcahqdFwf/27Nc0sfRLKYvGK5KedWiPk/img.png&quot; data-phocus=&quot;https://blog.kakaocdn.net/dn/xtrXf/dJMcahqdFwf/27Nc0sfRLKYvGK5KedWiPk/img.png&quot;&gt;&lt;img src=&quot;https://blog.kakaocdn.net/dn/xtrXf/dJMcahqdFwf/27Nc0sfRLKYvGK5KedWiPk/img.png&quot; srcset=&quot;https://img1.daumcdn.net/thumb/R1280x0/?scode=mtistory2&amp;fname=https%3A%2F%2Fblog.kakaocdn.net%2Fdn%2FxtrXf%2FdJMcahqdFwf%2F27Nc0sfRLKYvGK5KedWiPk%2Fimg.png&quot; onerror=&quot;this.onerror=null; this.src='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png'; this.srcset='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png';&quot; loading=&quot;lazy&quot; width=&quot;517&quot; height=&quot;326&quot; data-origin-width=&quot;517&quot; data-origin-height=&quot;326&quot;/&gt;&lt;/span&gt;&lt;/figure&gt;
&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;그리고 위 코드로 postman 을 통해 oauth 2.0 인증을 해 ntn_ 토큰을 가져왔다.&lt;/p&gt;
&lt;p&gt;&lt;figure class=&quot;imageblock alignCenter&quot; data-ke-mobileStyle=&quot;widthOrigin&quot; data-origin-width=&quot;711&quot; data-origin-height=&quot;865&quot;&gt;&lt;span data-url=&quot;https://blog.kakaocdn.net/dn/btOa1Y/dJMcafsnKcu/NhNoGziF9RWsdEz9uHtSPK/img.png&quot; data-phocus=&quot;https://blog.kakaocdn.net/dn/btOa1Y/dJMcafsnKcu/NhNoGziF9RWsdEz9uHtSPK/img.png&quot;&gt;&lt;img src=&quot;https://blog.kakaocdn.net/dn/btOa1Y/dJMcafsnKcu/NhNoGziF9RWsdEz9uHtSPK/img.png&quot; srcset=&quot;https://img1.daumcdn.net/thumb/R1280x0/?scode=mtistory2&amp;fname=https%3A%2F%2Fblog.kakaocdn.net%2Fdn%2FbtOa1Y%2FdJMcafsnKcu%2FNhNoGziF9RWsdEz9uHtSPK%2Fimg.png&quot; onerror=&quot;this.onerror=null; this.src='//t1.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;711&quot; height=&quot;865&quot; data-origin-width=&quot;711&quot; data-origin-height=&quot;865&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;마찬가지로 mcp-inspector 로 연결체크!!&lt;/p&gt;
&lt;p&gt;&lt;figure class=&quot;imageblock alignCenter&quot; data-ke-mobileStyle=&quot;widthOrigin&quot; data-origin-width=&quot;1493&quot; data-origin-height=&quot;887&quot;&gt;&lt;span data-url=&quot;https://blog.kakaocdn.net/dn/dDSZen/dJMcahwZzap/AOd3okOlrEnf6Mv0Cg3Iuk/img.png&quot; data-phocus=&quot;https://blog.kakaocdn.net/dn/dDSZen/dJMcahwZzap/AOd3okOlrEnf6Mv0Cg3Iuk/img.png&quot;&gt;&lt;img src=&quot;https://blog.kakaocdn.net/dn/dDSZen/dJMcahwZzap/AOd3okOlrEnf6Mv0Cg3Iuk/img.png&quot; srcset=&quot;https://img1.daumcdn.net/thumb/R1280x0/?scode=mtistory2&amp;fname=https%3A%2F%2Fblog.kakaocdn.net%2Fdn%2FdDSZen%2FdJMcahwZzap%2FAOd3okOlrEnf6Mv0Cg3Iuk%2Fimg.png&quot; onerror=&quot;this.onerror=null; this.src='//t1.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;1493&quot; height=&quot;887&quot; data-origin-width=&quot;1493&quot; data-origin-height=&quot;887&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;문제: user_id 를 자꾸 찾음&lt;/h3&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;아니 자꾸 user_id 를 입력하라는거야. 그래서 mcp-inspector 로 get-users tool을 요청해서 내 id를 알아냈고, 프롬프트에 해당 값을 박아놨음&lt;/p&gt;
&lt;pre id=&quot;code_1773991707561&quot; class=&quot;java&quot; data-ke-language=&quot;java&quot; data-ke-type=&quot;codeblock&quot;&gt;&lt;code&gt;당신은 노션 관리 에이전트입니다
user_id 값은 3264d4d8-7956-810a-8939-0027b6fd1837 이거야.
추가로, '노션 AI 전용공간'이라는 이름의 페이지를 먼저 검색(search)한 뒤, 그곳에 페이지를 생성하세요.
사용자 식별 정보 없이 페이지 내용만 생성하세요.

당신은 노션 시인입니다. 다음 순서를 엄격히 지키세요:
1. 'search' 도구로 '노션 AI 전용공간' 페이지 ID를 찾는다.
2. 'create_page' 도구로 해당 공간 아래에 제목을 정해 페이지를 만든다.
3. 위에서 생성된 'new_page_id'를 사용하여 'append_block_children' 도구로 시 본문을 작성한다.
모든 단계가 끝나기 전에는 답변을 종료하지 마세요.&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;/h3&gt;
&lt;p&gt;&lt;figure class=&quot;imageblock alignCenter&quot; data-ke-mobileStyle=&quot;widthOrigin&quot; data-origin-width=&quot;1211&quot; data-origin-height=&quot;508&quot;&gt;&lt;span data-url=&quot;https://blog.kakaocdn.net/dn/c4OWhi/dJMcagERt9j/GXdP0W03i97wSeyzwaXKjk/img.png&quot; data-phocus=&quot;https://blog.kakaocdn.net/dn/c4OWhi/dJMcagERt9j/GXdP0W03i97wSeyzwaXKjk/img.png&quot;&gt;&lt;img src=&quot;https://blog.kakaocdn.net/dn/c4OWhi/dJMcagERt9j/GXdP0W03i97wSeyzwaXKjk/img.png&quot; srcset=&quot;https://img1.daumcdn.net/thumb/R1280x0/?scode=mtistory2&amp;fname=https%3A%2F%2Fblog.kakaocdn.net%2Fdn%2Fc4OWhi%2FdJMcagERt9j%2FGXdP0W03i97wSeyzwaXKjk%2Fimg.png&quot; onerror=&quot;this.onerror=null; this.src='//t1.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;1211&quot; height=&quot;508&quot; data-origin-width=&quot;1211&quot; data-origin-height=&quot;508&quot;/&gt;&lt;/span&gt;&lt;/figure&gt;
&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p style=&quot;color: #333333; text-align: start;&quot; data-ke-size=&quot;size16&quot;&gt;&lt;a style=&quot;background-color: #e6f5ff; color: #0070d1;&quot; href=&quot;https://flowlog.tistory.com/118&quot;&gt;[Spring AI] 1편: 토이 프로젝트 개요&lt;/a&gt;&lt;span&gt;&amp;nbsp;&lt;/span&gt;&lt;/p&gt;
&lt;p style=&quot;color: #333333; text-align: start;&quot; data-ke-size=&quot;size16&quot;&gt;&lt;a style=&quot;color: #0070d1;&quot; href=&quot;https://flowlog.tistory.com/119&quot;&gt;[Spring AI] 2편: RAG 문서 임베딩&lt;/a&gt;&lt;/p&gt;
&lt;p style=&quot;color: #333333; text-align: start;&quot; data-ke-size=&quot;size16&quot;&gt;&lt;a style=&quot;color: #0070d1;&quot; href=&quot;https://flowlog.tistory.com/120&quot;&gt;&lt;span style=&quot;color: #333333; text-align: start;&quot;&gt;[Spring AI]&lt;span&gt;&lt;span&gt;&amp;nbsp;&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;3편: Database 쿼리생성 및 실행을 위한 HITL(Human in the Loop)&lt;/a&gt;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;figure id=&quot;og_1773988001790&quot; contenteditable=&quot;false&quot; data-ke-type=&quot;opengraph&quot; data-ke-align=&quot;alignCenter&quot; data-og-type=&quot;object&quot; data-og-title=&quot;GitHub - joonhyeok95/spring-ai-google-gen: 제미나이 무료API로 구현하는 PDF RAG, 데이터베이스 추출, LLM 서비&quot; data-og-description=&quot;제미나이 무료API로 구현하는 PDF RAG, 데이터베이스 추출, LLM 서비스. Contribute to joonhyeok95/spring-ai-google-gen development by creating an account on GitHub.&quot; data-og-host=&quot;github.com&quot; data-og-source-url=&quot;https://github.com/joonhyeok95/spring-ai-google-gen&quot; data-og-url=&quot;https://github.com/joonhyeok95/spring-ai-google-gen&quot; data-og-image=&quot;https://scrap.kakaocdn.net/dn/c1UuW0/dJMb84p7QiQ/KG3jydPcJmwjwtz7Shu5H0/img.png?width=1200&amp;amp;height=600&amp;amp;face=0_0_1200_600,https://scrap.kakaocdn.net/dn/bgJou5/dJMb84XXTFk/ToN0tR8sT04IkWOkk1KWs1/img.png?width=1200&amp;amp;height=600&amp;amp;face=0_0_1200_600,https://scrap.kakaocdn.net/dn/bmuvXK/dJMb8952K6c/kKrtQ3U9krlYUTmBAG6Qj0/img.png?width=1026&amp;amp;height=907&amp;amp;face=0_0_1026_907&quot;&gt;&lt;a href=&quot;https://github.com/joonhyeok95/spring-ai-google-gen&quot; target=&quot;_blank&quot; rel=&quot;noopener&quot; data-source-url=&quot;https://github.com/joonhyeok95/spring-ai-google-gen&quot;&gt;
&lt;div class=&quot;og-image&quot; style=&quot;background-image: url('https://scrap.kakaocdn.net/dn/c1UuW0/dJMb84p7QiQ/KG3jydPcJmwjwtz7Shu5H0/img.png?width=1200&amp;amp;height=600&amp;amp;face=0_0_1200_600,https://scrap.kakaocdn.net/dn/bgJou5/dJMb84XXTFk/ToN0tR8sT04IkWOkk1KWs1/img.png?width=1200&amp;amp;height=600&amp;amp;face=0_0_1200_600,https://scrap.kakaocdn.net/dn/bmuvXK/dJMb8952K6c/kKrtQ3U9krlYUTmBAG6Qj0/img.png?width=1026&amp;amp;height=907&amp;amp;face=0_0_1026_907');&quot;&gt;&amp;nbsp;&lt;/div&gt;
&lt;div class=&quot;og-text&quot;&gt;
&lt;p class=&quot;og-title&quot; data-ke-size=&quot;size16&quot;&gt;GitHub - joonhyeok95/spring-ai-google-gen: 제미나이 무료API로 구현하는 PDF RAG, 데이터베이스 추출, LLM 서비&lt;/p&gt;
&lt;p class=&quot;og-desc&quot; data-ke-size=&quot;size16&quot;&gt;제미나이 무료API로 구현하는 PDF RAG, 데이터베이스 추출, LLM 서비스. Contribute to joonhyeok95/spring-ai-google-gen development by creating an account on GitHub.&lt;/p&gt;
&lt;p class=&quot;og-host&quot; data-ke-size=&quot;size16&quot;&gt;github.com&lt;/p&gt;
&lt;/div&gt;
&lt;/a&gt;&lt;/figure&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;</description>
      <category>개발/Spring</category>
      <author>joon95</author>
      <guid isPermaLink="true">https://flowlog.tistory.com/121</guid>
      <comments>https://flowlog.tistory.com/121#entry121comment</comments>
      <pubDate>Fri, 20 Mar 2026 14:14:03 +0900</pubDate>
    </item>
    <item>
      <title>[Spring AI] 3편: Database 쿼리생성 및 실행을 위한 HITL(Human in the Loop)</title>
      <link>https://flowlog.tistory.com/120</link>
      <description>&lt;h2 data-ke-size=&quot;size26&quot;&gt;샘플데이터베이스&lt;/h2&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;postgresql용도 데이터베이스 샘플인 dvdrental 을 활용&lt;/p&gt;
&lt;p&gt;&lt;figure class=&quot;imageblock alignCenter&quot; data-ke-mobileStyle=&quot;widthOrigin&quot; data-origin-width=&quot;544&quot; data-origin-height=&quot;683&quot;&gt;&lt;span data-url=&quot;https://blog.kakaocdn.net/dn/cbJlqz/dJMcaio7ZJx/g8ZldKpBlmVUwgCH0IQ6d1/img.png&quot; data-phocus=&quot;https://blog.kakaocdn.net/dn/cbJlqz/dJMcaio7ZJx/g8ZldKpBlmVUwgCH0IQ6d1/img.png&quot; data-alt=&quot;DVDRental ERD&quot;&gt;&lt;img src=&quot;https://blog.kakaocdn.net/dn/cbJlqz/dJMcaio7ZJx/g8ZldKpBlmVUwgCH0IQ6d1/img.png&quot; srcset=&quot;https://img1.daumcdn.net/thumb/R1280x0/?scode=mtistory2&amp;fname=https%3A%2F%2Fblog.kakaocdn.net%2Fdn%2FcbJlqz%2FdJMcaio7ZJx%2Fg8ZldKpBlmVUwgCH0IQ6d1%2Fimg.png&quot; onerror=&quot;this.onerror=null; this.src='//t1.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;544&quot; height=&quot;683&quot; data-origin-width=&quot;544&quot; data-origin-height=&quot;683&quot;/&gt;&lt;/span&gt;&lt;figcaption&gt;DVDRental ERD&lt;/figcaption&gt;
&lt;/figure&gt;
&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;figure id=&quot;og_1773989535539&quot; contenteditable=&quot;false&quot; data-ke-type=&quot;opengraph&quot; data-ke-align=&quot;alignCenter&quot; data-og-type=&quot;article&quot; data-og-title=&quot;PostgreSQL Sample Database&quot; data-og-description=&quot;This tutorial introduces you to a PostgreSQL sample database that you can use for learning and practicing with PostgreSQL.&quot; data-og-host=&quot;neon.com&quot; data-og-source-url=&quot;https://neon.com/postgresql/postgresql-getting-started/postgresql-sample-database&quot; data-og-url=&quot;https://neon.com/postgresql/postgresql-getting-started/postgresql-sample-database&quot; data-og-image=&quot;https://scrap.kakaocdn.net/dn/taEEZ/dJMb9fZuul0/7VJBeq7Wdxcc0YYkjaFCb0/img.png?width=1200&amp;amp;height=630&amp;amp;face=0_0_1200_630,https://scrap.kakaocdn.net/dn/gpU1X/dJMb9iIGgPX/MKe6NvD0QZbFuqdCo5UrkK/img.png?width=1200&amp;amp;height=630&amp;amp;face=0_0_1200_630&quot;&gt;&lt;a href=&quot;https://neon.com/postgresql/postgresql-getting-started/postgresql-sample-database&quot; target=&quot;_blank&quot; rel=&quot;noopener&quot; data-source-url=&quot;https://neon.com/postgresql/postgresql-getting-started/postgresql-sample-database&quot;&gt;
&lt;div class=&quot;og-image&quot; style=&quot;background-image: url('https://scrap.kakaocdn.net/dn/taEEZ/dJMb9fZuul0/7VJBeq7Wdxcc0YYkjaFCb0/img.png?width=1200&amp;amp;height=630&amp;amp;face=0_0_1200_630,https://scrap.kakaocdn.net/dn/gpU1X/dJMb9iIGgPX/MKe6NvD0QZbFuqdCo5UrkK/img.png?width=1200&amp;amp;height=630&amp;amp;face=0_0_1200_630');&quot;&gt;&amp;nbsp;&lt;/div&gt;
&lt;div class=&quot;og-text&quot;&gt;
&lt;p class=&quot;og-title&quot; data-ke-size=&quot;size16&quot;&gt;PostgreSQL Sample Database&lt;/p&gt;
&lt;p class=&quot;og-desc&quot; data-ke-size=&quot;size16&quot;&gt;This tutorial introduces you to a PostgreSQL sample database that you can use for learning and practicing with PostgreSQL.&lt;/p&gt;
&lt;p class=&quot;og-host&quot; data-ke-size=&quot;size16&quot;&gt;neon.com&lt;/p&gt;
&lt;/div&gt;
&lt;/a&gt;&lt;/figure&gt;
&lt;h2 data-ke-size=&quot;size26&quot;&gt;구현&lt;/h2&gt;
&lt;h3 data-ke-size=&quot;size23&quot;&gt;Prompt&lt;/h3&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;sqlTool 에 기능을 분류하여 agent가 테이블 스키마를 조회하고 테이블을 조죄할 수 있도록 했다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;그리고 잘못된 컬럼을 요청했을 때 &lt;b&gt;자기 수정 규칙&lt;/b&gt;을 두어 최대 3번까지 수정을 재시도할 수 있다.&lt;/p&gt;
&lt;pre id=&quot;code_1773988302803&quot; class=&quot;java&quot; data-ke-language=&quot;java&quot; data-ke-type=&quot;codeblock&quot;&gt;&lt;code&gt;당신은 PostgreSQL 전문가입니다. 데이터베이스 이름은 'aidb'이며, DVD 대여 시스템입니다.

1. [탐색]: 질문에 관련된 테이블이 무엇인지 'listTables'로 확인합니다.
2. [분석]: 해당 테이블의 구조를 'describeTable'로 파악하여 정확한 컬럼명을 확인합니다.
3. [계획]: 위 정보를 바탕으로 쿼리 실행 순서를 계획합니다.
4. [실행]: 'executeSqlQuery'로 최종 데이터를 가져옵니다.                    

반드시 제공된 도구를 사용해 SQL을 실행한 후, 그 결과를 바탕으로 자연스럽게 답변하세요.

[자기 수정 규칙]
1. 'executeSqlQuery' 실행 결과 'SQL 에러 발생'이 포함되어 있다면, 에러 메시지를 분석하세요.
2. 잘못된 컬럼명이나 테이블명 때문이라면, 스키마를 추측하지 말고 information_schema를 조회하거나 쿼리를 수정하세요.
3. 최대 3번까지 수정을 시도할 수 있습니다.
4. 최종적으로 성공한 결과만 요약해서 사용자에게 전달하세요.&lt;/code&gt;&lt;/pre&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;h3 data-ke-size=&quot;size23&quot;&gt;승인 Service&lt;/h3&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;그리고 생성된 쿼리를 DB에 수행요청 하기 전에 사용자에게 승인을 위임한다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;사용자는 60sec 이내에 승인API를 전송해야한다.&lt;/p&gt;
&lt;pre id=&quot;code_1773988403018&quot; class=&quot;java&quot; data-ke-language=&quot;java&quot; data-ke-type=&quot;codeblock&quot;&gt;&lt;code&gt;approvalService.request(chatId, query).get(60, TimeUnit.SECONDS);&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;agent.service.ApprovalService.java&lt;/p&gt;
&lt;pre id=&quot;code_1773988466508&quot; class=&quot;java&quot; data-ke-language=&quot;java&quot; data-ke-type=&quot;codeblock&quot;&gt;&lt;code&gt;// AI가 호출: 승인 요청을 등록하고 UI에 알림
public CompletableFuture&amp;lt;Boolean&amp;gt; request(String chatId, String query) {
    String id = UUID.randomUUID().toString().substring(0, 8);// 60초 후 자동 만료되는 로직 추가 (Optional)
    CompletableFuture&amp;lt;Boolean&amp;gt; future = new CompletableFuture&amp;lt;Boolean&amp;gt;()
            .orTimeout(60, TimeUnit.SECONDS);
    pendingApprovals.put(id, future);
 // 1. DTO 객체 생성
    ApprovalRequest approvalRequest = ApprovalRequest.builder()
            .id(id)
            .query(query)
            .message(&quot;쿼리가 생성되어 사용자 승인을 기다립니다.&quot;)
            .requestedAt(LocalDateTime.now())
            .build();

    // ⭐️ 해당 ChatId에 연결된 Sink를 찾아 데이터 방출
    Sinks.Many&amp;lt;ApprovalRequest&amp;gt; sink = userSinks.get(chatId);
    if (sink != null) {
        Sinks.EmitResult result = sink.tryEmitNext(approvalRequest);
        log.info(&quot;  [백엔드] ChatId: {} 에게 승인 요청 전송 - ID: {}, 결과: {}&quot;, chatId, id, result);
    } else {
        log.warn(&quot;⚠️ ChatId: {} 에 대한 활성화된 구독자가 없습니다.&quot;, chatId);
        // 구독자가 없으면 즉시 실패 처리하거나 로직에 따라 조절
        future.complete(false);
    }        
    return future;
}&lt;/code&gt;&lt;/pre&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;h3 data-ke-size=&quot;size23&quot;&gt;승인 요청 화면&lt;/h3&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;채팅이 생성되면 클라이언트는 서버에 sse 구독을 해놓고,&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;서버가 sse를 전달하면 modal event가 발생되어 생성된 쿼리를 보여준다.&lt;/p&gt;
&lt;p&gt;&lt;figure class=&quot;imageblock alignCenter&quot; data-ke-mobileStyle=&quot;widthOrigin&quot; data-origin-width=&quot;442&quot; data-origin-height=&quot;306&quot;&gt;&lt;span data-url=&quot;https://blog.kakaocdn.net/dn/OMsVi/dJMcaady1fa/iOLMeHktvwydtoS1UQ3X6k/img.png&quot; data-phocus=&quot;https://blog.kakaocdn.net/dn/OMsVi/dJMcaady1fa/iOLMeHktvwydtoS1UQ3X6k/img.png&quot;&gt;&lt;img src=&quot;https://blog.kakaocdn.net/dn/OMsVi/dJMcaady1fa/iOLMeHktvwydtoS1UQ3X6k/img.png&quot; srcset=&quot;https://img1.daumcdn.net/thumb/R1280x0/?scode=mtistory2&amp;fname=https%3A%2F%2Fblog.kakaocdn.net%2Fdn%2FOMsVi%2FdJMcaady1fa%2FiOLMeHktvwydtoS1UQ3X6k%2Fimg.png&quot; onerror=&quot;this.onerror=null; this.src='//t1.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;442&quot; height=&quot;306&quot; data-origin-width=&quot;442&quot; data-origin-height=&quot;306&quot;/&gt;&lt;/span&gt;&lt;/figure&gt;
&lt;/p&gt;
&lt;h3 data-ke-size=&quot;size23&quot;&gt;승인API&lt;/h3&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;agent.web.ApprovalUiController.java&lt;/p&gt;
&lt;pre id=&quot;code_1773988932248&quot; class=&quot;java&quot; data-ke-language=&quot;java&quot; data-ke-type=&quot;codeblock&quot;&gt;&lt;code&gt;// 2. UI 버튼 클릭 시 호출
@PostMapping(&quot;/{id}/respond&quot;)
public Mono&amp;lt;ResponseEntity&amp;lt;String&amp;gt;&amp;gt; respond(@PathVariable(&quot;id&quot;) String id, 
        @RequestParam(&quot;approved&quot;) boolean approved) {
    return Mono.just(id)
        .publishOn(Schedulers.boundedElastic()) // 승인 처리를 별도 스레드에서 수행
        .map(requestId -&amp;gt; {
            approvalService.handleDecision(requestId, approved);
            return ResponseEntity.ok(&quot;Success&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;agent.web.ApprovalService.java&lt;/p&gt;
&lt;pre id=&quot;code_1773988955637&quot; class=&quot;java&quot; data-ke-language=&quot;java&quot; data-ke-type=&quot;codeblock&quot;&gt;&lt;code&gt;// 1. UI로 실시간 알림을 보내기 위한 통로 (SSE)
private final Map&amp;lt;String, Sinks.Many&amp;lt;ApprovalRequest&amp;gt;&amp;gt; userSinks = new ConcurrentHashMap&amp;lt;&amp;gt;();
// 2. 대기 중인 요청 저장소
private final Map&amp;lt;String, CompletableFuture&amp;lt;Boolean&amp;gt;&amp;gt; pendingApprovals = new ConcurrentHashMap&amp;lt;&amp;gt;();
// UI 버튼 클릭 시 호출: 승인 또는 취소
public void handleDecision(String id, boolean approved) {
    log.info(&quot;  [백엔드] 결정 수신 - ID: {}, 승인여부: {}&quot;, id, approved);
    CompletableFuture&amp;lt;Boolean&amp;gt; future = pendingApprovals.get(id);
    if (future != null) {
        future.complete(approved);
        pendingApprovals.remove(id); // 결정 완료 후 삭제
    } else {
        log.warn(&quot;⚠️ 이미 만료되었거나 존재하지 않는 승인 ID입니다: {}&quot;, id);
    }
}&lt;/code&gt;&lt;/pre&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;상태를 complete로 만들어 중단되었던 쿼리를 수행한다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;h3 data-ke-size=&quot;size23&quot;&gt;검색해보기&lt;/h3&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;이제 이런 검색을 해볼 수 있다!!&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&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;&quot;영화(film) 테이블에서 상영 시간이 180분 이상인 영화 제목 5개만 알려줘.&amp;rdquo;&lt;/li&gt;
&lt;li&gt;&quot;특정 조건 + 리미트&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;&quot;우리 서비스에 등록된 전체 고객(customer)은 총 몇 명이야?&quot;&lt;/li&gt;
&lt;li&gt;COUNT 함수 유도&lt;/li&gt;
&lt;/ul&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;b&gt;조인(Join)&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;&quot;가장 많이 대여(rental)된 영화 제목 TOP 3가 뭐야?&quot;&lt;/li&gt;
&lt;li&gt;film + inventory + rental 조인&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;&quot;각 영화 카테고리별로 총 매출(payment) 합계를 계산해줘.&quot;&lt;/li&gt;
&lt;li&gt;category + payment 집계&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;&quot;이메일 주소가 'P'로 시작하는 고객들의 이름과 성을 나열해줘.&quot;&lt;/li&gt;
&lt;li&gt;LIKE 패턴 검색&lt;/li&gt;
&lt;/ul&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;h2 data-ke-size=&quot;size26&quot;&gt;에이전트 협업&lt;/h2&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;더 나아가 분석된 데이터를 수치화 할 수도 있다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;chartService 를 만들어 놨는데 &quot;차트&quot; 라는 단어가 질문에 포함되어있을 경우 sql+chart 두개의 Tool을 사용하도록 만들었다.&lt;/p&gt;
&lt;p&gt;&lt;figure class=&quot;imageblock alignCenter&quot; data-ke-mobileStyle=&quot;widthOrigin&quot; data-origin-width=&quot;467&quot; data-origin-height=&quot;548&quot;&gt;&lt;span data-url=&quot;https://blog.kakaocdn.net/dn/baQWgK/dJMcaiJnxgi/PTQxlZyxdqrlL6BU7mCKK1/img.png&quot; data-phocus=&quot;https://blog.kakaocdn.net/dn/baQWgK/dJMcaiJnxgi/PTQxlZyxdqrlL6BU7mCKK1/img.png&quot; data-alt=&quot;사용자 수 증가 추이 그래프&quot;&gt;&lt;img src=&quot;https://blog.kakaocdn.net/dn/baQWgK/dJMcaiJnxgi/PTQxlZyxdqrlL6BU7mCKK1/img.png&quot; srcset=&quot;https://img1.daumcdn.net/thumb/R1280x0/?scode=mtistory2&amp;fname=https%3A%2F%2Fblog.kakaocdn.net%2Fdn%2FbaQWgK%2FdJMcaiJnxgi%2FPTQxlZyxdqrlL6BU7mCKK1%2Fimg.png&quot; onerror=&quot;this.onerror=null; this.src='//t1.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;467&quot; height=&quot;548&quot; data-origin-width=&quot;467&quot; data-origin-height=&quot;548&quot;/&gt;&lt;/span&gt;&lt;figcaption&gt;사용자 수 증가 추이 그래프&lt;/figcaption&gt;
&lt;/figure&gt;
&lt;figure class=&quot;imageblock alignCenter&quot; data-ke-mobileStyle=&quot;widthOrigin&quot; data-origin-width=&quot;539&quot; data-origin-height=&quot;742&quot;&gt;&lt;span data-url=&quot;https://blog.kakaocdn.net/dn/bPXI1V/dJMb99Z2GDZ/eGyBbpH9tqCkDBRY9lp5pk/img.png&quot; data-phocus=&quot;https://blog.kakaocdn.net/dn/bPXI1V/dJMb99Z2GDZ/eGyBbpH9tqCkDBRY9lp5pk/img.png&quot; data-alt=&quot;특정 영화 관람 건수 그래프&quot;&gt;&lt;img src=&quot;https://blog.kakaocdn.net/dn/bPXI1V/dJMb99Z2GDZ/eGyBbpH9tqCkDBRY9lp5pk/img.png&quot; srcset=&quot;https://img1.daumcdn.net/thumb/R1280x0/?scode=mtistory2&amp;fname=https%3A%2F%2Fblog.kakaocdn.net%2Fdn%2FbPXI1V%2FdJMb99Z2GDZ%2FeGyBbpH9tqCkDBRY9lp5pk%2Fimg.png&quot; onerror=&quot;this.onerror=null; this.src='//t1.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;539&quot; height=&quot;742&quot; data-origin-width=&quot;539&quot; data-origin-height=&quot;742&quot;/&gt;&lt;/span&gt;&lt;figcaption&gt;특정 영화 관람 건수 그래프&lt;/figcaption&gt;
&lt;/figure&gt;
&lt;figure class=&quot;imageblock alignCenter&quot; data-ke-mobileStyle=&quot;widthOrigin&quot; data-origin-width=&quot;528&quot; data-origin-height=&quot;743&quot;&gt;&lt;span data-url=&quot;https://blog.kakaocdn.net/dn/XkeWL/dJMb99TeMWt/WehZrdxpVF4XkMNraP2kc0/img.png&quot; data-phocus=&quot;https://blog.kakaocdn.net/dn/XkeWL/dJMb99TeMWt/WehZrdxpVF4XkMNraP2kc0/img.png&quot; data-alt=&quot;DVD 장르별 매출 순위&quot;&gt;&lt;img src=&quot;https://blog.kakaocdn.net/dn/XkeWL/dJMb99TeMWt/WehZrdxpVF4XkMNraP2kc0/img.png&quot; srcset=&quot;https://img1.daumcdn.net/thumb/R1280x0/?scode=mtistory2&amp;fname=https%3A%2F%2Fblog.kakaocdn.net%2Fdn%2FXkeWL%2FdJMb99TeMWt%2FWehZrdxpVF4XkMNraP2kc0%2Fimg.png&quot; onerror=&quot;this.onerror=null; this.src='//t1.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;743&quot; data-origin-width=&quot;528&quot; data-origin-height=&quot;743&quot;/&gt;&lt;/span&gt;&lt;figcaption&gt;DVD 장르별 매출 순위&lt;/figcaption&gt;
&lt;/figure&gt;
&lt;/p&gt;
&lt;p style=&quot;color: #333333; text-align: start;&quot; data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p style=&quot;color: #333333; text-align: start;&quot; data-ke-size=&quot;size16&quot;&gt;&lt;a href=&quot;https://flowlog.tistory.com/118&quot; target=&quot;_blank&quot; rel=&quot;noopener&quot;&gt;[Spring AI] 1편: 토이 프로젝트 개요&lt;/a&gt;&lt;span&gt;&amp;nbsp;&lt;/span&gt;&lt;/p&gt;
&lt;p style=&quot;color: #333333; text-align: start;&quot; data-ke-size=&quot;size16&quot;&gt;&lt;a style=&quot;color: #0070d1;&quot; href=&quot;https://flowlog.tistory.com/119&quot;&gt;[Spring AI] 2편: RAG 문서 임베딩&lt;/a&gt;&lt;/p&gt;
&lt;p style=&quot;color: #333333; text-align: start;&quot; data-ke-size=&quot;size16&quot;&gt;&lt;a style=&quot;color: #0070d1;&quot; href=&quot;http://%20https://flowlog.tistory.com/121&quot;&gt;&lt;span style=&quot;color: #333333; text-align: start;&quot;&gt;[Spring AI]&lt;span&gt;&lt;span&gt;&amp;nbsp;&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;4편: MCP 서버 구동과 실행&lt;/a&gt;&lt;/p&gt;
&lt;figure id=&quot;og_1773987974335&quot; contenteditable=&quot;false&quot; data-ke-type=&quot;opengraph&quot; data-ke-align=&quot;alignCenter&quot; data-og-type=&quot;object&quot; data-og-title=&quot;GitHub - joonhyeok95/spring-ai-google-gen: 제미나이 무료API로 구현하는 PDF RAG, 데이터베이스 추출, LLM 서비&quot; data-og-description=&quot;제미나이 무료API로 구현하는 PDF RAG, 데이터베이스 추출, LLM 서비스. Contribute to joonhyeok95/spring-ai-google-gen development by creating an account on GitHub.&quot; data-og-host=&quot;github.com&quot; data-og-source-url=&quot;https://github.com/joonhyeok95/spring-ai-google-gen&quot; data-og-url=&quot;https://github.com/joonhyeok95/spring-ai-google-gen&quot; data-og-image=&quot;https://scrap.kakaocdn.net/dn/c1UuW0/dJMb84p7QiQ/KG3jydPcJmwjwtz7Shu5H0/img.png?width=1200&amp;amp;height=600&amp;amp;face=0_0_1200_600,https://scrap.kakaocdn.net/dn/bgJou5/dJMb84XXTFk/ToN0tR8sT04IkWOkk1KWs1/img.png?width=1200&amp;amp;height=600&amp;amp;face=0_0_1200_600,https://scrap.kakaocdn.net/dn/bmuvXK/dJMb8952K6c/kKrtQ3U9krlYUTmBAG6Qj0/img.png?width=1026&amp;amp;height=907&amp;amp;face=0_0_1026_907&quot;&gt;&lt;a href=&quot;https://github.com/joonhyeok95/spring-ai-google-gen&quot; target=&quot;_blank&quot; rel=&quot;noopener&quot; data-source-url=&quot;https://github.com/joonhyeok95/spring-ai-google-gen&quot;&gt;
&lt;div class=&quot;og-image&quot; style=&quot;background-image: url('https://scrap.kakaocdn.net/dn/c1UuW0/dJMb84p7QiQ/KG3jydPcJmwjwtz7Shu5H0/img.png?width=1200&amp;amp;height=600&amp;amp;face=0_0_1200_600,https://scrap.kakaocdn.net/dn/bgJou5/dJMb84XXTFk/ToN0tR8sT04IkWOkk1KWs1/img.png?width=1200&amp;amp;height=600&amp;amp;face=0_0_1200_600,https://scrap.kakaocdn.net/dn/bmuvXK/dJMb8952K6c/kKrtQ3U9krlYUTmBAG6Qj0/img.png?width=1026&amp;amp;height=907&amp;amp;face=0_0_1026_907');&quot;&gt;&amp;nbsp;&lt;/div&gt;
&lt;div class=&quot;og-text&quot;&gt;
&lt;p class=&quot;og-title&quot; data-ke-size=&quot;size16&quot;&gt;GitHub - joonhyeok95/spring-ai-google-gen: 제미나이 무료API로 구현하는 PDF RAG, 데이터베이스 추출, LLM 서비&lt;/p&gt;
&lt;p class=&quot;og-desc&quot; data-ke-size=&quot;size16&quot;&gt;제미나이 무료API로 구현하는 PDF RAG, 데이터베이스 추출, LLM 서비스. Contribute to joonhyeok95/spring-ai-google-gen development by creating an account on GitHub.&lt;/p&gt;
&lt;p class=&quot;og-host&quot; data-ke-size=&quot;size16&quot;&gt;github.com&lt;/p&gt;
&lt;/div&gt;
&lt;/a&gt;&lt;/figure&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;</description>
      <category>개발/Spring</category>
      <author>joon95</author>
      <guid isPermaLink="true">https://flowlog.tistory.com/120</guid>
      <comments>https://flowlog.tistory.com/120#entry120comment</comments>
      <pubDate>Fri, 20 Mar 2026 14:13:53 +0900</pubDate>
    </item>
    <item>
      <title>[Spring AI] 2편: RAG 문서 임베딩</title>
      <link>https://flowlog.tistory.com/119</link>
      <description>&lt;h2 data-ke-size=&quot;size26&quot;&gt;사용사례&lt;/h2&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;필자는 경력기술서를 업로드하여 아래 질문을 확인하는 용도로 개발&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;- 내 기술 경력서 요약&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;- 기술 경력서 내용 개선포인트 체크&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;- 면접관이 본다면 어떤 질문을 할까?&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;h2 data-ke-size=&quot;size26&quot;&gt;RAG(Retrieval-Augmented Generation)&lt;/h2&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;레그를 적재하고 검색할 수 있도록 해보자.&lt;/p&gt;
&lt;h3 data-ke-size=&quot;size23&quot;&gt;적재&lt;/h3&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;RAG 적재에는 아래의 흐름을 가진다.&lt;/p&gt;
&lt;ul style=&quot;list-style-type: disc;&quot; data-ke-list-type=&quot;disc&quot;&gt;
&lt;li&gt;Document Loader -&amp;gt; Text Splitter -&amp;gt; Embedding -&amp;gt; VectorDB&lt;/li&gt;
&lt;/ul&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;우리는 수많은 Chunk 로 잘려진 데이터를 임베딩하여 VectorDB에 적재해야한다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;VectorDB는 다양한데 필자는 &lt;b&gt;Postgresql의 vectorDB plugin&lt;/b&gt;을 활용해보았다.&lt;/p&gt;
&lt;h4 data-ke-size=&quot;size20&quot;&gt;Cloud Navtive VectorDB&lt;/h4&gt;
&lt;table id=&quot;3184d4d8-7956-80fd-976f-f1499ff751fe&quot; style=&quot;border-collapse: collapse; width: 100%;&quot; border=&quot;1&quot; data-ke-align=&quot;alignLeft&quot;&gt;
&lt;tbody&gt;
&lt;tr id=&quot;3184d4d8-7956-8022-ad48-f4661bcf0d77&quot;&gt;
&lt;td id=&quot;]?hS&quot; style=&quot;width: 11.5116%;&quot;&gt;&lt;b&gt;종류&lt;/b&gt;&lt;/td&gt;
&lt;td id=&quot;^Fbt&quot; style=&quot;width: 44.5349%;&quot;&gt;&lt;b&gt;특징&lt;/b&gt;&lt;/td&gt;
&lt;td id=&quot;lTeE&quot; style=&quot;width: 43.8372%;&quot;&gt;&lt;b&gt;추천 상황&lt;/b&gt;&lt;/td&gt;
&lt;/tr&gt;
&lt;tr id=&quot;3184d4d8-7956-8095-a4a8-d6f0ce734646&quot;&gt;
&lt;td id=&quot;]?hS&quot; style=&quot;width: 11.5116%;&quot;&gt;&lt;b&gt;Pinecone&lt;/b&gt;&lt;/td&gt;
&lt;td id=&quot;^Fbt&quot; style=&quot;width: 44.5349%;&quot;&gt;완전 관리형(SaaS), 설정이 거의 필요 없음.&lt;/td&gt;
&lt;td id=&quot;lTeE&quot; style=&quot;width: 43.8372%;&quot;&gt;인프라 관리 없이 &lt;b&gt;빠르게 MVP를 출시&lt;/b&gt;하고 싶을 때.&lt;/td&gt;
&lt;/tr&gt;
&lt;tr id=&quot;3184d4d8-7956-8062-9918-e8c0701b43d3&quot;&gt;
&lt;td id=&quot;]?hS&quot; style=&quot;width: 11.5116%;&quot;&gt;&lt;b&gt;Milvus&lt;/b&gt;&lt;/td&gt;
&lt;td id=&quot;^Fbt&quot; style=&quot;width: 44.5349%;&quot;&gt;오픈소스 기반, 매우 높은 확장성과 성능.&lt;/td&gt;
&lt;td id=&quot;lTeE&quot; style=&quot;width: 43.8372%;&quot;&gt;&lt;b&gt;대규모 트래픽&lt;/b&gt;과 대용량 데이터를 다루는 엔터프라이즈 급.&lt;/td&gt;
&lt;/tr&gt;
&lt;tr id=&quot;3184d4d8-7956-80b5-af14-ce82ff2e0059&quot;&gt;
&lt;td id=&quot;]?hS&quot; style=&quot;width: 11.5116%;&quot;&gt;&lt;b&gt;Weaviate&lt;/b&gt;&lt;/td&gt;
&lt;td id=&quot;^Fbt&quot; style=&quot;width: 44.5349%;&quot;&gt;키워드 검색(BM25)과 벡터 검색을 합친 하이브리드 검색 강점.&lt;/td&gt;
&lt;td id=&quot;lTeE&quot; style=&quot;width: 43.8372%;&quot;&gt;객체 지향적인 데이터 구조와 &lt;b&gt;하이브리드 검색&lt;/b&gt;이 중요할 때.&lt;/td&gt;
&lt;/tr&gt;
&lt;tr id=&quot;3184d4d8-7956-8037-bb26-c8db16df5255&quot;&gt;
&lt;td id=&quot;]?hS&quot; style=&quot;width: 11.5116%;&quot;&gt;&lt;b&gt;Chroma&lt;/b&gt;&lt;/td&gt;
&lt;td id=&quot;^Fbt&quot; style=&quot;width: 44.5349%;&quot;&gt;가볍고 사용이 간편함 (In-memory 지원).&lt;/td&gt;
&lt;td id=&quot;lTeE&quot; style=&quot;width: 43.8372%;&quot;&gt;로컬 개발 환경이나 &lt;b&gt;소규모 프로젝트&lt;/b&gt; 테스트용.&lt;/td&gt;
&lt;/tr&gt;
&lt;/tbody&gt;
&lt;/table&gt;
&lt;h4 data-ke-size=&quot;size20&quot;&gt;설치형 VectorDB&lt;/h4&gt;
&lt;ul style=&quot;list-style-type: disc;&quot; data-ke-list-type=&quot;disc&quot;&gt;
&lt;li&gt;&lt;b&gt;pgvector (PostgreSQL):&lt;/b&gt; 풀스택 개발자들에게 가장 인기가 많습니다. 기존 SQL 쿼리와 벡터 검색을 JOIN해서 쓸 수 있습니다.&lt;/li&gt;
&lt;li&gt;&lt;b&gt;Elasticsearch / OpenSearch:&lt;/b&gt; 이미 검색 엔진으로 쓰고 있다면 최고의 선택입니다. 텍스트 검색과 벡터 검색을 버무리기 좋습니다.&lt;/li&gt;
&lt;li&gt;&lt;b&gt;Redis (RedisVL):&lt;/b&gt; 실시간성이 극도로 중요할 때 사용합니다. 캐시 레이어에서 바로 벡터 검색을 수행합니다.&lt;/li&gt;
&lt;/ul&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;h4 data-ke-size=&quot;size20&quot;&gt;PGVector 셋팅&lt;/h4&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;docker-compose 기동&lt;/p&gt;
&lt;pre id=&quot;code_1773985423498&quot; class=&quot;bash&quot; data-ke-language=&quot;bash&quot; data-ke-type=&quot;codeblock&quot;&gt;&lt;code&gt;version: '3.8'
services:
  db:
    image: pgvector/pgvector:pg16 # pgvector가 내장된 Postgres 16 이미지
    container_name: pgvector_db
    ports:
      - &quot;5432:5432&quot;
    environment:
      - POSTGRES_USER=myuser
      - POSTGRES_PASSWORD=mypassword
      - POSTGRES_DB=aidb
    volumes:
      - ./postgres_data:/var/lib/postgresql/data&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;vector 확장자 실행 명령어&lt;/p&gt;
&lt;pre id=&quot;code_1773985461915&quot; class=&quot;bash&quot; data-ke-language=&quot;bash&quot; data-ke-type=&quot;codeblock&quot;&gt;&lt;code&gt;-- db 접속
docker exec -it pgvector_db psql -U myuser -d aidb

-- DB 접속 후 실행
CREATE EXTENSION vector;

-- 설치 확인 (버전이 나오면 성공!)
SELECT * FROM pg_extension WHERE extname = 'vector';&lt;/code&gt;&lt;/pre&gt;
&lt;h4 data-ke-size=&quot;size20&quot;&gt;임베딩(Embadding)&lt;/h4&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;처음에 로컬로 모두 구현하려고 했으나 노트북이 너무 느린관계로 제미나이로 변경했다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;참고로 적재할 때 임베딩모델과 검색할 때 모델은 동일해야한다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;올라마로 임베딩 모델 all-minilm 을 쓰면 384 차원 으로 임베딩했고,&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;제미나이 임베딩 모델 gemini-embedding-001 을 쓰면 최대 3072 차원까지 지원했다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;하지만 pgvector 에서 2000 차원까지 지원한다고 하기에 설정을 조정(768 차원).&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;변경하면서 테스트 했기 때문에 차원정보가 다르다는 에러를 마주했었고 그런 경우 해당 벡터테이블의 데이터를 지우면된다&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;span style=&quot;background-color: #ffffff;&quot;&gt;&lt;span style=&quot;background-color: #ffffff; color: #000000;&quot;&gt;&lt;span style=&quot;color: #000000;&quot;&gt;- ERROR: different vector dimensions 768 and 3072&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;/p&gt;
&lt;pre id=&quot;code_1773986248694&quot; class=&quot;bash&quot; data-ke-language=&quot;bash&quot; data-ke-type=&quot;codeblock&quot;&gt;&lt;code&gt;-- 1. 기존 데이터 전부 삭제 (차원이 다르면 검색이 안 되므로 어차피 지워야 함)
DELETE FROM vector_store;

-- 2. 이제 타입 변경 가능!
ALTER TABLE vector_store ALTER COLUMN embedding TYPE vector(768);&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;h4 data-ke-size=&quot;size20&quot;&gt;코드&lt;/h4&gt;
&lt;pre id=&quot;code_1773986352543&quot; class=&quot;bash&quot; data-ke-language=&quot;bash&quot; data-ke-type=&quot;codeblock&quot;&gt;&lt;code&gt;spring:
  ai:
    vectorstore:
      pgvector:
        # index-type: HNSW      # 대규모 검색에 효율적인 인덱스 방식
        dimension: 768        # Gemini Embedding 모델의 기본 차원 (모델에 따라 확인 필요)
        collection-name: spring_pdf_rag
#    ollama:
#      base-url: http://localhost:11434
#      embedding:
#        options:
#          model: all-minilm
    google:
      genai:
        # spring AI 1.1.2&amp;gt;채팅모델&amp;gt;Google GenAI 규격인데 동작안함
        api-key: ${GOOGLE_API_KEY:missing_key}
        chat:
          options:
            model: gemini-3.1-flash-lite-preview
        # spring AI 1.1.2&amp;gt;임베딩모델&amp;gt;Google GenAI 규격인데 동작안함
        embedding:
          api-key: ${GOOGLE_API_KEY:missing_key}
          text:
            options:
              model: gemini-embedding-001 # 3072 지원함
              task-type: RETRIEVAL_DOCUMENT # 문서 저장 시&lt;/code&gt;&lt;/pre&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;spring AI의 설정을 spring docs를 보고 하는데 잘 안되서.....직접 bean 생성함.&lt;/p&gt;
&lt;pre id=&quot;code_1773986642739&quot; class=&quot;bash&quot; data-ke-language=&quot;bash&quot; data-ke-type=&quot;codeblock&quot;&gt;&lt;code&gt;ai:
  api-key: ${GOOGLE_API_KEY:missing_key}
  model: gemini-3.1-flash-lite-preview
  default-system: &quot;너는 20년 경력의 시니어 개발자야. 친절하고 간결하게 대답해줘.&quot;
  embed-model: gemini-embedding-001
  prompts:
    intent-route: classpath:prompts/intent-route.st
    general: classpath:prompts/general.st
    rag: classpath:prompts/rag.st&lt;/code&gt;&lt;/pre&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;시스템프롬프트를 .st 형식으로 분류 함&lt;/p&gt;
&lt;pre id=&quot;code_1773986718221&quot; class=&quot;java&quot; data-ke-language=&quot;java&quot; data-ke-type=&quot;codeblock&quot;&gt;&lt;code&gt;@Getter
@Setter
@ConfigurationProperties(prefix = &quot;ai&quot;)
public class AiProperties {
	private String apiKey;
    private String model = &quot;gemini-1.5-flash&quot;; // 기본값 설정
    private String defaultSystem;
 
    // 중첩 클래스로 프롬프트 관리
    private final Prompts prompts = new Prompts();

    @Getter
    @Setter
    public static class Prompts {
        private Resource intentRoute;
        private Resource general;
        private Resource rag;
    }
}&lt;/code&gt;&lt;/pre&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;프로퍼티 값을 읽어 오고&lt;/p&gt;
&lt;pre id=&quot;code_1773986811131&quot; class=&quot;java&quot; data-ke-language=&quot;java&quot; data-ke-type=&quot;codeblock&quot;&gt;&lt;code&gt;@Primary
@Bean
public ChatClient chatClientSimple(GoogleGenAiChatModel chatModel, CustomRedisChatMemoryRepository chatMemoryRepository) {

    return ChatClient.builder(chatModel)
            .defaultSystem(aiProperties.getDefaultSystem())
            .defaultAdvisors(
                    MessageChatMemoryAdvisor.builder(
                            MessageWindowChatMemory.builder()
                            .chatMemoryRepository(chatMemoryRepository)
                            .maxMessages(20)
                            .build()
                            )
                    .conversationId(&quot;default&quot;)
                    .order(1)
                    .build()
            )
            .build();
}
// VectorDB 연결 클라이언트
@Bean
public ChatClient chatClientVector(GoogleGenAiChatModel chatModel, VectorStore vectorStore, CustomRedisChatMemoryRepository chatMemoryRepository) {

    return ChatClient.builder(chatModel)
            .defaultSystem(aiProperties.getDefaultSystem())
            .defaultAdvisors(QuestionAnswerAdvisor.builder(vectorStore)
                    .searchRequest(SearchRequest.builder()
                            .topK(3).build())
                    .build(),
                    MessageChatMemoryAdvisor.builder(
                            MessageWindowChatMemory.builder()
                            .chatMemoryRepository(chatMemoryRepository)
                            .maxMessages(20)
                            .build()
                            )
                    .conversationId(&quot;default&quot;)
                    .order(1)
                    .build()
            )
            .build();
}
@Bean
@Primary
public GoogleGenAiChatModel googleGenAiChatModel(
        Client googleGenAiClient,
        ObjectProvider&amp;lt;ToolCallingManager&amp;gt; toolCallingManager,
        ObjectProvider&amp;lt;RetryTemplate&amp;gt; retryTemplate,
        ObjectProvider&amp;lt;ObservationRegistry&amp;gt; observationRegistry) {
    // 모델 옵션 설정
    GoogleGenAiChatOptions options = GoogleGenAiChatOptions.builder()
            .model(aiProperties.getModel())
            .temperature(0.7) // 창의성 조절 (0.0 ~ 2.0)
            .build();
    // 모델 생성
    return new GoogleGenAiChatModel(
            googleGenAiClient, 
            options, 
            toolCallingManager.getIfAvailable(() -&amp;gt; null), // Tool 호출 관리
            retryTemplate.getIfAvailable(RetryTemplate::new), // 재시도 로직
            observationRegistry.getIfAvailable(() -&amp;gt; ObservationRegistry.NOOP) // 모니터링
    );    
}

@Bean
public EmbeddingModel embeddingModel() {
    // 1. 연결 정보 설정 (API Key 방식)
    GoogleGenAiEmbeddingConnectionDetails connectionDetails = 
        GoogleGenAiEmbeddingConnectionDetails.builder()
            .apiKey(aiProperties.getApiKey())
            .build();

    // 2. 옵션 설정 (TaskType 설정이 중요합니다)
    GoogleGenAiTextEmbeddingOptions options = GoogleGenAiTextEmbeddingOptions.builder()
            .model(&quot;gemini-embedding-001&quot;) // 공식 추천 모델
            // RAG용 문서 인덱싱이라면 RETRIEVAL_DOCUMENT가 정석입니다.
            .taskType(GoogleGenAiTextEmbeddingOptions.TaskType.RETRIEVAL_DOCUMENT)
            .dimensions(768) // default 3072나 postgresql 이 2000까지만 지원함
            .build();

    // 3. 모델 객체 생성 및 반환
    return new GoogleGenAiTextEmbeddingModel(connectionDetails, options);
}&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;백터DB가 없는 챗클라이언트와 백터용챗클라이언트를 생성해주었다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;추가로 레디스를 채팅기억장치로 쓰기 위해 CustomRedisChatMemoryRepository.java 를 생성하여 연결함&lt;/p&gt;
&lt;pre id=&quot;code_1773987022732&quot; class=&quot;java&quot; data-ke-language=&quot;java&quot; data-ke-type=&quot;codeblock&quot;&gt;&lt;code&gt;// 비동기 방식 webflux
public Mono&amp;lt;Void&amp;gt; ingestPdfStream(FilePart filePart) {
    // 1. FilePart의 내용을 Resource로 변환 (비동기 처리)
    return DataBufferUtils.join(filePart.content())
        .map(dataBuffer -&amp;gt; {
            // TikaReader는 InputStream이 필요하므로 버퍼에서 읽어옵니다.
            return new InputStreamResource(dataBuffer.asInputStream(true));
        })
        .publishOn(Schedulers.boundedElastic()) // 블로킹 작업(I/O, 임베딩)을 위한 스레드 전환
        .doOnNext(resource -&amp;gt; {
            // 2. PDF 읽기 및 분할
            TikaDocumentReader reader = new TikaDocumentReader(resource);
            List&amp;lt;Document&amp;gt; documents = reader.get();

            TokenTextSplitter splitter = new TokenTextSplitter(800, 100, 5, 10000, true);
            List&amp;lt;Document&amp;gt; splitDocuments = splitter.apply(documents);

            // 3. 벡터 DB 저장 (Gemini 임베딩 발생 지점)
            vectorStore.accept(splitDocuments);

            System.out.println(&quot;✅ Gemini 적재 완료: &quot; + splitDocuments.size() + &quot; 청크.&quot;);
        })
        .then(); // 결과값 없이 완료 신호만 보냄
}&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;http://localhost:8080/rag 에 접근해 확인할 수 있음&lt;/p&gt;
&lt;p&gt;&lt;figure class=&quot;imageblock alignCenter&quot; data-ke-mobileStyle=&quot;widthOrigin&quot; data-origin-width=&quot;372&quot; data-origin-height=&quot;814&quot;&gt;&lt;span data-url=&quot;https://blog.kakaocdn.net/dn/oQKSw/dJMcabKkgsY/cHMDpdd4R3jQK65kDSuaB0/img.png&quot; data-phocus=&quot;https://blog.kakaocdn.net/dn/oQKSw/dJMcabKkgsY/cHMDpdd4R3jQK65kDSuaB0/img.png&quot;&gt;&lt;img src=&quot;https://blog.kakaocdn.net/dn/oQKSw/dJMcabKkgsY/cHMDpdd4R3jQK65kDSuaB0/img.png&quot; srcset=&quot;https://img1.daumcdn.net/thumb/R1280x0/?scode=mtistory2&amp;fname=https%3A%2F%2Fblog.kakaocdn.net%2Fdn%2FoQKSw%2FdJMcabKkgsY%2FcHMDpdd4R3jQK65kDSuaB0%2Fimg.png&quot; onerror=&quot;this.onerror=null; this.src='//t1.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;372&quot; height=&quot;814&quot; data-origin-width=&quot;372&quot; data-origin-height=&quot;814&quot;/&gt;&lt;/span&gt;&lt;/figure&gt;
&lt;/p&gt;
&lt;h3 data-ke-size=&quot;size23&quot;&gt;검색&lt;/h3&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;클라이언트에게 만족감을 주기 위해 비동기 방식으로 데이터를 전달! (Webflux)&lt;/p&gt;
&lt;pre id=&quot;code_1773987366167&quot; class=&quot;java&quot; data-ke-language=&quot;java&quot; data-ke-type=&quot;codeblock&quot;&gt;&lt;code&gt;public Flux&amp;lt;String&amp;gt; askStream(String chatId, String message) {
    // 1. 유사 문서 검색 (Blocking 작업을 Flux 흐름으로 변환)
    return Mono.fromCallable(() -&amp;gt; {
                SearchRequest searchRequest = SearchRequest.builder()
                        .topK(4)
                        .query(message)
                        .build();
                return vectorStore.similaritySearch(searchRequest);
            })
            .subscribeOn(Schedulers.boundedElastic()) // 검색 작업은 워커 쓰레드에서!
            .flatMapMany(docs -&amp;gt; {
                // 2. 컨텍스트 조립
                String context = docs.stream()
                        .map(Document::getText)
                        .collect(Collectors.joining(&quot;\n\n&quot;));

                return chatClient.prompt()
                        .advisors(advisor -&amp;gt; advisor.param(&quot;chat_memory_conversation_id&quot;, chatId))
                        .system(sp -&amp;gt; sp.text(prompts.getPrompts().getRag()).param(&quot;context&quot;, context))
                        .user(message) 
                        .stream()
                        .content();
            });
}&lt;/code&gt;&lt;/pre&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;vectorStore 객체를 가져와 유사도 검색을 하면 된다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;+실제 운영에서는 하이브리드하게 유사도+단어 기반으로 검색을 구현해야 하는 경우가 생긴다고 함&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;h3 data-ke-size=&quot;size23&quot;&gt;Apache Tika&lt;/h3&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;문서를 파싱하는데 어떤 라이브러리를 썼냐면&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;아파치 티카라고 다양한 문서포맷을 텍스트로 추출해주는 오픈소스가 있는데&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;spring 에서 티카를 채택하여 제공하고 있다.&lt;/p&gt;
&lt;pre id=&quot;code_1773983761594&quot; class=&quot;bash&quot; data-ke-language=&quot;bash&quot; data-ke-type=&quot;codeblock&quot;&gt;&lt;code&gt;    implementation 'org.springframework.ai:spring-ai-tika-document-reader'&lt;/code&gt;&lt;/pre&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;h3 data-ke-size=&quot;size23&quot;&gt;문서 파싱의 아쉬움&lt;/h3&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;작년에 프로젝트를 수행하며 타 AI 업체에서 아파치 티카를 사용한 사례가 있었는데,&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;apache tika의 장점은 지원하는 문서포맷이 많고, 텍스트로 추출해주는 것이다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;하지만 단점은 문서에 따라 하드하게 추출할 수 없는 점이였다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;(이미지 추출, 페이지 분류, 이미지 검색 등...)&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;이 경우 python 서버를 따로 구축해서 사용하길 권함.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;물론 문서별로 파싱하는 것과 템플릿을 기준으로 딥하게 케이스를 나누면 아주 좋겠으나 힘들듯.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;그리고 파이선진영에 다양한 라이브러리가 존재하니 검색해보면 좋음.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;h3 data-ke-size=&quot;size23&quot;&gt;딥한 PDF 파싱 시 생각해 볼 점&lt;/h3&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;아래는 유튜브에서 1년간 AI 프로젝트 사례를 보고 정리한 내용으로&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;PDF파일을 파싱하려면 어떤 고민 Point가 있는지 점검할 수 있었다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;1. 페이지 단위 분할이 되는가?&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp; &amp;nbsp; ex) 출처를 알아내기 위해 필요함.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;2. 메타데이터 정보가 태깅되는가?&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp; &amp;nbsp; ex) 해당 문서의 몇페이지에서 발견한 내용인지, 파일명, 수정일, 작성자는 누구.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;3. 영역을 Crop 할 수 있는가?&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp; &amp;nbsp; ex) 머리말이나 꼬리말로 이상한 검색 결과가 나올 수 있어 파싱영역을 지정.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;4. 페이지 분할 형태를 읽을 수 있는가?&lt;br /&gt;&amp;nbsp; &amp;nbsp; ex) 뉴스페이퍼처럼 분할되어있는 문서를 보고 영역별로 파싱할 수 있어야함&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;5. 표(테이블)을 추출 할 수 있는가?&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp; &amp;nbsp; ex) 정형화된 데이터로 추출하며, 메타데이터를 추출해야 함.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;6. 이미지를 추출할 수 있는가?&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp; &amp;nbsp; ex) 커머스의 상품 이미지, 안전수칙, 그래프/차트 이미지와 같은 것을 데이터화 할 수 있어야 함.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;7. 페이지가 넘어가며 맥락이 유지되는가?&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp; &amp;nbsp; ex) 유사도 청크를 체크 해야함, &lt;span style=&quot;color: #333333; text-align: start;&quot;&gt;청크오버랩(Chunk Overlab)&lt;/span&gt;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p style=&quot;color: #333333; text-align: start;&quot; data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p style=&quot;color: #333333; text-align: start;&quot; data-ke-size=&quot;size16&quot;&gt;&lt;a style=&quot;background-color: #e6f5ff; color: #0070d1;&quot; href=&quot;https://flowlog.tistory.com/118&quot;&gt;[Spring AI] 1편: 토이 프로젝트 개요&lt;/a&gt;&lt;span&gt;&amp;nbsp;&lt;/span&gt;&lt;/p&gt;
&lt;p style=&quot;color: #333333; text-align: start;&quot; data-ke-size=&quot;size16&quot;&gt;&lt;a style=&quot;color: #0070d1;&quot; href=&quot;https://flowlog.tistory.com/120&quot;&gt;&lt;span style=&quot;color: #333333; text-align: start;&quot;&gt;[Spring AI]&lt;span&gt;&lt;span&gt;&amp;nbsp;&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;3편: Database 쿼리생성 및 실행을 위한 HITL(Human in the Loop)&lt;/a&gt;&lt;/p&gt;
&lt;p style=&quot;color: #333333; text-align: start;&quot; data-ke-size=&quot;size16&quot;&gt;&lt;a style=&quot;color: #0070d1;&quot; href=&quot;http://%20https://flowlog.tistory.com/121&quot;&gt;&lt;span style=&quot;color: #333333; text-align: start;&quot;&gt;[Spring AI]&lt;span&gt;&lt;span&gt;&amp;nbsp;&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;4편: MCP 서버 구동과 실행&lt;/a&gt;&lt;/p&gt;
&lt;figure id=&quot;og_1773987918292&quot; contenteditable=&quot;false&quot; data-ke-type=&quot;opengraph&quot; data-ke-align=&quot;alignCenter&quot; data-og-type=&quot;object&quot; data-og-title=&quot;GitHub - joonhyeok95/spring-ai-google-gen: 제미나이 무료API로 구현하는 PDF RAG, 데이터베이스 추출, LLM 서비&quot; data-og-description=&quot;제미나이 무료API로 구현하는 PDF RAG, 데이터베이스 추출, LLM 서비스. Contribute to joonhyeok95/spring-ai-google-gen development by creating an account on GitHub.&quot; data-og-host=&quot;github.com&quot; data-og-source-url=&quot;https://github.com/joonhyeok95/spring-ai-google-gen&quot; data-og-url=&quot;https://github.com/joonhyeok95/spring-ai-google-gen&quot; data-og-image=&quot;https://scrap.kakaocdn.net/dn/c1UuW0/dJMb84p7QiQ/KG3jydPcJmwjwtz7Shu5H0/img.png?width=1200&amp;amp;height=600&amp;amp;face=0_0_1200_600,https://scrap.kakaocdn.net/dn/bgJou5/dJMb84XXTFk/ToN0tR8sT04IkWOkk1KWs1/img.png?width=1200&amp;amp;height=600&amp;amp;face=0_0_1200_600,https://scrap.kakaocdn.net/dn/bmuvXK/dJMb8952K6c/kKrtQ3U9krlYUTmBAG6Qj0/img.png?width=1026&amp;amp;height=907&amp;amp;face=0_0_1026_907&quot;&gt;&lt;a href=&quot;https://github.com/joonhyeok95/spring-ai-google-gen&quot; target=&quot;_blank&quot; rel=&quot;noopener&quot; data-source-url=&quot;https://github.com/joonhyeok95/spring-ai-google-gen&quot;&gt;
&lt;div class=&quot;og-image&quot; style=&quot;background-image: url('https://scrap.kakaocdn.net/dn/c1UuW0/dJMb84p7QiQ/KG3jydPcJmwjwtz7Shu5H0/img.png?width=1200&amp;amp;height=600&amp;amp;face=0_0_1200_600,https://scrap.kakaocdn.net/dn/bgJou5/dJMb84XXTFk/ToN0tR8sT04IkWOkk1KWs1/img.png?width=1200&amp;amp;height=600&amp;amp;face=0_0_1200_600,https://scrap.kakaocdn.net/dn/bmuvXK/dJMb8952K6c/kKrtQ3U9krlYUTmBAG6Qj0/img.png?width=1026&amp;amp;height=907&amp;amp;face=0_0_1026_907');&quot;&gt;&amp;nbsp;&lt;/div&gt;
&lt;div class=&quot;og-text&quot;&gt;
&lt;p class=&quot;og-title&quot; data-ke-size=&quot;size16&quot;&gt;GitHub - joonhyeok95/spring-ai-google-gen: 제미나이 무료API로 구현하는 PDF RAG, 데이터베이스 추출, LLM 서비&lt;/p&gt;
&lt;p class=&quot;og-desc&quot; data-ke-size=&quot;size16&quot;&gt;제미나이 무료API로 구현하는 PDF RAG, 데이터베이스 추출, LLM 서비스. Contribute to joonhyeok95/spring-ai-google-gen development by creating an account on GitHub.&lt;/p&gt;
&lt;p class=&quot;og-host&quot; data-ke-size=&quot;size16&quot;&gt;github.com&lt;/p&gt;
&lt;/div&gt;
&lt;/a&gt;&lt;/figure&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;</description>
      <category>개발/Spring</category>
      <author>joon95</author>
      <guid isPermaLink="true">https://flowlog.tistory.com/119</guid>
      <comments>https://flowlog.tistory.com/119#entry119comment</comments>
      <pubDate>Fri, 20 Mar 2026 14:13:36 +0900</pubDate>
    </item>
    <item>
      <title>[Spring AI] 1편: 토이 프로젝트 개요</title>
      <link>https://flowlog.tistory.com/118</link>
      <description>&lt;p data-ke-size=&quot;size16&quot;&gt;spring AI 에서는 어떤식으로 활용될 수 있을지&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;2주간 구현해본 토이프로젝트(포스팅 맨하단에 github 링크)이며,&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;완전 &lt;b&gt;무료&lt;/b&gt;로 사용할 수 있는 Gemini 를 활용했다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;span style=&quot;background-color: #ffffff; color: #1f2328; text-align: start;&quot;&gt;genAI 모델: gemini-3.1-flash-lite-preview&lt;/span&gt; (분당 15회 제한이 있으니 천천히..ㅎ)&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;Embedd 모델 : &lt;span style=&quot;background-color: #ffffff; color: #1f2328; text-align: start;&quot;&gt;gemini-embedding-001&lt;/span&gt;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;올라마 임베딩 모델 : &lt;span style=&quot;background-color: #ffffff; color: #1f2328; text-align: start;&quot;&gt;all-minilm&lt;/span&gt;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;span style=&quot;background-color: #ffffff; color: #1f2328; text-align: start;&quot;&gt;- 로컬에서 돌리려고 올라마 세팅을 했다가, &lt;br /&gt;&amp;nbsp; Rag 임베딩 수준이 처참(내 노트북 기준)해서 제미나이로 변경함&lt;/span&gt;&lt;/p&gt;
&lt;h3 data-ke-size=&quot;size23&quot;&gt;개발환경&lt;/h3&gt;
&lt;ul style=&quot;list-style-type: disc;&quot; data-ke-list-type=&quot;disc&quot;&gt;
&lt;li&gt;springboot : 3.5.11&lt;/li&gt;
&lt;li&gt;spring AI : 1.1.2&lt;/li&gt;
&lt;li&gt;openJDK : 17&lt;/li&gt;
&lt;li&gt;Gradle 8.14.4&lt;/li&gt;
&lt;li&gt;VectorDB: pgvector 0.8.2&lt;/li&gt;
&lt;li&gt;Database: postgresql(16.13-1)&lt;/li&gt;
&lt;li&gt;Redis: version 3.0.504&lt;/li&gt;
&lt;li&gt;UI : thymeleaf&lt;/li&gt;
&lt;/ul&gt;
&lt;h3 data-ke-size=&quot;size23&quot;&gt;Workflow&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-origin-width=&quot;1830&quot; data-origin-height=&quot;1036&quot;&gt;&lt;span data-url=&quot;https://blog.kakaocdn.net/dn/b0Jaii/dJMcach8t5A/yTw2ymqONzm7FbV4Zgis7K/img.png&quot; data-phocus=&quot;https://blog.kakaocdn.net/dn/b0Jaii/dJMcach8t5A/yTw2ymqONzm7FbV4Zgis7K/img.png&quot; data-alt=&quot;application workflow&quot;&gt;&lt;img src=&quot;https://blog.kakaocdn.net/dn/b0Jaii/dJMcach8t5A/yTw2ymqONzm7FbV4Zgis7K/img.png&quot; srcset=&quot;https://img1.daumcdn.net/thumb/R1280x0/?scode=mtistory2&amp;fname=https%3A%2F%2Fblog.kakaocdn.net%2Fdn%2Fb0Jaii%2FdJMcach8t5A%2FyTw2ymqONzm7FbV4Zgis7K%2Fimg.png&quot; onerror=&quot;this.onerror=null; this.src='//t1.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;1830&quot; height=&quot;1036&quot; data-origin-width=&quot;1830&quot; data-origin-height=&quot;1036&quot;/&gt;&lt;/span&gt;&lt;figcaption&gt;application workflow&lt;/figcaption&gt;
&lt;/figure&gt;
&lt;/p&gt;
&lt;h4 data-ke-size=&quot;size20&quot;&gt;프로세스 흐름&lt;/h4&gt;
&lt;ol style=&quot;list-style-type: decimal;&quot; data-ke-list-type=&quot;decimal&quot;&gt;
&lt;li&gt;사용자가 질문을 던진다.&lt;/li&gt;
&lt;li&gt;Orchestrator 에서 질문이 어떤 타입인지 추론하여 라우팅한다.&lt;/li&gt;
&lt;li&gt;라우팅된 Agent Layer 에서 각각 정의된 시스템프롬프팅을 통해 Tool을 사용한다.&lt;/li&gt;
&lt;/ol&gt;
&lt;h4 data-ke-size=&quot;size20&quot;&gt;Agent 종류&lt;/h4&gt;
&lt;ul style=&quot;list-style-type: disc;&quot; data-ke-list-type=&quot;disc&quot;&gt;
&lt;li&gt;LLM : 기본적인 질문&lt;/li&gt;
&lt;li&gt;RAG : 문서적재(임베딩), 문서 유사도 검색&lt;/li&gt;
&lt;li&gt;DATABASE : SQL 생성 및 실행&lt;/li&gt;
&lt;li&gt;협업 : 특정 Agent를 두개 이상 합쳐서 사용하는 경우&lt;/li&gt;
&lt;li&gt;MCP : LLM이 사용할 수 있는 프로토콜&lt;/li&gt;
&lt;/ul&gt;
&lt;h3 data-ke-size=&quot;size23&quot;&gt;구현된 화면&lt;/h3&gt;
&lt;p&gt;&lt;figure class=&quot;imageblock alignCenter&quot; data-ke-mobileStyle=&quot;widthOrigin&quot; data-origin-width=&quot;539&quot; data-origin-height=&quot;742&quot;&gt;&lt;span data-url=&quot;https://blog.kakaocdn.net/dn/FuLTY/dJMcahKwDQe/sx17X9cXwU8xu9wuVuQ3u1/img.png&quot; data-phocus=&quot;https://blog.kakaocdn.net/dn/FuLTY/dJMcahKwDQe/sx17X9cXwU8xu9wuVuQ3u1/img.png&quot; data-alt=&quot;협업 에이전트(SQL+차트)&quot;&gt;&lt;img src=&quot;https://blog.kakaocdn.net/dn/FuLTY/dJMcahKwDQe/sx17X9cXwU8xu9wuVuQ3u1/img.png&quot; srcset=&quot;https://img1.daumcdn.net/thumb/R1280x0/?scode=mtistory2&amp;fname=https%3A%2F%2Fblog.kakaocdn.net%2Fdn%2FFuLTY%2FdJMcahKwDQe%2Fsx17X9cXwU8xu9wuVuQ3u1%2Fimg.png&quot; onerror=&quot;this.onerror=null; this.src='//t1.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;539&quot; height=&quot;742&quot; data-origin-width=&quot;539&quot; data-origin-height=&quot;742&quot;/&gt;&lt;/span&gt;&lt;figcaption&gt;협업 에이전트(SQL+차트)&lt;/figcaption&gt;
&lt;/figure&gt;
&lt;figure class=&quot;imageblock alignCenter&quot; data-ke-mobileStyle=&quot;widthOrigin&quot; data-origin-width=&quot;891&quot; data-origin-height=&quot;352&quot;&gt;&lt;span data-url=&quot;https://blog.kakaocdn.net/dn/287sS/dJMcajnWPS0/K3h60uU0t79JUKCnztRCQ0/img.png&quot; data-phocus=&quot;https://blog.kakaocdn.net/dn/287sS/dJMcajnWPS0/K3h60uU0t79JUKCnztRCQ0/img.png&quot; data-alt=&quot;구글 MCP) 일정관리 Agent&quot;&gt;&lt;img src=&quot;https://blog.kakaocdn.net/dn/287sS/dJMcajnWPS0/K3h60uU0t79JUKCnztRCQ0/img.png&quot; srcset=&quot;https://img1.daumcdn.net/thumb/R1280x0/?scode=mtistory2&amp;fname=https%3A%2F%2Fblog.kakaocdn.net%2Fdn%2F287sS%2FdJMcajnWPS0%2FK3h60uU0t79JUKCnztRCQ0%2Fimg.png&quot; onerror=&quot;this.onerror=null; this.src='//t1.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;891&quot; height=&quot;352&quot; data-origin-width=&quot;891&quot; data-origin-height=&quot;352&quot;/&gt;&lt;/span&gt;&lt;figcaption&gt;구글 MCP) 일정관리 Agent&lt;/figcaption&gt;
&lt;/figure&gt;
&lt;figure class=&quot;imageblock alignCenter&quot; data-ke-mobileStyle=&quot;widthOrigin&quot; data-origin-width=&quot;1211&quot; data-origin-height=&quot;508&quot;&gt;&lt;span data-url=&quot;https://blog.kakaocdn.net/dn/bACZWY/dJMcaaYUekZ/7RTwAsbkAG8swJ1zrJufK1/img.png&quot; data-phocus=&quot;https://blog.kakaocdn.net/dn/bACZWY/dJMcaaYUekZ/7RTwAsbkAG8swJ1zrJufK1/img.png&quot; data-alt=&quot;노션 MCP) 시인 Agent&quot;&gt;&lt;img src=&quot;https://blog.kakaocdn.net/dn/bACZWY/dJMcaaYUekZ/7RTwAsbkAG8swJ1zrJufK1/img.png&quot; srcset=&quot;https://img1.daumcdn.net/thumb/R1280x0/?scode=mtistory2&amp;fname=https%3A%2F%2Fblog.kakaocdn.net%2Fdn%2FbACZWY%2FdJMcaaYUekZ%2F7RTwAsbkAG8swJ1zrJufK1%2Fimg.png&quot; onerror=&quot;this.onerror=null; this.src='//t1.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;1211&quot; height=&quot;508&quot; data-origin-width=&quot;1211&quot; data-origin-height=&quot;508&quot;/&gt;&lt;/span&gt;&lt;figcaption&gt;노션 MCP) 시인 Agent&lt;/figcaption&gt;
&lt;/figure&gt;
&lt;figure class=&quot;imageblock alignCenter&quot; data-ke-mobileStyle=&quot;widthOrigin&quot; data-origin-width=&quot;538&quot; data-origin-height=&quot;735&quot;&gt;&lt;span data-url=&quot;https://blog.kakaocdn.net/dn/nQI0Y/dJMcacJdPTS/t8OKnHsIeMDMEO31w5jvv1/img.png&quot; data-phocus=&quot;https://blog.kakaocdn.net/dn/nQI0Y/dJMcacJdPTS/t8OKnHsIeMDMEO31w5jvv1/img.png&quot; data-alt=&quot;SQL Agent 자가 비판&quot;&gt;&lt;img src=&quot;https://blog.kakaocdn.net/dn/nQI0Y/dJMcacJdPTS/t8OKnHsIeMDMEO31w5jvv1/img.png&quot; srcset=&quot;https://img1.daumcdn.net/thumb/R1280x0/?scode=mtistory2&amp;fname=https%3A%2F%2Fblog.kakaocdn.net%2Fdn%2FnQI0Y%2FdJMcacJdPTS%2Ft8OKnHsIeMDMEO31w5jvv1%2Fimg.png&quot; onerror=&quot;this.onerror=null; this.src='//t1.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;538&quot; height=&quot;735&quot; data-origin-width=&quot;538&quot; data-origin-height=&quot;735&quot;/&gt;&lt;/span&gt;&lt;figcaption&gt;SQL Agent 자가 비판&lt;/figcaption&gt;
&lt;/figure&gt;
&lt;figure class=&quot;imageblock alignCenter&quot; data-ke-mobileStyle=&quot;widthOrigin&quot; data-origin-width=&quot;546&quot; data-origin-height=&quot;755&quot;&gt;&lt;span data-url=&quot;https://blog.kakaocdn.net/dn/bBbtwA/dJMcaaxRrKW/Mrpu2zgYk6rcAbRiT2MTkK/img.png&quot; data-phocus=&quot;https://blog.kakaocdn.net/dn/bBbtwA/dJMcaaxRrKW/Mrpu2zgYk6rcAbRiT2MTkK/img.png&quot; data-alt=&quot;협업(SQL + 디지털마케터)&quot;&gt;&lt;img src=&quot;https://blog.kakaocdn.net/dn/bBbtwA/dJMcaaxRrKW/Mrpu2zgYk6rcAbRiT2MTkK/img.png&quot; srcset=&quot;https://img1.daumcdn.net/thumb/R1280x0/?scode=mtistory2&amp;fname=https%3A%2F%2Fblog.kakaocdn.net%2Fdn%2FbBbtwA%2FdJMcaaxRrKW%2FMrpu2zgYk6rcAbRiT2MTkK%2Fimg.png&quot; onerror=&quot;this.onerror=null; this.src='//t1.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;546&quot; height=&quot;755&quot; data-origin-width=&quot;546&quot; data-origin-height=&quot;755&quot;/&gt;&lt;/span&gt;&lt;figcaption&gt;협업(SQL + 디지털마케터)&lt;/figcaption&gt;
&lt;/figure&gt;
&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p&gt;&lt;figure class=&quot;imageblock alignCenter&quot; data-ke-mobileStyle=&quot;widthOrigin&quot; data-origin-width=&quot;1026&quot; data-origin-height=&quot;907&quot;&gt;&lt;span data-url=&quot;https://blog.kakaocdn.net/dn/cuRmqe/dJMcacoTTtG/bKJUWXe4ubniFQd7NRNru0/img.png&quot; data-phocus=&quot;https://blog.kakaocdn.net/dn/cuRmqe/dJMcacoTTtG/bKJUWXe4ubniFQd7NRNru0/img.png&quot; data-alt=&quot;LLM Agent와 RAG Agent&quot;&gt;&lt;img src=&quot;https://blog.kakaocdn.net/dn/cuRmqe/dJMcacoTTtG/bKJUWXe4ubniFQd7NRNru0/img.png&quot; srcset=&quot;https://img1.daumcdn.net/thumb/R1280x0/?scode=mtistory2&amp;fname=https%3A%2F%2Fblog.kakaocdn.net%2Fdn%2FcuRmqe%2FdJMcacoTTtG%2FbKJUWXe4ubniFQd7NRNru0%2Fimg.png&quot; onerror=&quot;this.onerror=null; this.src='//t1.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;1026&quot; height=&quot;907&quot; data-origin-width=&quot;1026&quot; data-origin-height=&quot;907&quot;/&gt;&lt;/span&gt;&lt;figcaption&gt;LLM Agent와 RAG Agent&lt;/figcaption&gt;
&lt;/figure&gt;
&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;포스팅 아래의 순서로 작성함.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;a href=&quot;https://flowlog.tistory.com/119&quot; target=&quot;_blank&quot; rel=&quot;noopener&quot;&gt;[Spring AI] 2편: RAG 문서 임베딩&lt;/a&gt;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;a href=&quot;https://flowlog.tistory.com/120&quot; target=&quot;_blank&quot; rel=&quot;noopener&quot;&gt;&lt;span style=&quot;color: #333333; text-align: start;&quot;&gt;[Spring AI]&lt;span&gt; &lt;/span&gt;&lt;/span&gt;3편: Database 쿼리생성 및 실행을 위한 HITL(Human in the Loop)&lt;/a&gt;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;a href=&quot;http://%20https://flowlog.tistory.com/121&quot; target=&quot;_blank&quot; rel=&quot;noopener&quot;&gt;&lt;span style=&quot;color: #333333; text-align: start;&quot;&gt;[Spring AI]&lt;span&gt; &lt;/span&gt;&lt;/span&gt;4편: MCP 서버 구동과 실행&lt;/a&gt;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;figure id=&quot;og_1773981101086&quot; contenteditable=&quot;false&quot; data-ke-type=&quot;opengraph&quot; data-ke-align=&quot;alignCenter&quot; data-og-type=&quot;object&quot; data-og-title=&quot;GitHub - joonhyeok95/spring-ai-google-gen: 제미나이 무료API로 구현하는 PDF RAG, 데이터베이스 추출, LLM 서비&quot; data-og-description=&quot;제미나이 무료API로 구현하는 PDF RAG, 데이터베이스 추출, LLM 서비스. Contribute to joonhyeok95/spring-ai-google-gen development by creating an account on GitHub.&quot; data-og-host=&quot;github.com&quot; data-og-source-url=&quot;https://github.com/joonhyeok95/spring-ai-google-gen&quot; data-og-url=&quot;https://github.com/joonhyeok95/spring-ai-google-gen&quot; data-og-image=&quot;https://scrap.kakaocdn.net/dn/c1UuW0/dJMb84p7QiQ/KG3jydPcJmwjwtz7Shu5H0/img.png?width=1200&amp;amp;height=600&amp;amp;face=0_0_1200_600,https://scrap.kakaocdn.net/dn/bgJou5/dJMb84XXTFk/ToN0tR8sT04IkWOkk1KWs1/img.png?width=1200&amp;amp;height=600&amp;amp;face=0_0_1200_600,https://scrap.kakaocdn.net/dn/bmuvXK/dJMb8952K6c/kKrtQ3U9krlYUTmBAG6Qj0/img.png?width=1026&amp;amp;height=907&amp;amp;face=0_0_1026_907&quot;&gt;&lt;a href=&quot;https://github.com/joonhyeok95/spring-ai-google-gen&quot; target=&quot;_blank&quot; rel=&quot;noopener&quot; data-source-url=&quot;https://github.com/joonhyeok95/spring-ai-google-gen&quot;&gt;
&lt;div class=&quot;og-image&quot; style=&quot;background-image: url('https://scrap.kakaocdn.net/dn/c1UuW0/dJMb84p7QiQ/KG3jydPcJmwjwtz7Shu5H0/img.png?width=1200&amp;amp;height=600&amp;amp;face=0_0_1200_600,https://scrap.kakaocdn.net/dn/bgJou5/dJMb84XXTFk/ToN0tR8sT04IkWOkk1KWs1/img.png?width=1200&amp;amp;height=600&amp;amp;face=0_0_1200_600,https://scrap.kakaocdn.net/dn/bmuvXK/dJMb8952K6c/kKrtQ3U9krlYUTmBAG6Qj0/img.png?width=1026&amp;amp;height=907&amp;amp;face=0_0_1026_907');&quot;&gt;&amp;nbsp;&lt;/div&gt;
&lt;div class=&quot;og-text&quot;&gt;
&lt;p class=&quot;og-title&quot; data-ke-size=&quot;size16&quot;&gt;GitHub - joonhyeok95/spring-ai-google-gen: 제미나이 무료API로 구현하는 PDF RAG, 데이터베이스 추출, LLM 서비&lt;/p&gt;
&lt;p class=&quot;og-desc&quot; data-ke-size=&quot;size16&quot;&gt;제미나이 무료API로 구현하는 PDF RAG, 데이터베이스 추출, LLM 서비스. Contribute to joonhyeok95/spring-ai-google-gen development by creating an account on GitHub.&lt;/p&gt;
&lt;p class=&quot;og-host&quot; data-ke-size=&quot;size16&quot;&gt;github.com&lt;/p&gt;
&lt;/div&gt;
&lt;/a&gt;&lt;/figure&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;</description>
      <category>개발/Spring</category>
      <author>joon95</author>
      <guid isPermaLink="true">https://flowlog.tistory.com/118</guid>
      <comments>https://flowlog.tistory.com/118#entry118comment</comments>
      <pubDate>Fri, 20 Mar 2026 14:04:46 +0900</pubDate>
    </item>
    <item>
      <title>요새 Hot한 MCP, 자동으로 개발 시켜보자</title>
      <link>https://flowlog.tistory.com/117</link>
      <description>&lt;p data-ke-size=&quot;size16&quot;&gt;이번주에 팀장님이 A2A와 MCP로 시대가 빠르게 변하고 있다고 알려주셔서 잠시 시간을 내어 테스트를 해보았다.&lt;/p&gt;
&lt;h2 data-ke-size=&quot;size26&quot;&gt;MCP란?&lt;/h2&gt;
&lt;p data-ke-size=&quot;size18&quot;&gt;&lt;span style=&quot;font-family: AppleSDGothicNeo-Regular, 'Malgun Gothic', '맑은 고딕', dotum, 돋움, sans-serif;&quot;&gt;&lt;b&gt;Model Context Protocol&lt;/b&gt;&lt;/span&gt;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;span style=&quot;font-family: AppleSDGothicNeo-Regular, 'Malgun Gothic', '맑은 고딕', dotum, 돋움, sans-serif;&quot;&gt;&lt;b&gt;개념&lt;/b&gt;&lt;span style=&quot;background-color: oklch(0.99 0.004 106.471); color: oklch(0.304 0.04 213.681); letter-spacing: 0px;&quot;&gt;: MCP는 Anthropic(Claude 개발사)이 만든 오픈 표준 프로토콜로, AI 모델(예: Claude)이 외부 시스템(파일시스템, 데이터베이스, API 등)과 안전하고 구조화된 방식으로 상호작용할 수 있도록 해줍니다&lt;/span&gt;&lt;/span&gt;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;span style=&quot;font-family: AppleSDGothicNeo-Regular, 'Malgun Gothic', '맑은 고딕', dotum, 돋움, sans-serif;&quot;&gt;&lt;b&gt;주요 역할&lt;/b&gt;: Claude 같은 LLM이 로컬 PC, 서버, 클라우드 등 다양한 리소스와 직접 연결되어 데이터를 읽거나, 도구를 실행하거나, 파일을 관리하는 등 실제 업무 자동화를 할 수 있게 합니다&lt;/span&gt;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;span style=&quot;font-family: AppleSDGothicNeo-Regular, 'Malgun Gothic', '맑은 고딕', dotum, 돋움, sans-serif;&quot;&gt;&lt;b&gt;비유&lt;/b&gt;: USB Type-C처럼, 다양한 시스템과 AI를 손쉽게 연결하는 &quot;커넥터&quot; 역할&lt;/span&gt;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;span style=&quot;font-family: AppleSDGothicNeo-Regular, 'Malgun Gothic', '맑은 고딕', dotum, 돋움, sans-serif;&quot;&gt;&lt;b&gt;활용 예시&lt;/b&gt;&lt;/span&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;span style=&quot;font-family: AppleSDGothicNeo-Regular, 'Malgun Gothic', '맑은 고딕', dotum, 돋움, sans-serif;&quot;&gt;파일 생성/수정/삭제, 폴더 정리&lt;/span&gt;&lt;/li&gt;
&lt;li&gt;&lt;span style=&quot;font-family: AppleSDGothicNeo-Regular, 'Malgun Gothic', '맑은 고딕', dotum, 돋움, sans-serif;&quot;&gt;로컬 코드 분석 및 개발 지원&lt;/span&gt;&lt;/li&gt;
&lt;li&gt;&lt;span style=&quot;font-family: AppleSDGothicNeo-Regular, 'Malgun Gothic', '맑은 고딕', dotum, 돋움, sans-serif;&quot;&gt;데이터 파일 읽기 및 자동화&lt;/span&gt;&lt;/li&gt;
&lt;li&gt;&lt;span style=&quot;font-family: AppleSDGothicNeo-Regular, 'Malgun Gothic', '맑은 고딕', dotum, 돋움, sans-serif;&quot;&gt;이메일/일정 관리 자동화&lt;/span&gt;&lt;/li&gt;
&lt;/ul&gt;
&lt;h2 data-ke-size=&quot;size26&quot;&gt;&lt;span style=&quot;font-family: AppleSDGothicNeo-Regular, 'Malgun Gothic', '맑은 고딕', dotum, 돋움, sans-serif;&quot;&gt;A2A란?&lt;/span&gt;&lt;/h2&gt;
&lt;p data-ke-size=&quot;size18&quot;&gt;&lt;span style=&quot;font-family: AppleSDGothicNeo-Regular, 'Malgun Gothic', '맑은 고딕', dotum, 돋움, sans-serif;&quot;&gt;&lt;b&gt;Agent-to-Agent Protocol&lt;/b&gt;&lt;/span&gt;&lt;/p&gt;
&lt;p data-ke-size=&quot;size18&quot;&gt;&lt;span style=&quot;font-family: AppleSDGothicNeo-Regular, 'Malgun Gothic', '맑은 고딕', dotum, 돋움, sans-serif;&quot;&gt;&lt;b&gt;개념&lt;/b&gt;: A2A는 Google이 주도해 만든 개방형 프로토콜로, 여러 AI 에이전트(Claude, GPT, 커스텀 등)가 서로 협력하고 통신할 수 있게 해주는 표준입니다&lt;/span&gt;&lt;/p&gt;
&lt;p data-ke-size=&quot;size18&quot;&gt;&lt;span style=&quot;font-family: AppleSDGothicNeo-Regular, 'Malgun Gothic', '맑은 고딕', dotum, 돋움, sans-serif;&quot;&gt;&lt;b&gt;주요 역할&lt;/b&gt;&lt;span style=&quot;background-color: oklch(0.99 0.004 106.471); color: oklch(0.304 0.04 213.681); font-size: 16px; letter-spacing: 0px;&quot;&gt;: 각각 독립적으로 동작하는 여러 AI 에이전트가 서로를 발견하고, 역할을 분담하며, 협업해 복잡한 목표를 달성할 수 있게 합니다&lt;/span&gt;&lt;/span&gt;&lt;/p&gt;
&lt;p data-ke-size=&quot;size18&quot;&gt;&lt;span style=&quot;font-family: AppleSDGothicNeo-Regular, 'Malgun Gothic', '맑은 고딕', dotum, 돋움, sans-serif;&quot;&gt;&lt;b&gt;비유&lt;/b&gt;&lt;span style=&quot;background-color: oklch(0.99 0.004 106.471); color: oklch(0.304 0.04 213.681); font-size: 16px; letter-spacing: 0px;&quot;&gt;: 여러 명의 전문가(에이전트)가 팀을 이뤄 각자 역할을 맡아 협력하는 &quot;팀워크 프로토콜&quot;&lt;/span&gt;&lt;/span&gt;&lt;/p&gt;
&lt;p data-ke-size=&quot;size18&quot;&gt;&lt;span style=&quot;font-family: AppleSDGothicNeo-Regular, 'Malgun Gothic', '맑은 고딕', dotum, 돋움, sans-serif;&quot;&gt;&lt;b&gt;핵심 기능&lt;/b&gt;&lt;span style=&quot;background-color: oklch(0.99 0.004 106.471); color: oklch(0.304 0.04 213.681); font-size: 16px; letter-spacing: 0px;&quot;&gt;:&lt;/span&gt;&lt;/span&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;span style=&quot;font-family: AppleSDGothicNeo-Regular, 'Malgun Gothic', '맑은 고딕', dotum, 돋움, sans-serif;&quot;&gt;에이전트가 자신의 기능과 엔드포인트를 &quot;에이전트 카드&quot;라는 표준 JSON 파일로 공개&lt;/span&gt;&lt;/li&gt;
&lt;li&gt;&lt;span style=&quot;font-family: AppleSDGothicNeo-Regular, 'Malgun Gothic', '맑은 고딕', dotum, 돋움, sans-serif;&quot;&gt;다른 에이전트가 이 카드를 읽고 적합한 파트너를 찾아 작업을 요청&lt;/span&gt;&lt;/li&gt;
&lt;li&gt;HTTP/JSON-RPC 기반 통신, 실시간 스트리밍 지원&lt;/li&gt;
&lt;li style=&quot;list-style-type: none;&quot;&gt;&amp;nbsp;&lt;/li&gt;
&lt;/ul&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;h2 data-ke-size=&quot;size26&quot;&gt;실제 활용 예시&lt;/h2&gt;
&lt;p data-ke-size=&quot;size18&quot;&gt;MCP: Claude가 내 PC의 파일을 읽고, DB에서 데이터 추출, API 호출 등 &quot;외부 도구와 직접 연결&quot;해 자동화된 업무 처리.&lt;br /&gt;A2A:&amp;nbsp;여행&amp;nbsp;예약처럼&amp;nbsp;항공권,&amp;nbsp;날씨,&amp;nbsp;숙박,&amp;nbsp;액티비티&amp;nbsp;등&amp;nbsp;각&amp;nbsp;역할별&amp;nbsp;에이전트가&amp;nbsp;서로&amp;nbsp;협업해&amp;nbsp;전체&amp;nbsp;일정을&amp;nbsp;구성. &lt;br /&gt;&lt;br /&gt;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;MCP 툴로는 &lt;b&gt;Claude&lt;/b&gt; 라는 친구를 활용하는데 아래 링크를 통해 Windows 에서 filesystem을 사용할 수 있도록 세팅한다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;figure id=&quot;og_1748181045478&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;For Claude Desktop Users - Model Context Protocol&quot; data-og-description=&quot;In this tutorial, you will extend Claude for Desktop so that it can read from your computer&amp;rsquo;s file system, write new files, move files, and even search files. Don&amp;rsquo;t worry &amp;mdash; it will ask you for your permission before executing these actions! 1. Downlo&quot; data-og-host=&quot;modelcontextprotocol.io&quot; data-og-source-url=&quot;https://modelcontextprotocol.io/quickstart/user#windows&quot; data-og-url=&quot;https://modelcontextprotocol.io/quickstart/user#windows&quot; data-og-image=&quot;https://scrap.kakaocdn.net/dn/nFzs2/hyYYzCzQfc/EykZqXCkXQgIGYlMXiAo9k/img.png?width=1200&amp;amp;height=630&amp;amp;face=0_0_1200_630&quot;&gt;&lt;a href=&quot;https://modelcontextprotocol.io/quickstart/user#windows&quot; target=&quot;_blank&quot; rel=&quot;noopener&quot; data-source-url=&quot;https://modelcontextprotocol.io/quickstart/user#windows&quot;&gt;
&lt;div class=&quot;og-image&quot; style=&quot;background-image: url('https://scrap.kakaocdn.net/dn/nFzs2/hyYYzCzQfc/EykZqXCkXQgIGYlMXiAo9k/img.png?width=1200&amp;amp;height=630&amp;amp;face=0_0_1200_630');&quot;&gt;&amp;nbsp;&lt;/div&gt;
&lt;div class=&quot;og-text&quot;&gt;
&lt;p class=&quot;og-title&quot; data-ke-size=&quot;size16&quot;&gt;For Claude Desktop Users - Model Context Protocol&lt;/p&gt;
&lt;p class=&quot;og-desc&quot; data-ke-size=&quot;size16&quot;&gt;In this tutorial, you will extend Claude for Desktop so that it can read from your computer&amp;rsquo;s file system, write new files, move files, and even search files. Don&amp;rsquo;t worry &amp;mdash; it will ask you for your permission before executing these actions! 1. Downlo&lt;/p&gt;
&lt;p class=&quot;og-host&quot; data-ke-size=&quot;size16&quot;&gt;modelcontextprotocol.io&lt;/p&gt;
&lt;/div&gt;
&lt;/a&gt;&lt;/figure&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;세팅내용&lt;/p&gt;
&lt;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;size16&quot;&gt;window 설치형 Claude 를 켜서 개발자 설정을 열어 '설정 편집'을 누른다.&lt;/p&gt;
&lt;figure class=&quot;imageblock alignCenter&quot; data-ke-mobileStyle=&quot;widthOrigin&quot; data-origin-width=&quot;989&quot; data-origin-height=&quot;673&quot;&gt;&lt;span data-url=&quot;https://blog.kakaocdn.net/dn/JaYJk/btsObQ2zzdu/TnyUPBzmdOd5tuvA02fbB0/img.png&quot; data-phocus=&quot;https://blog.kakaocdn.net/dn/JaYJk/btsObQ2zzdu/TnyUPBzmdOd5tuvA02fbB0/img.png&quot;&gt;&lt;img src=&quot;https://blog.kakaocdn.net/dn/JaYJk/btsObQ2zzdu/TnyUPBzmdOd5tuvA02fbB0/img.png&quot; srcset=&quot;https://img1.daumcdn.net/thumb/R1280x0/?scode=mtistory2&amp;fname=https%3A%2F%2Fblog.kakaocdn.net%2Fdn%2FJaYJk%2FbtsObQ2zzdu%2FTnyUPBzmdOd5tuvA02fbB0%2Fimg.png&quot; onerror=&quot;this.onerror=null; this.src='//t1.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;989&quot; height=&quot;673&quot; data-origin-width=&quot;989&quot; data-origin-height=&quot;673&quot;/&gt;&lt;/span&gt;&lt;/figure&gt;

&lt;p data-ke-size=&quot;size16&quot;&gt;그럼 디렉토리에 파일이 하나 생겼는데 여기에 내용을 추가한다.&lt;/p&gt;
&lt;figure class=&quot;imageblock alignCenter&quot; data-ke-mobileStyle=&quot;widthOrigin&quot; data-origin-width=&quot;657&quot; data-origin-height=&quot;547&quot;&gt;&lt;span data-url=&quot;https://blog.kakaocdn.net/dn/bgiVT9/btsObyairNg/Ufk2GQDAm6uGKtQBH4zmx0/img.png&quot; data-phocus=&quot;https://blog.kakaocdn.net/dn/bgiVT9/btsObyairNg/Ufk2GQDAm6uGKtQBH4zmx0/img.png&quot;&gt;&lt;img src=&quot;https://blog.kakaocdn.net/dn/bgiVT9/btsObyairNg/Ufk2GQDAm6uGKtQBH4zmx0/img.png&quot; srcset=&quot;https://img1.daumcdn.net/thumb/R1280x0/?scode=mtistory2&amp;fname=https%3A%2F%2Fblog.kakaocdn.net%2Fdn%2FbgiVT9%2FbtsObyairNg%2FUfk2GQDAm6uGKtQBH4zmx0%2Fimg.png&quot; onerror=&quot;this.onerror=null; this.src='//t1.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;657&quot; height=&quot;547&quot; data-origin-width=&quot;657&quot; data-origin-height=&quot;547&quot;/&gt;&lt;/span&gt;&lt;/figure&gt;

&lt;p data-ke-size=&quot;size16&quot;&gt;추가&lt;/p&gt;
&lt;figure class=&quot;imageblock alignCenter&quot; data-ke-mobileStyle=&quot;widthOrigin&quot; data-origin-width=&quot;516&quot; data-origin-height=&quot;279&quot;&gt;&lt;span data-url=&quot;https://blog.kakaocdn.net/dn/uaLgh/btsOcALEMxH/0xkOJTjSmaK07XKMahN14k/img.png&quot; data-phocus=&quot;https://blog.kakaocdn.net/dn/uaLgh/btsOcALEMxH/0xkOJTjSmaK07XKMahN14k/img.png&quot;&gt;&lt;img src=&quot;https://blog.kakaocdn.net/dn/uaLgh/btsOcALEMxH/0xkOJTjSmaK07XKMahN14k/img.png&quot; srcset=&quot;https://img1.daumcdn.net/thumb/R1280x0/?scode=mtistory2&amp;fname=https%3A%2F%2Fblog.kakaocdn.net%2Fdn%2FuaLgh%2FbtsOcALEMxH%2F0xkOJTjSmaK07XKMahN14k%2Fimg.png&quot; onerror=&quot;this.onerror=null; this.src='//t1.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;516&quot; height=&quot;279&quot; data-origin-width=&quot;516&quot; data-origin-height=&quot;279&quot;/&gt;&lt;/span&gt;&lt;/figure&gt;

&lt;p data-ke-size=&quot;size16&quot;&gt;이제 Claude를 껐다 키면&lt;/p&gt;
&lt;figure class=&quot;imageblock alignCenter&quot; data-ke-mobileStyle=&quot;widthOrigin&quot; data-origin-width=&quot;994&quot; data-origin-height=&quot;800&quot;&gt;&lt;span data-url=&quot;https://blog.kakaocdn.net/dn/coyHtG/btsOb6RRstu/Nv42cDnmHmhP8ylPmWqKk0/img.png&quot; data-phocus=&quot;https://blog.kakaocdn.net/dn/coyHtG/btsOb6RRstu/Nv42cDnmHmhP8ylPmWqKk0/img.png&quot;&gt;&lt;img src=&quot;https://blog.kakaocdn.net/dn/coyHtG/btsOb6RRstu/Nv42cDnmHmhP8ylPmWqKk0/img.png&quot; srcset=&quot;https://img1.daumcdn.net/thumb/R1280x0/?scode=mtistory2&amp;fname=https%3A%2F%2Fblog.kakaocdn.net%2Fdn%2FcoyHtG%2FbtsOb6RRstu%2FNv42cDnmHmhP8ylPmWqKk0%2Fimg.png&quot; onerror=&quot;this.onerror=null; this.src='//t1.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;994&quot; height=&quot;800&quot; data-origin-width=&quot;994&quot; data-origin-height=&quot;800&quot;/&gt;&lt;/span&gt;&lt;/figure&gt;

&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;span style=&quot;background-color: #fafafa; color: #333333; text-align: start;&quot;&gt;filesystem을 사용할 수 있게 된다.&lt;/span&gt;&lt;/p&gt;
&lt;figure class=&quot;imageblock alignCenter&quot; data-ke-mobileStyle=&quot;widthOrigin&quot; data-origin-width=&quot;765&quot; data-origin-height=&quot;657&quot;&gt;&lt;span data-url=&quot;https://blog.kakaocdn.net/dn/bLfo2Y/btsOb9HLY3S/mNdEkmKmQMBYuZ9V7UwTP0/img.png&quot; data-phocus=&quot;https://blog.kakaocdn.net/dn/bLfo2Y/btsOb9HLY3S/mNdEkmKmQMBYuZ9V7UwTP0/img.png&quot;&gt;&lt;img src=&quot;https://blog.kakaocdn.net/dn/bLfo2Y/btsOb9HLY3S/mNdEkmKmQMBYuZ9V7UwTP0/img.png&quot; srcset=&quot;https://img1.daumcdn.net/thumb/R1280x0/?scode=mtistory2&amp;fname=https%3A%2F%2Fblog.kakaocdn.net%2Fdn%2FbLfo2Y%2FbtsOb9HLY3S%2FmNdEkmKmQMBYuZ9V7UwTP0%2Fimg.png&quot; onerror=&quot;this.onerror=null; this.src='//t1.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;765&quot; height=&quot;657&quot; data-origin-width=&quot;765&quot; data-origin-height=&quot;657&quot;/&gt;&lt;/span&gt;&lt;/figure&gt;
&lt;/div&gt;
&lt;/div&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;세팅을 한 뒤 아래의 프롬프트를 입력했다.(참고로 유튜브 숏츠에서 우연히 보게됨)&lt;/p&gt;
&lt;pre id=&quot;code_1748181816698&quot; class=&quot;bash&quot; data-ke-language=&quot;bash&quot; data-ke-type=&quot;codeblock&quot;&gt;&lt;code&gt;directory: C:\Users\joonhyeok\works\weather-app\
위 디렉토리에 Modern한 디자인의 weather 앱을 만들어줘
* vue3, vuetify, tailwind 사용
상세 스펙
* weather 앱은 카드리스트 형태로 5일간의 날씨정보를 표시해주면 됨
* openweathermap을 사용하면 됨
.env 관리
* openweathermap api 키&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;&lt;figure class=&quot;imageblock alignCenter&quot; data-ke-mobileStyle=&quot;widthOrigin&quot; data-origin-width=&quot;743&quot; data-origin-height=&quot;733&quot;&gt;&lt;span data-url=&quot;https://blog.kakaocdn.net/dn/dCs221/btsOa1j0t85/qKBsuNTB42JzwzhpPcXJhK/img.png&quot; data-phocus=&quot;https://blog.kakaocdn.net/dn/dCs221/btsOa1j0t85/qKBsuNTB42JzwzhpPcXJhK/img.png&quot;&gt;&lt;img src=&quot;https://blog.kakaocdn.net/dn/dCs221/btsOa1j0t85/qKBsuNTB42JzwzhpPcXJhK/img.png&quot; srcset=&quot;https://img1.daumcdn.net/thumb/R1280x0/?scode=mtistory2&amp;fname=https%3A%2F%2Fblog.kakaocdn.net%2Fdn%2FdCs221%2FbtsOa1j0t85%2FqKBsuNTB42JzwzhpPcXJhK%2Fimg.png&quot; onerror=&quot;this.onerror=null; this.src='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png'; this.srcset='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png';&quot; loading=&quot;lazy&quot; width=&quot;743&quot; height=&quot;733&quot; data-origin-width=&quot;743&quot; data-origin-height=&quot;733&quot;/&gt;&lt;/span&gt;&lt;/figure&gt;
&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;파일 생성하기 위한 알람이 뜨고, 허용해준다.&lt;/p&gt;
&lt;p&gt;&lt;figure class=&quot;imageblock alignCenter&quot; data-ke-mobileStyle=&quot;widthOrigin&quot; data-origin-width=&quot;531&quot; data-origin-height=&quot;395&quot;&gt;&lt;span data-url=&quot;https://blog.kakaocdn.net/dn/cexZTJ/btsOcFM1I3C/gpXq2XNiV0XGvrkY1RTCL0/img.png&quot; data-phocus=&quot;https://blog.kakaocdn.net/dn/cexZTJ/btsOcFM1I3C/gpXq2XNiV0XGvrkY1RTCL0/img.png&quot;&gt;&lt;img src=&quot;https://blog.kakaocdn.net/dn/cexZTJ/btsOcFM1I3C/gpXq2XNiV0XGvrkY1RTCL0/img.png&quot; srcset=&quot;https://img1.daumcdn.net/thumb/R1280x0/?scode=mtistory2&amp;fname=https%3A%2F%2Fblog.kakaocdn.net%2Fdn%2FcexZTJ%2FbtsOcFM1I3C%2FgpXq2XNiV0XGvrkY1RTCL0%2Fimg.png&quot; onerror=&quot;this.onerror=null; this.src='//t1.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;531&quot; height=&quot;395&quot; data-origin-width=&quot;531&quot; data-origin-height=&quot;395&quot;/&gt;&lt;/span&gt;&lt;/figure&gt;
&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;무료 이기 때문에 자꾸 응답이 멈춘다.&lt;/p&gt;
&lt;p&gt;&lt;figure class=&quot;imageblock alignCenter&quot; data-ke-mobileStyle=&quot;widthOrigin&quot; data-origin-width=&quot;742&quot; data-origin-height=&quot;65&quot;&gt;&lt;span data-url=&quot;https://blog.kakaocdn.net/dn/dqqFh7/btsObUqjpF4/a934efUMKrGCYrPAhzkRe0/img.png&quot; data-phocus=&quot;https://blog.kakaocdn.net/dn/dqqFh7/btsObUqjpF4/a934efUMKrGCYrPAhzkRe0/img.png&quot;&gt;&lt;img src=&quot;https://blog.kakaocdn.net/dn/dqqFh7/btsObUqjpF4/a934efUMKrGCYrPAhzkRe0/img.png&quot; srcset=&quot;https://img1.daumcdn.net/thumb/R1280x0/?scode=mtistory2&amp;fname=https%3A%2F%2Fblog.kakaocdn.net%2Fdn%2FdqqFh7%2FbtsObUqjpF4%2Fa934efUMKrGCYrPAhzkRe0%2Fimg.png&quot; onerror=&quot;this.onerror=null; this.src='//t1.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;742&quot; height=&quot;65&quot; data-origin-width=&quot;742&quot; data-origin-height=&quot;65&quot;/&gt;&lt;/span&gt;&lt;/figure&gt;
&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;계속, 계속...&lt;/p&gt;
&lt;p&gt;&lt;figure class=&quot;imageblock alignCenter&quot; data-ke-mobileStyle=&quot;widthOrigin&quot; data-origin-width=&quot;705&quot; data-origin-height=&quot;634&quot;&gt;&lt;span data-url=&quot;https://blog.kakaocdn.net/dn/bdKpX3/btsOb9VkzVZ/UlriS0kXwTSKoHdnoIDBRK/img.png&quot; data-phocus=&quot;https://blog.kakaocdn.net/dn/bdKpX3/btsOb9VkzVZ/UlriS0kXwTSKoHdnoIDBRK/img.png&quot;&gt;&lt;img src=&quot;https://blog.kakaocdn.net/dn/bdKpX3/btsOb9VkzVZ/UlriS0kXwTSKoHdnoIDBRK/img.png&quot; srcset=&quot;https://img1.daumcdn.net/thumb/R1280x0/?scode=mtistory2&amp;fname=https%3A%2F%2Fblog.kakaocdn.net%2Fdn%2FbdKpX3%2FbtsOb9VkzVZ%2FUlriS0kXwTSKoHdnoIDBRK%2Fimg.png&quot; onerror=&quot;this.onerror=null; this.src='//t1.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;705&quot; height=&quot;634&quot; data-origin-width=&quot;705&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;실행방법.. 겁나 친절하다!&lt;/p&gt;
&lt;h2 data-ke-size=&quot;size26&quot;&gt;openWeatherMap API 키 발급&lt;/h2&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;openWeatherMap에 접속해서 회원가입을 한 뒤 API키를 발급한다.&lt;/p&gt;
&lt;figure id=&quot;og_1748184385010&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;Members&quot; data-og-description=&quot;Enter your email address and we will send you a link to reset your password.&quot; data-og-host=&quot;home.openweathermap.org&quot; data-og-source-url=&quot;https://home.openweathermap.org/api_keys&quot; data-og-url=&quot;https://home.openweathermap.org/users/sign_in&quot; data-og-image=&quot;&quot;&gt;&lt;a href=&quot;https://home.openweathermap.org/api_keys&quot; target=&quot;_blank&quot; rel=&quot;noopener&quot; data-source-url=&quot;https://home.openweathermap.org/api_keys&quot;&gt;
&lt;div class=&quot;og-image&quot; style=&quot;background-image: url();&quot;&gt;&amp;nbsp;&lt;/div&gt;
&lt;div class=&quot;og-text&quot;&gt;
&lt;p class=&quot;og-title&quot; data-ke-size=&quot;size16&quot;&gt;Members&lt;/p&gt;
&lt;p class=&quot;og-desc&quot; data-ke-size=&quot;size16&quot;&gt;Enter your email address and we will send you a link to reset your password.&lt;/p&gt;
&lt;p class=&quot;og-host&quot; data-ke-size=&quot;size16&quot;&gt;home.openweathermap.org&lt;/p&gt;
&lt;/div&gt;
&lt;/a&gt;&lt;/figure&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;근데 계속 401 인증에러가 발생해서 postman으로 API를 계속 호출해보았고...&lt;/p&gt;
&lt;p&gt;&lt;figure class=&quot;imageblock alignCenter&quot; data-ke-mobileStyle=&quot;widthOrigin&quot; data-origin-width=&quot;821&quot; data-origin-height=&quot;53&quot;&gt;&lt;span data-url=&quot;https://blog.kakaocdn.net/dn/K949e/btsObw4EkT8/khPnkjsS4kF95QVwK1vBf0/img.png&quot; data-phocus=&quot;https://blog.kakaocdn.net/dn/K949e/btsObw4EkT8/khPnkjsS4kF95QVwK1vBf0/img.png&quot;&gt;&lt;img src=&quot;https://blog.kakaocdn.net/dn/K949e/btsObw4EkT8/khPnkjsS4kF95QVwK1vBf0/img.png&quot; srcset=&quot;https://img1.daumcdn.net/thumb/R1280x0/?scode=mtistory2&amp;fname=https%3A%2F%2Fblog.kakaocdn.net%2Fdn%2FK949e%2FbtsObw4EkT8%2FkhPnkjsS4kF95QVwK1vBf0%2Fimg.png&quot; onerror=&quot;this.onerror=null; this.src='//t1.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;821&quot; height=&quot;53&quot; data-origin-width=&quot;821&quot; data-origin-height=&quot;53&quot;/&gt;&lt;/span&gt;&lt;/figure&gt;
&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;컴퓨터를 끄고 내일 하려고 포기할 즈음 갑자기 응답이 왔다.&lt;/p&gt;
&lt;p&gt;&lt;figure class=&quot;imageblock alignCenter&quot; data-ke-mobileStyle=&quot;widthOrigin&quot; data-origin-width=&quot;795&quot; data-origin-height=&quot;888&quot;&gt;&lt;span data-url=&quot;https://blog.kakaocdn.net/dn/bU894Y/btsOcVhM5Cc/ZtSP1vK2lkPWLVE74HHjSk/img.png&quot; data-phocus=&quot;https://blog.kakaocdn.net/dn/bU894Y/btsOcVhM5Cc/ZtSP1vK2lkPWLVE74HHjSk/img.png&quot;&gt;&lt;img src=&quot;https://blog.kakaocdn.net/dn/bU894Y/btsOcVhM5Cc/ZtSP1vK2lkPWLVE74HHjSk/img.png&quot; srcset=&quot;https://img1.daumcdn.net/thumb/R1280x0/?scode=mtistory2&amp;fname=https%3A%2F%2Fblog.kakaocdn.net%2Fdn%2FbU894Y%2FbtsOcVhM5Cc%2FZtSP1vK2lkPWLVE74HHjSk%2Fimg.png&quot; onerror=&quot;this.onerror=null; this.src='//t1.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;795&quot; height=&quot;888&quot; data-origin-width=&quot;795&quot; data-origin-height=&quot;888&quot;/&gt;&lt;/span&gt;&lt;/figure&gt;
&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;서울로 검색한 위치 위도/경도로 실제 날짜 검색도 정상으로 응답이 옴.&lt;/p&gt;
&lt;p&gt;&lt;figure class=&quot;imageblock alignCenter&quot; data-ke-mobileStyle=&quot;widthOrigin&quot; data-origin-width=&quot;928&quot; data-origin-height=&quot;751&quot;&gt;&lt;span data-url=&quot;https://blog.kakaocdn.net/dn/pxoLB/btsOcXzTect/RwABxrf64wamirsamxPGw0/img.png&quot; data-phocus=&quot;https://blog.kakaocdn.net/dn/pxoLB/btsOcXzTect/RwABxrf64wamirsamxPGw0/img.png&quot;&gt;&lt;img src=&quot;https://blog.kakaocdn.net/dn/pxoLB/btsOcXzTect/RwABxrf64wamirsamxPGw0/img.png&quot; srcset=&quot;https://img1.daumcdn.net/thumb/R1280x0/?scode=mtistory2&amp;fname=https%3A%2F%2Fblog.kakaocdn.net%2Fdn%2FpxoLB%2FbtsOcXzTect%2FRwABxrf64wamirsamxPGw0%2Fimg.png&quot; onerror=&quot;this.onerror=null; this.src='//t1.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;928&quot; height=&quot;751&quot; data-origin-width=&quot;928&quot; data-origin-height=&quot;751&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;/h2&gt;
&lt;p&gt;&lt;figure class=&quot;imageblock alignCenter&quot; data-ke-mobileStyle=&quot;widthOrigin&quot; data-origin-width=&quot;1230&quot; data-origin-height=&quot;962&quot;&gt;&lt;span data-url=&quot;https://blog.kakaocdn.net/dn/cACGgu/btsOcgs7vjw/rr7yxhnOD402ywC70EWSEk/img.png&quot; data-phocus=&quot;https://blog.kakaocdn.net/dn/cACGgu/btsOcgs7vjw/rr7yxhnOD402ywC70EWSEk/img.png&quot;&gt;&lt;img src=&quot;https://blog.kakaocdn.net/dn/cACGgu/btsOcgs7vjw/rr7yxhnOD402ywC70EWSEk/img.png&quot; srcset=&quot;https://img1.daumcdn.net/thumb/R1280x0/?scode=mtistory2&amp;fname=https%3A%2F%2Fblog.kakaocdn.net%2Fdn%2FcACGgu%2FbtsOcgs7vjw%2Frr7yxhnOD402ywC70EWSEk%2Fimg.png&quot; onerror=&quot;this.onerror=null; this.src='//t1.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;1230&quot; height=&quot;962&quot; data-origin-width=&quot;1230&quot; data-origin-height=&quot;962&quot;/&gt;&lt;/span&gt;&lt;/figure&gt;
&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;모바일&lt;/p&gt;
&lt;p&gt;&lt;figure class=&quot;imageblock alignCenter&quot; data-ke-mobileStyle=&quot;widthOrigin&quot; data-origin-width=&quot;385&quot; data-origin-height=&quot;839&quot;&gt;&lt;span data-url=&quot;https://blog.kakaocdn.net/dn/dok31l/btsOcW8PnTR/KSXx34h0ATSg0psdX1ZXK1/img.png&quot; data-phocus=&quot;https://blog.kakaocdn.net/dn/dok31l/btsOcW8PnTR/KSXx34h0ATSg0psdX1ZXK1/img.png&quot;&gt;&lt;img src=&quot;https://blog.kakaocdn.net/dn/dok31l/btsOcW8PnTR/KSXx34h0ATSg0psdX1ZXK1/img.png&quot; srcset=&quot;https://img1.daumcdn.net/thumb/R1280x0/?scode=mtistory2&amp;fname=https%3A%2F%2Fblog.kakaocdn.net%2Fdn%2Fdok31l%2FbtsOcW8PnTR%2FKSXx34h0ATSg0psdX1ZXK1%2Fimg.png&quot; onerror=&quot;this.onerror=null; this.src='//t1.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;385&quot; height=&quot;839&quot; data-origin-width=&quot;385&quot; data-origin-height=&quot;839&quot;/&gt;&lt;/span&gt;&lt;/figure&gt;
&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;와, 진짜 이제는 애플리케이션을 이렇게 쉽게 만들 수 있는 세상이 왔다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;이제 엔지니어들은 자기가 구축한 서버에 이런식으로 애플리케이션을 생성하여 순식간에 App이 돌아가는 시연을 할수 있게되었고, 아주 높은 수준의 PoC를 수행할 수 있을 것 같다.&lt;/p&gt;</description>
      <category>개발/기타</category>
      <category>A2A</category>
      <category>Ai</category>
      <category>MCP</category>
      <author>joon95</author>
      <guid isPermaLink="true">https://flowlog.tistory.com/117</guid>
      <comments>https://flowlog.tistory.com/117#entry117comment</comments>
      <pubDate>Sun, 25 May 2025 23:51:59 +0900</pubDate>
    </item>
    <item>
      <title>이제 6년 차, 그동안의 내 프로젝트를 정리하기 시작하다.</title>
      <link>https://flowlog.tistory.com/116</link>
      <description>&lt;p data-ke-size=&quot;size16&quot;&gt;그동안 프로젝트를 진행하면서 어떠한 고민들을 했었는지 내용을 짧게나마 정리해보려고 한다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;이 내용들을 기반으로 더 Deep하게 복기해보며 면접을 준비해야겠다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;span data-tt=&quot;{&amp;quot;paragraphStyle&amp;quot;:{&amp;quot;alignment&amp;quot;:4,&amp;quot;style&amp;quot;:0,&amp;quot;writingDirection&amp;quot;:1}}&quot;&gt;[S사 프라이빗 클라우드 쿠버네티스 운영] &lt;/span&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;span data-tt=&quot;{&amp;quot;paragraphStyle&amp;quot;:{&amp;quot;alignment&amp;quot;:4,&amp;quot;style&amp;quot;:103,&amp;quot;writingDirection&amp;quot;:1,&amp;quot;todo&amp;quot;:{&amp;quot;todoUUID&amp;quot;:{&amp;quot;0&amp;quot;:143,&amp;quot;1&amp;quot;:206,&amp;quot;2&amp;quot;:11,&amp;quot;3&amp;quot;:240,&amp;quot;4&amp;quot;:184,&amp;quot;5&amp;quot;:96,&amp;quot;6&amp;quot;:72,&amp;quot;7&amp;quot;:40,&amp;quot;8&amp;quot;:177,&amp;quot;9&amp;quot;:253,&amp;quot;10&amp;quot;:5,&amp;quot;11&amp;quot;:212,&amp;quot;12&amp;quot;:217,&amp;quot;13&amp;quot;:119,&amp;quot;14&amp;quot;:212,&amp;quot;15&amp;quot;:97},&amp;quot;done&amp;quot;:false}}}&quot;&gt;Iptable 설정 kube-proxy가 설정하는 네트워크의 체인들&lt;/span&gt;&lt;/li&gt;
&lt;li&gt;&lt;span data-tt=&quot;{&amp;quot;paragraphStyle&amp;quot;:{&amp;quot;alignment&amp;quot;:4,&amp;quot;style&amp;quot;:103,&amp;quot;writingDirection&amp;quot;:1,&amp;quot;todo&amp;quot;:{&amp;quot;todoUUID&amp;quot;:{&amp;quot;0&amp;quot;:2,&amp;quot;1&amp;quot;:104,&amp;quot;2&amp;quot;:16,&amp;quot;3&amp;quot;:222,&amp;quot;4&amp;quot;:254,&amp;quot;5&amp;quot;:193,&amp;quot;6&amp;quot;:69,&amp;quot;7&amp;quot;:126,&amp;quot;8&amp;quot;:173,&amp;quot;9&amp;quot;:111,&amp;quot;10&amp;quot;:210,&amp;quot;11&amp;quot;:104,&amp;quot;12&amp;quot;:31,&amp;quot;13&amp;quot;:184,&amp;quot;14&amp;quot;:174,&amp;quot;15&amp;quot;:48},&amp;quot;done&amp;quot;:false}}}&quot;&gt;클러스터 업데이트ㄱ Calico 이슈&lt;span style=&quot;color: #333333; text-align: left;&quot;&gt;&lt;span&gt;(&lt;/span&gt;&lt;/span&gt;IP설정방식의 변화 IPIP VS vxlan)&lt;/span&gt;&lt;/li&gt;
&lt;li&gt;&lt;span data-tt=&quot;{&amp;quot;paragraphStyle&amp;quot;:{&amp;quot;alignment&amp;quot;:4,&amp;quot;style&amp;quot;:103,&amp;quot;writingDirection&amp;quot;:1,&amp;quot;todo&amp;quot;:{&amp;quot;todoUUID&amp;quot;:{&amp;quot;0&amp;quot;:107,&amp;quot;1&amp;quot;:176,&amp;quot;2&amp;quot;:47,&amp;quot;3&amp;quot;:244,&amp;quot;4&amp;quot;:114,&amp;quot;5&amp;quot;:136,&amp;quot;6&amp;quot;:77,&amp;quot;7&amp;quot;:73,&amp;quot;8&amp;quot;:169,&amp;quot;9&amp;quot;:58,&amp;quot;10&amp;quot;:184,&amp;quot;11&amp;quot;:105,&amp;quot;12&amp;quot;:177,&amp;quot;13&amp;quot;:127,&amp;quot;14&amp;quot;:235,&amp;quot;15&amp;quot;:34},&amp;quot;done&amp;quot;:false}}}&quot;&gt;Longhorn 스토리지클래스 커널 이슈 &lt;/span&gt;&lt;/li&gt;
&lt;li&gt;&lt;span data-tt=&quot;{&amp;quot;paragraphStyle&amp;quot;:{&amp;quot;alignment&amp;quot;:4,&amp;quot;style&amp;quot;:103,&amp;quot;writingDirection&amp;quot;:1,&amp;quot;todo&amp;quot;:{&amp;quot;todoUUID&amp;quot;:{&amp;quot;0&amp;quot;:52,&amp;quot;1&amp;quot;:207,&amp;quot;2&amp;quot;:154,&amp;quot;3&amp;quot;:247,&amp;quot;4&amp;quot;:177,&amp;quot;5&amp;quot;:167,&amp;quot;6&amp;quot;:77,&amp;quot;7&amp;quot;:120,&amp;quot;8&amp;quot;:147,&amp;quot;9&amp;quot;:175,&amp;quot;10&amp;quot;:108,&amp;quot;11&amp;quot;:173,&amp;quot;12&amp;quot;:13,&amp;quot;13&amp;quot;:245,&amp;quot;14&amp;quot;:185,&amp;quot;15&amp;quot;:142},&amp;quot;done&amp;quot;:false}}}&quot;&gt;Istio PoC (Virtual Routing, 트래픽)&lt;/span&gt;&lt;/li&gt;
&lt;li&gt;&lt;span data-tt=&quot;{&amp;quot;paragraphStyle&amp;quot;:{&amp;quot;alignment&amp;quot;:4,&amp;quot;style&amp;quot;:103,&amp;quot;writingDirection&amp;quot;:1,&amp;quot;todo&amp;quot;:{&amp;quot;todoUUID&amp;quot;:{&amp;quot;0&amp;quot;:253,&amp;quot;1&amp;quot;:152,&amp;quot;2&amp;quot;:60,&amp;quot;3&amp;quot;:87,&amp;quot;4&amp;quot;:10,&amp;quot;5&amp;quot;:202,&amp;quot;6&amp;quot;:78,&amp;quot;7&amp;quot;:92,&amp;quot;8&amp;quot;:172,&amp;quot;9&amp;quot;:20,&amp;quot;10&amp;quot;:196,&amp;quot;11&amp;quot;:116,&amp;quot;12&amp;quot;:57,&amp;quot;13&amp;quot;:34,&amp;quot;14&amp;quot;:229,&amp;quot;15&amp;quot;:244},&amp;quot;done&amp;quot;:false}}}&quot;&gt;Elastic APM PoC (Go/Vue Performance monitoring)&lt;/span&gt;&lt;/li&gt;
&lt;li&gt;&lt;span data-tt=&quot;{&amp;quot;paragraphStyle&amp;quot;:{&amp;quot;alignment&amp;quot;:4,&amp;quot;style&amp;quot;:103,&amp;quot;writingDirection&amp;quot;:1,&amp;quot;todo&amp;quot;:{&amp;quot;todoUUID&amp;quot;:{&amp;quot;0&amp;quot;:195,&amp;quot;1&amp;quot;:65,&amp;quot;2&amp;quot;:207,&amp;quot;3&amp;quot;:75,&amp;quot;4&amp;quot;:123,&amp;quot;5&amp;quot;:82,&amp;quot;6&amp;quot;:73,&amp;quot;7&amp;quot;:193,&amp;quot;8&amp;quot;:191,&amp;quot;9&amp;quot;:3,&amp;quot;10&amp;quot;:80,&amp;quot;11&amp;quot;:117,&amp;quot;12&amp;quot;:253,&amp;quot;13&amp;quot;:112,&amp;quot;14&amp;quot;:96,&amp;quot;15&amp;quot;:203},&amp;quot;done&amp;quot;:false}}}&quot;&gt;Kubespray 클러스터 운영 (ansible)&lt;/span&gt;&lt;/li&gt;
&lt;li&gt;&lt;span data-tt=&quot;{&amp;quot;paragraphStyle&amp;quot;:{&amp;quot;alignment&amp;quot;:4,&amp;quot;style&amp;quot;:103,&amp;quot;writingDirection&amp;quot;:1,&amp;quot;todo&amp;quot;:{&amp;quot;todoUUID&amp;quot;:{&amp;quot;0&amp;quot;:176,&amp;quot;1&amp;quot;:61,&amp;quot;2&amp;quot;:132,&amp;quot;3&amp;quot;:219,&amp;quot;4&amp;quot;:188,&amp;quot;5&amp;quot;:108,&amp;quot;6&amp;quot;:64,&amp;quot;7&amp;quot;:182,&amp;quot;8&amp;quot;:153,&amp;quot;9&amp;quot;:50,&amp;quot;10&amp;quot;:203,&amp;quot;11&amp;quot;:237,&amp;quot;12&amp;quot;:29,&amp;quot;13&amp;quot;:83,&amp;quot;14&amp;quot;:1,&amp;quot;15&amp;quot;:139},&amp;quot;done&amp;quot;:false}}}&quot;&gt;Prometheus thanos 설계/구축 (중복제거,예외) &lt;/span&gt;&lt;/li&gt;
&lt;li&gt;&lt;span data-tt=&quot;{&amp;quot;paragraphStyle&amp;quot;:{&amp;quot;alignment&amp;quot;:4,&amp;quot;style&amp;quot;:103,&amp;quot;writingDirection&amp;quot;:1,&amp;quot;todo&amp;quot;:{&amp;quot;todoUUID&amp;quot;:{&amp;quot;0&amp;quot;:220,&amp;quot;1&amp;quot;:193,&amp;quot;2&amp;quot;:74,&amp;quot;3&amp;quot;:31,&amp;quot;4&amp;quot;:28,&amp;quot;5&amp;quot;:129,&amp;quot;6&amp;quot;:70,&amp;quot;7&amp;quot;:246,&amp;quot;8&amp;quot;:162,&amp;quot;9&amp;quot;:178,&amp;quot;10&amp;quot;:61,&amp;quot;11&amp;quot;:146,&amp;quot;12&amp;quot;:5,&amp;quot;13&amp;quot;:180,&amp;quot;14&amp;quot;:161,&amp;quot;15&amp;quot;:218},&amp;quot;done&amp;quot;:false}}}&quot;&gt;Fluent-bit 의 리소스 부하 사례 &lt;/span&gt;&lt;/li&gt;
&lt;li&gt;&lt;span data-tt=&quot;{&amp;quot;paragraphStyle&amp;quot;:{&amp;quot;alignment&amp;quot;:4,&amp;quot;style&amp;quot;:103,&amp;quot;writingDirection&amp;quot;:1,&amp;quot;todo&amp;quot;:{&amp;quot;todoUUID&amp;quot;:{&amp;quot;0&amp;quot;:202,&amp;quot;1&amp;quot;:159,&amp;quot;2&amp;quot;:127,&amp;quot;3&amp;quot;:175,&amp;quot;4&amp;quot;:216,&amp;quot;5&amp;quot;:130,&amp;quot;6&amp;quot;:71,&amp;quot;7&amp;quot;:27,&amp;quot;8&amp;quot;:156,&amp;quot;9&amp;quot;:254,&amp;quot;10&amp;quot;:107,&amp;quot;11&amp;quot;:40,&amp;quot;12&amp;quot;:148,&amp;quot;13&amp;quot;:191,&amp;quot;14&amp;quot;:79,&amp;quot;15&amp;quot;:126},&amp;quot;done&amp;quot;:false}}}&quot;&gt;EFK 운영 이슈 (샤드 복제)&lt;/span&gt;&lt;/li&gt;
&lt;/ul&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;span data-tt=&quot;{&amp;quot;paragraphStyle&amp;quot;:{&amp;quot;alignment&amp;quot;:4,&amp;quot;style&amp;quot;:0,&amp;quot;writingDirection&amp;quot;:1}}&quot;&gt;[S사 의료 차세대 개발] &lt;/span&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;span data-tt=&quot;{&amp;quot;paragraphStyle&amp;quot;:{&amp;quot;alignment&amp;quot;:4,&amp;quot;style&amp;quot;:103,&amp;quot;writingDirection&amp;quot;:1,&amp;quot;todo&amp;quot;:{&amp;quot;todoUUID&amp;quot;:{&amp;quot;0&amp;quot;:85,&amp;quot;1&amp;quot;:39,&amp;quot;2&amp;quot;:194,&amp;quot;3&amp;quot;:195,&amp;quot;4&amp;quot;:122,&amp;quot;5&amp;quot;:135,&amp;quot;6&amp;quot;:69,&amp;quot;7&amp;quot;:247,&amp;quot;8&amp;quot;:189,&amp;quot;9&amp;quot;:43,&amp;quot;10&amp;quot;:149,&amp;quot;11&amp;quot;:154,&amp;quot;12&amp;quot;:146,&amp;quot;13&amp;quot;:108,&amp;quot;14&amp;quot;:205,&amp;quot;15&amp;quot;:84},&amp;quot;done&amp;quot;:false}}}&quot;&gt;배치 관리 서비스k8s 라이브러리 - fabric VS k8s client &lt;/span&gt;&lt;/li&gt;
&lt;li&gt;&lt;span data-tt=&quot;{&amp;quot;paragraphStyle&amp;quot;:{&amp;quot;alignment&amp;quot;:4,&amp;quot;style&amp;quot;:103,&amp;quot;writingDirection&amp;quot;:1,&amp;quot;todo&amp;quot;:{&amp;quot;todoUUID&amp;quot;:{&amp;quot;0&amp;quot;:197,&amp;quot;1&amp;quot;:11,&amp;quot;2&amp;quot;:77,&amp;quot;3&amp;quot;:169,&amp;quot;4&amp;quot;:29,&amp;quot;5&amp;quot;:232,&amp;quot;6&amp;quot;:79,&amp;quot;7&amp;quot;:92,&amp;quot;8&amp;quot;:161,&amp;quot;9&amp;quot;:196,&amp;quot;10&amp;quot;:37,&amp;quot;11&amp;quot;:62,&amp;quot;12&amp;quot;:138,&amp;quot;13&amp;quot;:71,&amp;quot;14&amp;quot;:230,&amp;quot;15&amp;quot;:104},&amp;quot;done&amp;quot;:false}}}&quot;&gt;Keycloak 인증 인가 (Oauth2.0)&lt;/span&gt;&lt;/li&gt;
&lt;li&gt;&lt;span data-tt=&quot;{&amp;quot;paragraphStyle&amp;quot;:{&amp;quot;alignment&amp;quot;:4,&amp;quot;style&amp;quot;:103,&amp;quot;writingDirection&amp;quot;:1,&amp;quot;todo&amp;quot;:{&amp;quot;todoUUID&amp;quot;:{&amp;quot;0&amp;quot;:78,&amp;quot;1&amp;quot;:168,&amp;quot;2&amp;quot;:233,&amp;quot;3&amp;quot;:33,&amp;quot;4&amp;quot;:75,&amp;quot;5&amp;quot;:53,&amp;quot;6&amp;quot;:70,&amp;quot;7&amp;quot;:192,&amp;quot;8&amp;quot;:139,&amp;quot;9&amp;quot;:126,&amp;quot;10&amp;quot;:199,&amp;quot;11&amp;quot;:136,&amp;quot;12&amp;quot;:50,&amp;quot;13&amp;quot;:80,&amp;quot;14&amp;quot;:108,&amp;quot;15&amp;quot;:72},&amp;quot;done&amp;quot;:false}}}&quot;&gt;파일업로드 프로세스 (Nexacro to springboot)&lt;/span&gt;&lt;/li&gt;
&lt;li&gt;&lt;span data-tt=&quot;{&amp;quot;paragraphStyle&amp;quot;:{&amp;quot;alignment&amp;quot;:4,&amp;quot;style&amp;quot;:103,&amp;quot;writingDirection&amp;quot;:1,&amp;quot;todo&amp;quot;:{&amp;quot;todoUUID&amp;quot;:{&amp;quot;0&amp;quot;:93,&amp;quot;1&amp;quot;:192,&amp;quot;2&amp;quot;:153,&amp;quot;3&amp;quot;:24,&amp;quot;4&amp;quot;:240,&amp;quot;5&amp;quot;:243,&amp;quot;6&amp;quot;:74,&amp;quot;7&amp;quot;:7,&amp;quot;8&amp;quot;:174,&amp;quot;9&amp;quot;:233,&amp;quot;10&amp;quot;:211,&amp;quot;11&amp;quot;:252,&amp;quot;12&amp;quot;:50,&amp;quot;13&amp;quot;:215,&amp;quot;14&amp;quot;:137,&amp;quot;15&amp;quot;:22},&amp;quot;done&amp;quot;:false}}}&quot;&gt;다국어 프로세스 설계 (Gitlab EE API / Redis)&lt;/span&gt;&lt;/li&gt;
&lt;li&gt;&lt;span data-tt=&quot;{&amp;quot;paragraphStyle&amp;quot;:{&amp;quot;alignment&amp;quot;:4,&amp;quot;style&amp;quot;:103,&amp;quot;writingDirection&amp;quot;:1,&amp;quot;todo&amp;quot;:{&amp;quot;todoUUID&amp;quot;:{&amp;quot;0&amp;quot;:7,&amp;quot;1&amp;quot;:156,&amp;quot;2&amp;quot;:171,&amp;quot;3&amp;quot;:74,&amp;quot;4&amp;quot;:153,&amp;quot;5&amp;quot;:111,&amp;quot;6&amp;quot;:71,&amp;quot;7&amp;quot;:169,&amp;quot;8&amp;quot;:184,&amp;quot;9&amp;quot;:92,&amp;quot;10&amp;quot;:192,&amp;quot;11&amp;quot;:66,&amp;quot;12&amp;quot;:232,&amp;quot;13&amp;quot;:163,&amp;quot;14&amp;quot;:185,&amp;quot;15&amp;quot;:33},&amp;quot;done&amp;quot;:false}}}&quot;&gt;MSA 도메인 분리부터 설계까지&lt;/span&gt;&lt;/li&gt;
&lt;/ul&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;span data-tt=&quot;{&amp;quot;paragraphStyle&amp;quot;:{&amp;quot;alignment&amp;quot;:4,&amp;quot;style&amp;quot;:0,&amp;quot;writingDirection&amp;quot;:1}}&quot;&gt;[S사 채용포털 개발] &lt;/span&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;span data-tt=&quot;{&amp;quot;paragraphStyle&amp;quot;:{&amp;quot;alignment&amp;quot;:4,&amp;quot;style&amp;quot;:103,&amp;quot;writingDirection&amp;quot;:1,&amp;quot;todo&amp;quot;:{&amp;quot;todoUUID&amp;quot;:{&amp;quot;0&amp;quot;:193,&amp;quot;1&amp;quot;:94,&amp;quot;2&amp;quot;:27,&amp;quot;3&amp;quot;:174,&amp;quot;4&amp;quot;:253,&amp;quot;5&amp;quot;:110,&amp;quot;6&amp;quot;:69,&amp;quot;7&amp;quot;:138,&amp;quot;8&amp;quot;:139,&amp;quot;9&amp;quot;:75,&amp;quot;10&amp;quot;:40,&amp;quot;11&amp;quot;:206,&amp;quot;12&amp;quot;:93,&amp;quot;13&amp;quot;:196,&amp;quot;14&amp;quot;:100,&amp;quot;15&amp;quot;:138},&amp;quot;done&amp;quot;:false}}}&quot;&gt;파일프로세스 설계(AWS-&amp;gt;on-prem) &lt;/span&gt;&lt;/li&gt;
&lt;li&gt;&lt;span data-tt=&quot;{&amp;quot;paragraphStyle&amp;quot;:{&amp;quot;alignment&amp;quot;:4,&amp;quot;style&amp;quot;:103,&amp;quot;writingDirection&amp;quot;:1,&amp;quot;todo&amp;quot;:{&amp;quot;todoUUID&amp;quot;:{&amp;quot;0&amp;quot;:199,&amp;quot;1&amp;quot;:223,&amp;quot;2&amp;quot;:204,&amp;quot;3&amp;quot;:78,&amp;quot;4&amp;quot;:32,&amp;quot;5&amp;quot;:197,&amp;quot;6&amp;quot;:75,&amp;quot;7&amp;quot;:218,&amp;quot;8&amp;quot;:165,&amp;quot;9&amp;quot;:58,&amp;quot;10&amp;quot;:114,&amp;quot;11&amp;quot;:12,&amp;quot;12&amp;quot;:58,&amp;quot;13&amp;quot;:124,&amp;quot;14&amp;quot;:11,&amp;quot;15&amp;quot;:25},&amp;quot;done&amp;quot;:false}}}&quot;&gt;다국어 프로세스 설계(Github Enterprise API, Redis)&lt;/span&gt;&lt;/li&gt;
&lt;li&gt;&lt;span data-tt=&quot;{&amp;quot;paragraphStyle&amp;quot;:{&amp;quot;alignment&amp;quot;:4,&amp;quot;style&amp;quot;:103,&amp;quot;writingDirection&amp;quot;:1,&amp;quot;todo&amp;quot;:{&amp;quot;todoUUID&amp;quot;:{&amp;quot;0&amp;quot;:189,&amp;quot;1&amp;quot;:240,&amp;quot;2&amp;quot;:42,&amp;quot;3&amp;quot;:99,&amp;quot;4&amp;quot;:213,&amp;quot;5&amp;quot;:209,&amp;quot;6&amp;quot;:78,&amp;quot;7&amp;quot;:19,&amp;quot;8&amp;quot;:141,&amp;quot;9&amp;quot;:14,&amp;quot;10&amp;quot;:58,&amp;quot;11&amp;quot;:127,&amp;quot;12&amp;quot;:80,&amp;quot;13&amp;quot;:54,&amp;quot;14&amp;quot;:212,&amp;quot;15&amp;quot;:50},&amp;quot;done&amp;quot;:false}}}&quot;&gt;MSA 환경에서 클라우드 개발&lt;/span&gt;&lt;/li&gt;
&lt;/ul&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;span data-tt=&quot;{&amp;quot;paragraphStyle&amp;quot;:{&amp;quot;alignment&amp;quot;:4,&amp;quot;style&amp;quot;:0,&amp;quot;writingDirection&amp;quot;:1}}&quot;&gt;[S사 클라우드 포털 개발] &lt;/span&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;span data-tt=&quot;{&amp;quot;paragraphStyle&amp;quot;:{&amp;quot;alignment&amp;quot;:4,&amp;quot;style&amp;quot;:103,&amp;quot;writingDirection&amp;quot;:1,&amp;quot;todo&amp;quot;:{&amp;quot;todoUUID&amp;quot;:{&amp;quot;0&amp;quot;:243,&amp;quot;1&amp;quot;:90,&amp;quot;2&amp;quot;:232,&amp;quot;3&amp;quot;:110,&amp;quot;4&amp;quot;:10,&amp;quot;5&amp;quot;:29,&amp;quot;6&amp;quot;:70,&amp;quot;7&amp;quot;:205,&amp;quot;8&amp;quot;:152,&amp;quot;9&amp;quot;:60,&amp;quot;10&amp;quot;:79,&amp;quot;11&amp;quot;:195,&amp;quot;12&amp;quot;:99,&amp;quot;13&amp;quot;:16,&amp;quot;14&amp;quot;:243,&amp;quot;15&amp;quot;:131},&amp;quot;done&amp;quot;:false}}}&quot;&gt;VRA/NSX Vmware 컴퓨팅/네트워크 자동화 &lt;/span&gt;&lt;/li&gt;
&lt;li&gt;&lt;span data-tt=&quot;{&amp;quot;paragraphStyle&amp;quot;:{&amp;quot;alignment&amp;quot;:4,&amp;quot;style&amp;quot;:103,&amp;quot;writingDirection&amp;quot;:1,&amp;quot;todo&amp;quot;:{&amp;quot;todoUUID&amp;quot;:{&amp;quot;0&amp;quot;:182,&amp;quot;1&amp;quot;:146,&amp;quot;2&amp;quot;:37,&amp;quot;3&amp;quot;:198,&amp;quot;4&amp;quot;:160,&amp;quot;5&amp;quot;:120,&amp;quot;6&amp;quot;:68,&amp;quot;7&amp;quot;:226,&amp;quot;8&amp;quot;:183,&amp;quot;9&amp;quot;:206,&amp;quot;10&amp;quot;:234,&amp;quot;11&amp;quot;:216,&amp;quot;12&amp;quot;:8,&amp;quot;13&amp;quot;:150,&amp;quot;14&amp;quot;:243,&amp;quot;15&amp;quot;:34},&amp;quot;done&amp;quot;:false}}}&quot;&gt;Knox 결재, 메일 &lt;/span&gt;&lt;/li&gt;
&lt;li&gt;&lt;span data-tt=&quot;{&amp;quot;paragraphStyle&amp;quot;:{&amp;quot;alignment&amp;quot;:4,&amp;quot;style&amp;quot;:103,&amp;quot;writingDirection&amp;quot;:1,&amp;quot;todo&amp;quot;:{&amp;quot;todoUUID&amp;quot;:{&amp;quot;0&amp;quot;:253,&amp;quot;1&amp;quot;:42,&amp;quot;2&amp;quot;:171,&amp;quot;3&amp;quot;:75,&amp;quot;4&amp;quot;:40,&amp;quot;5&amp;quot;:145,&amp;quot;6&amp;quot;:77,&amp;quot;7&amp;quot;:10,&amp;quot;8&amp;quot;:185,&amp;quot;9&amp;quot;:197,&amp;quot;10&amp;quot;:171,&amp;quot;11&amp;quot;:132,&amp;quot;12&amp;quot;:145,&amp;quot;13&amp;quot;:100,&amp;quot;14&amp;quot;:142,&amp;quot;15&amp;quot;:242},&amp;quot;done&amp;quot;:false}}}&quot;&gt;SVDI 가상데스크톱 &lt;/span&gt;&lt;/li&gt;
&lt;li&gt;&lt;span data-tt=&quot;{&amp;quot;paragraphStyle&amp;quot;:{&amp;quot;alignment&amp;quot;:4,&amp;quot;style&amp;quot;:103,&amp;quot;writingDirection&amp;quot;:1,&amp;quot;todo&amp;quot;:{&amp;quot;todoUUID&amp;quot;:{&amp;quot;0&amp;quot;:102,&amp;quot;1&amp;quot;:86,&amp;quot;2&amp;quot;:146,&amp;quot;3&amp;quot;:80,&amp;quot;4&amp;quot;:35,&amp;quot;5&amp;quot;:31,&amp;quot;6&amp;quot;:72,&amp;quot;7&amp;quot;:130,&amp;quot;8&amp;quot;:160,&amp;quot;9&amp;quot;:223,&amp;quot;10&amp;quot;:183,&amp;quot;11&amp;quot;:188,&amp;quot;12&amp;quot;:157,&amp;quot;13&amp;quot;:134,&amp;quot;14&amp;quot;:113,&amp;quot;15&amp;quot;:146},&amp;quot;done&amp;quot;:false}}}&quot;&gt;HIWARE 서버접근제어 &lt;/span&gt;&lt;/li&gt;
&lt;li&gt;&lt;span data-tt=&quot;{&amp;quot;paragraphStyle&amp;quot;:{&amp;quot;alignment&amp;quot;:4,&amp;quot;style&amp;quot;:103,&amp;quot;writingDirection&amp;quot;:1,&amp;quot;todo&amp;quot;:{&amp;quot;todoUUID&amp;quot;:{&amp;quot;0&amp;quot;:97,&amp;quot;1&amp;quot;:65,&amp;quot;2&amp;quot;:99,&amp;quot;3&amp;quot;:84,&amp;quot;4&amp;quot;:136,&amp;quot;5&amp;quot;:167,&amp;quot;6&amp;quot;:71,&amp;quot;7&amp;quot;:100,&amp;quot;8&amp;quot;:160,&amp;quot;9&amp;quot;:48,&amp;quot;10&amp;quot;:220,&amp;quot;11&amp;quot;:27,&amp;quot;12&amp;quot;:209,&amp;quot;13&amp;quot;:141,&amp;quot;14&amp;quot;:15,&amp;quot;15&amp;quot;:108},&amp;quot;done&amp;quot;:false}}}&quot;&gt;다국어 프로세스 설계(Github Enterprise API / Redis)&lt;/span&gt;&lt;/li&gt;
&lt;li&gt;&lt;span data-tt=&quot;{&amp;quot;paragraphStyle&amp;quot;:{&amp;quot;alignment&amp;quot;:4,&amp;quot;style&amp;quot;:103,&amp;quot;writingDirection&amp;quot;:1,&amp;quot;todo&amp;quot;:{&amp;quot;todoUUID&amp;quot;:{&amp;quot;0&amp;quot;:146,&amp;quot;1&amp;quot;:161,&amp;quot;2&amp;quot;:215,&amp;quot;3&amp;quot;:126,&amp;quot;4&amp;quot;:124,&amp;quot;5&amp;quot;:244,&amp;quot;6&amp;quot;:71,&amp;quot;7&amp;quot;:60,&amp;quot;8&amp;quot;:154,&amp;quot;9&amp;quot;:44,&amp;quot;10&amp;quot;:7,&amp;quot;11&amp;quot;:58,&amp;quot;12&amp;quot;:5,&amp;quot;13&amp;quot;:92,&amp;quot;14&amp;quot;:169,&amp;quot;15&amp;quot;:250},&amp;quot;done&amp;quot;:false}}}&quot;&gt;스프링 클라우드 MSA 환경 구축 &lt;/span&gt;&lt;/li&gt;
&lt;li&gt;&lt;span data-tt=&quot;{&amp;quot;paragraphStyle&amp;quot;:{&amp;quot;alignment&amp;quot;:4,&amp;quot;style&amp;quot;:103,&amp;quot;writingDirection&amp;quot;:1,&amp;quot;todo&amp;quot;:{&amp;quot;todoUUID&amp;quot;:{&amp;quot;0&amp;quot;:234,&amp;quot;1&amp;quot;:240,&amp;quot;2&amp;quot;:119,&amp;quot;3&amp;quot;:106,&amp;quot;4&amp;quot;:153,&amp;quot;5&amp;quot;:96,&amp;quot;6&amp;quot;:74,&amp;quot;7&amp;quot;:241,&amp;quot;8&amp;quot;:155,&amp;quot;9&amp;quot;:43,&amp;quot;10&amp;quot;:38,&amp;quot;11&amp;quot;:209,&amp;quot;12&amp;quot;:150,&amp;quot;13&amp;quot;:160,&amp;quot;14&amp;quot;:128,&amp;quot;15&amp;quot;:98},&amp;quot;done&amp;quot;:false}}}&quot;&gt;MSA 환경에서 배치와 카프카 &lt;/span&gt;&lt;/li&gt;
&lt;/ul&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;span data-tt=&quot;{&amp;quot;paragraphStyle&amp;quot;:{&amp;quot;alignment&amp;quot;:4,&amp;quot;style&amp;quot;:0,&amp;quot;writingDirection&amp;quot;:1}}&quot;&gt;[L사 마이데이터 TA] &lt;/span&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;span data-tt=&quot;{&amp;quot;paragraphStyle&amp;quot;:{&amp;quot;alignment&amp;quot;:4,&amp;quot;style&amp;quot;:103,&amp;quot;writingDirection&amp;quot;:1,&amp;quot;todo&amp;quot;:{&amp;quot;todoUUID&amp;quot;:{&amp;quot;0&amp;quot;:126,&amp;quot;1&amp;quot;:31,&amp;quot;2&amp;quot;:181,&amp;quot;3&amp;quot;:51,&amp;quot;4&amp;quot;:57,&amp;quot;5&amp;quot;:28,&amp;quot;6&amp;quot;:73,&amp;quot;7&amp;quot;:210,&amp;quot;8&amp;quot;:170,&amp;quot;9&amp;quot;:100,&amp;quot;10&amp;quot;:172,&amp;quot;11&amp;quot;:97,&amp;quot;12&amp;quot;:30,&amp;quot;13&amp;quot;:102,&amp;quot;14&amp;quot;:105,&amp;quot;15&amp;quot;:219},&amp;quot;done&amp;quot;:false}}}&quot;&gt;WAF Rule (기본Rule에 대한 이해)&lt;/span&gt;&lt;/li&gt;
&lt;li&gt;&lt;span data-tt=&quot;{&amp;quot;paragraphStyle&amp;quot;:{&amp;quot;alignment&amp;quot;:4,&amp;quot;style&amp;quot;:103,&amp;quot;writingDirection&amp;quot;:1,&amp;quot;todo&amp;quot;:{&amp;quot;todoUUID&amp;quot;:{&amp;quot;0&amp;quot;:195,&amp;quot;1&amp;quot;:248,&amp;quot;2&amp;quot;:250,&amp;quot;3&amp;quot;:215,&amp;quot;4&amp;quot;:164,&amp;quot;5&amp;quot;:90,&amp;quot;6&amp;quot;:65,&amp;quot;7&amp;quot;:140,&amp;quot;8&amp;quot;:154,&amp;quot;9&amp;quot;:133,&amp;quot;10&amp;quot;:62,&amp;quot;11&amp;quot;:131,&amp;quot;12&amp;quot;:116,&amp;quot;13&amp;quot;:133,&amp;quot;14&amp;quot;:119,&amp;quot;15&amp;quot;:120},&amp;quot;done&amp;quot;:false}}}&quot;&gt;사내 사업자서버와 제공자서버 인증 통과 &lt;/span&gt;&lt;/li&gt;
&lt;li&gt;&lt;span data-tt=&quot;{&amp;quot;paragraphStyle&amp;quot;:{&amp;quot;alignment&amp;quot;:4,&amp;quot;style&amp;quot;:103,&amp;quot;writingDirection&amp;quot;:1,&amp;quot;todo&amp;quot;:{&amp;quot;todoUUID&amp;quot;:{&amp;quot;0&amp;quot;:161,&amp;quot;1&amp;quot;:238,&amp;quot;2&amp;quot;:73,&amp;quot;3&amp;quot;:69,&amp;quot;4&amp;quot;:47,&amp;quot;5&amp;quot;:113,&amp;quot;6&amp;quot;:64,&amp;quot;7&amp;quot;:214,&amp;quot;8&amp;quot;:189,&amp;quot;9&amp;quot;:165,&amp;quot;10&amp;quot;:143,&amp;quot;11&amp;quot;:238,&amp;quot;12&amp;quot;:77,&amp;quot;13&amp;quot;:81,&amp;quot;14&amp;quot;:123,&amp;quot;15&amp;quot;:91},&amp;quot;done&amp;quot;:false}}}&quot;&gt;MTLS by openresty &lt;/span&gt;&lt;/li&gt;
&lt;li&gt;&lt;span data-tt=&quot;{&amp;quot;paragraphStyle&amp;quot;:{&amp;quot;alignment&amp;quot;:4,&amp;quot;style&amp;quot;:103,&amp;quot;writingDirection&amp;quot;:1,&amp;quot;todo&amp;quot;:{&amp;quot;todoUUID&amp;quot;:{&amp;quot;0&amp;quot;:186,&amp;quot;1&amp;quot;:165,&amp;quot;2&amp;quot;:63,&amp;quot;3&amp;quot;:98,&amp;quot;4&amp;quot;:88,&amp;quot;5&amp;quot;:145,&amp;quot;6&amp;quot;:79,&amp;quot;7&amp;quot;:1,&amp;quot;8&amp;quot;:132,&amp;quot;9&amp;quot;:129,&amp;quot;10&amp;quot;:4,&amp;quot;11&amp;quot;:147,&amp;quot;12&amp;quot;:175,&amp;quot;13&amp;quot;:202,&amp;quot;14&amp;quot;:11,&amp;quot;15&amp;quot;:146},&amp;quot;done&amp;quot;:false}}}&quot;&gt;Proxy서버 DSR(Direct Source Return)&lt;/span&gt;&lt;/li&gt;
&lt;li&gt;&lt;span data-tt=&quot;{&amp;quot;paragraphStyle&amp;quot;:{&amp;quot;alignment&amp;quot;:4,&amp;quot;style&amp;quot;:103,&amp;quot;writingDirection&amp;quot;:1,&amp;quot;todo&amp;quot;:{&amp;quot;todoUUID&amp;quot;:{&amp;quot;0&amp;quot;:115,&amp;quot;1&amp;quot;:213,&amp;quot;2&amp;quot;:147,&amp;quot;3&amp;quot;:194,&amp;quot;4&amp;quot;:54,&amp;quot;5&amp;quot;:111,&amp;quot;6&amp;quot;:74,&amp;quot;7&amp;quot;:101,&amp;quot;8&amp;quot;:152,&amp;quot;9&amp;quot;:151,&amp;quot;10&amp;quot;:216,&amp;quot;11&amp;quot;:160,&amp;quot;12&amp;quot;:140,&amp;quot;13&amp;quot;:184,&amp;quot;14&amp;quot;:251,&amp;quot;15&amp;quot;:174},&amp;quot;done&amp;quot;:false}}}&quot;&gt;ELK스택 logstash DLQ(dead lock queue) &lt;/span&gt;&lt;/li&gt;
&lt;li&gt;&lt;span data-tt=&quot;{&amp;quot;paragraphStyle&amp;quot;:{&amp;quot;alignment&amp;quot;:4,&amp;quot;style&amp;quot;:103,&amp;quot;writingDirection&amp;quot;:1,&amp;quot;todo&amp;quot;:{&amp;quot;todoUUID&amp;quot;:{&amp;quot;0&amp;quot;:226,&amp;quot;1&amp;quot;:16,&amp;quot;2&amp;quot;:35,&amp;quot;3&amp;quot;:4,&amp;quot;4&amp;quot;:189,&amp;quot;5&amp;quot;:109,&amp;quot;6&amp;quot;:66,&amp;quot;7&amp;quot;:127,&amp;quot;8&amp;quot;:139,&amp;quot;9&amp;quot;:14,&amp;quot;10&amp;quot;:3,&amp;quot;11&amp;quot;:189,&amp;quot;12&amp;quot;:27,&amp;quot;13&amp;quot;:120,&amp;quot;14&amp;quot;:44,&amp;quot;15&amp;quot;:222},&amp;quot;done&amp;quot;:false}}}&quot;&gt;MSA기반의 OAuth2.0 (3scale, rhsso) &lt;/span&gt;&lt;/li&gt;
&lt;/ul&gt;</description>
      <author>joon95</author>
      <guid isPermaLink="true">https://flowlog.tistory.com/116</guid>
      <comments>https://flowlog.tistory.com/116#entry116comment</comments>
      <pubDate>Fri, 2 May 2025 22:40:58 +0900</pubDate>
    </item>
    <item>
      <title>이미지에서 사물을 분석해주는 모델(VGG16)을 사용해 API로 만들어보자</title>
      <link>https://flowlog.tistory.com/115</link>
      <description>&lt;p data-ke-size=&quot;size16&quot;&gt;언젠가 AI를 해봐야지 생각하면서.. 미루고 있었다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;perplexity(GPT) 와 채팅을 하면서 문득 사전에 학습된 모델을 알게되어&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;VGG16 모델을 통해 이미지를 분석하는 테스트를 해보았다.&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;VGG16&lt;/h2&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;먼저 VGG16 이란&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;- 키보드, 동물, 연필 등의 정보를 1000개의 Class로 분류 가능&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;- 3x3 커널을 사용&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;- 모델 이미지 입력 크기 224x224&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;GPU (CUDA) 세팅&lt;/h2&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;CPU 를 사용했더니 너무 느려서 노트북에 내장되어 있는 그래픽카드를 셋업했다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;필자의 그래픽 카드정보는 NVIDIA GeForce GTX 1650 Ti with Max-Q Design 이며,&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;NVIDIA 사이트에서 Cuda를 지원하는지 체크하여 가능한 것임을 알았다.&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;TensorFlow GPU 지원-소프트웨어 요구사항&lt;/h3&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;다음 NVIDIA&amp;reg; 소프트웨어가 시스템에 설치되어 있어야 합니다.&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://www.nvidia.com/drivers&quot;&gt;NVIDIA&amp;reg; GPU 드라이버&lt;/a&gt;&amp;nbsp;- CUDA&amp;reg; 11.2에는 450.80.02 이상이 필요합니다.&lt;/li&gt;
&lt;li&gt;&lt;a href=&quot;https://developer.nvidia.com/cuda-toolkit-archive&quot;&gt;CUDA&amp;reg; Toolkit&lt;/a&gt;&amp;nbsp;- TensorFlow는 CUDA&amp;reg; 11.2(TensorFlow 2.5.0 이상)를 지원합니다.&lt;/li&gt;
&lt;li&gt;&lt;a href=&quot;http://docs.nvidia.com/cuda/cupti/&quot;&gt;CUPTI&lt;/a&gt;는 CUDA&amp;reg; Toolkit과 함께 제공됩니다.&lt;/li&gt;
&lt;li&gt;&lt;a href=&quot;https://developer.nvidia.com/cudnn&quot;&gt;cuDNN SDK 8.1.0&lt;/a&gt;(&lt;a href=&quot;https://developer.nvidia.com/rdp/cudnn-archive&quot;&gt;cuDNN 버전&lt;/a&gt;)&lt;/li&gt;
&lt;li&gt;(선택사항)&amp;nbsp;&lt;a href=&quot;https://docs.nvidia.com/deeplearning/tensorrt/archives/index.html#trt_6&quot;&gt;TensorRT 6.0&lt;/a&gt;&amp;nbsp;- 일부 모델에서 추론 처리량과 지연 시간을 향상합니다.&lt;/li&gt;
&lt;/ul&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;h3 data-ke-size=&quot;size23&quot;&gt;세팅 시작&lt;/h3&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;1. 그래픽카드 드라이버 업데이트&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;업무용PC라 한번도 안했던 것이라 바로 고.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;2. CUDA 설치&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;b&gt;CUDA ToolKit 버전 11.2&lt;/b&gt;&lt;/p&gt;
&lt;figure id=&quot;og_1733906347428&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;CUDA Toolkit 11.2 Downloads&quot; data-og-description=&quot;Get CUDA Toolkit 11.2 for Linux and Windows.&quot; data-og-host=&quot;developer.nvidia.com&quot; data-og-source-url=&quot;https://developer.nvidia.com/cuda-11.2.0-download-archive?target_os=Windows&amp;amp;target_arch=x86_64&amp;amp;target_version=10&amp;amp;target_type=exelocal&quot; data-og-url=&quot;https://developer.nvidia.com/cuda-11.2.0-download-archive&quot; data-og-image=&quot;https://scrap.kakaocdn.net/dn/b5fIQ0/hyXKzci6l2/Kk6wAgt0nk2lCDINKnuO91/img.jpg?width=1200&amp;amp;height=630&amp;amp;face=0_0_1200_630,https://scrap.kakaocdn.net/dn/dngMHg/hyXKuB4Yug/N379qpk22AvQ4G8c8PpM6K/img.jpg?width=1200&amp;amp;height=630&amp;amp;face=0_0_1200_630&quot;&gt;&lt;a href=&quot;https://developer.nvidia.com/cuda-11.2.0-download-archive?target_os=Windows&amp;amp;target_arch=x86_64&amp;amp;target_version=10&amp;amp;target_type=exelocal&quot; target=&quot;_blank&quot; rel=&quot;noopener&quot; data-source-url=&quot;https://developer.nvidia.com/cuda-11.2.0-download-archive?target_os=Windows&amp;amp;target_arch=x86_64&amp;amp;target_version=10&amp;amp;target_type=exelocal&quot;&gt;
&lt;div class=&quot;og-image&quot; style=&quot;background-image: url('https://scrap.kakaocdn.net/dn/b5fIQ0/hyXKzci6l2/Kk6wAgt0nk2lCDINKnuO91/img.jpg?width=1200&amp;amp;height=630&amp;amp;face=0_0_1200_630,https://scrap.kakaocdn.net/dn/dngMHg/hyXKuB4Yug/N379qpk22AvQ4G8c8PpM6K/img.jpg?width=1200&amp;amp;height=630&amp;amp;face=0_0_1200_630');&quot;&gt;&amp;nbsp;&lt;/div&gt;
&lt;div class=&quot;og-text&quot;&gt;
&lt;p class=&quot;og-title&quot; data-ke-size=&quot;size16&quot;&gt;CUDA Toolkit 11.2 Downloads&lt;/p&gt;
&lt;p class=&quot;og-desc&quot; data-ke-size=&quot;size16&quot;&gt;Get CUDA Toolkit 11.2 for Linux and Windows.&lt;/p&gt;
&lt;p class=&quot;og-host&quot; data-ke-size=&quot;size16&quot;&gt;developer.nvidia.com&lt;/p&gt;
&lt;/div&gt;
&lt;/a&gt;&lt;/figure&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;현재 최신버전 12.6 여서 설치했더니, 에러가 나서 알아보니&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;텐서플로우에서 지원하는 안정된 버전을 쓰라고 한다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;3. cuDNN 설치&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;b&gt;cuDNN 8.1 (cuda 11.2 지원)&lt;/b&gt;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;NVIDIA 사이트에서 cuDNN 메뉴에 들어가 로그인을 해야만 다운로드가 가능하였다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;4. 라이브러리 다운로드&lt;/p&gt;
&lt;pre id=&quot;code_1733906628594&quot; class=&quot;python&quot; data-ke-language=&quot;python&quot; data-ke-type=&quot;codeblock&quot;&gt;&lt;code&gt;pip install tensorflow-gpu --user&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;4. 실제 GPU 사용하는지 Test Code 검증&lt;/p&gt;
&lt;pre id=&quot;code_1733906594258&quot; class=&quot;python&quot; data-ke-language=&quot;python&quot; data-ke-type=&quot;codeblock&quot;&gt;&lt;code&gt;import tensorflow as tf

# GPU 사용 가능 여부 확인
print(&quot;GPU 사용 가능 여부:&quot;, tf.test.is_built_with_cuda())

# 사용 가능한 GPU 목록 출력
print(&quot;사용 가능한 GPU 목록:&quot;, tf.config.list_physical_devices('GPU'))

# GPU 메모리 할당 테스트
if tf.test.is_built_with_cuda():
    # 간단한 GPU 연산 수행
    with tf.device('/GPU:0'):
        a = tf.constant([[1.0, 2.0, 3.0], [4.0, 5.0, 6.0]])
        b = tf.constant([[1.0, 2.0], [3.0, 4.0], [5.0, 6.0]])
        c = tf.matmul(a, b)
        print(&quot;GPU 연산 결과:&quot;, c)
else:
    print(&quot;GPU를 사용할 수 없습니다.&quot;)

# TensorFlow 버전 출력
print(&quot;TensorFlow 버전:&quot;, tf.__version__)

# CUDA 버전 출력 (사용 가능한 경우)
if tf.test.is_built_with_cuda():
    print(&quot;CUDA 버전:&quot;, tf.sysconfig.get_build_info()[&quot;cuda_version&quot;])&lt;/code&gt;&lt;/pre&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;위 파이선 코드를 실행하면 작업관리자에서 GPU 메모리 사용량이 올라간다.&lt;/p&gt;
&lt;p&gt;&lt;figure class=&quot;imageblock alignCenter&quot; data-ke-mobileStyle=&quot;widthOrigin&quot; data-origin-width=&quot;696&quot; data-origin-height=&quot;613&quot;&gt;&lt;span data-url=&quot;https://blog.kakaocdn.net/dn/WsmBa/btsLereuYHm/4uAK7yS4FRzXjINFXQKOKK/img.png&quot; data-phocus=&quot;https://blog.kakaocdn.net/dn/WsmBa/btsLereuYHm/4uAK7yS4FRzXjINFXQKOKK/img.png&quot;&gt;&lt;img src=&quot;https://blog.kakaocdn.net/dn/WsmBa/btsLereuYHm/4uAK7yS4FRzXjINFXQKOKK/img.png&quot; srcset=&quot;https://img1.daumcdn.net/thumb/R1280x0/?scode=mtistory2&amp;fname=https%3A%2F%2Fblog.kakaocdn.net%2Fdn%2FWsmBa%2FbtsLereuYHm%2F4uAK7yS4FRzXjINFXQKOKK%2Fimg.png&quot; onerror=&quot;this.onerror=null; this.src='//t1.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;696&quot; height=&quot;613&quot; data-origin-width=&quot;696&quot; data-origin-height=&quot;613&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;h2 data-ke-size=&quot;size26&quot;&gt;VGG16 활용 코드&lt;/h2&gt;
&lt;pre id=&quot;code_1733906735792&quot; class=&quot;python&quot; data-ke-language=&quot;python&quot; data-ke-type=&quot;codeblock&quot;&gt;&lt;code&gt;from tensorflow.keras.applications.vgg16 import VGG16, preprocess_input, decode_predictions
from tensorflow.keras.preprocessing import image
import numpy as np

# VGG16 모델 로드 (완전 연결 층 포함)
model = VGG16(weights='imagenet')

def identify_objects(img_path, top_n=5):
    # 이미지 로드 및 전처리
    img = image.load_img(img_path, target_size=(224, 224))
    x = image.img_to_array(img)
    x = np.expand_dims(x, axis=0)
    x = preprocess_input(x)

    # 예측 수행
    preds = model.predict(x)
    
    # 예측 결과 디코딩
    results = decode_predictions(preds, top=top_n)[0]

    # 결과 출력
    print(f&quot;이미지 '{img_path}'에서 식별된 객체들:&quot;)
    for i, (imagenet_id, label, score) in enumerate(results):
        print(f&quot;{i+1}. {label}: {score:.2%}&quot;)

# 사용 예시
image_path = 'C:\\ML\\tf\\bg.jpg'
identify_objects(image_path)&lt;/code&gt;&lt;/pre&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;이제 위 소스를 저장하고 실행하면 C:\ML\tf\bg.jpg 파일을 읽어 나오게된다.&lt;/p&gt;
&lt;p&gt;&lt;figure class=&quot;imageblock alignCenter&quot; data-ke-mobileStyle=&quot;widthOrigin&quot; data-origin-width=&quot;500&quot; data-origin-height=&quot;750&quot;&gt;&lt;span data-url=&quot;https://blog.kakaocdn.net/dn/Ed74O/btsLe8rOu3D/kHXyvQPgLR5wBVVwdzEwjk/img.jpg&quot; data-phocus=&quot;https://blog.kakaocdn.net/dn/Ed74O/btsLe8rOu3D/kHXyvQPgLR5wBVVwdzEwjk/img.jpg&quot;&gt;&lt;img src=&quot;https://blog.kakaocdn.net/dn/Ed74O/btsLe8rOu3D/kHXyvQPgLR5wBVVwdzEwjk/img.jpg&quot; srcset=&quot;https://img1.daumcdn.net/thumb/R1280x0/?scode=mtistory2&amp;fname=https%3A%2F%2Fblog.kakaocdn.net%2Fdn%2FEd74O%2FbtsLe8rOu3D%2FkHXyvQPgLR5wBVVwdzEwjk%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;500&quot; height=&quot;750&quot; data-origin-width=&quot;500&quot; data-origin-height=&quot;750&quot;/&gt;&lt;/span&gt;&lt;/figure&gt;
&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;이미지&amp;nbsp;'C:\azureML\tf\bg1.jpeg'에서&amp;nbsp;식별된&amp;nbsp;객체들: &lt;br /&gt;1.&amp;nbsp;patio:&amp;nbsp;55.62% &lt;br /&gt;2.&amp;nbsp;park_bench:&amp;nbsp;15.68% &lt;br /&gt;3.&amp;nbsp;shopping_cart:&amp;nbsp;6.67% &lt;br /&gt;4.&amp;nbsp;rocking_chair:&amp;nbsp;3.07% &lt;br /&gt;5.&amp;nbsp;shopping_basket:&amp;nbsp;1.81%&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;API로 만들기(Flask)&lt;/h2&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;이제 편하게 사용하기 위해 API로 만들어보자.&lt;/p&gt;
&lt;pre id=&quot;code_1733906793797&quot; class=&quot;python&quot; data-ke-language=&quot;python&quot; data-ke-type=&quot;codeblock&quot;&gt;&lt;code&gt;from flask import Flask, request, jsonify
from werkzeug.utils import secure_filename
from tensorflow.keras.applications.vgg16 import VGG16, preprocess_input, decode_predictions
from tensorflow.keras.preprocessing import image
import numpy as np
import os

app = Flask(__name__)

# VGG16 모델 로드
model = VGG16(weights='imagenet')

# 업로드된 파일을 저장할 디렉토리 설정
UPLOAD_FOLDER = 'uploads'
if not os.path.exists(UPLOAD_FOLDER):
    os.makedirs(UPLOAD_FOLDER)

app.config['UPLOAD_FOLDER'] = UPLOAD_FOLDER

def analyze_single_image(filepath):
    img = image.load_img(filepath, target_size=(224, 224))
    x = image.img_to_array(img)
    x = np.expand_dims(x, axis=0)
    x = preprocess_input(x)

    preds = model.predict(x)
    results = decode_predictions(preds, top=5)[0]

    return [
        {'label': label, 'score': float(score)}
        for (imagenet_id, label, score) in results
    ]

@app.route('/analyze', methods=['POST'])
def analyze_images():
    if 'files' not in request.files:
        return jsonify({'error': '파일이 없습니다'}), 400
    
    files = request.files.getlist('files')
    if not files or files[0].filename == '':
        return jsonify({'error': '선택된 파일이 없습니다'}), 400
    
    results = {}
    for file in files:
        filename = secure_filename(file.filename)
        filepath = os.path.join(app.config['UPLOAD_FOLDER'], filename)
        file.save(filepath)
        
        results[filename] = analyze_single_image(filepath)
        
        # 임시 파일 삭제
        os.remove(filepath)

    return jsonify(results)

if __name__ == '__main__':
    app.run(debug=True)&lt;/code&gt;&lt;/pre&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;span style=&quot;background-color: #ffffff; color: #212121; text-align: left;&quot;&gt;POST http://localhost:5000/analyze&lt;/span&gt;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;span style=&quot;background-color: #ffffff; color: #212121; text-align: left;&quot;&gt; files 로 사진을 업로드하면 된다.&lt;/span&gt;&lt;/p&gt;
&lt;p&gt;&lt;figure class=&quot;imageblock alignCenter&quot; data-ke-mobileStyle=&quot;widthOrigin&quot; data-origin-width=&quot;972&quot; data-origin-height=&quot;746&quot;&gt;&lt;span data-url=&quot;https://blog.kakaocdn.net/dn/bdqrmz/btsLevgQesR/3mj9KJMmcc0WiwFJ5so38K/img.png&quot; data-phocus=&quot;https://blog.kakaocdn.net/dn/bdqrmz/btsLevgQesR/3mj9KJMmcc0WiwFJ5so38K/img.png&quot;&gt;&lt;img src=&quot;https://blog.kakaocdn.net/dn/bdqrmz/btsLevgQesR/3mj9KJMmcc0WiwFJ5so38K/img.png&quot; srcset=&quot;https://img1.daumcdn.net/thumb/R1280x0/?scode=mtistory2&amp;fname=https%3A%2F%2Fblog.kakaocdn.net%2Fdn%2Fbdqrmz%2FbtsLevgQesR%2F3mj9KJMmcc0WiwFJ5so38K%2Fimg.png&quot; onerror=&quot;this.onerror=null; this.src='//t1.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;972&quot; height=&quot;746&quot; data-origin-width=&quot;972&quot; data-origin-height=&quot;746&quot;/&gt;&lt;/span&gt;&lt;/figure&gt;
&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;응답값:&lt;/p&gt;
&lt;pre id=&quot;code_1733907997940&quot; class=&quot;json&quot; data-ke-language=&quot;json&quot; data-ke-type=&quot;codeblock&quot;&gt;&lt;code&gt;{
    &quot;bg.jpg&quot;: [
        {
            &quot;label&quot;: &quot;desk&quot;,
            &quot;score&quot;: 0.27180394530296326
        },
        {
            &quot;label&quot;: &quot;guillotine&quot;,
            &quot;score&quot;: 0.2136017382144928
        },
        {
            &quot;label&quot;: &quot;file&quot;,
            &quot;score&quot;: 0.06649696826934814
        },
        {
            &quot;label&quot;: &quot;apiary&quot;,
            &quot;score&quot;: 0.04862062633037567
        },
        {
            &quot;label&quot;: &quot;crate&quot;,
            &quot;score&quot;: 0.026243386790156364
        }
    ],
    &quot;bg2.png&quot;: [
        {
            &quot;label&quot;: &quot;necklace&quot;,
            &quot;score&quot;: 0.2780897617340088
        },
        {
            &quot;label&quot;: &quot;hook&quot;,
            &quot;score&quot;: 0.07838109880685806
        },
        {
            &quot;label&quot;: &quot;Christmas_stocking&quot;,
            &quot;score&quot;: 0.042882807552814484
        },
        {
            &quot;label&quot;: &quot;letter_opener&quot;,
            &quot;score&quot;: 0.03623732924461365
        },
        {
            &quot;label&quot;: &quot;safety_pin&quot;,
            &quot;score&quot;: 0.03522750362753868
        }
    ],
    &quot;bg3.png&quot;: [
        {
            &quot;label&quot;: &quot;paintbrush&quot;,
            &quot;score&quot;: 0.7162522077560425
        },
        {
            &quot;label&quot;: &quot;rubber_eraser&quot;,
            &quot;score&quot;: 0.04436235502362251
        },
        {
            &quot;label&quot;: &quot;ballpoint&quot;,
            &quot;score&quot;: 0.024050327017903328
        },
        {
            &quot;label&quot;: &quot;face_powder&quot;,
            &quot;score&quot;: 0.023242339491844177
        },
        {
            &quot;label&quot;: &quot;wooden_spoon&quot;,
            &quot;score&quot;: 0.016569746658205986
        }
    ]
}&lt;/code&gt;&lt;/pre&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;</description>
      <category>개발/Python</category>
      <category>Ai</category>
      <category>CUDA</category>
      <category>cuDNN</category>
      <category>Python</category>
      <category>VGG16</category>
      <category>분석</category>
      <author>joon95</author>
      <guid isPermaLink="true">https://flowlog.tistory.com/115</guid>
      <comments>https://flowlog.tistory.com/115#entry115comment</comments>
      <pubDate>Wed, 11 Dec 2024 18:13:46 +0900</pubDate>
    </item>
    <item>
      <title>[kubernetes] Storageclass Longhorn vs Kadalu</title>
      <link>https://flowlog.tistory.com/114</link>
      <description>&lt;p data-ke-size=&quot;size16&quot;&gt;올해 1년간 on-prem 쿠버네티스를 운영 프로젝트를 진행하고 있다. 특별히 RKE(Rancher Kubernetes Engine)에서는 Longhorn 이라는 스토리지클래스를 사용하도록 가이드 하고 있어 Rancher Cluster 들은 모두 Longhorn을 사용하고 있다. 그리고 kubespray로 구축된 클러스터는 Kadalu 라는 스토리지클래스를 사용하고 있는데 두 스토리지클래스의 사용 후기를 짧게 나마 적어놓으려 한다.&lt;/p&gt;
&lt;p&gt;&lt;figure class=&quot;imageblock alignCenter&quot; data-ke-mobileStyle=&quot;widthOrigin&quot; data-origin-width=&quot;1474&quot; data-origin-height=&quot;253&quot;&gt;&lt;span data-url=&quot;https://blog.kakaocdn.net/dn/7qvLD/btsJ2X0fzom/1kRGOAXnohx1Xm8lOniFfk/img.png&quot; data-phocus=&quot;https://blog.kakaocdn.net/dn/7qvLD/btsJ2X0fzom/1kRGOAXnohx1Xm8lOniFfk/img.png&quot; data-alt=&quot;CNCF Cloud Native Storage Landscape&quot;&gt;&lt;img src=&quot;https://blog.kakaocdn.net/dn/7qvLD/btsJ2X0fzom/1kRGOAXnohx1Xm8lOniFfk/img.png&quot; srcset=&quot;https://img1.daumcdn.net/thumb/R1280x0/?scode=mtistory2&amp;fname=https%3A%2F%2Fblog.kakaocdn.net%2Fdn%2F7qvLD%2FbtsJ2X0fzom%2F1kRGOAXnohx1Xm8lOniFfk%2Fimg.png&quot; onerror=&quot;this.onerror=null; this.src='//t1.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;1474&quot; height=&quot;253&quot; data-origin-width=&quot;1474&quot; data-origin-height=&quot;253&quot;/&gt;&lt;/span&gt;&lt;figcaption&gt;CNCF Cloud Native Storage Landscape&lt;/figcaption&gt;
&lt;/figure&gt;
&lt;/p&gt;
&lt;table style=&quot;border-collapse: collapse; width: 100%;&quot; border=&quot;1&quot; data-ke-align=&quot;alignLeft&quot; data-ke-style=&quot;style1&quot;&gt;
&lt;tbody&gt;
&lt;tr&gt;
&lt;td style=&quot;width: 50%;&quot;&gt;&lt;b&gt;Longhorn&lt;/b&gt;&lt;/td&gt;
&lt;td style=&quot;width: 50%;&quot;&gt;&lt;b&gt;Kadalu&lt;/b&gt;&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td style=&quot;width: 50%;&quot;&gt;&lt;span&gt;분산 블록 스토리지&lt;/span&gt;&lt;/td&gt;
&lt;td style=&quot;width: 50%;&quot;&gt;분산 파일 스토리지&lt;/td&gt;
&lt;/tr&gt;
&lt;/tbody&gt;
&lt;/table&gt;
&lt;h2 data-ke-size=&quot;size26&quot;&gt;Longhorn&lt;/h2&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;롱혼은 분산 블록 스토리지 솔루션으로, Blob Storage를 제공한다.&lt;br /&gt;이로 인해 볼륨 기반의 고가용성과 복제 기능에 중점을 둘 수 있다.&lt;/p&gt;
&lt;h3 data-ke-size=&quot;size23&quot;&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;간단한 관리 : 설치와 사용이 간단하며 k8s 통합&lt;/li&gt;
&lt;li&gt;UI 제공 : 직관적인 웹 UI 제공, 스토리지를 쉽게 관리할 수 있음&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;1. Longhorn 은 node 서비스에 의존적이기 때문에 서비스가 active 상태인지 모니터링이 필요&lt;/p&gt;
&lt;ul style=&quot;list-style-type: disc;&quot; data-ke-list-type=&quot;disc&quot;&gt;
&lt;li&gt;ISCSID - RWO 제공&lt;/li&gt;
&lt;li&gt;NFS-COMMON - RWX 제공&lt;/li&gt;
&lt;/ul&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;2. WEB UI 를 활용한 스토리지 관리가 매우 편리함&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;h2 data-ke-size=&quot;size26&quot;&gt;Kadalu&lt;/h2&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;카달루는 분산 파일 스토리지 솔루션으로, File Storage를 제공한다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;내부 로직은 GlusterFS 기반으로 동작한다.&lt;/p&gt;
&lt;h3 data-ke-size=&quot;size23&quot;&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;GlusterFS 통합 : 큰 용량의 데이터를 분산 처리할 수 있는 파일 스토리지 제공&lt;/li&gt;
&lt;li&gt;저비용 : 블록 스토리지와 비교했을 때 파일 스토리지는 비용이 덜 들며, 로그 파일, 백업 파일, 대규모 비구조화 데이터 저장에 적합&lt;/li&gt;
&lt;li&gt;확장성 : 대용량 데이터 처리, 동적 확장&lt;/li&gt;
&lt;li&gt;간편한 설치 및 관리 : k8s 통합, 관리 용이&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;1. 알아서 잘됨.. 문제가 난 적이 없음;;;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;2. UI 가 있다고 해서 nginx에 적용해서 시도해봤으나 kadalu api 구성부분이 필요하다고 해서 gg침&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;h2 data-ke-size=&quot;size26&quot;&gt;정리&lt;/h2&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;b&gt;longhorn&lt;/b&gt; 은 node 에 의존적이다!!!! (nfs-common 서비스가 왜 죽을까.....!!! 좀 열받는다.)&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;b&gt;kadalu&lt;/b&gt; 는 DISK 를 POD에서 포맷하기 때문에 node에 아무 관련이 없다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;즉, Cloud Native 에 적합하다. landscape에 GRADUATED 상태인 'Rook' 도 동일한 아키텍처로 봤다.&lt;/p&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;</description>
      <category>엔지니어링/Kubernetes</category>
      <author>joon95</author>
      <guid isPermaLink="true">https://flowlog.tistory.com/114</guid>
      <comments>https://flowlog.tistory.com/114#entry114comment</comments>
      <pubDate>Sat, 12 Oct 2024 21:04:42 +0900</pubDate>
    </item>
    <item>
      <title>Golang Test Standard</title>
      <link>https://flowlog.tistory.com/113</link>
      <description>&lt;p data-ke-size=&quot;size16&quot;&gt;golang application 개발팀에서 테스트에 대한 가이드가 필요하다고 요청받아 찾아본 내용을 기술한다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;좋은 사례로 Thanos Teams, Uber, Devocean, Buzzvil 의 내용을 참고하였다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;Test Code의 표준화의 목적은 &lt;b&gt;테이블 주도 테스트(Table-driven Test)&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;먼저 테스트의 범위를 생각하면 아래 3가지의 항목이 존재한다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;1. Data Access Layer - 데이터베이스와의 상호작용, SQL 쿼리 검증&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;2. Business Layer - 비즈니스 로직이 의도한대로 작동하는가, 외부 의존성&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;3. Presentation Layer - API Endpoint 또는 Controller, Handler&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;로직에 대한 검증이 대부분이라 Business Layer를 주로 테스트 한다.&lt;/p&gt;
&lt;h2 data-ke-size=&quot;size26&quot;&gt;Library&lt;/h2&gt;
&lt;p&gt;&lt;figure class=&quot;imageblock alignCenter&quot; data-ke-mobileStyle=&quot;widthOrigin&quot; data-origin-width=&quot;870&quot; data-origin-height=&quot;342&quot;&gt;&lt;span data-url=&quot;https://blog.kakaocdn.net/dn/APATq/btsJX9zrCKE/zohhnvJHRukJ4MdKC1IZZ1/img.png&quot; data-phocus=&quot;https://blog.kakaocdn.net/dn/APATq/btsJX9zrCKE/zohhnvJHRukJ4MdKC1IZZ1/img.png&quot;&gt;&lt;img src=&quot;https://blog.kakaocdn.net/dn/APATq/btsJX9zrCKE/zohhnvJHRukJ4MdKC1IZZ1/img.png&quot; srcset=&quot;https://img1.daumcdn.net/thumb/R1280x0/?scode=mtistory2&amp;fname=https%3A%2F%2Fblog.kakaocdn.net%2Fdn%2FAPATq%2FbtsJX9zrCKE%2FzohhnvJHRukJ4MdKC1IZZ1%2Fimg.png&quot; onerror=&quot;this.onerror=null; this.src='//t1.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;870&quot; height=&quot;342&quot; data-origin-width=&quot;870&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;제일 많이 사용되는 라이브러리는 &lt;b&gt;testify&lt;/b&gt; 이다.&amp;nbsp;하지만 메소드 명을 &lt;u&gt;문자열로 받아&lt;/u&gt; type safe 하게 코드를 작성할 수 없다. 그리고 함께 붙어 있는 &lt;b&gt;mockery&lt;/b&gt;는 Mocking 기법(&lt;span style=&quot;color: #333333; text-align: start;&quot;&gt;가짜모듈)인데, 인터페이스를 읽어 자동으로 mock 객체를 생성해 준다.&lt;span&gt;&amp;nbsp;&lt;/span&gt;&lt;/span&gt;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;그 다음으로 핫한 &lt;b&gt;gomock&lt;/b&gt; 을 사용해보기로 했다. 이건 type safe 하게 코드를 작성할 수 있다.(타입을 기반한 메소드 자동완성)&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;참고로 &lt;b&gt;gomock&lt;/b&gt;에서도 Mocking을 자동으로 해주는&amp;nbsp;&lt;b&gt;mockgen&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;&amp;nbsp;&lt;/p&gt;
&lt;h2 data-ke-size=&quot;size26&quot;&gt;의존성 주입 및 변수 초기화 방법&lt;/h2&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;pre class=&quot;go&quot;&gt;&lt;code&gt;// 1. 의존성 주입 형태 - Good
func NewUserService(userRepository UserRepository) *UserService{
    return &amp;amp;UserService{
        userRepository: userRepository,
    }
}

// 2. 의존성을 주입하지 않는 형태 - Bad
func NewUserServiceWithoutInjection(client *ent.UserClient) *UserService{
    return &amp;amp;UserService{
        userRepository: NewUserRepository(client),
    }
}
&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;h2 data-ke-size=&quot;size26&quot;&gt;표준화&lt;/h2&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;타노스팀, 우버에서 거의 비슷한 표준규칙을 가지고 있어 거의 그대로 가져왔다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;h3 data-ke-size=&quot;size23&quot;&gt;기본 작성 규칙&lt;/h3&gt;
&lt;ol style=&quot;list-style-type: decimal;&quot; data-ke-list-type=&quot;decimal&quot;&gt;
&lt;li&gt;파일명은 _test.go 형식을 따른다.&lt;/li&gt;
&lt;li&gt;테스트 대상 패키지에 _test 를 붙여서 테스트를 작성한다(Blackbox 테스트)&lt;/li&gt;
&lt;li&gt;함수명은 맨 앞에 Test 를 붙인다.&lt;/li&gt;
&lt;li&gt;매개변수는 t *testing.T 를 받는다.&lt;/li&gt;
&lt;li&gt;실패지점에서 t.Fail() 을 호출한다.&lt;/li&gt;
&lt;/ol&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;&lt;b&gt;파일&lt;/b&gt;&lt;/td&gt;
&lt;td&gt;controller.go&lt;/td&gt;
&lt;td&gt;controller_test.go&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;&lt;b&gt;패키지&lt;/b&gt;&lt;/td&gt;
&lt;td&gt;package articlesvc&lt;/td&gt;
&lt;td&gt;package articlsvc_test&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;&lt;b&gt;함수&lt;/b&gt;&lt;/td&gt;
&lt;td&gt;GetArticles()&lt;/td&gt;
&lt;td&gt;TestGetArticles()&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;tests라고 하고, 각 테스트 케이스를&amp;nbsp;tt라고 한다. 또한 각 테스트 케이스의 입력 및 출력 값을&amp;nbsp;give&amp;nbsp;및&amp;nbsp;want&amp;nbsp;접두어를 사용하여 설명(explicating)하는 것을 권장한다.&lt;/p&gt;
&lt;pre class=&quot;go&quot;&gt;&lt;code&gt;tests := []struct{
  give     string
  wantHost string
  wantPort string
}{
  // ...
}

for _, tt := range tests {
  // ...
}
&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;Mocking 예제&lt;/h3&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;모듈 인터페이스 파일 - doer.go&lt;/li&gt;
&lt;li&gt;모듈 로직 구현 파일 - test.go&lt;/li&gt;
&lt;li&gt;실제 메소드 구현 파일 - user.go&lt;/li&gt;
&lt;li&gt;_test 파일 - user_test.go&lt;/li&gt;
&lt;/ul&gt;
&lt;pre id=&quot;code_1728402711299&quot; class=&quot;go&quot; data-ke-language=&quot;go&quot; data-ke-type=&quot;codeblock&quot;&gt;&lt;code&gt;package repository

// doer.go
type Doer interface {
	MyFuncPlus(int) int
}&lt;/code&gt;&lt;/pre&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;인터페이스 파일을 생성했으면 mockgen 라이브러리로 mock 객체를 생성해 준다.(mock 폴더아래)&lt;/p&gt;
&lt;pre id=&quot;code_1728402953735&quot; class=&quot;go&quot; data-ke-type=&quot;codeblock&quot; data-ke-language=&quot;bash&quot;&gt;&lt;code&gt;mockgen -source=./repository.go -destination=./mock/mock_repository.go -package=mock&lt;/code&gt;&lt;/pre&gt;
&lt;pre id=&quot;code_1728402729129&quot; class=&quot;go&quot; data-ke-language=&quot;go&quot; data-ke-type=&quot;codeblock&quot;&gt;&lt;code&gt;package repository

// test.go
func MyFuncPlus(limit int) int {
	return limit + 22
}&lt;/code&gt;&lt;/pre&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;pre id=&quot;code_1728402740367&quot; class=&quot;go&quot; data-ke-language=&quot;go&quot; data-ke-type=&quot;codeblock&quot;&gt;&lt;code&gt;package repository

// user.go
type DoerUser struct {
	Doer Doer
}

func (u *DoerUser) DoerUse(limit int) int {
	return u.Doer.MyFuncPlus(limit)
}&lt;/code&gt;&lt;/pre&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;pre id=&quot;code_1728402786916&quot; class=&quot;go&quot; data-ke-language=&quot;go&quot; data-ke-type=&quot;codeblock&quot;&gt;&lt;code&gt;package repository_test

import (
	&quot;testing&quot;

	repository &quot;github.com/{myRepo}/repo&quot;
	mock &quot;github.com/{myRepo}/repo/mocks&quot;
	&quot;github.com/stretchr/testify/assert&quot;
	&quot;github.com/golang/mock/gomock&quot;
)

// Mocking Test
func TestMyFuncPlus(t *testing.T) {
	ctrl := gomock.NewController(t)
	defer ctrl.Finish()
	tests := []struct {
		name         string
		giveLimit    int
		wantExpected int
	}{
		{
			name:         &quot;valid case&quot;,
			giveLimit:    50,
			wantExpected: 72,
		},
	}

	// Mock repository 설정
	mockRepo := mock.NewMockDoer(ctrl)
	testUser := &amp;amp;repository.DoerUser{Doer: mockRepo}

	for _, tt := range tests {
		t.Run(tt.name, func(t *testing.T) {
			// 기대하는 동작을 설정
			mockRepo.EXPECT().
				MyFuncPlus(tt.giveLimit).
				Return(tt.wantExpected).
				Times(1)
			// 테스트 진행
			result := testUser.DoerUse(tt.giveLimit)

			// 결과 확인
			assert.Equal(t, tt.wantExpected, result)
		})
	}

}&lt;/code&gt;&lt;/pre&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;h4 data-ke-size=&quot;size20&quot;&gt;테스트 결과&lt;/h4&gt;
&lt;pre class=&quot;bash&quot; data-ke-language=&quot;bash&quot;&gt;&lt;code&gt;&amp;gt; go test user_test.go -v  -run TestMyFuncPlus
=== RUN   TestMyFuncPlus
=== RUN   TestMyFuncPlus/valid_case
--- PASS: TestMyFuncPlus (0.00s)
    --- PASS: TestMyFuncPlus/valid_case (0.00s)
PASS
ok      command-line-arguments  0.489s&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;h2 data-ke-size=&quot;size26&quot;&gt;마치며..&lt;/h2&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;go application 개발을 시작한다면 &lt;b&gt;thanos team&lt;/b&gt;, &lt;b&gt;uber&lt;/b&gt; 의 github에 가서 &lt;b&gt;코딩스타일&lt;/b&gt;, &lt;b&gt;테스트&lt;/b&gt;에 관한 내용을 다 읽어보길 바란다. 추가로 이번에 자료를 검토해보면서 k8s resource(nginx-ingress, api-server, calico) code를 다운로드해서 test case를 분석해보니 정말 모든 테스트 케이스를 정의를 해 놓은 것을 보았다... 언젠가 그렇게 코딩하는 날이 오기를....&lt;/p&gt;</description>
      <category>개발/기타</category>
      <author>joon95</author>
      <guid isPermaLink="true">https://flowlog.tistory.com/113</guid>
      <comments>https://flowlog.tistory.com/113#entry113comment</comments>
      <pubDate>Thu, 10 Oct 2024 01:58:53 +0900</pubDate>
    </item>
    <item>
      <title>Elastic APM으로 VUE Application 모니터링</title>
      <link>https://flowlog.tistory.com/112</link>
      <description>&lt;p data-ke-size=&quot;size16&quot;&gt;지난번 Go Application 모니터링을 Elastic APM으로 적용하였고, 그 애플리케이션과 연결된 VUE(frontend) 프로젝트도 간단히 개발해놨었다. 그래서 이번엔 VUE에 RUM 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;/p&gt;
&lt;p&gt;&lt;figure class=&quot;imageblock alignCenter&quot; data-ke-mobileStyle=&quot;widthOrigin&quot; data-origin-width=&quot;2048&quot; data-origin-height=&quot;698&quot;&gt;&lt;span data-url=&quot;https://blog.kakaocdn.net/dn/k7Uai/btsJZPNibWa/fawvDSkXPnCp6jkbdIT9H0/img.png&quot; data-phocus=&quot;https://blog.kakaocdn.net/dn/k7Uai/btsJZPNibWa/fawvDSkXPnCp6jkbdIT9H0/img.png&quot;&gt;&lt;img src=&quot;https://blog.kakaocdn.net/dn/k7Uai/btsJZPNibWa/fawvDSkXPnCp6jkbdIT9H0/img.png&quot; srcset=&quot;https://img1.daumcdn.net/thumb/R1280x0/?scode=mtistory2&amp;fname=https%3A%2F%2Fblog.kakaocdn.net%2Fdn%2Fk7Uai%2FbtsJZPNibWa%2FfawvDSkXPnCp6jkbdIT9H0%2Fimg.png&quot; onerror=&quot;this.onerror=null; this.src='//t1.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;2048&quot; height=&quot;698&quot; data-origin-width=&quot;2048&quot; data-origin-height=&quot;698&quot;/&gt;&lt;/span&gt;&lt;/figure&gt;
&lt;figure class=&quot;imageblock alignCenter&quot; data-ke-mobileStyle=&quot;widthOrigin&quot; data-origin-width=&quot;1657&quot; data-origin-height=&quot;832&quot;&gt;&lt;span data-url=&quot;https://blog.kakaocdn.net/dn/k5pr0/btsJZaEmBT0/wYTEmjGVr30MfmYKbcDRE1/img.png&quot; data-phocus=&quot;https://blog.kakaocdn.net/dn/k5pr0/btsJZaEmBT0/wYTEmjGVr30MfmYKbcDRE1/img.png&quot;&gt;&lt;img src=&quot;https://blog.kakaocdn.net/dn/k5pr0/btsJZaEmBT0/wYTEmjGVr30MfmYKbcDRE1/img.png&quot; srcset=&quot;https://img1.daumcdn.net/thumb/R1280x0/?scode=mtistory2&amp;fname=https%3A%2F%2Fblog.kakaocdn.net%2Fdn%2Fk5pr0%2FbtsJZaEmBT0%2FwYTEmjGVr30MfmYKbcDRE1%2Fimg.png&quot; onerror=&quot;this.onerror=null; this.src='//t1.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;1657&quot; height=&quot;832&quot; data-origin-width=&quot;1657&quot; data-origin-height=&quot;832&quot;/&gt;&lt;/span&gt;&lt;/figure&gt;
&lt;/p&gt;
&lt;h2 data-ke-size=&quot;size26&quot;&gt;작업 순서&lt;/h2&gt;
&lt;ol style=&quot;list-style-type: decimal;&quot; data-ke-list-type=&quot;decimal&quot;&gt;
&lt;li&gt;npm install&lt;/li&gt;
&lt;li&gt;rum agent js 파일을 다운 받아 public/js/ 안에 복사.&lt;/li&gt;
&lt;li&gt;환경변수 설정.&lt;/li&gt;
&lt;li&gt;main.js 에 javascript load 및 init 설정&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;NPM install&lt;/h3&gt;
&lt;pre class=&quot;css&quot;&gt;&lt;code&gt;npm install @elastic/apm-rum --save
&lt;/code&gt;&lt;/pre&gt;
&lt;h3 data-ke-size=&quot;size23&quot;&gt;Rum Agent JS 파일 다운&lt;/h3&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;아래 공식 github 에 들어가 Release 버전 확인 후 다운로드하자.&lt;/p&gt;
&lt;figure id=&quot;og_1728399799314&quot; contenteditable=&quot;false&quot; data-ke-type=&quot;opengraph&quot; data-ke-align=&quot;alignCenter&quot; data-og-type=&quot;object&quot; data-og-title=&quot;GitHub - elastic/apm-agent-rum-js&quot; data-og-description=&quot;Contribute to elastic/apm-agent-rum-js development by creating an account on GitHub.&quot; data-og-host=&quot;github.com&quot; data-og-source-url=&quot;https://github.com/elastic/apm-agent-rum-js&quot; data-og-url=&quot;https://github.com/elastic/apm-agent-rum-js&quot; data-og-image=&quot;https://scrap.kakaocdn.net/dn/jZCVa/hyXd4jGCON/4U0KkKkx5kEaWv03hpFKo0/img.png?width=1200&amp;amp;height=600&amp;amp;face=0_0_1200_600,https://scrap.kakaocdn.net/dn/bQ1pEU/hyXeeUaQ7c/LpKCiKowIH3tvHWrk3vWh1/img.png?width=1200&amp;amp;height=600&amp;amp;face=0_0_1200_600&quot;&gt;&lt;a href=&quot;https://github.com/elastic/apm-agent-rum-js&quot; target=&quot;_blank&quot; rel=&quot;noopener&quot; data-source-url=&quot;https://github.com/elastic/apm-agent-rum-js&quot;&gt;
&lt;div class=&quot;og-image&quot; style=&quot;background-image: url('https://scrap.kakaocdn.net/dn/jZCVa/hyXd4jGCON/4U0KkKkx5kEaWv03hpFKo0/img.png?width=1200&amp;amp;height=600&amp;amp;face=0_0_1200_600,https://scrap.kakaocdn.net/dn/bQ1pEU/hyXeeUaQ7c/LpKCiKowIH3tvHWrk3vWh1/img.png?width=1200&amp;amp;height=600&amp;amp;face=0_0_1200_600');&quot;&gt;&amp;nbsp;&lt;/div&gt;
&lt;div class=&quot;og-text&quot;&gt;
&lt;p class=&quot;og-title&quot; data-ke-size=&quot;size16&quot;&gt;GitHub - elastic/apm-agent-rum-js&lt;/p&gt;
&lt;p class=&quot;og-desc&quot; data-ke-size=&quot;size16&quot;&gt;Contribute to elastic/apm-agent-rum-js development by creating an account on GitHub.&lt;/p&gt;
&lt;p class=&quot;og-host&quot; data-ke-size=&quot;size16&quot;&gt;github.com&lt;/p&gt;
&lt;/div&gt;
&lt;/a&gt;&lt;/figure&gt;
&lt;p&gt;&lt;figure class=&quot;imageblock alignCenter&quot; data-ke-mobileStyle=&quot;widthOrigin&quot; data-origin-width=&quot;274&quot; data-origin-height=&quot;125&quot;&gt;&lt;span data-url=&quot;https://blog.kakaocdn.net/dn/cR71It/btsJZQZFU1Q/TYRskvqHD5uSvduDBXlGrk/img.png&quot; data-phocus=&quot;https://blog.kakaocdn.net/dn/cR71It/btsJZQZFU1Q/TYRskvqHD5uSvduDBXlGrk/img.png&quot;&gt;&lt;img src=&quot;https://blog.kakaocdn.net/dn/cR71It/btsJZQZFU1Q/TYRskvqHD5uSvduDBXlGrk/img.png&quot; srcset=&quot;https://img1.daumcdn.net/thumb/R1280x0/?scode=mtistory2&amp;fname=https%3A%2F%2Fblog.kakaocdn.net%2Fdn%2FcR71It%2FbtsJZQZFU1Q%2FTYRskvqHD5uSvduDBXlGrk%2Fimg.png&quot; onerror=&quot;this.onerror=null; this.src='//t1.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;274&quot; height=&quot;125&quot; data-origin-width=&quot;274&quot; data-origin-height=&quot;125&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&gt;&lt;figure class=&quot;imageblock alignCenter&quot; data-ke-mobileStyle=&quot;widthOrigin&quot; data-origin-width=&quot;928&quot; data-origin-height=&quot;496&quot;&gt;&lt;span data-url=&quot;https://blog.kakaocdn.net/dn/bILlOx/btsJ0eltQir/AVupQhO6DuYiRWa6JsEVo0/img.png&quot; data-phocus=&quot;https://blog.kakaocdn.net/dn/bILlOx/btsJ0eltQir/AVupQhO6DuYiRWa6JsEVo0/img.png&quot;&gt;&lt;img src=&quot;https://blog.kakaocdn.net/dn/bILlOx/btsJ0eltQir/AVupQhO6DuYiRWa6JsEVo0/img.png&quot; srcset=&quot;https://img1.daumcdn.net/thumb/R1280x0/?scode=mtistory2&amp;fname=https%3A%2F%2Fblog.kakaocdn.net%2Fdn%2FbILlOx%2FbtsJ0eltQir%2FAVupQhO6DuYiRWa6JsEVo0%2Fimg.png&quot; onerror=&quot;this.onerror=null; this.src='//t1.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;928&quot; height=&quot;496&quot; data-origin-width=&quot;928&quot; data-origin-height=&quot;496&quot;/&gt;&lt;/span&gt;&lt;/figure&gt;
&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;파일을 다운 받아 vue3 프로젝트의 js 폴더안에 넣었다.&lt;/p&gt;
&lt;p&gt;&lt;figure class=&quot;imageblock alignCenter&quot; data-ke-mobileStyle=&quot;widthOrigin&quot; data-origin-width=&quot;226&quot; data-origin-height=&quot;177&quot;&gt;&lt;span data-url=&quot;https://blog.kakaocdn.net/dn/bjo5aw/btsJXUJg5Zh/q4Y9orJRJTZ5KdP2hPDdC1/img.png&quot; data-phocus=&quot;https://blog.kakaocdn.net/dn/bjo5aw/btsJXUJg5Zh/q4Y9orJRJTZ5KdP2hPDdC1/img.png&quot;&gt;&lt;img src=&quot;https://blog.kakaocdn.net/dn/bjo5aw/btsJXUJg5Zh/q4Y9orJRJTZ5KdP2hPDdC1/img.png&quot; srcset=&quot;https://img1.daumcdn.net/thumb/R1280x0/?scode=mtistory2&amp;fname=https%3A%2F%2Fblog.kakaocdn.net%2Fdn%2Fbjo5aw%2FbtsJXUJg5Zh%2Fq4Y9orJRJTZ5KdP2hPDdC1%2Fimg.png&quot; onerror=&quot;this.onerror=null; this.src='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png'; this.srcset='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png';&quot; loading=&quot;lazy&quot; width=&quot;226&quot; height=&quot;177&quot; data-origin-width=&quot;226&quot; data-origin-height=&quot;177&quot;/&gt;&lt;/span&gt;&lt;/figure&gt;
&lt;/p&gt;
&lt;h3 data-ke-size=&quot;size23&quot;&gt;main.js 설정&lt;/h3&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;다운로드한 js 파일을 로드하고 세팅할 수 있도록 소스를 작성한다.&lt;/p&gt;
&lt;pre class=&quot;javascript&quot;&gt;&lt;code&gt;import { createApp } from 'vue'
import App from './App.vue'
import router from './router'

// Elastic APM RUM 에이전트 로드 및 초기화
const script = document.createElement('script');
script.src = '/js/elastic-apm-rum.umd.min.js'; // public 폴더 내 경로에 따라 수정
script.crossOrigin = '';
script.onload = () =&amp;gt; {
  const apm = window.elasticApm.init({
    serviceName: process.env.VUE_APP_APM_SERVICE_NAME,
    serverUrl: process.env.VUE_APP_APM_SERVER_URL,
    environment: process.env.VUE_APP_APM_ENVIRONMEMT
  });

  console.log('Elastic APM initialized', apm);
};
document.head.appendChild(script);

// Vue 애플리케이션 생성
createApp(App).use(router).mount('#app')
&lt;/code&gt;&lt;/pre&gt;
&lt;h3 data-ke-size=&quot;size23&quot;&gt;환경변수 설정&lt;/h3&gt;
&lt;p style=&quot;color: #333333; text-align: start;&quot; data-ke-size=&quot;size16&quot;&gt;Rum Agent는 Client Brower에서 통신이 이루어지기 때문에 Client는 Elastic APM Server과 연결할 수 있어야한다.&lt;/p&gt;
&lt;p style=&quot;color: #333333; text-align: start;&quot; data-ke-size=&quot;size16&quot;&gt;그래서 필자는 kubernetes에 elasticAPM 이 구축되어있어서 nodePort로 열어 연결하였다.&lt;/p&gt;
&lt;h4 data-ke-size=&quot;size20&quot;&gt;env.local&lt;/h4&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;개발자 PC에서 먼저 확인하기 위해 local 환경을 잡아준다.&lt;/p&gt;
&lt;pre class=&quot;bash&quot; data-ke-language=&quot;bash&quot;&gt;&lt;code&gt;VUE_APP_APM_SERVICE_NAME=local-fe
VUE_APP_APM_SERVER_URL=http://{ip}:{port}
VUE_APP_APM_ENVIRONMEMT=local&lt;/code&gt;&lt;/pre&gt;
&lt;h4 data-ke-size=&quot;size20&quot;&gt;&lt;span style=&quot;color: #000000; font-size: 1.25em; letter-spacing: -1px;&quot;&gt;env.dev&lt;/span&gt;&lt;/h4&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;pre class=&quot;bash&quot; data-ke-language=&quot;bash&quot;&gt;&lt;code&gt;VUE_APP_APM_SERVICE_NAME=my-goapp-fe
VUE_APP_APM_SERVER_URL=http://{ip}:{port}
VUE_APP_APM_ENVIRONMEMT=dev&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;/h3&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;설정을 마치고 개발자콘솔을 열어 events 라는 네트워크명과 Status가 202가 되었다면 정상적으로 Elastic APM에게 데이터를 전송한 걸 확인할 수 있다.&lt;/p&gt;
&lt;p&gt;&lt;figure class=&quot;imageblock alignCenter&quot; data-ke-mobileStyle=&quot;widthOrigin&quot; data-origin-width=&quot;742&quot; data-origin-height=&quot;368&quot;&gt;&lt;span data-url=&quot;https://blog.kakaocdn.net/dn/bpzZKs/btsJ0vm4qqK/7RNoYxFM58c1eXGZRjDzm0/img.png&quot; data-phocus=&quot;https://blog.kakaocdn.net/dn/bpzZKs/btsJ0vm4qqK/7RNoYxFM58c1eXGZRjDzm0/img.png&quot;&gt;&lt;img src=&quot;https://blog.kakaocdn.net/dn/bpzZKs/btsJ0vm4qqK/7RNoYxFM58c1eXGZRjDzm0/img.png&quot; srcset=&quot;https://img1.daumcdn.net/thumb/R1280x0/?scode=mtistory2&amp;fname=https%3A%2F%2Fblog.kakaocdn.net%2Fdn%2FbpzZKs%2FbtsJ0vm4qqK%2F7RNoYxFM58c1eXGZRjDzm0%2Fimg.png&quot; onerror=&quot;this.onerror=null; this.src='//t1.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;742&quot; height=&quot;368&quot; data-origin-width=&quot;742&quot; data-origin-height=&quot;368&quot;/&gt;&lt;/span&gt;&lt;/figure&gt;
&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;만약 Status 202이 아니라면 접속에 대한 이슈이니 네트워크 설정을 잘 해보자.&lt;/p&gt;
&lt;p data-ke-size=&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;figure id=&quot;og_1728400408493&quot; contenteditable=&quot;false&quot; data-ke-type=&quot;opengraph&quot; data-ke-align=&quot;alignCenter&quot; data-og-type=&quot;object&quot; data-og-title=&quot;GitHub - joonhyeok95/vue3_study: golang_study2 와 연동하는 frontend app&quot; data-og-description=&quot;golang_study2 와 연동하는 frontend app. Contribute to joonhyeok95/vue3_study development by creating an account on GitHub.&quot; data-og-host=&quot;github.com&quot; data-og-source-url=&quot;https://github.com/joonhyeok95/vue3_study&quot; data-og-url=&quot;https://github.com/joonhyeok95/vue3_study&quot; data-og-image=&quot;https://scrap.kakaocdn.net/dn/el6fju/hyXd3yjVS4/a8Umjov3ELo01lSmE5h361/img.png?width=1200&amp;amp;height=600&amp;amp;face=0_0_1200_600,https://scrap.kakaocdn.net/dn/shMpC/hyXegLd5Vg/pCuzvPg8C1Lr0nKyFd4qR0/img.png?width=1200&amp;amp;height=600&amp;amp;face=0_0_1200_600&quot;&gt;&lt;a href=&quot;https://github.com/joonhyeok95/vue3_study&quot; target=&quot;_blank&quot; rel=&quot;noopener&quot; data-source-url=&quot;https://github.com/joonhyeok95/vue3_study&quot;&gt;
&lt;div class=&quot;og-image&quot; style=&quot;background-image: url('https://scrap.kakaocdn.net/dn/el6fju/hyXd3yjVS4/a8Umjov3ELo01lSmE5h361/img.png?width=1200&amp;amp;height=600&amp;amp;face=0_0_1200_600,https://scrap.kakaocdn.net/dn/shMpC/hyXegLd5Vg/pCuzvPg8C1Lr0nKyFd4qR0/img.png?width=1200&amp;amp;height=600&amp;amp;face=0_0_1200_600');&quot;&gt;&amp;nbsp;&lt;/div&gt;
&lt;div class=&quot;og-text&quot;&gt;
&lt;p class=&quot;og-title&quot; data-ke-size=&quot;size16&quot;&gt;GitHub - joonhyeok95/vue3_study: golang_study2 와 연동하는 frontend app&lt;/p&gt;
&lt;p class=&quot;og-desc&quot; data-ke-size=&quot;size16&quot;&gt;golang_study2 와 연동하는 frontend app. Contribute to joonhyeok95/vue3_study development by creating an account on GitHub.&lt;/p&gt;
&lt;p class=&quot;og-host&quot; data-ke-size=&quot;size16&quot;&gt;github.com&lt;/p&gt;
&lt;/div&gt;
&lt;/a&gt;&lt;/figure&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;</description>
      <category>엔지니어링/Kubernetes</category>
      <category>Agent</category>
      <category>apm</category>
      <category>elastic</category>
      <category>elasticapm</category>
      <category>monitoring</category>
      <category>VUE</category>
      <category>vue3</category>
      <author>joon95</author>
      <guid isPermaLink="true">https://flowlog.tistory.com/112</guid>
      <comments>https://flowlog.tistory.com/112#entry112comment</comments>
      <pubDate>Wed, 9 Oct 2024 00:12:27 +0900</pubDate>
    </item>
    <item>
      <title>Elastic APM으로 Go Application 모니터링</title>
      <link>https://flowlog.tistory.com/111</link>
      <description>&lt;p data-ke-size=&quot;size16&quot;&gt;진행하고 있는 프로젝트의 개발팀에서 Golang 기반 Backend API를 개발하는게 있어서 연초에 VUE, GO Application 샘플을 개발해놓았다. (매우 간단하게...)&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;이후 Application Performance Monitoring을 어떻게 할 것인가?&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;에 대한 질문들로 Elastic APM을 사용해보기로 했다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;지금까지 java APM만을 셋업했었는데 golang은 어떻게 다른지 파악해보자.&lt;/p&gt;
&lt;h2 data-ke-size=&quot;size26&quot;&gt;Java와의 차이&lt;/h2&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;java application은 javaagent 로 올리면 바이트코드를 읽어 자동으로 수집하지만 go application은 아니다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;그래도 http route, gorm 을 통해 어느정도 요청에 대한 그래프를 볼 수 있다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;필자는 라우터 라이브러리로 고릴라, 데이터베이스는 gorm으로 프로젝트를 구성하였다.&lt;/p&gt;
&lt;ul style=&quot;list-style-type: disc;&quot; data-ke-list-type=&quot;disc&quot;&gt;
&lt;li&gt;gorilla/mux&lt;/li&gt;
&lt;li&gt;gorm&lt;/li&gt;
&lt;/ul&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;h2 data-ke-size=&quot;size26&quot;&gt;적용방법&lt;/h2&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;span style=&quot;color: #333333; text-align: start;&quot;&gt;elastic에서 지원하는 라이브러리를&lt;span&gt; &lt;/span&gt;&lt;/span&gt;사용하여 아주 간편하게 적용할 수 있는데, 라우터에는 미들웨어를 추가하고, gorm은 전용 driver를 활용하면 되며, 모두 Elastic 공식 가이드 문서를 참고하여 진행하였다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;h3 data-ke-size=&quot;size23&quot;&gt;모듈 다운로드&lt;/h3&gt;
&lt;pre id=&quot;code_1728398473682&quot; class=&quot;bash&quot; data-ke-language=&quot;bash&quot; data-ke-type=&quot;codeblock&quot;&gt;&lt;code&gt;go get -u go.elastic.co/apm/v2&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;goilla/mux 적용&lt;/h3&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;route.go 파일에서 Route 생성 부분에 추가한다.(맨 처음으로 미들웨어 등록 필수)&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;참고로 golang 에서 지원하는 라우터 종류가 많은데 고릴라를 활용하면 elastic apm 에서 추적하는 것이 많다고 한다.&lt;/p&gt;
&lt;pre class=&quot;go&quot; data-ke-language=&quot;go&quot;&gt;&lt;code&gt;func NewHttpHandler() http.Hander {
	...
	// Elastic APM 추가 ( gorilla mux 는 자동으로 트랜잭션을 추적하도록 도와줌)
	mux.Use(apmgorilla.Middleware())
	...
}&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;gorm 적용&lt;/h3&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;dbConnection.go 파일에서 &lt;a href=&quot;http://gorm.io/driver/mysql&quot;&gt;gorm.io/driver/mysql&lt;/a&gt; 라이브러리를 &lt;a href=&quot;http://go.elastic.co/apm/module/apmgormv2/v2/driver/mysql&quot;&gt;go.elastic.co/apm/module/apmgormv2/v2/driver/mysql&lt;/a&gt; 로 교체한 뒤 &lt;a href=&quot;http://mysql.Open&quot;&gt;mysql.Open&lt;/a&gt; 을 변경한다..&lt;/p&gt;
&lt;pre class=&quot;go&quot;&gt;&lt;code&gt;import (
	...
	apmmysql &quot;go.elastic.co/apm/module/apmgormv2/v2/driver/mysql&quot;
	//&quot;gorm.io/driver/mysql&quot;
	...
)

func InitDB() {
	...
	db, err := gorm.Open(apmmysql.Open(dsn), &amp;amp;gorm.Config{})
	...
}
&lt;/code&gt;&lt;/pre&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;route.go 파일에서 미들웨어를 추가한다.&lt;/p&gt;
&lt;pre class=&quot;go&quot;&gt;&lt;code&gt;func NewHttpHandler() http.Hander {
  ...
	// DB Context 미들웨어 추가
  mux.Use(DBContextMiddleware)
  ...
}
// APM 미들웨어 함수
func DBContextMiddleware(next http.Handler) http.Handler {
	return http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) {
		ctx := r.Context()
		DB = DB.WithContext(ctx)
		next.ServeHTTP(w, r)
	})
}

&lt;/code&gt;&lt;/pre&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;h3 data-ke-size=&quot;size23&quot;&gt;환경 변수 설정(Local)&lt;/h3&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;먼저 개발자 PC에서 검증을 거치기 위해 local 테스트를 진행한다.&lt;/p&gt;
&lt;pre class=&quot;bash&quot; data-ke-language=&quot;bash&quot;&gt;&lt;code&gt;ELASTIC_APM_SECRET_TOKEN=QnVsV0laRUJ4OWo3QVBpdVZQbTU6MEVkcjJkRklUN2E0NDFCNUY1T3Yydw==
ELASTIC_APM_SERVER_URL=http://{ip}:{port}/
ELASTIC_APM_SERVER_TIMEOUT=60s

ELASTIC_APM_SERVICE_NAME=local-test
ELASTIC_APM_ENVIRONMENT=local

ELASTIC_APM_LOG_FILE=stderr
ELASTIC_APM_LOG_LEVEL=debug&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;설정 후 application 을 구동 하면 아래와 같이 나온다.(주기적으로 트랜잭션을 서버에 전달함)&lt;/p&gt;
&lt;pre class=&quot;json&quot;&gt;&lt;code&gt;{&quot;level&quot;:&quot;debug&quot;,&quot;time&quot;:&quot;2024-08-07T14:40:39+09:00&quot;,&quot;message&quot;:&quot;sent request with 2 transactions, 1 span, 0 errors, 0 metricsets&quot;}
{&quot;level&quot;:&quot;debug&quot;,&quot;time&quot;:&quot;2024-08-07T14:40:53+09:00&quot;,&quot;message&quot;:&quot;gathering metrics&quot;}
{&quot;level&quot;:&quot;debug&quot;,&quot;time&quot;:&quot;2024-08-07T14:41:03+09:00&quot;,&quot;message&quot;:&quot;sent request with 0 transactions, 0 spans, 0 errors, 4 metricsets&quot;}
&lt;/code&gt;&lt;/pre&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;h3 data-ke-size=&quot;size23&quot;&gt;환경 변수 설정(Server)&lt;/h3&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;서버에 배포했을 때 연결될 네트워크 정보를 설정한다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;필자의 환경은 애플리케이션을 kubernetes 에 배포하여 &lt;b&gt;서비스명.네임스페이스.svc&lt;/b&gt; 경로로 서버를 지정하였고, APM 에서 수집될 명, 환경에 대한 구분을 변경해주었다.&lt;/p&gt;
&lt;pre class=&quot;ini&quot;&gt;&lt;code&gt;ELASTIC_APM_SECRET_TOKEN=QnVsV0laRUJ4OWo3QVBpdVZQbTU6MEVkcjJkRklUN2E0NDFCNUY1T3Yydw==
ELASTIC_APM_SERVER_URL=http://apm-server-apm-server.logging.svc:8200
ELASTIC_APM_SERVER_TIMEOUT=60s

ELASTIC_APM_SERVICE_NAME=my-goapp-service
ELASTIC_APM_ENVIRONMENT=dev

#ELASTIC_APM_LOG_FILE=stderr
#ELASTIC_APM_LOG_LEVEL=debug
&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;Kibana에서 확인&lt;/h3&gt;
&lt;h4 data-ke-size=&quot;size20&quot;&gt;Postman API Test&lt;/h4&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;먼저 간단하게 API를 호출하는 스크립트를 작성하였다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;랜덤값을 발생시켜 PUT API(데이터를 insert 하는 로직)에서 일부로 Duplicate Key 에러를 발생시키고자 한다.&lt;/p&gt;
&lt;p&gt;&lt;figure class=&quot;imageblock alignCenter&quot; data-ke-mobileStyle=&quot;widthOrigin&quot; data-origin-width=&quot;812&quot; data-origin-height=&quot;287&quot;&gt;&lt;span data-url=&quot;https://blog.kakaocdn.net/dn/cOF918/btsJX8tTjTI/55oYUrbIs4QfOyos1rAiN0/img.png&quot; data-phocus=&quot;https://blog.kakaocdn.net/dn/cOF918/btsJX8tTjTI/55oYUrbIs4QfOyos1rAiN0/img.png&quot; data-alt=&quot;Collection Enviroment Variable Setting&quot;&gt;&lt;img src=&quot;https://blog.kakaocdn.net/dn/cOF918/btsJX8tTjTI/55oYUrbIs4QfOyos1rAiN0/img.png&quot; srcset=&quot;https://img1.daumcdn.net/thumb/R1280x0/?scode=mtistory2&amp;fname=https%3A%2F%2Fblog.kakaocdn.net%2Fdn%2FcOF918%2FbtsJX8tTjTI%2F55oYUrbIs4QfOyos1rAiN0%2Fimg.png&quot; onerror=&quot;this.onerror=null; this.src='//t1.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;812&quot; height=&quot;287&quot; data-origin-width=&quot;812&quot; data-origin-height=&quot;287&quot;/&gt;&lt;/span&gt;&lt;figcaption&gt;Collection Enviroment Variable Setting&lt;/figcaption&gt;
&lt;/figure&gt;
&lt;figure class=&quot;imageblock alignCenter&quot; data-ke-mobileStyle=&quot;widthOrigin&quot; data-origin-width=&quot;623&quot; data-origin-height=&quot;298&quot;&gt;&lt;span data-url=&quot;https://blog.kakaocdn.net/dn/BScnj/btsJZKysxVp/pFPAkfIjmvC9OgnkY26YGK/img.png&quot; data-phocus=&quot;https://blog.kakaocdn.net/dn/BScnj/btsJZKysxVp/pFPAkfIjmvC9OgnkY26YGK/img.png&quot; data-alt=&quot;body raw data&quot;&gt;&lt;img src=&quot;https://blog.kakaocdn.net/dn/BScnj/btsJZKysxVp/pFPAkfIjmvC9OgnkY26YGK/img.png&quot; srcset=&quot;https://img1.daumcdn.net/thumb/R1280x0/?scode=mtistory2&amp;fname=https%3A%2F%2Fblog.kakaocdn.net%2Fdn%2FBScnj%2FbtsJZKysxVp%2FpFPAkfIjmvC9OgnkY26YGK%2Fimg.png&quot; onerror=&quot;this.onerror=null; this.src='//t1.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;623&quot; height=&quot;298&quot; data-origin-width=&quot;623&quot; data-origin-height=&quot;298&quot;/&gt;&lt;/span&gt;&lt;figcaption&gt;body raw data&lt;/figcaption&gt;
&lt;/figure&gt;
&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p&gt;&lt;figure class=&quot;imageblock alignCenter&quot; data-ke-mobileStyle=&quot;widthOrigin&quot; data-origin-width=&quot;734&quot; data-origin-height=&quot;225&quot;&gt;&lt;span data-url=&quot;https://blog.kakaocdn.net/dn/bi0TDG/btsJXYkzIf0/ttSYIQVWziK9A5Nvmyvmf1/img.png&quot; data-phocus=&quot;https://blog.kakaocdn.net/dn/bi0TDG/btsJXYkzIf0/ttSYIQVWziK9A5Nvmyvmf1/img.png&quot; data-alt=&quot;Env Variable Random Rotate&quot;&gt;&lt;img src=&quot;https://blog.kakaocdn.net/dn/bi0TDG/btsJXYkzIf0/ttSYIQVWziK9A5Nvmyvmf1/img.png&quot; srcset=&quot;https://img1.daumcdn.net/thumb/R1280x0/?scode=mtistory2&amp;fname=https%3A%2F%2Fblog.kakaocdn.net%2Fdn%2Fbi0TDG%2FbtsJXYkzIf0%2FttSYIQVWziK9A5Nvmyvmf1%2Fimg.png&quot; onerror=&quot;this.onerror=null; this.src='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png'; this.srcset='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png';&quot; loading=&quot;lazy&quot; width=&quot;734&quot; height=&quot;225&quot; data-origin-width=&quot;734&quot; data-origin-height=&quot;225&quot;/&gt;&lt;/span&gt;&lt;figcaption&gt;Env Variable Random Rotate&lt;/figcaption&gt;
&lt;/figure&gt;
&lt;/p&gt;
&lt;pre class=&quot;routeros&quot;&gt;&lt;code&gt; pm.environment.set(&quot;randomNumber&quot;, Math.floor(Math.random() * 500000));
 --- body raw
 {
    &quot;UserId&quot;:&quot;auto{{randomNumber}}&quot;,
    &quot;FirstName&quot;:&quot;first{{randomNumber}}&quot;,
    &quot;LastName&quot;:&quot;lim&quot;,
    &quot;Email&quot;:&quot;auto@{{randomNumber}}&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;설정을 마친 뒤 Runner 를 통해 API를 자동으로 호출하자.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;h4 data-ke-size=&quot;size20&quot;&gt;Kibana View&lt;/h4&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;APM 탭에서 이제 아래와 같은 데이터를 확인할 수 있다. 트랜잭션 처리 속도는 분단위(TPM)로 볼 수 있는게 약간 아쉽다.&lt;/p&gt;
&lt;p&gt;&lt;figure class=&quot;imageblock alignCenter&quot; data-ke-mobileStyle=&quot;widthOrigin&quot; data-origin-width=&quot;1529&quot; data-origin-height=&quot;705&quot;&gt;&lt;span data-url=&quot;https://blog.kakaocdn.net/dn/p5LlY/btsJZVs1rfH/oKj158RkiVaCLD4zR8KKu1/img.png&quot; data-phocus=&quot;https://blog.kakaocdn.net/dn/p5LlY/btsJZVs1rfH/oKj158RkiVaCLD4zR8KKu1/img.png&quot;&gt;&lt;img src=&quot;https://blog.kakaocdn.net/dn/p5LlY/btsJZVs1rfH/oKj158RkiVaCLD4zR8KKu1/img.png&quot; srcset=&quot;https://img1.daumcdn.net/thumb/R1280x0/?scode=mtistory2&amp;fname=https%3A%2F%2Fblog.kakaocdn.net%2Fdn%2Fp5LlY%2FbtsJZVs1rfH%2FoKj158RkiVaCLD4zR8KKu1%2Fimg.png&quot; onerror=&quot;this.onerror=null; this.src='//t1.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;1529&quot; height=&quot;705&quot; data-origin-width=&quot;1529&quot; data-origin-height=&quot;705&quot;/&gt;&lt;/span&gt;&lt;/figure&gt;
&lt;figure class=&quot;imageblock alignCenter&quot; data-ke-mobileStyle=&quot;widthOrigin&quot; data-origin-width=&quot;1532&quot; data-origin-height=&quot;787&quot;&gt;&lt;span data-url=&quot;https://blog.kakaocdn.net/dn/dv0Ick/btsJZJ7nO71/Ldb7aOuZV5Li22m4IcpZok/img.png&quot; data-phocus=&quot;https://blog.kakaocdn.net/dn/dv0Ick/btsJZJ7nO71/Ldb7aOuZV5Li22m4IcpZok/img.png&quot;&gt;&lt;img src=&quot;https://blog.kakaocdn.net/dn/dv0Ick/btsJZJ7nO71/Ldb7aOuZV5Li22m4IcpZok/img.png&quot; srcset=&quot;https://img1.daumcdn.net/thumb/R1280x0/?scode=mtistory2&amp;fname=https%3A%2F%2Fblog.kakaocdn.net%2Fdn%2Fdv0Ick%2FbtsJZJ7nO71%2FLdb7aOuZV5Li22m4IcpZok%2Fimg.png&quot; onerror=&quot;this.onerror=null; this.src='//t1.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;1532&quot; height=&quot;787&quot; data-origin-width=&quot;1532&quot; data-origin-height=&quot;787&quot;/&gt;&lt;/span&gt;&lt;/figure&gt;
&lt;figure class=&quot;imageblock alignCenter&quot; data-ke-mobileStyle=&quot;widthOrigin&quot; data-origin-width=&quot;1525&quot; data-origin-height=&quot;781&quot;&gt;&lt;span data-url=&quot;https://blog.kakaocdn.net/dn/cURZ5f/btsJYW7upo8/SIKcqH1BISPLgedpxFA1xK/img.png&quot; data-phocus=&quot;https://blog.kakaocdn.net/dn/cURZ5f/btsJYW7upo8/SIKcqH1BISPLgedpxFA1xK/img.png&quot;&gt;&lt;img src=&quot;https://blog.kakaocdn.net/dn/cURZ5f/btsJYW7upo8/SIKcqH1BISPLgedpxFA1xK/img.png&quot; srcset=&quot;https://img1.daumcdn.net/thumb/R1280x0/?scode=mtistory2&amp;fname=https%3A%2F%2Fblog.kakaocdn.net%2Fdn%2FcURZ5f%2FbtsJYW7upo8%2FSIKcqH1BISPLgedpxFA1xK%2Fimg.png&quot; onerror=&quot;this.onerror=null; this.src='//t1.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;1525&quot; height=&quot;781&quot; data-origin-width=&quot;1525&quot; data-origin-height=&quot;781&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;Error 가 발생한 Case를 확인할 수 있다.&lt;/p&gt;
&lt;p&gt;&lt;figure class=&quot;imageblock alignCenter&quot; data-ke-mobileStyle=&quot;widthOrigin&quot; data-origin-width=&quot;1540&quot; data-origin-height=&quot;868&quot;&gt;&lt;span data-url=&quot;https://blog.kakaocdn.net/dn/bnLMoq/btsJXYLH0um/aRknQbGEZWcKm0SK3k3gTK/img.png&quot; data-phocus=&quot;https://blog.kakaocdn.net/dn/bnLMoq/btsJXYLH0um/aRknQbGEZWcKm0SK3k3gTK/img.png&quot; data-alt=&quot;Elastic APM Golang Error View&quot;&gt;&lt;img src=&quot;https://blog.kakaocdn.net/dn/bnLMoq/btsJXYLH0um/aRknQbGEZWcKm0SK3k3gTK/img.png&quot; srcset=&quot;https://img1.daumcdn.net/thumb/R1280x0/?scode=mtistory2&amp;fname=https%3A%2F%2Fblog.kakaocdn.net%2Fdn%2FbnLMoq%2FbtsJXYLH0um%2FaRknQbGEZWcKm0SK3k3gTK%2Fimg.png&quot; onerror=&quot;this.onerror=null; this.src='//t1.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;1540&quot; height=&quot;868&quot; data-origin-width=&quot;1540&quot; data-origin-height=&quot;868&quot;/&gt;&lt;/span&gt;&lt;figcaption&gt;Elastic APM Golang Error View&lt;/figcaption&gt;
&lt;/figure&gt;
&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;APM의 핵심인 구간별 레이턴시도 확인할 수 있다.&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;1534&quot; data-origin-height=&quot;729&quot;&gt;&lt;span data-url=&quot;https://blog.kakaocdn.net/dn/btb6Rq/btsJZ9q9ZWA/FCNmEZwZOvTH6obakF7A3k/img.png&quot; data-phocus=&quot;https://blog.kakaocdn.net/dn/btb6Rq/btsJZ9q9ZWA/FCNmEZwZOvTH6obakF7A3k/img.png&quot; data-alt=&quot;Elastic APM Trace View&quot;&gt;&lt;img src=&quot;https://blog.kakaocdn.net/dn/btb6Rq/btsJZ9q9ZWA/FCNmEZwZOvTH6obakF7A3k/img.png&quot; srcset=&quot;https://img1.daumcdn.net/thumb/R1280x0/?scode=mtistory2&amp;fname=https%3A%2F%2Fblog.kakaocdn.net%2Fdn%2Fbtb6Rq%2FbtsJZ9q9ZWA%2FFCNmEZwZOvTH6obakF7A3k%2Fimg.png&quot; onerror=&quot;this.onerror=null; this.src='//t1.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;1534&quot; height=&quot;729&quot; data-origin-width=&quot;1534&quot; data-origin-height=&quot;729&quot;/&gt;&lt;/span&gt;&lt;figcaption&gt;Elastic APM Trace View&lt;/figcaption&gt;
&lt;/figure&gt;
&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;무료로 이런 기능을 제공한다는 점에서 매우 괜찮게 느꼈다. 그동안 Logging 적재/분석용으로 ElasticSearch를 구축/운영 했었는데, 더 많은 기능들이 있었고, 실제로 사용하는 사례도 적잖게 있다고하니 도입을 검토 해볼만하다고 느낀다.&lt;/p&gt;
&lt;p data-ke-size=&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;figure id=&quot;og_1728398229306&quot; contenteditable=&quot;false&quot; data-ke-type=&quot;opengraph&quot; data-ke-align=&quot;alignCenter&quot; data-og-type=&quot;object&quot; data-og-title=&quot;GitHub - joonhyeok95/golang_study2: 배포 환경 변수화&quot; data-og-description=&quot;배포 환경 변수화. Contribute to joonhyeok95/golang_study2 development by creating an account on GitHub.&quot; data-og-host=&quot;github.com&quot; data-og-source-url=&quot;https://github.com/joonhyeok95/golang_study2&quot; data-og-url=&quot;https://github.com/joonhyeok95/golang_study2&quot; data-og-image=&quot;https://scrap.kakaocdn.net/dn/bAZDfw/hyXedVf4PW/m8fMjPVyV9o9KkBkPtLHJ0/img.png?width=1200&amp;amp;height=600&amp;amp;face=0_0_1200_600,https://scrap.kakaocdn.net/dn/ShgNs/hyXd6IAnaC/ywsNOLvgkCgVEIGYin011k/img.png?width=1200&amp;amp;height=600&amp;amp;face=0_0_1200_600&quot;&gt;&lt;a href=&quot;https://github.com/joonhyeok95/golang_study2&quot; target=&quot;_blank&quot; rel=&quot;noopener&quot; data-source-url=&quot;https://github.com/joonhyeok95/golang_study2&quot;&gt;
&lt;div class=&quot;og-image&quot; style=&quot;background-image: url('https://scrap.kakaocdn.net/dn/bAZDfw/hyXedVf4PW/m8fMjPVyV9o9KkBkPtLHJ0/img.png?width=1200&amp;amp;height=600&amp;amp;face=0_0_1200_600,https://scrap.kakaocdn.net/dn/ShgNs/hyXd6IAnaC/ywsNOLvgkCgVEIGYin011k/img.png?width=1200&amp;amp;height=600&amp;amp;face=0_0_1200_600');&quot;&gt;&amp;nbsp;&lt;/div&gt;
&lt;div class=&quot;og-text&quot;&gt;
&lt;p class=&quot;og-title&quot; data-ke-size=&quot;size16&quot;&gt;GitHub - joonhyeok95/golang_study2: 배포 환경 변수화&lt;/p&gt;
&lt;p class=&quot;og-desc&quot; data-ke-size=&quot;size16&quot;&gt;배포 환경 변수화. Contribute to joonhyeok95/golang_study2 development by creating an account on GitHub.&lt;/p&gt;
&lt;p class=&quot;og-host&quot; data-ke-size=&quot;size16&quot;&gt;github.com&lt;/p&gt;
&lt;/div&gt;
&lt;/a&gt;&lt;/figure&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;figure id=&quot;og_1728398496217&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;Set up the Agent | APM Go Agent Reference [2.x] | Elastic&quot; data-og-description=&quot;To start reporting your Go application&amp;rsquo;s performance to Elastic APM, you need to do a few things: Within a Go module, install the Elastic APM Go agent package using go get: go get -u go.elastic.co/apm/v2 You can find a list of the supported frameworks an&quot; data-og-host=&quot;www.elastic.co&quot; data-og-source-url=&quot;https://www.elastic.co/guide/en/apm/agent/go/current/getting-started.html#installation&quot; data-og-url=&quot;https://www.elastic.co/guide/en/apm/agent/go/current/getting-started.html#installation&quot; data-og-image=&quot;&quot;&gt;&lt;a href=&quot;https://www.elastic.co/guide/en/apm/agent/go/current/getting-started.html#installation&quot; target=&quot;_blank&quot; rel=&quot;noopener&quot; data-source-url=&quot;https://www.elastic.co/guide/en/apm/agent/go/current/getting-started.html#installation&quot;&gt;
&lt;div class=&quot;og-image&quot; style=&quot;background-image: url();&quot;&gt;&amp;nbsp;&lt;/div&gt;
&lt;div class=&quot;og-text&quot;&gt;
&lt;p class=&quot;og-title&quot; data-ke-size=&quot;size16&quot;&gt;Set up the Agent | APM Go Agent Reference [2.x] | Elastic&lt;/p&gt;
&lt;p class=&quot;og-desc&quot; data-ke-size=&quot;size16&quot;&gt;To start reporting your Go application&amp;rsquo;s performance to Elastic APM, you need to do a few things: Within a Go module, install the Elastic APM Go agent package using go get: go get -u go.elastic.co/apm/v2 You can find a list of the supported frameworks an&lt;/p&gt;
&lt;p class=&quot;og-host&quot; data-ke-size=&quot;size16&quot;&gt;www.elastic.co&lt;/p&gt;
&lt;/div&gt;
&lt;/a&gt;&lt;/figure&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;</description>
      <category>엔지니어링/Kubernetes</category>
      <category>apm</category>
      <category>Application</category>
      <category>elastic</category>
      <category>elasticapm</category>
      <category>go</category>
      <category>golang</category>
      <category>monitoring</category>
      <category>Trace</category>
      <author>joon95</author>
      <guid isPermaLink="true">https://flowlog.tistory.com/111</guid>
      <comments>https://flowlog.tistory.com/111#entry111comment</comments>
      <pubDate>Wed, 9 Oct 2024 00:00:57 +0900</pubDate>
    </item>
    <item>
      <title>Mariadb 백업, 복원하기(mariabackup)</title>
      <link>https://flowlog.tistory.com/110</link>
      <description>&lt;h2 data-ke-size=&quot;size26&quot;&gt;백업 종류&lt;/h2&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;mariadb 를 백업하는 방법은 논리적인 방법과 물리적인 방법이 있다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;1. 논리적 백업(mysqldump)&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;- SQL 형태의 방식&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;- 모든 행을 스캔하여 테이블마다 insert 구문을 생성함&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;- 복원 시 시간이 오래 걸림(20GB Data -&amp;gt; 50분 소요)&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;2. 물리적 백업(mariabackup)&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;- XtraBackup 방식으로 구현된 mariadb 전용 백업 툴(mariadb 10.3 이상부터 지원)&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;- DB 데이터를 통째로 복사&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;- 복사중 데이터 업데이트 발생 가능성으로 인해 주의해야 함&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;물리적 백업이 복구에 시간이 훨씬 빠르기 때문에 물리적 백업을 채택(20GB Data -&amp;gt; 4분 소요)&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;h2 data-ke-size=&quot;size26&quot;&gt;백업&lt;/h2&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;mariabackup 의 --backup 옵션을 활용한다.&lt;/p&gt;
&lt;pre id=&quot;code_1728392378261&quot; class=&quot;bash&quot; data-ke-language=&quot;bash&quot; data-ke-type=&quot;codeblock&quot;&gt;&lt;code&gt;mariabackup --backup --no-lock --target-dir=/tmp/$BACKUP_NAME --user=$USER --password=$PASSWORD&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;h2 data-ke-size=&quot;size26&quot;&gt;복원 절차&lt;/h2&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;1. Get Backup File&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;2. Mariadb Container Stop&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;3. Mariadb-restore Container Start&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;4. mariadb-restore Backup File Copy &amp;amp; mariabackup Prepare/Copy Volume&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;5. Mariadb Container Start(Volume Mount)&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;Working Directory : /data&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;Mariadb Data Directory : /data/db_data&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;Mariadb Image : &lt;a style=&quot;color: #000000;&quot; href=&quot;http://docker.io/bitnami/mariadb:11.4.3-debian-12-r2&quot; data-token-index=&quot;0&quot;&gt;&lt;span&gt;docker.io/bitnami/mariadb:11.4.3-debian-12-r2&lt;/span&gt;&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;실제 기동될 컨테이너(mariadb)와, 복원용 컨테이너(mariadb-restore)를 사용한다. (data 영역을 스왑하기 때문에 어쩔 수 없음)&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;서로 같은 볼륨을 마운트하는데&amp;nbsp;&lt;b&gt;컨테이너 볼륨 마운트&lt;/b&gt; &lt;b&gt;경로&lt;/b&gt;는 다르게 한다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;span style=&quot;color: #333333; text-align: start;&quot;&gt;- mariadb&lt;span&gt;&lt;span&gt;&amp;nbsp;&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;: /bitnami/mariadb/data&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;span style=&quot;color: #333333; text-align: start;&quot;&gt;- mariadb-restore&lt;span&gt;&lt;span&gt;&amp;nbsp;&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;: /tmp/db_restore&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;docker run&lt;/h3&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;먼저 작업할 컨테이너의 정보이다. 기동 후 stop 한다.&lt;/p&gt;
&lt;pre id=&quot;code_1728392886793&quot; class=&quot;bash&quot; data-ke-language=&quot;bash&quot; data-ke-type=&quot;codeblock&quot;&gt;&lt;code&gt;## mariadb-restore run
docker run -d \
  --name mariadb-restore \
  -e MARIADB_ROOT_PASSWORD={rootPassword} \
  -e MARIADB_USER={username} \
  -e MARIADB_PASSWORD={password} \
  -e MARIADB_DATABASE={databaseName} \
  -v /data/db_data:/tmp/db_restore/ \
  docker.io/bitnami/mariadb:11.4.3-debian-12-r2

docker stop mariadb-restore

## mariadb run
docker run -d \
  --name mariadb \
  -e MARIADB_ROOT_PASSWORD={rootPassword} \
  -e MARIADB_USER={username} \
  -e MARIADB_PASSWORD={password} \
  -e MARIADB_DATABASE={databaseName} \
  -v /data/db_data:/bitnami/mariadb/data \
  docker.io/bitnami/mariadb:11.4.3-debian-12-r2

docker stop mariadb&lt;/code&gt;&lt;/pre&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;h3 data-ke-size=&quot;size23&quot;&gt;Backup File Download&lt;/h3&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;백업한 파일은 tar 로 압축하여 minio 에 업로드해놨다. mc 를 사용해 파일을 다운로드 할 것이다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;*password 에 특수문자가 존재할 경우 작은 따옴표를 사용해 묶어줘야한다.&lt;/p&gt;
&lt;pre id=&quot;code_1728392981993&quot; class=&quot;bash&quot; data-ke-language=&quot;bash&quot; data-ke-type=&quot;codeblock&quot;&gt;&lt;code&gt;wget https://dl.min.io/client/mc/release/linux-amd64/mc
chmod +x mc
./mc alias set myminio http://{ip}:{api port}  'username' 'password'&lt;/code&gt;&lt;/pre&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;백업되는 파일명은 'backup-YYYY-MM-DD.tar.gz'&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;1일 전 데이터를 가져와 /data 영역에 복사하자.&lt;/p&gt;
&lt;pre id=&quot;code_1728393066780&quot; class=&quot;bash&quot; data-ke-language=&quot;bash&quot; data-ke-type=&quot;codeblock&quot;&gt;&lt;code&gt;date=`date -d &quot;-1 day&quot; +&quot;%Y-%m-%d&quot;`
./mc cp myminio/{BucketName}/backup-$date.tar.gz /data/&lt;/code&gt;&lt;/pre&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;h3 data-ke-size=&quot;size23&quot;&gt;Mariadb-restore Container&lt;/h3&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;이제 복원용 컨테이너를 기동시키고, 백업한 파일을 카피, 검증, 복구한다.&lt;/p&gt;
&lt;pre id=&quot;code_1728393188629&quot; class=&quot;bash&quot; data-ke-language=&quot;bash&quot; data-ke-type=&quot;codeblock&quot;&gt;&lt;code&gt;cd /data

docker stop mariadb
docker start mariadb-restore
docker cp backup-$date.tar.gz mariadb-restore:/tmp/
docker exec -i mariadb-restore mkdir /tmp/db_temp
docker exec -i mariadb-restore tar -zxvf /tmp/backup-$date.tar.gz -C /tmp/db_temp
docker exec -i mariadb-restore mariabackup --prepare --target-dir=/tmp/db_temp
docker exec -i mariadb-restore mariabackup --copy-back --target-dir=/tmp/db_temp --datadir=/tmp/db_restore --user={username} --password={password}&lt;/code&gt;&lt;/pre&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;--prepare 로 검증을 하고 --copy-back 명령어로 실제 data 영역에 복구하게 된다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;이제 mariadb Container에서 복구한 data 영역을 마운트하여 기동시켜야 한다.&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;Mariadb Container&lt;/h3&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;실제 기동될 컨테이너를 기동 시키고 database에 접근하여 백업된 데이터베이스와 테이블을 조회해보자.&lt;/p&gt;
&lt;pre id=&quot;code_1728393292530&quot; class=&quot;bash&quot; data-ke-language=&quot;bash&quot; data-ke-type=&quot;codeblock&quot;&gt;&lt;code&gt;docker stop mariadb-restore
docker start mariadb

docker exec -it mariadb mariadb -u root -p
Enter password: {password}

MariaDB [(none)]&amp;gt; use {dbname};
Reading table information for completion of table and column names
You can turn off this feature to get a quicker startup with -A

Database changed
MariaDB [dbname]&amp;gt; select * from user;&lt;/code&gt;&lt;/pre&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;h4 data-ke-size=&quot;size20&quot;&gt;docker-compose&lt;/h4&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;필자는 이렇게 복구한 데이터베이스 컨테이너에 애플리케이션을 붙여야해서 docker-compose 를 사용했다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;healthcheck 와 depends_on 을 추가하여 db가 완전히 기동된 후 application container를 기동하도록 설정한다.&lt;/p&gt;
&lt;pre id=&quot;code_1728393746563&quot; class=&quot;yaml&quot; data-ke-language=&quot;yaml&quot; data-ke-type=&quot;codeblock&quot;&gt;&lt;code&gt;# vi docker-compose.yml
version: '3.7'
services:
  mariadb:
    image: docker.io/bitnami/mariadb:11.4.3-debian-12-r2 
    environment:
      - MARIADB_ROOT_PASSWORD={rootPassword}
      - MARIADB_USER={initUsername}
      - MARIADB_PASSWORD={initPassword}
      - MARIADB_DATABASE={createDatebasename}
    ports:
      - &quot;3306:3306&quot;  # MySQL 기본 포트
    volumes:
      - /data/db_data:/bitnami/mariadb/data
    healthcheck:
      test: [&quot;CMD&quot;, &quot;mysqladmin&quot;, &quot;ping&quot;, &quot;--silent&quot;]
      interval: 10s
      timeout: 5s
      retries: 5      
# 추후 app 추가
  backend:
    image: ...
    ports:
      - &quot;80:80&quot;
    depends_on:
      mariadb:
        condition: service_healthy&lt;/code&gt;&lt;/pre&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;pre id=&quot;code_1728393829304&quot; class=&quot;bash&quot; data-ke-language=&quot;bash&quot; data-ke-type=&quot;codeblock&quot;&gt;&lt;code&gt;docker-compose up -d&lt;/code&gt;&lt;/pre&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;</description>
      <category>데이터베이스</category>
      <category>backup</category>
      <category>Data Backup</category>
      <category>database</category>
      <category>mariabackup</category>
      <category>mariadb</category>
      <category>Restore</category>
      <category>물리적인 백업</category>
      <category>통백업</category>
      <author>joon95</author>
      <guid isPermaLink="true">https://flowlog.tistory.com/110</guid>
      <comments>https://flowlog.tistory.com/110#entry110comment</comments>
      <pubDate>Tue, 8 Oct 2024 22:24:46 +0900</pubDate>
    </item>
    <item>
      <title>Kubernetes Korea Community Day 2024 후기</title>
      <link>https://flowlog.tistory.com/109</link>
      <description>&lt;p&gt;&lt;figure class=&quot;imageblock alignCenter&quot; data-ke-mobileStyle=&quot;widthOrigin&quot; data-filename=&quot;edited_KCD2024.jpg&quot; data-origin-width=&quot;3000&quot; data-origin-height=&quot;643&quot;&gt;&lt;span data-url=&quot;https://blog.kakaocdn.net/dn/bT0hF9/btsJKkHu7sU/XviVDxPOIm5ANmRe6hWyKK/img.png&quot; data-phocus=&quot;https://blog.kakaocdn.net/dn/bT0hF9/btsJKkHu7sU/XviVDxPOIm5ANmRe6hWyKK/img.png&quot;&gt;&lt;img src=&quot;https://blog.kakaocdn.net/dn/bT0hF9/btsJKkHu7sU/XviVDxPOIm5ANmRe6hWyKK/img.png&quot; srcset=&quot;https://img1.daumcdn.net/thumb/R1280x0/?scode=mtistory2&amp;fname=https%3A%2F%2Fblog.kakaocdn.net%2Fdn%2FbT0hF9%2FbtsJKkHu7sU%2FXviVDxPOIm5ANmRe6hWyKK%2Fimg.png&quot; onerror=&quot;this.onerror=null; this.src='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png'; this.srcset='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png';&quot; loading=&quot;lazy&quot; width=&quot;3000&quot; height=&quot;643&quot; data-filename=&quot;edited_KCD2024.jpg&quot; data-origin-width=&quot;3000&quot; data-origin-height=&quot;643&quot;/&gt;&lt;/span&gt;&lt;/figure&gt;
&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;span style=&quot;font-family: 'Noto Serif KR';&quot;&gt;24년 9월 24일 서울 백범김구기념관에서 &lt;b&gt;Kubernetes Korea Community Day 2024&lt;/b&gt;를 개최하여 다녀왔다.&lt;/span&gt;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;span style=&quot;font-family: 'Noto Serif KR';&quot;&gt;각 세션 제목을 보고 너무 듣고 싶어서 기대가 많았고, 도전의 깊이를 넓혀 온 후기를 써본다.&lt;/span&gt;&lt;/p&gt;
&lt;h2 data-ke-size=&quot;size26&quot;&gt;&lt;span style=&quot;font-family: 'Noto Serif KR';&quot;&gt;어떻게 알게되었나?&lt;/span&gt;&lt;/h2&gt;
&lt;p data-ke-size=&quot;size18&quot;&gt;&lt;span style=&quot;font-family: 'Noto Serif KR';&quot;&gt;직장동료가 페이스북 kubernetes korea group 에 수시로 들어가며 확인하던 중 발견하여 Join.&lt;/span&gt;&lt;/p&gt;
&lt;h2 data-ke-size=&quot;size26&quot;&gt;&lt;span style=&quot;font-family: 'Noto Serif KR';&quot;&gt;참가방법?&lt;/span&gt;&lt;/h2&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;span style=&quot;font-family: 'Noto Serif KR';&quot;&gt;9월6일에 입장료 80,000원을 결제했고, 9월23일(행사 1일전) QR 코드가 문자로 날아왔다.&lt;/span&gt;&lt;/p&gt;
&lt;h2 data-ke-size=&quot;size26&quot;&gt;&lt;span style=&quot;font-family: 'Noto Serif KR';&quot;&gt;오전 세션&lt;/span&gt;&lt;/h2&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;span style=&quot;font-family: 'Noto Serif KR';&quot;&gt;행사는 09시20분부터 QR을 찍어 네임카드를 발급받고, 10시부터 시작되었다.&lt;/span&gt;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;span style=&quot;font-family: 'Noto Serif KR';&quot;&gt;약 800명 정도가 참여하였고,&amp;nbsp;오전은 전체가 한 자리에서 Akamai, F5, CNAI 세션을 들었다.&lt;/span&gt;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;h3 data-ke-size=&quot;size23&quot;&gt;&lt;span style=&quot;font-family: 'Noto Serif KR';&quot;&gt;&lt;b&gt;&lt;span style=&quot;background-color: #ffffff; color: #1a1e2c; text-align: start;&quot;&gt;Kubernetes 10년, 그 너머의 항해: 살아남은 자의 인사이트&lt;/span&gt;&lt;/b&gt;&lt;/span&gt;&lt;/h3&gt;
&lt;blockquote data-ke-style=&quot;style3&quot;&gt;&lt;span style=&quot;font-family: 'Noto Serif KR';&quot;&gt;2014년부터 시작되었던 Kubernetes 첫 Commit, Kubernetes의 공식적인 배포는 2015년&lt;/span&gt;&lt;br /&gt;&lt;span style=&quot;font-family: 'Noto Serif KR';&quot;&gt;빈약했던 오픈소스가 거대한 프로젝트가 되었고, 각종 서드파티마저 엄청난 규모로 성장하게 된 오픈소스 생태계&lt;/span&gt;&lt;/blockquote&gt;
&lt;h3 data-ke-size=&quot;size23&quot;&gt;&lt;span style=&quot;font-family: 'Noto Serif KR';&quot;&gt;&lt;b&gt;&lt;span style=&quot;background-color: #fcfcfc; color: #666666; text-align: left;&quot;&gt;엣지로 확장된 Kubernetes: 미래의 클라우드 인프라를 재정의하다&lt;/span&gt; &lt;/b&gt;&lt;/span&gt;&lt;/h3&gt;
&lt;blockquote data-ke-style=&quot;style3&quot;&gt;&lt;span style=&quot;font-family: 'Noto Serif KR';&quot;&gt;아카마이는 기존 Global CDN 서비스를 운용하는 회사라 글로벌 리전에 충분한 네트워크 기술력이 바탕이되었고, 이제 컴퓨팅리소스를 구축하여 빠른 속도의 글로벌서비스를 제공할 수 있다. 특히 Linode라는 회사를 인수하여 LKE(Linode Kubernetes Engine)&amp;nbsp;서비스를 제공한다.&lt;/span&gt;&lt;/blockquote&gt;
&lt;h3 data-ke-size=&quot;size23&quot;&gt;&lt;span style=&quot;font-family: 'Noto Serif KR';&quot;&gt;&lt;b&gt;&lt;span style=&quot;color: #1a1e2c; letter-spacing: 0px;&quot;&gt;&lt;span style=&quot;background-color: #ffffff; color: #1a1e2c; text-align: left;&quot;&gt;F5 CIS 와 NGINX 의 환상적인 조합으로 멀티클러스터 네트워킹 단순화 및 자동화&lt;/span&gt;&lt;/span&gt;&lt;/b&gt;&lt;/span&gt;&lt;/h3&gt;
&lt;blockquote data-ke-style=&quot;style3&quot;&gt;&lt;span style=&quot;font-family: 'Noto Serif KR';&quot;&gt;주요 내용 : F5 -&amp;gt; K8S 접근을 하다.&lt;/span&gt;&lt;br /&gt;&lt;span style=&quot;font-family: 'Noto Serif KR';&quot;&gt;CIS로 멀티 쿠버네티스 클러스터에 접근하여 Network Hop을 줄였다.&lt;/span&gt;&lt;/blockquote&gt;
&lt;h3 data-ke-size=&quot;size23&quot;&gt;&lt;span style=&quot;font-family: 'Noto Serif KR';&quot;&gt;&lt;b&gt;&lt;span style=&quot;color: #1a1e2c; letter-spacing: 0px;&quot;&gt;Cloud Native AI: CNAI - CNCF&lt;/span&gt;&lt;/b&gt;&lt;/span&gt;&lt;/h3&gt;
&lt;blockquote data-ke-style=&quot;style3&quot;&gt;&lt;span style=&quot;font-family: 'Noto Serif KR';&quot;&gt;점심시간이 되어 듣지 않고 나와버림..^^;;&lt;/span&gt;&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;span style=&quot;font-family: 'Noto Serif KR';&quot;&gt;세션 후기&lt;/span&gt;&lt;/h3&gt;
&lt;p data-ke-size=&quot;size18&quot;&gt;&lt;span style=&quot;font-family: 'Noto Serif KR';&quot;&gt;2014년 kubernetes 가 첫 commit을 하였다고 한 해에 나는 대학교 1학년이였고, &lt;/span&gt;&lt;br /&gt;&lt;span style=&quot;font-family: 'Noto Serif KR';&quot;&gt;2015년 공식배포가 이뤄진 해에 나는 군대에 입대했고, &lt;/span&gt;&lt;br /&gt;&lt;span style=&quot;font-family: 'Noto Serif KR';&quot;&gt;2016년 kubernetes 로 떠들석할 때도 나는 군대에 있었고, &lt;/span&gt;&lt;br /&gt;&lt;span style=&quot;font-family: 'Noto Serif KR';&quot;&gt;2019년 kubernetes 를 대학교 졸업작품에 처음 적용했었다. &lt;/span&gt;&lt;br /&gt;&lt;span style=&quot;font-family: 'Noto Serif KR';&quot;&gt;2019년 하반기부터 회사에 취직하여 지금까지 프로젝트를 하며 다양한 환경(OCP, ARO, AKS, NKS, RKE)에서 많은 것을 배웠다. 하지만 여전히 들어보지 못한 다양한 이름의 리눅스도 있었고, 많은 회사의 고민인 Network latency로 인해 Network Hop을 줄이려는 다양한 노력들, 그저 L7, L4, L2 스위치를 두는 것이 당연하다는 틀에 박힌 마인드를 잠시 벗어나게 해준 시간이였다.&lt;/span&gt;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;h2 data-ke-size=&quot;size26&quot;&gt;&lt;span style=&quot;font-family: 'Noto Serif KR';&quot;&gt;오후 세션&lt;/span&gt;&lt;/h2&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;span style=&quot;font-family: 'Noto Serif KR';&quot;&gt;세션당 20~35분의 시간이 정해져있고, 동시에 3개의 홀에서 3개의 세션이 진행된다. 그래서 1개를 선택해서 홀을 옮겨가며 들어야했다. 내가 들은 세션은 아래와 같다.&lt;/span&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;span style=&quot;font-family: 'Noto Serif KR';&quot;&gt;Kubernetes 접근 제어 proxy 서버 개발기&lt;/span&gt;&lt;/li&gt;
&lt;li&gt;&lt;span style=&quot;font-family: 'Noto Serif KR';&quot;&gt;Kubernetes 에서 Kafka 활용하기&lt;/span&gt;&lt;/li&gt;
&lt;li&gt;&lt;span style=&quot;font-family: 'Noto Serif KR';&quot;&gt;자율주행을 위한 차량 데이터 분석 플랫폼을 Kubernetes 위에서 활용하기&lt;/span&gt;&lt;/li&gt;
&lt;li&gt;&lt;span style=&quot;font-family: 'Noto Serif KR';&quot;&gt;Lakehouse 구축을 위한 K8S 디자인패턴&lt;/span&gt;&lt;/li&gt;
&lt;li&gt;&lt;span style=&quot;font-family: 'Noto Serif KR';&quot;&gt;VDI 이제 그만! Kubevirt 로 가볍고 빠른 VM 사용하기&lt;/span&gt;&lt;/li&gt;
&lt;li&gt;&lt;span style=&quot;font-family: 'Noto Serif KR';&quot;&gt;안전한 쿠버네티스 운영을 위한 보안 Best Practice 소개&lt;/span&gt;&lt;/li&gt;
&lt;li&gt;&lt;span style=&quot;font-family: 'Noto Serif KR';&quot;&gt;쿠버네티스 정책 및 권한 관리를 위한 기술들&lt;/span&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;span style=&quot;font-family: 'Noto Serif KR';&quot;&gt;&lt;b&gt;Kubernetes 접근 제어 proxy 서버 개발기&lt;/b&gt;&lt;/span&gt;&lt;/h3&gt;
&lt;blockquote data-ke-style=&quot;style3&quot;&gt;&lt;span style=&quot;font-family: 'Noto Serif KR';&quot;&gt;RBAC의 한계를 통해 새로운 Proxy 솔루션을 개발하신 이야기 - QueryPie&lt;/span&gt;&lt;br /&gt;&lt;span style=&quot;font-family: 'Noto Serif KR';&quot;&gt;1. Kubeconfig를 자동으로 생성, 유지관리해주는 Agent.&lt;/span&gt;&lt;br /&gt;&lt;span style=&quot;font-family: 'Noto Serif KR';&quot;&gt;2. 관리서버를 통해 Kubernetes 앞단에서 API를 분석하고, 사용자 권한에 따라 데이터를 조작,재조합한다.&lt;/span&gt;&lt;br /&gt;&lt;span style=&quot;font-family: 'Noto Serif KR';&quot;&gt;Kubernetes API 를 크게 3가지로 정의&lt;/span&gt;&lt;br /&gt;&lt;span style=&quot;font-family: 'Noto Serif KR';&quot;&gt;1. Rest API (create, get, delete) - API Verb 로 해석, 파싱의 어려움으로 오픈소스 Convert 패키지 활용&lt;/span&gt;&lt;br /&gt;&lt;span style=&quot;font-family: 'Noto Serif KR';&quot;&gt;2. SPDY Protocal (exec, port-forward) - stdout stream을 사용하여 사용자+컨테이너의 전체 입출력 데이터 활용&lt;/span&gt;&lt;br /&gt;&lt;span style=&quot;font-family: 'Noto Serif KR';&quot;&gt;3. Event Stream (watch) - Decode 후 접근에 대한 필터링&lt;/span&gt;&lt;br /&gt;&lt;span style=&quot;font-family: 'Noto Serif KR';&quot;&gt;응답 결과 분석&lt;/span&gt;&lt;br /&gt;&lt;span style=&quot;font-family: 'Noto Serif KR';&quot;&gt;1. decompress&lt;/span&gt;&lt;br /&gt;&lt;span style=&quot;font-family: 'Noto Serif KR';&quot;&gt;2. decode&lt;/span&gt;&lt;br /&gt;&lt;span style=&quot;font-family: 'Noto Serif KR';&quot;&gt;3. check policy&lt;/span&gt;&lt;/blockquote&gt;
&lt;h3 data-ke-size=&quot;size23&quot;&gt;&lt;span style=&quot;font-family: 'Noto Serif KR';&quot;&gt;&lt;b&gt;Kubernetes 에서 Kafka 활용하기&lt;/b&gt;&lt;/span&gt;&lt;/h3&gt;
&lt;blockquote data-ke-style=&quot;style3&quot;&gt;&lt;span style=&quot;font-family: 'Noto Serif KR';&quot;&gt;Strimzi 라는 오픈소스를 활용하여 Kafka Enterprise 제공 가능&lt;/span&gt;&lt;br /&gt;&lt;span style=&quot;font-family: 'Noto Serif KR';&quot;&gt;현재 CNCF 인큐베이팅 성숙도로 올라가있는 프로젝트&lt;/span&gt;&lt;br /&gt;&lt;span style=&quot;font-family: 'Noto Serif KR';&quot;&gt;- Operator 기반의 설치로 이점이 존재&lt;/span&gt;&lt;br /&gt;&lt;span style=&quot;font-family: 'Noto Serif KR';&quot;&gt;- Kafka Connect 와 Calico의 유연한 연결&lt;/span&gt;&lt;br /&gt;&lt;span style=&quot;font-family: 'Noto Serif KR';&quot;&gt;- ODD(Open Data Discovery)로 데이타 흐름을 시각화&lt;/span&gt;&lt;br /&gt;&lt;span style=&quot;font-family: 'Noto Serif KR';&quot;&gt;- Kafka-UI 의 최신(kafbat)&lt;/span&gt;&lt;br /&gt;&lt;span style=&quot;font-family: 'Noto Serif KR';&quot;&gt;- API Curio - 스키마 레지스트리&lt;/span&gt;&lt;/blockquote&gt;
&lt;h3 data-ke-size=&quot;size23&quot;&gt;&lt;span style=&quot;font-family: 'Noto Serif KR';&quot;&gt;&lt;b&gt;자율주행을 위한 차량 데이터 분석 플랫폼을 Kubernetes 위에서 활용하기&lt;/b&gt;&lt;/span&gt;&lt;/h3&gt;
&lt;blockquote data-ke-style=&quot;style3&quot;&gt;&lt;span style=&quot;font-family: 'Noto Serif KR';&quot;&gt;안정성 등의 유럽 규제 강화로 인한 AI가 필수로 잡혀가고 있는 제조업 분야&lt;/span&gt;&lt;br /&gt;&lt;span style=&quot;font-family: 'Noto Serif KR';&quot;&gt;대용량 데이터 수집, 처리를 위한 쿠버네티스 도입이 필수적&lt;/span&gt;&lt;br /&gt;&lt;span style=&quot;font-family: 'Noto Serif KR';&quot;&gt;- 1초에 3-4GB의 카메라, 라이다센서 등의 데이터를 신속하게 전달해야함(k3s)&lt;/span&gt;&lt;br /&gt;&lt;span style=&quot;font-family: 'Noto Serif KR';&quot;&gt;- 데이터 전달의 레이턴시로 인한 Public Cloud 에 존재하게 된 Hub GKE (Kubernetes)&lt;/span&gt;&lt;br /&gt;&lt;span style=&quot;font-family: 'Noto Serif KR';&quot;&gt;- 방대한 데이터 분석을 위한 내부 서버(GPU기반의 Kubernetes Cluster)&lt;/span&gt;&lt;br /&gt;&lt;span style=&quot;font-family: 'Noto Serif KR';&quot;&gt;모니터링(mimir, prometheus)&lt;/span&gt;&lt;/blockquote&gt;
&lt;h3 data-ke-size=&quot;size23&quot;&gt;&lt;span style=&quot;font-family: 'Noto Serif KR';&quot;&gt;&lt;b&gt;Lakehouse 구축을 위한 K8S 디자인패턴&lt;/b&gt;&lt;/span&gt;&lt;/h3&gt;
&lt;blockquote data-ke-style=&quot;style3&quot;&gt;&lt;span style=&quot;font-family: 'Noto Serif KR';&quot;&gt;300TB 데이터에서 대용량 조회를 안정적으로 제공하기 위한 빅데이터플랫폼 구성 후기&lt;/span&gt;&lt;br /&gt;&lt;span style=&quot;font-family: 'Noto Serif KR';&quot;&gt;Network 최적화 패턴&lt;/span&gt;&lt;br /&gt;&lt;span style=&quot;font-family: 'Noto Serif KR';&quot;&gt;- eBPF 기반 CNI&lt;/span&gt;&lt;br /&gt;&lt;span style=&quot;font-family: 'Noto Serif KR';&quot;&gt;- XDP 기반 로드밸런싱&lt;/span&gt;&lt;br /&gt;&lt;span style=&quot;font-family: 'Noto Serif KR';&quot;&gt;- nodeLocalDNS, CoreDNS 활용 및 튜닝&lt;/span&gt;&lt;br /&gt;&lt;span style=&quot;font-family: 'Noto Serif KR';&quot;&gt;- Kube-proxy 비활성화&lt;/span&gt;&lt;br /&gt;&lt;span style=&quot;font-family: 'Noto Serif KR';&quot;&gt;- externalTrafficPolicy: Local 로 바라보게 설정&lt;/span&gt;&lt;br /&gt;&lt;span style=&quot;font-family: 'Noto Serif KR';&quot;&gt;- DSR 적용&lt;/span&gt;&lt;br /&gt;&lt;span style=&quot;font-family: 'Noto Serif KR';&quot;&gt;- LVS(Linux Virtual Server)를 써서 Peer To Peer 방식을 구성&lt;/span&gt;&lt;/blockquote&gt;
&lt;h3 data-ke-size=&quot;size23&quot;&gt;&lt;span style=&quot;font-family: 'Noto Serif KR';&quot;&gt;&lt;b&gt;VDI 이제 그만! Kubevirt 로 가볍고 빠른 VM 사용하기&lt;/b&gt;&lt;/span&gt;&lt;/h3&gt;
&lt;blockquote data-ke-style=&quot;style3&quot;&gt;&lt;span style=&quot;font-family: 'Noto Serif KR';&quot;&gt;Cloudflare, Linux Foundation, Cloud craft 에서 사용자에게 제공하여 사용중&lt;/span&gt;&lt;br /&gt;&lt;span style=&quot;font-family: 'Noto Serif KR';&quot;&gt;on-prem 에서 한계. GPU 서버 이슈가 존재&lt;/span&gt;&lt;br /&gt;&lt;span style=&quot;font-family: 'Noto Serif KR';&quot;&gt;vmware 구독제로 인한 가격상승으로 가능성이 있는 오픈 프로젝트이지 않을까? 라는 도전의식&lt;/span&gt;&lt;/blockquote&gt;
&lt;h3 data-ke-size=&quot;size23&quot;&gt;&lt;span style=&quot;font-family: 'Noto Serif KR';&quot;&gt;&lt;b&gt;안전한 쿠버네티스 운영을 위한 보안 Best Practice 소개&lt;/b&gt;&lt;/span&gt;&lt;/h3&gt;
&lt;blockquote data-ke-style=&quot;style3&quot;&gt;&lt;span style=&quot;font-family: 'Noto Serif KR';&quot;&gt;보안의 3요소(CIA)를 Kubernetes 에서 어떻게 녹여 낼 것인가?&amp;nbsp;&lt;/span&gt;&lt;br /&gt;&lt;span style=&quot;font-family: 'Noto Serif KR';&quot;&gt;1. 기밀성 : Secret, RBAC, Policy, Selector&lt;/span&gt;&lt;br /&gt;&lt;span style=&quot;font-family: 'Noto Serif KR';&quot;&gt;2. 보안성 : Image 검증, Security Context, Application 자체 보안&lt;/span&gt;&lt;br /&gt;&lt;span style=&quot;font-family: 'Noto Serif KR';&quot;&gt;3. 가용성 : Resource 제한, QoS, Auto-Healing, 중앙 집중식 로깅/모니터링&lt;/span&gt;&lt;br /&gt;&lt;span style=&quot;font-family: 'Noto Serif KR';&quot;&gt;클러스터 보안&lt;/span&gt;&lt;br /&gt;&lt;span style=&quot;font-family: 'Noto Serif KR';&quot;&gt;- Host OS(보안패치, 악성 Process)&lt;/span&gt;&lt;br /&gt;&lt;span style=&quot;font-family: 'Noto Serif KR';&quot;&gt;- 클러스터 접근 제어&lt;/span&gt;&lt;br /&gt;&lt;span style=&quot;font-family: 'Noto Serif KR';&quot;&gt;- 네트워크 CNI&lt;/span&gt;&lt;br /&gt;&lt;span style=&quot;font-family: 'Noto Serif KR';&quot;&gt;- ETCD(스토리지 보안, 암호화)&lt;/span&gt;&lt;br /&gt;&lt;span style=&quot;font-family: 'Noto Serif KR';&quot;&gt;- API 서버보안(인증,인가,감사로그)&lt;/span&gt;&lt;br /&gt;&lt;span style=&quot;font-family: 'Noto Serif KR';&quot;&gt;- TLS/SSL 적용, OAUTH, OIDC&lt;/span&gt;&lt;br /&gt;&lt;span style=&quot;font-family: 'Noto Serif KR';&quot;&gt;컨테이너 보안&lt;/span&gt;&lt;br /&gt;&lt;span style=&quot;font-family: 'Noto Serif KR';&quot;&gt;- Container Runtime 보안&lt;/span&gt;&lt;br /&gt;&lt;span style=&quot;font-family: 'Noto Serif KR';&quot;&gt;- 이미지보안(이미지 스캔, 서명 및 취약점 관리)&lt;/span&gt;&lt;br /&gt;&lt;span style=&quot;font-family: 'Noto Serif KR';&quot;&gt;- Cgroup, 네임스페이스 격리&lt;/span&gt;&lt;br /&gt;&lt;span style=&quot;font-family: 'Noto Serif KR';&quot;&gt;- 샌드박스에서의 컨테이너 격리 실행&lt;/span&gt;&lt;br /&gt;&lt;span style=&quot;font-family: 'Noto Serif KR';&quot;&gt;- 권한 제한 : root(x), 권한 최소화&lt;/span&gt;&lt;br /&gt;&lt;span style=&quot;font-family: 'Noto Serif KR';&quot;&gt;- 파드, 컨테이너 보안(Security Context)&lt;/span&gt;&lt;br /&gt;&lt;span style=&quot;font-family: 'Noto Serif KR';&quot;&gt;네트워크/서비스 보안&lt;/span&gt;&lt;br /&gt;&lt;span style=&quot;font-family: 'Noto Serif KR';&quot;&gt;- Network Traffic Controller : CNI Plugin&lt;/span&gt;&lt;br /&gt;&lt;span style=&quot;font-family: 'Noto Serif KR';&quot;&gt;- Network 정책(Ingress, Egress)&lt;/span&gt;&lt;br /&gt;&lt;span style=&quot;font-family: 'Noto Serif KR';&quot;&gt;- Ingress Controller 보안&lt;/span&gt;&lt;br /&gt;&lt;span style=&quot;font-family: 'Noto Serif KR';&quot;&gt;- CoreDNS 보안, DNS 암호화&lt;/span&gt;&lt;br /&gt;&lt;span style=&quot;font-family: 'Noto Serif KR';&quot;&gt;- TLS통신, 인증서 관리&lt;/span&gt;&lt;br /&gt;&lt;span style=&quot;font-family: 'Noto Serif KR';&quot;&gt;- Service Mesh 보안(Istio, LInkerd, ...)&lt;/span&gt;&lt;br /&gt;&lt;span style=&quot;font-family: 'Noto Serif KR';&quot;&gt;- Network 격리, 필터링(kube-proxy, iptables)&lt;/span&gt;&lt;br /&gt;&lt;span style=&quot;font-family: 'Noto Serif KR';&quot;&gt;- Rate Limiting, WAF, DDoS&lt;/span&gt;&lt;br /&gt;&lt;span style=&quot;font-family: 'Noto Serif KR';&quot;&gt;로그/모니터링&lt;/span&gt;&lt;br /&gt;&lt;span style=&quot;font-family: 'Noto Serif KR';&quot;&gt;- Prometheus, Grafana&lt;/span&gt;&lt;br /&gt;&lt;span style=&quot;font-family: 'Noto Serif KR';&quot;&gt;- Fluent, ELK&lt;/span&gt;&lt;br /&gt;&lt;span style=&quot;font-family: 'Noto Serif KR';&quot;&gt;- APM&lt;/span&gt;&lt;br /&gt;&lt;span style=&quot;font-family: 'Noto Serif KR';&quot;&gt;- 분산 트레이싱(MSA)&lt;/span&gt;&lt;br /&gt;&lt;span style=&quot;font-family: 'Noto Serif KR';&quot;&gt;- Audit 로깅&lt;/span&gt;&lt;br /&gt;&lt;span style=&quot;font-family: 'Noto Serif KR';&quot;&gt;쿠버 보안 관련 도구&lt;/span&gt;&lt;br /&gt;&lt;span style=&quot;font-family: 'Noto Serif KR';&quot;&gt;- kube-bench&lt;/span&gt;&lt;br /&gt;&lt;span style=&quot;font-family: 'Noto Serif KR';&quot;&gt;- gVisor&lt;/span&gt;&lt;br /&gt;&lt;span style=&quot;font-family: 'Noto Serif KR';&quot;&gt;- sysdig&lt;/span&gt;&lt;br /&gt;&lt;span style=&quot;font-family: 'Noto Serif KR';&quot;&gt;- Trivy&lt;/span&gt;&lt;br /&gt;&lt;span style=&quot;font-family: 'Noto Serif KR';&quot;&gt;- AppArmor&lt;/span&gt;&lt;/blockquote&gt;
&lt;h3 data-ke-size=&quot;size23&quot;&gt;&lt;span style=&quot;font-family: 'Noto Serif KR';&quot;&gt;&lt;b&gt;쿠버네티스 정책 및 권한 관리를 위한 기술들&lt;/b&gt;&lt;/span&gt;&lt;/h3&gt;
&lt;blockquote data-ke-style=&quot;style3&quot;&gt;&lt;span style=&quot;font-family: 'Noto Serif KR';&quot;&gt;RBAC 한계를 통해 드러난 솔루션&lt;/span&gt;&lt;br /&gt;&lt;span style=&quot;font-family: 'Noto Serif KR';&quot;&gt;- Validation Admission Policy&lt;/span&gt;&lt;br /&gt;&lt;span style=&quot;font-family: 'Noto Serif KR';&quot;&gt;- Kyverno&lt;/span&gt;&lt;br /&gt;&lt;span style=&quot;font-family: 'Noto Serif KR';&quot;&gt;- OPA Gatekeeper&lt;/span&gt;&lt;br /&gt;&lt;span style=&quot;font-family: 'Noto Serif KR';&quot;&gt;우리가 쓰고 싶은 기능&lt;/span&gt;&lt;br /&gt;&lt;span style=&quot;font-family: 'Noto Serif KR';&quot;&gt;1. ResourceName을 패턴으로 매칭시키고 싶음(fix or matcher)&lt;/span&gt;&lt;br /&gt;&lt;span style=&quot;font-family: 'Noto Serif KR';&quot;&gt;2. Object 별로 정의하고 싶음(Label)&lt;/span&gt;&lt;br /&gt;&lt;span style=&quot;font-family: 'Noto Serif KR';&quot;&gt;3. 임의의 패턴을 지정하고 싶음(특정 팀이름은 특정 리소스 접근가능하게)&lt;/span&gt;&lt;br /&gt;&lt;span style=&quot;font-family: 'Noto Serif KR';&quot;&gt;4. 외부연동제어를 하고 싶음(퇴사자 발생 시 서서히 권한을 제한시키는 등)&lt;/span&gt;&lt;br /&gt;&lt;span style=&quot;font-family: 'Noto Serif KR';&quot;&gt;정책 구현을 Code로 한다 == Policy as Code&lt;/span&gt;&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;span style=&quot;font-family: 'Noto Serif KR';&quot;&gt;세션 후기&lt;/span&gt;&lt;/h3&gt;
&lt;p data-ke-size=&quot;size18&quot;&gt;&lt;span style=&quot;font-family: 'Noto Serif KR';&quot;&gt;정말 빠르게 지나갔고, 너무 유익한 시간이였다. 나는 개발을 하며 쿠버네티스에 파이프라인을 구축하는 역할을 주로 수행하였는데, 늘 부족했던 부분인 '보안' 파트를 상세하게 정리해주는 세션이 있었기 때문이다. 이번 계기를 통해 보안파트를 잘 정리하려고 하며, 보안 관련 도구는 꼭 적용해봐야겠다!!! 그리고 실제 빅데이터플랫폼의 사례를 통해 수년의 고민들을 들을 수 있어 너무 좋았다. &lt;/span&gt;&lt;br /&gt;&lt;span style=&quot;font-family: 'Noto Serif KR';&quot;&gt;올해 1월부터 RKE1, RKE2 기반의 on-prem Kubernetes 프로젝트를 진행하며 VM의 설정도 나름 만져왔는데 전혀 고민해보지 않았던 영역이 있어 부끄러운 마음도 들었다. 그리고 OPA Gatekeeper 는 CNCF 랜드스케이프에서 아이콘만 구경했지 별생각은 안들었는데 꼭 해봐야겠다는 생각이 들었다.&lt;/span&gt;&lt;/p&gt;</description>
      <category>엔지니어링/Kubernetes</category>
      <category>cloudnative</category>
      <category>communityday</category>
      <category>Kubernetes</category>
      <category>RBAC</category>
      <category>권한</category>
      <category>보안</category>
      <category>정책</category>
      <category>컨테이너</category>
      <author>joon95</author>
      <guid isPermaLink="true">https://flowlog.tistory.com/109</guid>
      <comments>https://flowlog.tistory.com/109#entry109comment</comments>
      <pubDate>Tue, 24 Sep 2024 23:31:52 +0900</pubDate>
    </item>
    <item>
      <title>자바 Integer 비교 시 알아야 할 것</title>
      <link>https://flowlog.tistory.com/108</link>
      <description>&lt;p data-ke-size=&quot;size16&quot;&gt;이번년도는 1월부터 개발 프로젝트로 매우 바빠서 블로그 쓸 시간이 없다ㅜ(핑계는..)&lt;/p&gt;
&lt;figure contenteditable=&quot;false&quot; data-ke-type=&quot;emoticon&quot; data-ke-align=&quot;alignCenter&quot; data-emoticon-type=&quot;friends1&quot; data-emoticon-name=&quot;034&quot; data-emoticon-isanimation=&quot;false&quot; data-emoticon-src=&quot;https://t1.daumcdn.net/keditor/emoticon/friends1/large/034.gif&quot;&gt;&lt;img src=&quot;https://t1.daumcdn.net/keditor/emoticon/friends1/large/034.gif&quot; width=&quot;150&quot; /&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;지난 주에 내부적으로 개발한 core 모듈에서 버그를 하나 찾았는데 내부적으로 만든 함수 중 객체 비교를 위한 메소드(isEquals)에서 Integer 타입에 대한 처리를 == 으로 했었던 부분이였다.당연히 객체간 데이터 비교에서는 .equals 를 사용해야 하는 것은 익히 알고 있었는데, == 으로 되어있었다는...&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;그럼 Integer 데이터간 비교는 왜 ==로 하면 안되는지 알아보자!&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;래퍼 클래스(Wrapper Class)&lt;/h2&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;span style=&quot;letter-spacing: 0px;&quot;&gt;래퍼 클래스란 기본 타입의 데이터를 객체로 취급해야하는 경우 사용된다.&lt;/span&gt;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;- 기본 타입 데이터 타입 : byte,short,int,long,float,double,char,boolean (맨 앞이 소문자)&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;- 래퍼 클래스 : Byte,Short,Integer,Long,Float,Double,Character,Boolean (맨 앞이 대문자, 클래스 만들 때 보듯 ㅎㅎ)&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;tmi. 대학교 2학년 때 자바 수업에서 처음 래퍼클래스에 대해 배웠었음.&lt;/p&gt;
&lt;h3 data-ke-size=&quot;size23&quot;&gt;박싱(Boxing), 언박싱(Unboxing)&lt;/h3&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;- 박싱 : 기본타입을 포장객체(래퍼클래스)로 만드는 만드는 과정&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;- 언박싱 : 포장객체(래퍼클래스)에서 기본타입의 값을 얻어내는 과정&lt;/p&gt;
&lt;pre id=&quot;code_1691836247514&quot; style=&quot;background-color: #f8f8f8; color: #383a42; text-align: start;&quot; data-ke-language=&quot;java&quot; data-ke-type=&quot;codeblock&quot;&gt;&lt;code&gt;Integer a = new Integer(10); // 박싱
int i = a.intValue(); // 언박싱&lt;/code&gt;&lt;/pre&gt;
&lt;h3 data-ke-size=&quot;size23&quot;&gt;오토 박싱(AutoBoxing), 오토 언박싱(AutoUnBoxing)&lt;/h3&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;앞에 오토가 들어가면 자동적으로 해주는 거다 (JDK 1.5부터 지원됨)&lt;/p&gt;
&lt;pre id=&quot;code_1691836331314&quot; class=&quot;java&quot; style=&quot;background-color: #f8f8f8; color: #383a42; text-align: start;&quot; data-ke-language=&quot;java&quot; data-ke-type=&quot;codeblock&quot;&gt;&lt;code&gt;Integer a = 10; // 오토 박싱
int i = a; // 오토 언박싱&lt;/code&gt;&lt;/pre&gt;
&lt;h3 data-ke-size=&quot;size23&quot;&gt;오토 캐시&lt;/h3&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;Integer 클래스를 들어가보면 IntegerCache 라는 클래스가 존재한다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;기본적으로 -128~127 사이의 값에 대해서 지정된 heap memory 영역에 생성한다는 것이다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;-XX:AutoBoxCacheMax=&amp;lt;Size&amp;gt; 를 통해 수정도 가능하다.&lt;/p&gt;
&lt;pre id=&quot;code_1691836843737&quot; class=&quot;java&quot; style=&quot;background-color: #f8f8f8; color: #383a42; text-align: start;&quot; data-ke-language=&quot;java&quot; data-ke-type=&quot;codeblock&quot;&gt;&lt;code&gt;    /**
     * Cache to support the object identity semantics of autoboxing for values between
     * -128 and 127 (inclusive) as required by JLS.
     *
     * The cache is initialized on first usage.  The size of the cache
     * may be controlled by the {@code -XX:AutoBoxCacheMax=&amp;lt;size&amp;gt;} option.
     * During VM initialization, java.lang.Integer.IntegerCache.high property
     * may be set and saved in the private system properties in the
     * jdk.internal.misc.VM class.
     *
     * WARNING: The cache is archived with CDS and reloaded from the shared
     * archive at runtime. The archived cache (Integer[]) and Integer objects
     * reside in the closed archive heap regions. Care should be taken when
     * changing the implementation and the cache array should not be assigned
     * with new Integer object(s) after initialization.
     */

    private static class IntegerCache {
        static final int low = -128;
        static final int high;
        static final Integer[] cache;
        static Integer[] archivedCache;

        static {
            // high value may be configured by property
            int h = 127;
            String integerCacheHighPropValue =
                VM.getSavedProperty(&quot;java.lang.Integer.IntegerCache.high&quot;);
            if (integerCacheHighPropValue != null) {
                try {
                    h = Math.max(parseInt(integerCacheHighPropValue), 127);
                    // Maximum array size is Integer.MAX_VALUE
                    h = Math.min(h, Integer.MAX_VALUE - (-low) -1);
                } catch( NumberFormatException nfe) {
                    // If the property cannot be parsed into an int, ignore it.
                }
            }
            high = h;

            // Load IntegerCache.archivedCache from archive, if possible
            CDS.initializeFromArchive(IntegerCache.class);
            int size = (high - low) + 1;

            // Use the archived cache if it exists and is large enough
            if (archivedCache == null || size &amp;gt; archivedCache.length) {
                Integer[] c = new Integer[size];
                int j = low;
                for(int i = 0; i &amp;lt; c.length; i++) {
                    c[i] = new Integer(j++);
                }
                archivedCache = c;
            }
            cache = archivedCache;
            // range [-128, 127] must be interned (JLS7 5.1.7)
            assert IntegerCache.high &amp;gt;= 127;
        }

        private IntegerCache() {}
    }&lt;/code&gt;&lt;/pre&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;h2 data-ke-size=&quot;size26&quot;&gt;데이터 비교 코드&lt;/h2&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;그럼 위에서 알게된 오토캐싱 내용을 기준으로 데이터를 비교해보자.&lt;/p&gt;
&lt;pre id=&quot;code_1691837016631&quot; class=&quot;java&quot; style=&quot;background-color: #f8f8f8; color: #383a42; text-align: start;&quot; data-ke-language=&quot;java&quot; data-ke-type=&quot;codeblock&quot;&gt;&lt;code&gt;Integer a1 = 127;
Integer a2 = 128;
Integer a3 = -128;
Integer a4 = -129;

Integer b1 = 127;
Integer b2 = 128;
Integer b3 = -128;
Integer b4 = -129;

if(a1 == b1)	System.out.println(&quot;a1 == b1 : true&quot;);
else 			System.out.println(&quot;a1 == b1 : false&quot;);

if(a2 == b2)	System.out.println(&quot;a2 == b2 : true&quot;);
else 			System.out.println(&quot;a2 == b2 : false&quot;);

if(a3 == b3)	System.out.println(&quot;a3 == b3 : true&quot;);
else 			System.out.println(&quot;a3 == b3 : false&quot;);

if(a4 == b4)	System.out.println(&quot;a4 == b4 : true&quot;);
else 			System.out.println(&quot;a4 == b4 : false&quot;);&lt;/code&gt;&lt;/pre&gt;
&lt;p data-ke-size=&quot;size16&quot;&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;div style=&quot;background-color: #ffffff; color: #000000;&quot;&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;span style=&quot;color: #000000;&quot;&gt;a1 == b1 : true&lt;/span&gt;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;span style=&quot;color: #000000;&quot;&gt;a2 == b2 : false&lt;/span&gt;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;span style=&quot;color: #000000;&quot;&gt;a3 == b3 : true&lt;/span&gt;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;span style=&quot;color: #000000;&quot;&gt;a4 == b4 : false&lt;/span&gt;&lt;/p&gt;
&lt;/div&gt;
&lt;/div&gt;
&lt;/div&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;왜 이런 결과가 나온지 주소값을 출력해보자&lt;/p&gt;
&lt;pre id=&quot;code_1691837169103&quot; class=&quot;java&quot; style=&quot;background-color: #f8f8f8; color: #383a42; text-align: start;&quot; data-ke-language=&quot;java&quot; data-ke-type=&quot;codeblock&quot;&gt;&lt;code&gt;System.out.println(&quot;a1 address :&quot;+System.identityHashCode(a1));
System.out.println(&quot;b1 address :&quot;+System.identityHashCode(b1));
System.out.println(&quot;a2 address :&quot;+System.identityHashCode(a2));
System.out.println(&quot;b2 address :&quot;+System.identityHashCode(b2));
System.out.println(&quot;a3 address :&quot;+System.identityHashCode(a3));
System.out.println(&quot;b3 address :&quot;+System.identityHashCode(b3));
System.out.println(&quot;a4 address :&quot;+System.identityHashCode(a4));
System.out.println(&quot;b4 address :&quot;+System.identityHashCode(b4));&lt;/code&gt;&lt;/pre&gt;
&lt;p data-ke-size=&quot;size16&quot;&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;div style=&quot;background-color: #ffffff; color: #000000;&quot;&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;span style=&quot;color: #000000;&quot;&gt;a1 address :328241052&lt;/span&gt;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;span style=&quot;color: #000000;&quot;&gt;b1 address :328241052&lt;/span&gt;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;span style=&quot;color: #000000;&quot;&gt;a2 address :849031967&lt;/span&gt;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;span style=&quot;color: #000000;&quot;&gt;b2 address :1678413715&lt;/span&gt;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;span style=&quot;color: #000000;&quot;&gt;a3 address :1664479306&lt;/span&gt;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;span style=&quot;color: #000000;&quot;&gt;b3 address :1664479306&lt;/span&gt;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;span style=&quot;color: #000000;&quot;&gt;a4 address :1959690207&lt;/span&gt;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;span style=&quot;color: #000000;&quot;&gt;b4 address :522631570&lt;/span&gt;&lt;/p&gt;
&lt;/div&gt;
&lt;/div&gt;
&lt;/div&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;오토캐싱에 정의된 값을 같은 주소를 가지고 있는 것을 볼 수 있다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;그러므로 == 값은 래퍼클래스에서 주소를 비교하는 것이다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;(참고로 int 와 같은 기본데이터는 == 로 비교하는게 맞다.)&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;결론은 래퍼클래스는 .equals 를 통해 객체의 데이터를 비교해야한다!&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;h2 data-ke-size=&quot;size26&quot;&gt;추가 테스트&lt;/h2&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;그럼 오토박싱이 아닌 박싱으로 객체를 선언한다면?&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;(참고로 new Integer() 은 Deprecated Since 9)&lt;/p&gt;
&lt;pre id=&quot;code_1691837451328&quot; class=&quot;java&quot; style=&quot;background-color: #f8f8f8; color: #383a42; text-align: start;&quot; data-ke-language=&quot;java&quot; data-ke-type=&quot;codeblock&quot;&gt;&lt;code&gt;Integer c1 = new Integer(1);
Integer c2 = new Integer(1);

if(c1 == c2) 	System.out.println(&quot;c1 == c2 : true&quot;);
else 			System.out.println(&quot;c1 == c2 : false&quot;);

System.out.println(&quot;c1 address :&quot;+System.identityHashCode(c1));
System.out.println(&quot;c2 address :&quot;+System.identityHashCode(c2));&lt;/code&gt;&lt;/pre&gt;
&lt;p data-ke-size=&quot;size16&quot;&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;div style=&quot;background-color: #ffffff; color: #000000;&quot;&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;span style=&quot;color: #000000;&quot;&gt;c1 == c2 : false&lt;/span&gt;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;span style=&quot;color: #000000;&quot;&gt;c1 address :95055266&lt;/span&gt;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;span style=&quot;color: #000000;&quot;&gt;c2 address :27317011&lt;/span&gt;&lt;/p&gt;
&lt;/div&gt;
&lt;/div&gt;
&lt;/div&gt;
&lt;h2 data-ke-size=&quot;size26&quot;&gt;마치며&lt;/h2&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;오토박싱... 오토언박싱.. 자바를 배울 때 좋은 거구나~ 하면서 배웠던 기억이 있는데&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;오토캐싱은 처음이였따^^^^^.ㅋㅋㅋㅋㅋㅋㅋ&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;간만에 학생 때로 돌아간 것 같은 기분을 느꼈다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;</description>
      <category>개발/JAVA</category>
      <category>Java</category>
      <category>오토박싱</category>
      <category>오토캐싱</category>
      <category>자바</category>
      <author>joon95</author>
      <guid isPermaLink="true">https://flowlog.tistory.com/108</guid>
      <comments>https://flowlog.tistory.com/108#entry108comment</comments>
      <pubDate>Sat, 12 Aug 2023 19:54:34 +0900</pubDate>
    </item>
    <item>
      <title>[CKA] Certified Kubernetes Administrator 자격 취득 후기</title>
      <link>https://flowlog.tistory.com/107</link>
      <description>&lt;p&gt;&lt;figure class=&quot;imageblock alignCenter&quot; data-ke-mobileStyle=&quot;widthOrigin&quot; data-origin-width=&quot;1303&quot; data-origin-height=&quot;1009&quot;&gt;&lt;span data-url=&quot;https://blog.kakaocdn.net/dn/zukGQ/btsmUJuJAkE/3gcBbJuKVcoKqT8VRWHk3k/img.png&quot; data-phocus=&quot;https://blog.kakaocdn.net/dn/zukGQ/btsmUJuJAkE/3gcBbJuKVcoKqT8VRWHk3k/img.png&quot;&gt;&lt;img src=&quot;https://blog.kakaocdn.net/dn/zukGQ/btsmUJuJAkE/3gcBbJuKVcoKqT8VRWHk3k/img.png&quot; srcset=&quot;https://img1.daumcdn.net/thumb/R1280x0/?scode=mtistory2&amp;fname=https%3A%2F%2Fblog.kakaocdn.net%2Fdn%2FzukGQ%2FbtsmUJuJAkE%2F3gcBbJuKVcoKqT8VRWHk3k%2Fimg.png&quot; onerror=&quot;this.onerror=null; this.src='//t1.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;1303&quot; height=&quot;1009&quot; data-origin-width=&quot;1303&quot; data-origin-height=&quot;1009&quot;/&gt;&lt;/span&gt;&lt;/figure&gt;
&lt;/p&gt;
&lt;h2 data-ke-size=&quot;size26&quot;&gt;&lt;span style=&quot;font-family: 'Noto Serif KR';&quot;&gt;서론&lt;/span&gt;&lt;/h2&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;span style=&quot;font-family: 'Noto Serif KR';&quot;&gt;쿠버네티스를 처음 접했던 것은 대학교 3학년(2019) 졸업작품 때이다. 당시에는 app, db, svc, ing 서비스를 올리고 프로메테우스/그라파나를 통해 클라우드 모니터링부분을 다뤘었다. 이 후 취업을 하고 본격적으로 쿠버네티스 프로젝트를 진행하게 된건 2021년3월부터인데 벌써 2년4개월의 시간이 흘렀고 CKA 자격증이라는 것을 알게 되었다.(회사에서도 클라우드 사업을 위해 어느정도 CKA자격을 가진 인력이 필요로 했음)&lt;/span&gt;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;span style=&quot;font-family: 'Noto Serif KR';&quot;&gt;무튼, 시험을 본 자로써 어디에 초점을 두고 공부해야하는지 글을 남기려한다.&lt;/span&gt;&lt;/p&gt;
&lt;figure contenteditable=&quot;false&quot; data-ke-type=&quot;emoticon&quot; data-ke-align=&quot;alignCenter&quot; data-emoticon-type=&quot;face&quot; data-emoticon-name=&quot;018&quot; data-emoticon-isanimation=&quot;false&quot; data-emoticon-src=&quot;https://t1.daumcdn.net/keditor/emoticon/face/large/018.png&quot;&gt;&lt;img src=&quot;https://t1.daumcdn.net/keditor/emoticon/face/large/018.png&quot; width=&quot;80&quot; /&gt;&lt;/figure&gt;
&lt;h2 data-ke-size=&quot;size26&quot;&gt;&lt;span style=&quot;font-family: 'Noto Serif KR';&quot;&gt;준비기간&lt;/span&gt;&lt;/h2&gt;
&lt;blockquote data-ke-style=&quot;style1&quot;&gt;&lt;span style=&quot;font-family: 'Noto Serif KR';&quot;&gt;실제 자격증 준비 시간은 약 2.5일&lt;/span&gt;&lt;/blockquote&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;span style=&quot;font-family: 'Noto Serif KR';&quot;&gt;필자는 그동안 프로젝트에서 쿠버네티스를 경험해왔기에 짧은 시간 준비를 하고 시험을 쳤다.&lt;/span&gt;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;span style=&quot;font-family: 'Noto Serif KR';&quot;&gt;(실제 시험 수준은 너무 쉬웠다는거...+영어질문 해석에 오류가..좀)&lt;/span&gt;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;span style=&quot;font-family: 'Noto Serif KR';&quot;&gt;검색해보니 유데미교육이 엄청많던데...사람들 2달,3주 준비한다길래 Pass... &lt;/span&gt;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;span style=&quot;font-family: 'Noto Serif KR';&quot;&gt;CKA 신청을 하면 Killer.sh 이라는곳에서 무료로 2번 세션을 받아 실제 시험과 비슷한 환경에서 테스트를 할 수 있다고해서 Killer.sh을 이용하였다.&lt;/span&gt;&lt;/p&gt;
&lt;h2 data-ke-size=&quot;size26&quot;&gt;&lt;span style=&quot;font-family: 'Noto Serif KR';&quot;&gt;CKA 시험 핵심&lt;/span&gt;&lt;/h2&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;span style=&quot;font-family: 'Noto Serif KR';&quot;&gt;요즘은 누구나 쿠버네티스에 컨테이너를 올리고, 서비스를 노출시키고, 볼륨을 마운트한다는 등의 액션은 충분히 할 수 있을 텐데, 이 시험에서 더 부각되었던 것은 &lt;b&gt;Cluster Administrator&lt;/b&gt;로써의 더 상세한 통신관리라고 생각된다. (필자가 지금 껏 해오던 것은 CKA 보다는 CKAD(Kubernetes Certified Application Developer)로써의 Role이 상당히 일치했던 것)&lt;/span&gt;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;span style=&quot;font-family: 'Noto Serif KR';&quot;&gt;&lt;span style=&quot;&quot;&gt;그리고 시험시간은 총 120분인데 필자는 90분에 끝났으니 난이도는 &lt;b&gt;쉬운편&lt;/b&gt;이였다.&lt;/span&gt;&lt;/span&gt;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;span style=&quot;font-family: 'Noto Serif KR';&quot;&gt;아무튼 이 시험을 보고 나니 &lt;b&gt;요구하는 내용&lt;/b&gt;을 좀 알겠다.&lt;/span&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;span style=&quot;font-family: 'Noto Serif KR';&quot;&gt;&lt;b&gt;Multi Container Volume Shared&lt;/b&gt;&lt;/span&gt;&lt;/li&gt;
&lt;li&gt;&lt;span style=&quot;font-family: 'Noto Serif KR';&quot;&gt;&lt;b&gt;NetworkPolicy&lt;/b&gt;&lt;/span&gt;&lt;/li&gt;
&lt;li&gt;&lt;span style=&quot;font-family: 'Noto Serif KR';&quot;&gt;&lt;b&gt;TroublesShooting(K8S)&lt;/b&gt;&lt;/span&gt;&lt;/li&gt;
&lt;li&gt;&lt;span style=&quot;font-family: 'Noto Serif KR';&quot;&gt;&lt;b&gt;Cluster Upgrade &lt;/b&gt;&amp;amp;&amp;nbsp;Worker Node Join&lt;/span&gt;&lt;/li&gt;
&lt;li&gt;&lt;span style=&quot;font-family: 'Noto Serif KR';&quot;&gt;&lt;b&gt;Etcd Backup&amp;amp;Restore&lt;/b&gt;&lt;/span&gt;&lt;/li&gt;
&lt;li&gt;&lt;span style=&quot;font-family: 'Noto Serif KR';&quot;&gt;&lt;b&gt;Scale In&amp;amp;Out&lt;/b&gt;&lt;/span&gt;&lt;/li&gt;
&lt;li&gt;&lt;span style=&quot;font-family: 'Noto Serif KR';&quot;&gt;Static-Pod&lt;/span&gt;&lt;/li&gt;
&lt;li&gt;&lt;span style=&quot;font-family: 'Noto Serif KR';&quot;&gt;&lt;b&gt;Certificate&lt;/b&gt;&lt;/span&gt;&lt;/li&gt;
&lt;li&gt;&lt;span style=&quot;font-family: 'Noto Serif KR';&quot;&gt;&lt;b&gt;RBAC(Role Based Access Control)&lt;/b&gt;&lt;/span&gt;&lt;/li&gt;
&lt;li&gt;&lt;span style=&quot;font-family: 'Noto Serif KR';&quot;&gt;&lt;b&gt;Basic (Pod, Deploy, DaemonSet, &lt;/b&gt;StatefulSet&lt;b&gt;, Secret, ConfigMap, PersistentVolume, PersistentVolumeClaim, Ingress, Service)&lt;/b&gt;&lt;/span&gt;&lt;/li&gt;
&lt;/ul&gt;
&lt;h2 data-ke-size=&quot;size26&quot;&gt;&lt;span style=&quot;font-family: 'Noto Serif KR';&quot;&gt;Killer.sh&lt;/span&gt;&lt;/h2&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;span style=&quot;font-family: 'Noto Serif KR';&quot;&gt;필자는 Killer.sh로 준비하였는데, 문제를 딱 보면 말문이 막힌다(영어)&lt;/span&gt;&lt;/p&gt;
&lt;figure contenteditable=&quot;false&quot; data-ke-type=&quot;emoticon&quot; data-ke-align=&quot;alignCenter&quot; data-emoticon-type=&quot;friends1&quot; data-emoticon-name=&quot;046&quot; data-emoticon-isanimation=&quot;false&quot; data-emoticon-src=&quot;https://t1.daumcdn.net/keditor/emoticon/friends1/large/046.gif&quot;&gt;&lt;img src=&quot;https://t1.daumcdn.net/keditor/emoticon/friends1/large/046.gif&quot; width=&quot;150&quot; /&gt;&lt;/figure&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;span style=&quot;font-family: 'Noto Serif KR';&quot;&gt;다행히 정답과 풀이과정을 모두 정리한 &lt;a title=&quot;사이트&quot; href=&quot;https://gengwg.blogspot.com/2022/02/cka-simulator-kubernetes-122.html&quot; target=&quot;_blank&quot; rel=&quot;noopener&quot;&gt;사이트&lt;/a&gt;가 있다.(크롬 번역기 필수..ㅋㅋ) 다들 검색해보셨듯이 Killer.sh에서 말하는 시험의 난이도는 상당하다.... 하지만 겁먹지말라.. 필자도 해냇으니..!!!&lt;/span&gt;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;span style=&quot;font-family: 'Noto Serif KR';&quot;&gt;먼저 이 테스트 세션에서 배울 수 있는 내용을 보면(편하게 문제 순서대로^^)&lt;/span&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;span style=&quot;font-family: 'Noto Serif KR';&quot;&gt;&lt;b&gt;Cluster 정보를 추출할 수 있는가? (kubectl 사용/&lt;u&gt;미사용&lt;/u&gt;)&lt;/b&gt;&lt;/span&gt;&lt;/li&gt;
&lt;li&gt;&lt;span style=&quot;font-family: 'Noto Serif KR';&quot;&gt;&lt;b&gt;Master Node에 파드를 기동할 수 있는가? (Tolerations)&lt;/b&gt;&lt;/span&gt;&lt;/li&gt;
&lt;li&gt;&lt;span style=&quot;font-family: 'Noto Serif KR';&quot;&gt;pod 스케일 조정 (kubectl scale)&lt;/span&gt;&lt;/li&gt;
&lt;li&gt;&lt;span style=&quot;font-family: 'Noto Serif KR';&quot;&gt;readiness, liveness 활용&lt;/span&gt;&lt;/li&gt;
&lt;li&gt;&lt;span style=&quot;font-family: 'Noto Serif KR';&quot;&gt;pod 상태 정렬 (kubectl --sort-by)&lt;/span&gt;&lt;/li&gt;
&lt;li&gt;&lt;span style=&quot;font-family: 'Noto Serif KR';&quot;&gt;스토리지를 mount 해라 (Deploy, Pvc, PV)&lt;/span&gt;&lt;/li&gt;
&lt;li&gt;&lt;span style=&quot;font-family: 'Noto Serif KR';&quot;&gt;리소스 모니터링 (kubectl top)&lt;/span&gt;&lt;/li&gt;
&lt;li&gt;&lt;span style=&quot;font-family: 'Noto Serif KR';&quot;&gt;&lt;b&gt;클러스터 상태 체크 (Process, static-pod, pod 구분 및 dns 종류)&lt;/b&gt;&lt;/span&gt;&lt;/li&gt;
&lt;li&gt;&lt;span style=&quot;font-family: 'Noto Serif KR';&quot;&gt;&lt;b&gt;마스터노드의 스케줄링 서비스가 중단되었을 때 마스터노드에서 Pod 기동하기 (Static-pod 개념을 알자)&lt;/b&gt;&lt;/span&gt;&lt;/li&gt;
&lt;li&gt;&lt;span style=&quot;font-family: 'Noto Serif KR';&quot;&gt;&lt;b&gt;RBAC 서비스계정 역할 부여 (k auth can-i 로 검증하기)&lt;/b&gt;&lt;/span&gt;&lt;/li&gt;
&lt;li&gt;&lt;span style=&quot;font-family: 'Noto Serif KR';&quot;&gt;DaemonSet 생성&lt;/span&gt;&lt;/li&gt;
&lt;li&gt;&lt;span style=&quot;font-family: 'Noto Serif KR';&quot;&gt;&lt;b&gt;파드를 모든 워커노드에 1개만 분배하기 (podAntiAffinity)&lt;/b&gt;&lt;/span&gt;&lt;/li&gt;
&lt;li&gt;&lt;span style=&quot;font-family: 'Noto Serif KR';&quot;&gt;&lt;b&gt;다중 컨테이너 / 파드 공유 볼륨 (emptyDir)&lt;/b&gt;&lt;/span&gt;&lt;/li&gt;
&lt;li&gt;&lt;span style=&quot;font-family: 'Noto Serif KR';&quot;&gt;&lt;b&gt;클러스터 정보 체크 (서비스 CIDR, 네트워크 컨피그 파일 경로)&lt;/b&gt;&lt;/span&gt;&lt;/li&gt;
&lt;li&gt;&lt;span style=&quot;font-family: 'Noto Serif KR';&quot;&gt;이벤트 로깅 (kubectl get events, 컨테이너 죽이기:crictl stop [containerId])&lt;/span&gt;&lt;/li&gt;
&lt;li&gt;&lt;span style=&quot;font-family: 'Noto Serif KR';&quot;&gt;API Resources (kubectl api-resources)&lt;/span&gt;&lt;/li&gt;
&lt;li&gt;&lt;span style=&quot;font-family: 'Noto Serif KR';&quot;&gt;&lt;b&gt;컨테이너 RuntimeType 찾기 &lt;/b&gt;(&lt;/span&gt;&lt;span style=&quot;font-family: 'Noto Serif KR';&quot;&gt;crictl inspect [containerId])&lt;/span&gt;&lt;/li&gt;
&lt;li&gt;&lt;span style=&quot;font-family: 'Noto Serif KR';&quot;&gt;&lt;b&gt;트러블슈팅 kubelet 장애 (confing 경로를 잘 찾아서 해야함)&lt;/b&gt;&lt;/span&gt;&lt;/li&gt;
&lt;li&gt;&lt;span style=&quot;font-family: 'Noto Serif KR';&quot;&gt;Secret 생성 및 마운트 (+마스터노드에 띄우기)&lt;/span&gt;&lt;/li&gt;
&lt;li&gt;&lt;span style=&quot;font-family: 'Noto Serif KR';&quot;&gt;&lt;b&gt;클러스터 버전 업그레이드 및 클러스터 가입 (apt-get / kubectl token create --print-join-command, 여기문제에는 kubeadm은 없어서 따로 해봐야함 drain/uncordon)&lt;/b&gt;&lt;/span&gt;&lt;/li&gt;
&lt;li&gt;&lt;span style=&quot;font-family: 'Noto Serif KR';&quot;&gt;&lt;b&gt;Static-pod (마스터노드 manifests에 yaml을 두어 올리는 것, expose 로 서비스 생성)&lt;/b&gt;&lt;/span&gt;&lt;/li&gt;
&lt;li&gt;&lt;span style=&quot;font-family: 'Noto Serif KR';&quot;&gt;&lt;b&gt;인증서 유효기간 확인 및 업데이트 &lt;/b&gt;(&lt;/span&gt;&lt;span style=&quot;font-family: 'Noto Serif KR';&quot;&gt;마스터 노드에서 인증서를 추출 : openssl x509 -in [파일.crt] -noout -text, &lt;/span&gt;&lt;span style=&quot;font-family: 'Noto Serif KR';&quot;&gt;인증서 업데이트 : kubeadm certs renew)&lt;/span&gt;&lt;/li&gt;
&lt;li&gt;&lt;span style=&quot;font-family: 'Noto Serif KR';&quot;&gt;&lt;b&gt;kubelet 클라이언트/서버 인증서 정보 (/var/lib/kubelet/pki)&lt;/b&gt;&lt;/span&gt;&lt;/li&gt;
&lt;li&gt;&lt;span style=&quot;font-family: 'Noto Serif KR';&quot;&gt;&lt;b&gt;NetworkPolicy (Egress 구간 제어)&lt;/b&gt;&lt;/span&gt;&lt;/li&gt;
&lt;li&gt;&lt;span style=&quot;font-family: 'Noto Serif KR';&quot;&gt;ETCD backup&amp;amp;restore&lt;/span&gt;&lt;/li&gt;
&lt;/ol&gt;
&lt;figure contenteditable=&quot;false&quot; data-ke-type=&quot;emoticon&quot; data-ke-align=&quot;alignCenter&quot; data-emoticon-type=&quot;face&quot; data-emoticon-name=&quot;021&quot; data-emoticon-isanimation=&quot;false&quot; data-emoticon-src=&quot;https://t1.daumcdn.net/keditor/emoticon/face/large/021.png&quot;&gt;&lt;img src=&quot;https://t1.daumcdn.net/keditor/emoticon/face/large/021.png&quot; width=&quot;80&quot; /&gt;&lt;/figure&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;span style=&quot;font-family: 'Noto Serif KR';&quot;&gt;내용이.. 많다..ㅎㅎㅎㅎㅎㅎㅎㅎㅎ하하핳핳ㅎㅎ&lt;/span&gt;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;span style=&quot;font-family: 'Noto Serif KR';&quot;&gt;하지만 그만큼 재밌는 경험을 하게 되었고, 무엇보다 다양한 영어질문을 읽고 해석하는 훈련이...되어간다. 그리고 &lt;b&gt;120분이 지나면 세션을 재시작하여 시간을 체크 할 수 있다&lt;/b&gt;.(필자는 이걸 늦게 알아서 두번째 세션에서 120분 테스트해봄)&lt;/span&gt;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;span style=&quot;font-family: 'Noto Serif KR';&quot;&gt;+CKA는 17문제고 killer.sh는 25문제이기 때문에 당연히 다 풀 수는 없었다. &lt;/span&gt;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;span style=&quot;font-family: 'Noto Serif KR';&quot;&gt;하지만 겁먹지말고 시험보라!&lt;/span&gt;&lt;/p&gt;
&lt;h2 data-ke-size=&quot;size26&quot;&gt;&lt;span style=&quot;font-family: 'Noto Serif KR';&quot;&gt;KillerCoda&lt;/span&gt;&lt;/h2&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;span style=&quot;font-family: 'Noto Serif KR';&quot;&gt;killercoda는 killer.sh 사이트에서 돌아다니다가 우연히 알게되었는데, 약 12케이스의 CKA 문제가 준비되어있다. 간단히 워밍업 정도의 내용으로 쉬운 편이고 여기서 새로 알게된건&amp;nbsp;&lt;b&gt;priorityclass&lt;/b&gt;(우선순위)에 대해 알게 되었다. 옵션이 있는 건 알았는데 class로 정의하고 쓴다니....ㅎ&lt;/span&gt;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;span style=&quot;font-family: 'Noto Serif KR';&quot;&gt;---잠시 시간을 내어 풀어보면 좋다---&lt;/span&gt;&lt;/p&gt;
&lt;h2 data-ke-size=&quot;size26&quot;&gt;&lt;span style=&quot;font-family: 'Noto Serif KR';&quot;&gt;마치며&lt;/span&gt;&lt;/h2&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;span style=&quot;font-family: 'Noto Serif KR';&quot;&gt;만약 이 시험을 한 단어로 요약해보자면 &lt;b&gt;스케줄링&lt;/b&gt;이라고 말하고 싶다.&lt;/span&gt;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;span style=&quot;font-family: 'Noto Serif KR';&quot;&gt;항해를 떠나는 선장(Kubelet)님들에게 내 짐을 맡겨주세요... 라고&amp;nbsp;&lt;/span&gt;&lt;/p&gt;</description>
      <category>엔지니어링/Kubernetes</category>
      <category>Administrator</category>
      <category>cka</category>
      <category>DevOps</category>
      <category>Kubernetes</category>
      <author>joon95</author>
      <guid isPermaLink="true">https://flowlog.tistory.com/107</guid>
      <comments>https://flowlog.tistory.com/107#entry107comment</comments>
      <pubDate>Sat, 8 Jul 2023 15:16:43 +0900</pubDate>
    </item>
    <item>
      <title>[Android] Thread로 간단한 처리해보기</title>
      <link>https://flowlog.tistory.com/106</link>
      <description>&lt;h2 data-ke-size=&quot;size26&quot;&gt;서론&lt;/h2&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;갑작스럽게 회사 후임이 안드로이드를 물어봐서 간단히 테스트 글을 적어본다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;요구사항은 &lt;b&gt;시크바(SeekBar)를 조절하면서 지속적으로 다른 곳에 데이터를 전송&lt;/b&gt;하는 내용이다.&lt;/p&gt;
&lt;blockquote data-ke-style=&quot;style2&quot;&gt;시크바란?&lt;br /&gt;시크바는 사용자가 범위 내에서 값을 선택할 수 있도록 도와주는 막대 형태의 뷰입니다. 음량/밝기 조절, 이미지 필터 적용, 동영상 재생 등 다양한 곳에 활용됩니다.&lt;/blockquote&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;결과적으로 사용자가 바를 터치하고 있는 상태의 데이터를 지속적으로 전달해야한다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;(뭐 데이터가 변경될때만 보내면 간단한데, 변경이 아닌 사용자가 누르고 있는 상태에 대한 값을 보내고싶다고 했다)&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;그래서 Thread를 이용해서 0.3초마다 실행할 수 있도록 구현하고자한다.&lt;/p&gt;
&lt;p&gt;&lt;figure class=&quot;imageblock alignCenter&quot; data-ke-mobileStyle=&quot;widthOrigin&quot; data-origin-width=&quot;342&quot; data-origin-height=&quot;570&quot;&gt;&lt;span data-url=&quot;https://blog.kakaocdn.net/dn/td2zJ/btskCoUOOkW/ghuvaatdkwRWSHt1k9QxX1/img.png&quot; data-phocus=&quot;https://blog.kakaocdn.net/dn/td2zJ/btskCoUOOkW/ghuvaatdkwRWSHt1k9QxX1/img.png&quot; data-alt=&quot;결과화면&quot;&gt;&lt;img src=&quot;https://blog.kakaocdn.net/dn/td2zJ/btskCoUOOkW/ghuvaatdkwRWSHt1k9QxX1/img.png&quot; srcset=&quot;https://img1.daumcdn.net/thumb/R1280x0/?scode=mtistory2&amp;fname=https%3A%2F%2Fblog.kakaocdn.net%2Fdn%2Ftd2zJ%2FbtskCoUOOkW%2FghuvaatdkwRWSHt1k9QxX1%2Fimg.png&quot; onerror=&quot;this.onerror=null; this.src='//t1.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;342&quot; height=&quot;570&quot; data-origin-width=&quot;342&quot; data-origin-height=&quot;570&quot;/&gt;&lt;/span&gt;&lt;figcaption&gt;결과화면&lt;/figcaption&gt;
&lt;/figure&gt;
&lt;/p&gt;
&lt;h2 data-ke-size=&quot;size26&quot;&gt;MainActivity&lt;/h2&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;시크바의 리스너함수 start에서 Thread를 실행, stop에서 Thread를 종료시켰다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;span style=&quot;color: #333333; text-align: start;&quot;&gt;전역변수로 데이터를 보관하여 변경점에 대한 데이터를 가지고 있는다.&lt;/span&gt;&lt;/p&gt;
&lt;pre id=&quot;code_1687234900329&quot; class=&quot;java&quot; data-ke-language=&quot;java&quot; data-ke-type=&quot;codeblock&quot;&gt;&lt;code&gt;public class MainActivity extends AppCompatActivity {
    ExampleThread thread;
    static int data=0;
    TextView txtView;
    @Override
    protected void onCreate(Bundle savedInstanceState) {
        super.onCreate(savedInstanceState);
        setContentView(R.layout.activity_main);
        txtView = findViewById(R.id.text);
        SeekBar bar = findViewById(R.id.seekbar);
        bar.setOnSeekBarChangeListener(new SeekBar.OnSeekBarChangeListener(){
            @Override
            public void onProgressChanged(SeekBar p1, int p2, boolean p3)
            {
                data=p2;
            }
            @Override
            public void onStartTrackingTouch(SeekBar p1)
            {
                thread = new ExampleThread();
                thread.start();
            }
            @Override
            public void onStopTrackingTouch(SeekBar p1)
            {
                if(thread != null &amp;amp;&amp;amp; thread.isAlive()){
                    thread.interrupt();
                    txtView.setText(&quot;데이터 전송 중지(&quot; + data+&quot;)&quot;);
                }
            }
        });

    }

    private class ExampleThread extends Thread {
        @Override
        public void run() {
            while(!Thread.currentThread().isInterrupted()){
                try {
                    // 실제 로직
                    Log.d(&quot;onProgressChange&quot;, &quot;값:&quot; + data);
                    // UI에 적용
                    runOnUiThread(new Runnable() {
                        @Override
                        public void run() {
                            txtView.setText(&quot;실시간데이터전송중...&quot; + data);
                        }
                    });
                    // 작업대기
                    Thread.sleep(300); // 0.3초간 Thread를 잠재운다
                } catch (InterruptedException e) {
                    //e.printStackTrace();
                    this.interrupt();
                }
            }
        }
    }

}&lt;/code&gt;&lt;/pre&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;h2 data-ke-size=&quot;size26&quot;&gt;activity_main.xml&lt;/h2&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;시크바와 간단히 상태를 보여줄 텍스트뷰를 넣어주었다.&lt;/p&gt;
&lt;pre id=&quot;code_1687235083140&quot; class=&quot;xml&quot; data-ke-language=&quot;xml&quot; data-ke-type=&quot;codeblock&quot;&gt;&lt;code&gt;&amp;lt;?xml version=&quot;1.0&quot; encoding=&quot;utf-8&quot;?&amp;gt;
&amp;lt;androidx.constraintlayout.widget.ConstraintLayout xmlns:android=&quot;http://schemas.android.com/apk/res/android&quot;
    xmlns:app=&quot;http://schemas.android.com/apk/res-auto&quot;
    xmlns:tools=&quot;http://schemas.android.com/tools&quot;
    android:layout_width=&quot;match_parent&quot;
    android:layout_height=&quot;match_parent&quot;
    tools:context=&quot;.MainActivity&quot;&amp;gt;

    &amp;lt;TextView
        android:layout_width=&quot;wrap_content&quot;
        android:layout_height=&quot;wrap_content&quot;
        android:text=&quot;Hello World!&quot;
        android:id=&quot;@+id/text&quot;
        android:textSize=&quot;30dp&quot;
        app:layout_constraintBottom_toBottomOf=&quot;parent&quot;
        app:layout_constraintLeft_toLeftOf=&quot;parent&quot;
        app:layout_constraintRight_toRightOf=&quot;parent&quot;
        app:layout_constraintTop_toTopOf=&quot;parent&quot; /&amp;gt;

    &amp;lt;SeekBar
        android:id=&quot;@+id/seekbar&quot;
        android:layout_width=&quot;match_parent&quot;
        android:layout_height=&quot;150dp&quot;
        android:max=&quot;100&quot;/&amp;gt;
&amp;lt;/androidx.constraintlayout.widget.ConstraintLayout&amp;gt;&lt;/code&gt;&lt;/pre&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;h2 data-ke-size=&quot;size26&quot;&gt;결과화면&lt;/h2&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;1. 사용자가 시크바를 누르면 Thread가 동작한다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;2. Thread는 시크바의 값을 0.3초마다 출력한다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;3. 사용자가 시크바 클릭을 해지하면 Thread가 멈춘다.&lt;/p&gt;

            &lt;figure class=&quot;unsupported component-kakaotv&quot; contenteditable=&quot;false&quot; style=&quot;background:#000;margin:16px 0;min-height:72px;padding:10px 16px;display:flex;align-items:center;justify-content:center;text-align:center;box-sizing:border-box;width:100%;max-width:100%;&quot;&gt;
                &lt;p contenteditable=&quot;false&quot; style=&quot;margin:0;color:#8a8a8a;font-size:13px;line-height:1.6;user-select:none;pointer-events:none;&quot;&gt;동영상 서비스가 종료되어 해당 콘텐츠를 재생할 수 없습니다.&lt;/p&gt;
            &lt;/figure&gt;
        
&lt;p data-ke-size=&quot;size16&quot;&gt;오랜만에 안드로이드를 만지니 재밌었다.(옛날 생각)...&lt;/p&gt;
&lt;figure contenteditable=&quot;false&quot; data-ke-type=&quot;emoticon&quot; data-ke-align=&quot;alignCenter&quot; data-emoticon-type=&quot;friends1&quot; data-emoticon-name=&quot;014&quot; data-emoticon-isanimation=&quot;false&quot; data-emoticon-src=&quot;https://t1.daumcdn.net/keditor/emoticon/friends1/large/014.gif&quot;&gt;&lt;img src=&quot;https://t1.daumcdn.net/keditor/emoticon/friends1/large/014.gif&quot; width=&quot;150&quot; /&gt;&lt;/figure&gt;</description>
      <category>개발/JAVA</category>
      <category>Android</category>
      <category>Java</category>
      <category>Thread</category>
      <category>안드로이드</category>
      <author>joon95</author>
      <guid isPermaLink="true">https://flowlog.tistory.com/106</guid>
      <comments>https://flowlog.tistory.com/106#entry106comment</comments>
      <pubDate>Tue, 20 Jun 2023 13:33:59 +0900</pubDate>
    </item>
    <item>
      <title>[Springboot] @JsonProperty는 언제 써야 할까!</title>
      <link>https://flowlog.tistory.com/105</link>
      <description>&lt;p data-ke-size=&quot;size16&quot;&gt;이번에 개발하다가 DB 테이블 컬럼 중 숫자형태의 '&lt;b&gt;A_CNT&lt;/b&gt;' &lt;u&gt;이런 앞에가 짧은 컬럼이 있어서&lt;/u&gt;, 늘 하던대로 RequestDTO 에 aCnt 로 작성하고 난 뒤 Post 방식으로 데이터를 보내봤더니 자꾸만 0 이 넘어오는게 아닌가????&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;h2 data-ke-size=&quot;size26&quot;&gt;@JsonProperty&lt;/h2&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;이 때 @JsonProperty를 쓰면된다.(변수가 많다면 @JsonNaming 으로 통합해도 됨)&lt;/p&gt;
&lt;pre id=&quot;code_1676989977169&quot; class=&quot;java&quot; data-ke-language=&quot;java&quot; data-ke-type=&quot;codeblock&quot;&gt;&lt;code&gt;@Getter
@Setter
@NoArgsConstructor
@AllArgsConstructor
public class Sample {
  @JsonProperty(&quot;a_cnt&quot;)
  private int aCnt;
}&lt;/code&gt;&lt;/pre&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;이렇게 하고 요청할 때 a_cnt 로 던지면 정상적으로 오게된다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;추가로 mybatis 에서 camelCase vo를 자동으로 변환해 주는 설정은 아래와 같다.&lt;/p&gt;
&lt;pre id=&quot;code_1676991010603&quot; class=&quot;html xml&quot; data-ke-language=&quot;html&quot; data-ke-type=&quot;codeblock&quot;&gt;&lt;code&gt;&amp;lt;setting name=&quot;mapUnderscoreToCamelCase&quot; value=&quot;true&quot;/&amp;gt;&lt;/code&gt;&lt;/pre&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;이렇게 하면 Sample Class에서 카멜케이스 표기법으로 변수를 작성하면 mybatis mapper에서 자동으로 변환된다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;span style=&quot;color: #8a3db6;&quot;&gt;&lt;b&gt;[Post Request &lt;i&gt;스네이크&lt;/i&gt;] -&amp;gt; [springboot &lt;i&gt;카멜&lt;/i&gt;] -&amp;gt; [DataBase &lt;i&gt;스네이크&lt;/i&gt;]&lt;/b&gt;&lt;/span&gt;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;h2 data-ke-size=&quot;size26&quot;&gt;원인 : 서로 다른 표기법&lt;/h2&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;a_cnt 에 대한 API 요청의 처리가 달랐던 이유는 아래와 같다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;- POST Json : &lt;b&gt;&lt;span style=&quot;color: #409d00;&quot;&gt;snake_case&lt;/span&gt;&lt;/b&gt;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;- JAVA Entity : &lt;b&gt;&lt;span style=&quot;color: #f89009;&quot;&gt;camelCase&lt;/span&gt;&lt;/b&gt;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;span style=&quot;color: #666666;&quot;&gt;&lt;i&gt;&lt;u&gt;&lt;b&gt;근데 왜 다른건 되고 이것만 안되는 지는 잘 모르겠다. 누가알면 설명좀..&lt;/b&gt;&lt;/u&gt;&lt;/i&gt;&lt;/span&gt;&lt;/p&gt;
&lt;p&gt;&lt;span style=&quot;color: #666666;&quot;&gt;&lt;b&gt;&lt;i&gt;&lt;u&gt;간혹 변환이 안되는 경우라는데...&lt;/u&gt;&lt;/i&gt;&lt;/b&gt;&lt;/span&gt;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;h2 data-ke-size=&quot;size26&quot;&gt;스네이크 / 카멜 표기법&lt;/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;ex) my name is joon95 -&amp;gt; &lt;i&gt;my_name_is_joon95&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;카멜식은 첫 단어 소문자, 두번째 단어부터 대문자로 시작하여 단어사이를 붙인다.&lt;/b&gt;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;ex) my name is joon95 -&amp;gt;&lt;i&gt; myNameIsJoon95&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;/p&gt;
&lt;table style=&quot;border-collapse: collapse; width: 100%;&quot; border=&quot;1&quot; data-ke-align=&quot;alignLeft&quot;&gt;
&lt;tbody&gt;
&lt;tr&gt;
&lt;td style=&quot;width: 50%;&quot;&gt;&lt;b&gt;사용처&lt;/b&gt;&lt;/td&gt;
&lt;td style=&quot;width: 50%;&quot;&gt;&lt;b&gt;표기법&lt;/b&gt;&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td style=&quot;width: 50%;&quot;&gt;API&lt;/td&gt;
&lt;td style=&quot;width: 50%;&quot;&gt;snake_case (통신 규격)&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td style=&quot;width: 50%;&quot;&gt;DB&lt;/td&gt;
&lt;td style=&quot;width: 50%;&quot;&gt;snake_case&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td style=&quot;width: 50%;&quot;&gt;JAVA&lt;/td&gt;
&lt;td style=&quot;width: 50%;&quot;&gt;camelCase, Class 파일만 UpperCase&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;p data-ke-size=&quot;size16&quot;&gt;API 에서 Camel Case로 받는 것으로 정의하여 사용하는 케이스도 있다고 하는데&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;이런 부분은 jackson ObjectMapper를 사용해 데이터를 조작하면 된다고 한다.&lt;/p&gt;
&lt;pre id=&quot;code_1676991419214&quot; class=&quot;java&quot; data-ke-language=&quot;java&quot; data-ke-type=&quot;codeblock&quot;&gt;&lt;code&gt;ObjectMapper mapper = new ObjectMapper();
Sample sample = mapper.convertValue(params, Sample.class);&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;h2 data-ke-size=&quot;size26&quot;&gt;참고&lt;/h2&gt;
&lt;figure id=&quot;og_1676990101482&quot; contenteditable=&quot;false&quot; data-ke-type=&quot;opengraph&quot; data-ke-align=&quot;alignCenter&quot; data-og-type=&quot;article&quot; data-og-title=&quot;[Spring] 스네이크 케이스로 받은 JSON 데이터를 객체 내 케멀케이스 변수에 매핑시키는 방법은 무&quot; data-og-description=&quot;카멜 케이스 Entity에 스네이크 케이스 Json 데이터를 매핑시키는 방법을 알아보자&quot; data-og-host=&quot;velog.io&quot; data-og-source-url=&quot;https://velog.io/@ctp102/SpringBoot-JsonNaming-JsonProperty%EB%8A%94-%EC%96%B8%EC%A0%9C-%EC%82%AC%EC%9A%A9%ED%95%A0%EA%B9%8C&quot; data-og-url=&quot;https://velog.io/@ctp102/SpringBoot-JsonNaming-JsonProperty는-언제-사용할까&quot; data-og-image=&quot;https://scrap.kakaocdn.net/dn/tUAO9/hyRIn9bgGW/13nLMgTOVfV2OJTJ0Y1Np1/img.png?width=950&amp;amp;height=500&amp;amp;face=0_0_950_500&quot;&gt;&lt;a href=&quot;https://velog.io/@ctp102/SpringBoot-JsonNaming-JsonProperty%EB%8A%94-%EC%96%B8%EC%A0%9C-%EC%82%AC%EC%9A%A9%ED%95%A0%EA%B9%8C&quot; target=&quot;_blank&quot; rel=&quot;noopener&quot; data-source-url=&quot;https://velog.io/@ctp102/SpringBoot-JsonNaming-JsonProperty%EB%8A%94-%EC%96%B8%EC%A0%9C-%EC%82%AC%EC%9A%A9%ED%95%A0%EA%B9%8C&quot;&gt;
&lt;div class=&quot;og-image&quot; style=&quot;background-image: url('https://scrap.kakaocdn.net/dn/tUAO9/hyRIn9bgGW/13nLMgTOVfV2OJTJ0Y1Np1/img.png?width=950&amp;amp;height=500&amp;amp;face=0_0_950_500');&quot;&gt;&amp;nbsp;&lt;/div&gt;
&lt;div class=&quot;og-text&quot;&gt;
&lt;p class=&quot;og-title&quot; data-ke-size=&quot;size16&quot;&gt;[Spring] 스네이크 케이스로 받은 JSON 데이터를 객체 내 케멀케이스 변수에 매핑시키는 방법은 무&lt;/p&gt;
&lt;p class=&quot;og-desc&quot; data-ke-size=&quot;size16&quot;&gt;카멜 케이스 Entity에 스네이크 케이스 Json 데이터를 매핑시키는 방법을 알아보자&lt;/p&gt;
&lt;p class=&quot;og-host&quot; data-ke-size=&quot;size16&quot;&gt;velog.io&lt;/p&gt;
&lt;/div&gt;
&lt;/a&gt;&lt;/figure&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;</description>
      <category>개발/Spring</category>
      <author>joon95</author>
      <guid isPermaLink="true">https://flowlog.tistory.com/105</guid>
      <comments>https://flowlog.tistory.com/105#entry105comment</comments>
      <pubDate>Wed, 22 Feb 2023 00:07:22 +0900</pubDate>
    </item>
    <item>
      <title>[ORACLE] MERGE INTO 조건의 데이터가 없는 경우 NULL Row 처리하기</title>
      <link>https://flowlog.tistory.com/104</link>
      <description>&lt;p data-ke-size=&quot;size16&quot;&gt;지난 달부터 차세대 MSA 개발 프로젝트에 개발자로 투입되서 그동안 너무 바빴다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;FRONT-END로 넥사크로(NEXACRO)를 사용하고 있는데, GRID DataSet을 이용한 Multi Row 핸들링을 자주하게 되어 다중 처리에 대한 부분을 알아보았다.&lt;/p&gt;
&lt;figure contenteditable=&quot;false&quot; data-ke-type=&quot;emoticon&quot; data-ke-align=&quot;alignCenter&quot; data-emoticon-type=&quot;friends2&quot; data-emoticon-name=&quot;068&quot; data-emoticon-isanimation=&quot;false&quot; data-emoticon-src=&quot;https://t1.daumcdn.net/keditor/emoticon/friends2/large/068.png&quot;&gt;&lt;img src=&quot;https://t1.daumcdn.net/keditor/emoticon/friends2/large/068.png&quot; width=&quot;150&quot; /&gt;&lt;/figure&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;그러던 중 merge into 를 사용한 update, insert를 구현하고 있었는데 조건절에서 데이터가 없다면 아무것도 실행하지 않는 현상이 생겼다. 조건, 업데이트문, 삽입문을 따로 돌려보면 전혀 에러가 없는 코드이고, 함께 돌려도 정상적인 코드로 멘탈이 슬슬 나가려할 때... 구글링을 하다가 내 상황과 같은 글을 찾게 되었다.(링크는 참고)&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;그 글에서는 오라클 9.2.0.4.0 버전에서는 되는데 9.2.0.3.0 버전에서 정상적으로 동작하지 않는 현상에 대해 알려주었다.(참고로 우리는 19.0.0.0.0 버전이다...)&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;그래서 어떻게 해야할까 찾는 도중 &lt;b&gt;union all&lt;/b&gt; 로 묶고 &lt;b&gt;NOT EXISTS&lt;/b&gt; 로 null row를 만드는 방법을 찾았다.&lt;/p&gt;
&lt;h2 data-ke-size=&quot;size26&quot;&gt;MERGE INTO 절&lt;/h2&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;먼저 MERGE INTO 의 기본 형태를 알아보자.&lt;/p&gt;
&lt;pre id=&quot;code_1676987776715&quot; class=&quot;sql&quot; data-ke-language=&quot;sql&quot; data-ke-type=&quot;codeblock&quot;&gt;&lt;code&gt;MERGE /* sample.mergeSample */
INTO 테이블명
  USING dual
    ON (ID = #{id})
    WHEN MATCHED THEN
    	UPDATE SET
        	COL1 = #{val1}
            ...
    WHEN NOT MATCHED THEN
    	INSERT
        (
        	COL1,COL2,COL3,...
        )
        VALUES
        (
        	#{val1},#{val2},#{val3},...
        )&lt;/code&gt;&lt;/pre&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;b&gt;MERGE INTO 테이블명&lt;/b&gt; : 사용할 테이블을 정의&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;b&gt;USING DUAL&lt;/b&gt; : 비교할 테이블(현재는 1개 테이블 이용하기에 DUAL 사용)&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;b&gt;ON ( )&lt;/b&gt; : 비교 조건(해당 값을 기준으로 UPDATE를 하기 때문에 사용된 컬럼은 update하지 못함)&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;b&gt;WHEN MATCHED THEN&lt;/b&gt; : ON 조건이 맞다면 실행한다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;b&gt;WHEN NOT MATCHED THEN&lt;/b&gt; : ON 조건이 틀리다면 실행한다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;이렇게 코딩을 하면 1개의 쿼리에 insert, update로 알아서 처리되기 때문에&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;이미 있는 id 값이 넘어왔다면 자연스레 다른 컬럼을 update 하게 된다.&lt;/p&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;완성된 MERGE INTO&lt;/h2&gt;
&lt;pre id=&quot;code_1676987800862&quot; class=&quot;sql&quot; data-ke-language=&quot;sql&quot; data-ke-type=&quot;codeblock&quot;&gt;&lt;code&gt;MERGE /* sample.mergeSample */
INTO 테이블 A
    USING (
    	SELECT ID
        FROM 테이블
        WHERE 1=1
        AND ROWNUM=1
        AND ... 조건추가
        
        UNION ALL
        
        SELECT NULL AS ID
        FROM DUAL
        WHERE NOT EXISTS (
            SELECT ID
            FROM 테이블
            WHERE 1=1
            AND ROWNUM=1
            AND ... 조건추가
        )
        AND ROWNUM=1
    ) B
    ON (A.ID = B.ID)
    WHEN MATCHED THEN
    	UPDATE SET
        	COL1 = #{val1}
            ...
    WHEN NOT MATCHED THEN
    	INSERT
        (
        	COL1,COL2,COL3,...
        )
        VALUES
        (
        	#{val1},#{val2},#{val3},...
        )&lt;/code&gt;&lt;/pre&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;USING 부분의 코드가 상당부분 들어간걸 볼 수 있다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;이로써 조건에 맞는 row가 있다면 update를 실행하고,&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;조건에 맞는 데이터가 없다면 NULL row가 생겨 insert를 실행하게 된다.&lt;/p&gt;
&lt;h2 data-ke-size=&quot;size26&quot;&gt;참고&lt;/h2&gt;
&lt;figure id=&quot;og_1676986776284&quot; contenteditable=&quot;false&quot; data-ke-type=&quot;opengraph&quot; data-ke-align=&quot;alignCenter&quot; data-og-type=&quot;article&quot; data-og-title=&quot;[ORACLE] MERGE INTO&quot; data-og-description=&quot;9.2.0.4.0 버젼에선 아래와 같은 문법으로도 MERGE INTO 가 정상적으로 동작하였다. MERGE INTO TBL_1USING DUAL B ON (F6= 'Y' AND F1 = 'ALL' AND F3= '1') WHEN MATCHED THEN UPDATE SET REG_DATE = SYSDATE WHEN NOT MATCHED THEN INS&quot; data-og-host=&quot;egloos.zum.com&quot; data-og-source-url=&quot;http://egloos.zum.com/future2013/v/10242190&quot; data-og-url=&quot;http://egloos.zum.com/future2013/v/10242190&quot; data-og-image=&quot;https://scrap.kakaocdn.net/dn/bZhXTP/hyRHq0JCiD/VfMZHJsTFmqOokXZGxIVOK/img.jpg?width=250&amp;amp;height=250&amp;amp;face=0_0_250_250&quot;&gt;&lt;a href=&quot;http://egloos.zum.com/future2013/v/10242190&quot; target=&quot;_blank&quot; rel=&quot;noopener&quot; data-source-url=&quot;http://egloos.zum.com/future2013/v/10242190&quot;&gt;
&lt;div class=&quot;og-image&quot; style=&quot;background-image: url('https://scrap.kakaocdn.net/dn/bZhXTP/hyRHq0JCiD/VfMZHJsTFmqOokXZGxIVOK/img.jpg?width=250&amp;amp;height=250&amp;amp;face=0_0_250_250');&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;[ORACLE] MERGE INTO&lt;/p&gt;
&lt;p class=&quot;og-desc&quot; data-ke-size=&quot;size16&quot;&gt;9.2.0.4.0 버젼에선 아래와 같은 문법으로도 MERGE INTO 가 정상적으로 동작하였다. MERGE INTO TBL_1USING DUAL B ON (F6= 'Y' AND F1 = 'ALL' AND F3= '1') WHEN MATCHED THEN UPDATE SET REG_DATE = SYSDATE WHEN NOT MATCHED THEN INS&lt;/p&gt;
&lt;p class=&quot;og-host&quot; data-ke-size=&quot;size16&quot;&gt;egloos.zum.com&lt;/p&gt;
&lt;/div&gt;
&lt;/a&gt;&lt;/figure&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;</description>
      <category>데이터베이스</category>
      <category>merge into</category>
      <category>Oracle</category>
      <category>SQL</category>
      <category>union all</category>
      <author>joon95</author>
      <guid isPermaLink="true">https://flowlog.tistory.com/104</guid>
      <comments>https://flowlog.tistory.com/104#entry104comment</comments>
      <pubDate>Tue, 21 Feb 2023 23:13:50 +0900</pubDate>
    </item>
    <item>
      <title>[AzureDevops] Repos / Pipeline 구축기</title>
      <link>https://flowlog.tistory.com/103</link>
      <description>&lt;p data-ke-size=&quot;size16&quot;&gt;AzureDevops Pipeline 관련 포스팅을 이전에 했었는데, 당시엔 파이프라인 스크립트 라이브러리 사용에 익숙치 않았기 때문에 파이프라인 작성과 실행을 중심으로 작성하였다면, 오늘은 azure Pipeline 구축 및 사용에 대해 더 초점을 맞춰 글을 쓰려한다.&lt;/p&gt;
&lt;figure id=&quot;og_1672883170556&quot; contenteditable=&quot;false&quot; data-ke-type=&quot;opengraph&quot; data-ke-align=&quot;alignCenter&quot; data-og-type=&quot;article&quot; data-og-title=&quot;[AzureDevops] CI-CD Pipeline 구축 테스트&quot; data-og-description=&quot;11월 3주간 Github Actions 과 AzureDevops 두 개의 CI-CD Pipeline 구축테스트를 진행하였고 Rest API 호출 방법까지 케이스를 정리해보았다. AzureDevops 도 GHES와 같이 Private 용도의 Server를 제공하며, 별도의 Self-&quot; data-og-host=&quot;flowlog.tistory.com&quot; data-og-source-url=&quot;https://flowlog.tistory.com/95&quot; data-og-url=&quot;https://flowlog.tistory.com/95&quot; data-og-image=&quot;https://scrap.kakaocdn.net/dn/qLs3z/hyQ84c8yHe/0exsywx5qUk2fhtLeCPswK/img.png?width=800&amp;amp;height=333&amp;amp;face=0_0_800_333,https://scrap.kakaocdn.net/dn/5dFrV/hyQ8Un3u9T/JLTmXYYYw0DIMkgIaMrLU0/img.png?width=800&amp;amp;height=333&amp;amp;face=0_0_800_333,https://scrap.kakaocdn.net/dn/0zBVU/hyQ8SRkFzu/O5Cf1wgSIb2DOfod76yLy0/img.png?width=1200&amp;amp;height=500&amp;amp;face=0_0_1200_500&quot;&gt;&lt;a href=&quot;https://flowlog.tistory.com/95&quot; target=&quot;_blank&quot; rel=&quot;noopener&quot; data-source-url=&quot;https://flowlog.tistory.com/95&quot;&gt;
&lt;div class=&quot;og-image&quot; style=&quot;background-image: url('https://scrap.kakaocdn.net/dn/qLs3z/hyQ84c8yHe/0exsywx5qUk2fhtLeCPswK/img.png?width=800&amp;amp;height=333&amp;amp;face=0_0_800_333,https://scrap.kakaocdn.net/dn/5dFrV/hyQ8Un3u9T/JLTmXYYYw0DIMkgIaMrLU0/img.png?width=800&amp;amp;height=333&amp;amp;face=0_0_800_333,https://scrap.kakaocdn.net/dn/0zBVU/hyQ8SRkFzu/O5Cf1wgSIb2DOfod76yLy0/img.png?width=1200&amp;amp;height=500&amp;amp;face=0_0_1200_500');&quot;&gt;&amp;nbsp;&lt;/div&gt;
&lt;div class=&quot;og-text&quot;&gt;
&lt;p class=&quot;og-title&quot; data-ke-size=&quot;size16&quot;&gt;[AzureDevops] CI-CD Pipeline 구축 테스트&lt;/p&gt;
&lt;p class=&quot;og-desc&quot; data-ke-size=&quot;size16&quot;&gt;11월 3주간 Github Actions 과 AzureDevops 두 개의 CI-CD Pipeline 구축테스트를 진행하였고 Rest API 호출 방법까지 케이스를 정리해보았다. AzureDevops 도 GHES와 같이 Private 용도의 Server를 제공하며, 별도의 Self-&lt;/p&gt;
&lt;p class=&quot;og-host&quot; data-ke-size=&quot;size16&quot;&gt;flowlog.tistory.com&lt;/p&gt;
&lt;/div&gt;
&lt;/a&gt;&lt;/figure&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;h2 data-ke-size=&quot;size26&quot;&gt;Pipelines&lt;/h2&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;Azure Devops 에 접속하여 Pipeline&amp;gt;All 페이지에 들어오면 폴더별로 체크할 경우에 깔끔하다.&lt;/p&gt;
&lt;p&gt;&lt;figure class=&quot;imageblock alignCenter&quot; data-ke-mobileStyle=&quot;widthOrigin&quot; data-origin-width=&quot;864&quot; data-origin-height=&quot;572&quot;&gt;&lt;span data-url=&quot;https://blog.kakaocdn.net/dn/CFfju/btrVpCwoT2r/VRL2XG3YlH32UiXXdFg7Nk/img.png&quot; data-phocus=&quot;https://blog.kakaocdn.net/dn/CFfju/btrVpCwoT2r/VRL2XG3YlH32UiXXdFg7Nk/img.png&quot; data-alt=&quot;Azure Pipelines&quot;&gt;&lt;img src=&quot;https://blog.kakaocdn.net/dn/CFfju/btrVpCwoT2r/VRL2XG3YlH32UiXXdFg7Nk/img.png&quot; srcset=&quot;https://img1.daumcdn.net/thumb/R1280x0/?scode=mtistory2&amp;fname=https%3A%2F%2Fblog.kakaocdn.net%2Fdn%2FCFfju%2FbtrVpCwoT2r%2FVRL2XG3YlH32UiXXdFg7Nk%2Fimg.png&quot; onerror=&quot;this.onerror=null; this.src='//t1.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;864&quot; height=&quot;572&quot; data-origin-width=&quot;864&quot; data-origin-height=&quot;572&quot;/&gt;&lt;/span&gt;&lt;figcaption&gt;Azure Pipelines&lt;/figcaption&gt;
&lt;/figure&gt;
&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;h2 data-ke-size=&quot;size26&quot;&gt;Repos&lt;/h2&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;파이프라인에서 사용되는 &lt;b&gt;&lt;u&gt;yml 파일들은 모두 레포지토리에 저장&lt;/u&gt;&lt;/b&gt;된다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;b&gt;joon95-spring-maven&lt;/b&gt; 이라는 레포지토리에 application 소스코드를 업로드하여 사용하고 있고, 추가 파이프라인에서 사용될 폴더를 만들었다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;- azdevops : Pipeline 에서 사용될 yml (CI,CD)&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;- manifests : Pipeline 에서 AKS에 배포할 yml&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;s&gt;Dockerfile은 그냥 밖에 둠(귀찬)&lt;/s&gt;&lt;/p&gt;
&lt;p&gt;&lt;figure class=&quot;imageblock alignCenter&quot; data-ke-mobileStyle=&quot;widthOrigin&quot; data-origin-width=&quot;262&quot; data-origin-height=&quot;956&quot;&gt;&lt;span data-url=&quot;https://blog.kakaocdn.net/dn/qUgEx/btrVoG0nKQ3/OJFw7NYe3Ku5k3SpXqwbEK/img.png&quot; data-phocus=&quot;https://blog.kakaocdn.net/dn/qUgEx/btrVoG0nKQ3/OJFw7NYe3Ku5k3SpXqwbEK/img.png&quot; data-alt=&quot;Azure Repos&quot;&gt;&lt;img src=&quot;https://blog.kakaocdn.net/dn/qUgEx/btrVoG0nKQ3/OJFw7NYe3Ku5k3SpXqwbEK/img.png&quot; srcset=&quot;https://img1.daumcdn.net/thumb/R1280x0/?scode=mtistory2&amp;fname=https%3A%2F%2Fblog.kakaocdn.net%2Fdn%2FqUgEx%2FbtrVoG0nKQ3%2FOJFw7NYe3Ku5k3SpXqwbEK%2Fimg.png&quot; onerror=&quot;this.onerror=null; this.src='//t1.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;262&quot; height=&quot;956&quot; data-origin-width=&quot;262&quot; data-origin-height=&quot;956&quot;/&gt;&lt;/span&gt;&lt;figcaption&gt;Azure Repos&lt;/figcaption&gt;
&lt;/figure&gt;
&lt;/p&gt;
&lt;h2 data-ke-size=&quot;size26&quot;&gt;Dashboards&lt;/h2&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;애저데브옵스는 자체 대시보드가 있는데, 전체적인 현황을 체크할 수 있어 나름 괜찮은 기능인 것 같다.&lt;/p&gt;
&lt;p&gt;&lt;figure class=&quot;imageblock alignCenter&quot; data-ke-mobileStyle=&quot;widthOrigin&quot; data-origin-width=&quot;1618&quot; data-origin-height=&quot;819&quot;&gt;&lt;span data-url=&quot;https://blog.kakaocdn.net/dn/Mc4QI/btrVkgBpSYA/Kd1bDcKJjL6TqASgIxl4Fk/img.png&quot; data-phocus=&quot;https://blog.kakaocdn.net/dn/Mc4QI/btrVkgBpSYA/Kd1bDcKJjL6TqASgIxl4Fk/img.png&quot; data-alt=&quot;azure Devops Dashboard&quot;&gt;&lt;img src=&quot;https://blog.kakaocdn.net/dn/Mc4QI/btrVkgBpSYA/Kd1bDcKJjL6TqASgIxl4Fk/img.png&quot; srcset=&quot;https://img1.daumcdn.net/thumb/R1280x0/?scode=mtistory2&amp;fname=https%3A%2F%2Fblog.kakaocdn.net%2Fdn%2FMc4QI%2FbtrVkgBpSYA%2FKd1bDcKJjL6TqASgIxl4Fk%2Fimg.png&quot; onerror=&quot;this.onerror=null; this.src='//t1.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;1618&quot; height=&quot;819&quot; data-origin-width=&quot;1618&quot; data-origin-height=&quot;819&quot;/&gt;&lt;/span&gt;&lt;figcaption&gt;azure Devops Dashboard&lt;/figcaption&gt;
&lt;/figure&gt;
&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;h2 data-ke-size=&quot;size26&quot;&gt;마치며&lt;/h2&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;그동안 익숙했던 gitlab, jenkins 환경을 벗어던지고.. 이제는 각 클라우드의 &lt;u&gt;자체 git/pipeline&lt;/u&gt; 기능이 강력해지고 있다. 필자가 느끼기엔 아직 네이버클라우드쪽은 매우 부족하다고 느끼고 있고, 애저데브옵스는 애저리소스를 몇번의 클릭으로 바인딩할 수 있기 때문에 매우 편했다. 추가로 Github Actions도 애저파이프라인보다는 약간 부족한 감이 든다.(approve옵션에서 false할 경우 파이프라인을 종료시킨 깃헙액션)&lt;/p&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;결국 github나 gitlab이 git을 사용하는데 있어서 제일 익숙하고 편하다.&lt;/u&gt;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;클라우드 벤더들도 자기들의 경쟁력을 올리기 위해 많은 기능들이 추가될 것이다.&lt;/p&gt;</description>
      <category>엔지니어링/CI-CD</category>
      <category>AzureDevOps</category>
      <category>CI-CD</category>
      <category>Dashboard</category>
      <category>pipeline</category>
      <category>repos</category>
      <author>joon95</author>
      <guid isPermaLink="true">https://flowlog.tistory.com/103</guid>
      <comments>https://flowlog.tistory.com/103#entry103comment</comments>
      <pubDate>Thu, 5 Jan 2023 11:09:40 +0900</pubDate>
    </item>
    <item>
      <title>[TmaxSoft] Jeus Webtob 도커 이미지 보안 개선</title>
      <link>https://flowlog.tistory.com/102</link>
      <description>&lt;p data-ke-size=&quot;size16&quot;&gt;이전에 포스팅한 글은 솔루션의 기본 세팅에서 내가 바로 적용할 수 있는 부분까지 확인해보았다.&lt;/p&gt;
&lt;figure id=&quot;og_1672881759954&quot; contenteditable=&quot;false&quot; data-ke-type=&quot;opengraph&quot; data-ke-align=&quot;alignCenter&quot; data-og-type=&quot;article&quot; data-og-title=&quot;[TmaxSoft] Jeus Webtob 도커 이미지 연동하기&quot; data-og-description=&quot;이번에 on-prem을 MSA 클라우드 전환 프로젝트 PoC를 준비하며 Tmaxsoft 사의 Jeus, Webtob를 컨테이너로 옮기는 작업이 필요했다. 필자는 다른 프로젝트를 진행해오면서 React, Vue, Angluer 등의 스크립트언&quot; data-og-host=&quot;flowlog.tistory.com&quot; data-og-source-url=&quot;https://flowlog.tistory.com/101&quot; data-og-url=&quot;https://flowlog.tistory.com/101&quot; data-og-image=&quot;https://scrap.kakaocdn.net/dn/esOXWQ/hyRaAhaqGB/RRKSq9j79TJeD1LWTaUDg0/img.jpg?width=225&amp;amp;height=225&amp;amp;face=0_0_225_225,https://scrap.kakaocdn.net/dn/cgl0rz/hyRawMBQuL/aNbNSOLg4QKfYfsULPPpJk/img.jpg?width=225&amp;amp;height=225&amp;amp;face=0_0_225_225,https://scrap.kakaocdn.net/dn/bSRufv/hyRaspUNL5/GWJ9Wxkif4YUWhXSU15uk0/img.jpg?width=1785&amp;amp;height=738&amp;amp;face=0_0_1785_738&quot;&gt;&lt;a href=&quot;https://flowlog.tistory.com/101&quot; target=&quot;_blank&quot; rel=&quot;noopener&quot; data-source-url=&quot;https://flowlog.tistory.com/101&quot;&gt;
&lt;div class=&quot;og-image&quot; style=&quot;background-image: url('https://scrap.kakaocdn.net/dn/esOXWQ/hyRaAhaqGB/RRKSq9j79TJeD1LWTaUDg0/img.jpg?width=225&amp;amp;height=225&amp;amp;face=0_0_225_225,https://scrap.kakaocdn.net/dn/cgl0rz/hyRawMBQuL/aNbNSOLg4QKfYfsULPPpJk/img.jpg?width=225&amp;amp;height=225&amp;amp;face=0_0_225_225,https://scrap.kakaocdn.net/dn/bSRufv/hyRaspUNL5/GWJ9Wxkif4YUWhXSU15uk0/img.jpg?width=1785&amp;amp;height=738&amp;amp;face=0_0_1785_738');&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;[TmaxSoft] Jeus Webtob 도커 이미지 연동하기&lt;/p&gt;
&lt;p class=&quot;og-desc&quot; data-ke-size=&quot;size16&quot;&gt;이번에 on-prem을 MSA 클라우드 전환 프로젝트 PoC를 준비하며 Tmaxsoft 사의 Jeus, Webtob를 컨테이너로 옮기는 작업이 필요했다. 필자는 다른 프로젝트를 진행해오면서 React, Vue, Angluer 등의 스크립트언&lt;/p&gt;
&lt;p class=&quot;og-host&quot; data-ke-size=&quot;size16&quot;&gt;flowlog.tistory.com&lt;/p&gt;
&lt;/div&gt;
&lt;/a&gt;&lt;/figure&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;더 나아가서 프로세스 실행 유저와 프로세스 기동 위치 등을 수정하려 Dockerfile을 손보았다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;그럼 함께 확인해보자.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;h2 data-ke-size=&quot;size26&quot;&gt;Webtob Dockerfile&lt;/h2&gt;
&lt;pre id=&quot;code_1672882002430&quot; class=&quot;dockerfile&quot; data-ke-language=&quot;highlight.js&quot; data-ke-type=&quot;codeblock&quot;&gt;&lt;code&gt;FROM tmaxsoftofficial/webtob:5.0.0.2.217.41.3.2

##########################################################
# test package
# RUN yum update
# RUN yum install -y vim net-tools curl
##########################################################

# User add
RUN groupadd -r -g 2000 tmax &amp;amp;&amp;amp; useradd -r -d /home/webtob -u 1000 -g tmax webtob
#RUN mv /home/start.sh /home/webtob/start.sh
#RUN setcap 'cap_net_bind_service=+ep' /home/webtob/bin/htl

# resouces ADD
RUN mkdir /home/webtob/docs/styles
COPY ./health.html /home/webtob/docs

# configuration
COPY ./http.m /home/webtob/config/http.m

RUN chown -R webtob. /home/webtob
RUN chown webtob. /home/start.sh

USER webtob
WORKDIR /home/webtob

CMD [&quot;/home/start.sh&quot;]&lt;/code&gt;&lt;/pre&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;b&gt;유저그룹 tmax&lt;/b&gt; / &lt;b&gt;유저명 webtob&lt;/b&gt; 로 생성해주었고, 웹투비 실행폴더는 default &lt;b&gt;/home/webtob&lt;/b&gt; 이기 때문에 &lt;b&gt;권한&lt;/b&gt;만 변경해주었다.&amp;nbsp;그리고 유저를 지정한 뒤 작업 디렉토리를 정해준 뒤 실행 스크립트를 커맨드에 명시해주었다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;또, setcap 부분이 있는데 이는 &lt;u&gt;일반 유저가 1024 이하의 포트를 점유하기 위해 권한(&lt;b&gt;&lt;span style=&quot;background-color: #ffffff; color: #666666;&quot;&gt;privileged&lt;/span&gt;&lt;/b&gt;)을 풀어주는 명령어&lt;/u&gt;이다. 필자는 그냥 http.m 에서 &lt;u&gt;&lt;b&gt;listner 포트를 8000으로 변경&lt;/b&gt;&lt;/u&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;이제 이 베이스이미지로 pipeline을 태우면 되는데,&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;b&gt;/home/webtob/docs&lt;/b&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;Jeus Dockerfile&lt;/h2&gt;
&lt;pre id=&quot;code_1672882289234&quot; class=&quot;dockerfile&quot; data-ke-language=&quot;highlight.js&quot; data-ke-type=&quot;codeblock&quot;&gt;&lt;code&gt;FROM tmaxsoftofficial/jeus:8.1.105067.1-jdk8-openjdk-ubuntu

###########################################################################
# test package
# RUN apt update
# RUN apt install -y vim net-tools curl
###########################################################################

# User add
RUN groupadd -r -g 2000 tmax &amp;amp;&amp;amp; useradd -r -d /home/jeus -u 1000 -g tmax jeus
RUN mkdir /home/jeus

# Applications Deploy Location - pipeline
# COPY ./web-0.0.1-SNAPSHOT.war /root/app

# Configurations
COPY ./domain.xml /root/jeus8/domains/domain1/config/domain.xml

# scripts
COPY ./start.sh /root/script/
COPY ./env-run.sh /root/script/

RUN mv /root/* /home/jeus
RUN chown -R jeus. /home/jeus

USER jeus
WORKDIR /home/jeus

CMD [&quot;script/start.sh&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;b&gt;유저그룹 tmax&amp;nbsp;&lt;/b&gt;/&amp;nbsp;&lt;b&gt;유저명 jeus&lt;/b&gt; 로 생성해주었고, 제우스 실행 경로는 default &lt;b&gt;/root&lt;/b&gt; 였기 때문에 &lt;b&gt;/home/jeus 디렉토리를 생성&lt;/b&gt;하고 &lt;b&gt;마지막에 폴더를 이동&lt;/b&gt;시켜주었다. 지난 포스팅과 비교해 &lt;b&gt;script가 추가&lt;/b&gt;되었는데, &lt;u&gt;domain.xml에서 추가로 값을 치환하는 작업&lt;/u&gt;이 필요해져서 &lt;b&gt;별도의 env-run.sh 파일&lt;/b&gt;을 만들었고, 이를 &lt;u&gt;기동시키기 위해 기존에 있던 start.sh에 1line을 추가&lt;/u&gt;해주었다. 마지막으로 &lt;b&gt;컨테이너 실행 유저&lt;/b&gt;와 &lt;b&gt;작업디렉토리&lt;/b&gt;를 정해준 뒤 &lt;b&gt;실행스크립트를 커맨드&lt;/b&gt;에 넣었다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;이제 이 베이스이미지로 pipeline을 태우면 되는데,&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;u&gt;app base 경로&lt;/u&gt;는 &lt;b&gt;/home/jeus/app&lt;/b&gt; 이 되었으니 이 폴더에 실제 애플리케이션.war를 넣어주면 완성된다.&lt;/p&gt;
&lt;h2 data-ke-size=&quot;size26&quot;&gt;마치며&lt;/h2&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;컨테이너 보안이라고 하기에 너무많은 부분이 있을 텐데, 많은 부분에 대해 모르는게 많다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;그냥 Redhat OCP 를 사용할 때의 기억을 참고하여 일반계정 및 작업디렉토리 등의 설정을 해보았다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;이제 enviroment 를 정의해서 컨테이너를 기동하면 된다.&lt;/p&gt;</description>
      <category>엔지니어링/CI-CD</category>
      <category>Container</category>
      <category>docker</category>
      <category>jeus</category>
      <category>TMAX</category>
      <category>TmaxSoft</category>
      <category>webtob</category>
      <category>보안</category>
      <author>joon95</author>
      <guid isPermaLink="true">https://flowlog.tistory.com/102</guid>
      <comments>https://flowlog.tistory.com/102#entry102comment</comments>
      <pubDate>Thu, 5 Jan 2023 10:42:21 +0900</pubDate>
    </item>
    <item>
      <title>[TmaxSoft] Jeus Webtob 도커 이미지 연동하기</title>
      <link>https://flowlog.tistory.com/101</link>
      <description>&lt;p data-ke-size=&quot;size16&quot;&gt;이번에 on-prem을 MSA 클라우드 전환 프로젝트 PoC를 준비하며&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;Tmaxsoft 사의 Jeus, Webtob를 컨테이너로 옮기는 작업이 필요했다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;필자는 다른 프로젝트를 진행해오면서 React, Vue, Angluer 등의 스크립트언어를 통해&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;front-end 와 back-end가 명확히 분리된 환경으로 컨테이너를 구성해 왔다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;기존의 Web-&amp;gt;WAS 구조는 대학생 때 apache mod_jk를 통한 tomcat 연동을 해보았고,&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;이번기회에 Webtob와 Jeus 연동테스트를 진행하려한다.&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;Base Image&lt;/h2&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;먼저 Tmax에서 제공하는 이미지를 확인해보니 두 이미지 모두 tmaxsoftofficial 유저에 의해 2년전에 배포가 되어있었다.&lt;/p&gt;
&lt;p&gt;&lt;figure class=&quot;imageblock alignCenter&quot; data-ke-mobileStyle=&quot;widthOrigin&quot; data-origin-width=&quot;606&quot; data-origin-height=&quot;167&quot;&gt;&lt;span data-url=&quot;https://blog.kakaocdn.net/dn/Xz9uJ/btrUTZy9ry3/wgarpU1YY0VKo5zJddanIK/img.png&quot; data-phocus=&quot;https://blog.kakaocdn.net/dn/Xz9uJ/btrUTZy9ry3/wgarpU1YY0VKo5zJddanIK/img.png&quot;&gt;&lt;img src=&quot;https://blog.kakaocdn.net/dn/Xz9uJ/btrUTZy9ry3/wgarpU1YY0VKo5zJddanIK/img.png&quot; srcset=&quot;https://img1.daumcdn.net/thumb/R1280x0/?scode=mtistory2&amp;fname=https%3A%2F%2Fblog.kakaocdn.net%2Fdn%2FXz9uJ%2FbtrUTZy9ry3%2FwgarpU1YY0VKo5zJddanIK%2Fimg.png&quot; onerror=&quot;this.onerror=null; this.src='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png'; this.srcset='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png';&quot; loading=&quot;lazy&quot; width=&quot;606&quot; height=&quot;167&quot; data-origin-width=&quot;606&quot; data-origin-height=&quot;167&quot;/&gt;&lt;/span&gt;&lt;/figure&gt;
&lt;figure class=&quot;imageblock alignCenter&quot; data-ke-mobileStyle=&quot;widthOrigin&quot; data-origin-width=&quot;606&quot; data-origin-height=&quot;163&quot;&gt;&lt;span data-url=&quot;https://blog.kakaocdn.net/dn/c8zBBU/btrUVkpvt2W/p13hgKCTKI5DpGNfJHBn80/img.png&quot; data-phocus=&quot;https://blog.kakaocdn.net/dn/c8zBBU/btrUVkpvt2W/p13hgKCTKI5DpGNfJHBn80/img.png&quot;&gt;&lt;img src=&quot;https://blog.kakaocdn.net/dn/c8zBBU/btrUVkpvt2W/p13hgKCTKI5DpGNfJHBn80/img.png&quot; srcset=&quot;https://img1.daumcdn.net/thumb/R1280x0/?scode=mtistory2&amp;fname=https%3A%2F%2Fblog.kakaocdn.net%2Fdn%2Fc8zBBU%2FbtrUVkpvt2W%2Fp13hgKCTKI5DpGNfJHBn80%2Fimg.png&quot; onerror=&quot;this.onerror=null; this.src='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png'; this.srcset='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png';&quot; loading=&quot;lazy&quot; width=&quot;606&quot; height=&quot;163&quot; data-origin-width=&quot;606&quot; data-origin-height=&quot;163&quot;/&gt;&lt;/span&gt;&lt;/figure&gt;
&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;2022.12.30 기준으로 가장 마지막 태그 정보는 아래와 같다.&lt;/p&gt;
&lt;pre id=&quot;code_1672363804730&quot; class=&quot;shell&quot; data-ke-language=&quot;shell&quot; data-ke-type=&quot;codeblock&quot;&gt;&lt;code&gt;docker pull tmaxsoftofficial/jeus:8.1.105067.1-jdk8-openjdk-ubuntu
docker pull tmaxsoftofficial/webtob:5.0.0.2.217.41.3.2&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;- Webtob 5.0&amp;nbsp;SP&amp;nbsp;0&amp;nbsp;Fix&amp;nbsp;#2&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;- Jeus 8&amp;nbsp;Fix#1&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;h2 data-ke-size=&quot;size26&quot;&gt;Webtob&lt;/h2&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;이제 웹서버 역할을 하는 Webtob부터 설정해보자.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;여기서는 정적리소스와 jeus 연동을 위한 JSV 설정이 필요하며,&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;webtob의 설정파일은 config 폴더 하위에 &lt;b&gt;http.m&lt;/b&gt;을 기술한다.&lt;/p&gt;
&lt;h3 data-ke-size=&quot;size23&quot;&gt;컨텍스트경로설정&lt;/h3&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;최초 파일을 보면 examples 로 설정되어있는데 '/' 로 변경하자.&lt;/p&gt;
&lt;h4 data-ke-size=&quot;size20&quot;&gt;*URI&lt;/h4&gt;
&lt;pre id=&quot;code_1672366644450&quot; class=&quot;shell&quot; data-ke-language=&quot;shell&quot; data-ke-type=&quot;codeblock&quot;&gt;&lt;code&gt;*URI
uri             Uri = &quot;/&quot;,   Svrtype = JSV&lt;/code&gt;&lt;/pre&gt;
&lt;h3 data-ke-size=&quot;size23&quot;&gt;정적리소스&lt;/h3&gt;
&lt;h4 data-ke-size=&quot;size20&quot;&gt;&lt;span style=&quot;color: #000000; font-size: 1.44em; letter-spacing: -1px;&quot;&gt;*EXT 절&lt;/span&gt;&lt;/h4&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;http.m 파일은 &lt;b&gt;*설정명칭&lt;/b&gt; 으로 구분되는데, 확장자에 따라 아래의 &lt;b&gt;*EXT&lt;/b&gt; 내용을 추가해준다.&lt;/p&gt;
&lt;pre id=&quot;code_1672364244474&quot; class=&quot;shell&quot; data-ke-language=&quot;shell&quot; data-ke-type=&quot;codeblock&quot;&gt;&lt;code&gt;*EXT
htm             MimeType = &quot;text/html&quot;, SvrType = HTML
html            MimeType = &quot;text/html&quot;, SvrType = HTML
jsp             Mimetype = &quot;application/jsp&quot;,  Svrtype=JSV
gif             MimeType = &quot;image/gif&quot;, SvrType = HTML
jpeg            MimeType = &quot;image/jpeg&quot;, SvrType = HTML
jpg             MimeType = &quot;image/jpeg&quot;, SvrType = HTML
js              MimeType = &quot;application/x-javascript&quot;, SvrType = HTML
css             MimeType = &quot;text/css&quot;, SvrType = HTML
swf             MimeType = &quot;application/octet-stream&quot;, SvrType = HTML
hwp             MimeType = &quot;application/octet-stream&quot;, SvrType = HTML
doc             MimeType = &quot;application/msword&quot;, SvrType = HTML
ppt             MimeType = &quot;application/vns.ms-powerpoint&quot;, SvrType = HTML&lt;/code&gt;&lt;/pre&gt;
&lt;h4 data-ke-size=&quot;size20&quot;&gt;&lt;b&gt;*NODE 절&lt;/b&gt;&lt;/h4&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;다음은 위에서 설정한 EXT에 대한&lt;span&gt;&amp;nbsp;&lt;/span&gt;&lt;b&gt;우선순위 '&lt;/b&gt;ServiceOrder' 항목을 추가한다.&lt;/p&gt;
&lt;pre id=&quot;code_1672364433866&quot; class=&quot;shell&quot; data-ke-language=&quot;shell&quot; data-ke-type=&quot;codeblock&quot;&gt;&lt;code&gt;*NODE
webtob1	        ...,
                ServiceOrder=&quot;ext,uri&quot;&lt;/code&gt;&lt;/pre&gt;
&lt;h3 data-ke-size=&quot;size23&quot;&gt;JSV&lt;/h3&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;Webtob 에서 말하는 JSV는 Jeus와 연결하기 위한 옵션이다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;여기서 기존과 약간 다른 방식을 확인할 수 있었는데, &lt;b&gt;apache-tomcat&lt;/b&gt; 구조에서는 mod_jk를 통한 web서버가 was를 바라연결하는 구조였다면 &lt;b&gt;webtob&lt;/b&gt;는 jsvport를 listener로 열어두고 jeus가 연결하는 구조이다. 이는 webtob를 DMZ구간에서, jeus를 internal 망에서 운용할 경우 보안적 이점(out-bound)을 가져갈 수 있다.&lt;/p&gt;
&lt;h4 data-ke-size=&quot;size20&quot;&gt;*NODE 절&lt;/h4&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;Jeus서버에서 접근할 포트를 지정한다.&amp;nbsp;&lt;/p&gt;
&lt;pre id=&quot;code_1672366046250&quot; class=&quot;shell&quot; data-ke-language=&quot;shell&quot; data-ke-type=&quot;codeblock&quot;&gt;&lt;code&gt;*NODE
webtob1	        ...,
                JSVPORT=9900&lt;/code&gt;&lt;/pre&gt;
&lt;h4 data-ke-size=&quot;size20&quot;&gt;*SVRGROUP 절&lt;/h4&gt;
&lt;pre id=&quot;code_1672366123035&quot; class=&quot;shell&quot; data-ke-language=&quot;shell&quot; data-ke-type=&quot;codeblock&quot;&gt;&lt;code&gt;*SVRGROUP
jsvg            SVRTYPE = JSV&lt;/code&gt;&lt;/pre&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;base image에 기본적으로 추가되어있다.&lt;/p&gt;
&lt;h4 data-ke-size=&quot;size20&quot;&gt;*SERVER 절&lt;/h4&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;여기서 지정한 &lt;b&gt;이름은 Jeus에서 똑같이 적어주어야한다&lt;/b&gt;(MyGroup)&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;적당히 프로세스 수량을 지정해 주자.&lt;/p&gt;
&lt;pre id=&quot;code_1672366232207&quot; class=&quot;shell&quot; data-ke-language=&quot;shell&quot; data-ke-type=&quot;codeblock&quot;&gt;&lt;code&gt;*SERVER
MyGroup         SVGNAME = jsvg, MinProc = 2, MaxProc = 1000, ASQCount = 1&lt;/code&gt;&lt;/pre&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;MinProc : 웹 컨테이너와 최소 연결 개수&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;MaxProc : 웹 컨테이너와 최대 연결 개수&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;이 개수는 Jeus 환경 파일 중 &amp;lt;webtob-listener&amp;gt; 하위 &lt;b&gt;&amp;lt;thread-pool&amp;gt;&lt;/b&gt; 설정값과 각각 &lt;b&gt;일치하거나 커야 한다&lt;/b&gt;.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;h3 data-ke-size=&quot;size23&quot;&gt;완성된 http.m&lt;/h3&gt;
&lt;pre id=&quot;code_1672366389425&quot; class=&quot;shell&quot; data-ke-language=&quot;shell&quot; data-ke-type=&quot;codeblock&quot;&gt;&lt;code&gt;*DOMAIN
webtob


*NODE
webtob1         WEBTOBDIR=&quot;$WEBTOB_HOME&quot;,
                SHMKEY = 54000,
                DOCROOT=&quot;docs&quot;,
                PORT = &quot;80&quot;,
                HTH = 1,
                #Group = &quot;nobody&quot;,
                #User = &quot;nobody&quot;,
                NODENAME = &quot;$NODENAME&quot;,
                ERRORDOCUMENT = &quot;503&quot;,
                #Options=&quot;IgnoreExpect100Continue&quot;,
                JSVPORT = 9900,
                LOGGING = &quot;log1&quot;,
                ERRORLOG = &quot;log2&quot;,
                SYSLOG = &quot;syslog&quot;,
                ServiceOrder=&quot;ext,uri&quot;

*HTH_THREAD
hth_worker
                  SendfileThreads = 4,
                  AccessLogThread = Y,
                  #ReadBufSize=1048576, #1M
                  #HtmlsCompression=&quot;text/html&quot;,
                  #SendfileThreshold=32768,
                  WorkerThreads=8

*SVRGROUP
htmlg           SVRTYPE = HTML
jsvg            SVRTYPE = JSV

*SERVER
MyGroup         SVGNAME = jsvg, MinProc = 2, MaxProc = 1000, ASQCount = 1


*URI
uri             Uri = &quot;/&quot;,   Svrtype = JSV

*LOGGING
syslog          Format = &quot;SYSLOG&quot;, FileName = &quot;log/system.log_%M%%D%%Y%&quot;,
                        Option = &quot;sync&quot;
log1            Format = &quot;DEFAULT&quot;, FileName = &quot;log/access.log_%M%%D%%Y%&quot;,
                        Option = &quot;sync&quot;
log2            Format = &quot;ERROR&quot;, FileName = &quot;log/error.log_%M%%D%%Y%&quot;,
                        Option = &quot;sync&quot;

*ERRORDOCUMENT
503                     status = 503,
                        url = &quot;/503.html&quot;


*EXT
htm             MimeType = &quot;text/html&quot;, SvrType = HTML
html            MimeType = &quot;text/html&quot;, SvrType = HTML
jsp             Mimetype = &quot;application/jsp&quot;,  Svrtype=JSV
gif             MimeType = &quot;image/gif&quot;, SvrType = HTML
jpeg            MimeType = &quot;image/jpeg&quot;, SvrType = HTML
jpg             MimeType = &quot;image/jpeg&quot;, SvrType = HTML
js              MimeType = &quot;application/x-javascript&quot;, SvrType = HTML
css             MimeType = &quot;text/css&quot;, SvrType = HTML
swf             MimeType = &quot;application/octet-stream&quot;, SvrType = HTML
hwp             MimeType = &quot;application/octet-stream&quot;, SvrType = HTML
doc             MimeType = &quot;application/msword&quot;, SvrType = HTML
ppt             MimeType = &quot;application/vns.ms-powerpoint&quot;, SvrType = HTML&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;- DOCROOT : 리소스 제공 기본 경로&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;- PORT : webtob listener 포트&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;span&gt;- LOGGING : &lt;/span&gt;로그 파일 설정 정보&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;이제 기본적인 설정파일은 끝났다.&lt;/p&gt;
&lt;h3 data-ke-size=&quot;size23&quot;&gt;Dockerfile&lt;/h3&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;웹사이트에서 사용할 정적리소스 파일, 내가 설정한 http.m 파일을 넣어주면 끝난다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;추가로 yum 을 통해 디버깅에 필요한 패키지를 설치할 수 있다.&amp;nbsp;&lt;/p&gt;
&lt;pre id=&quot;code_1672366704826&quot; class=&quot;dockerfile&quot; data-ke-language=&quot;highlight.js&quot; data-ke-type=&quot;codeblock&quot;&gt;&lt;code&gt;FROM tmaxsoftofficial/webtob:5.0.0.2.217.41.3.2

RUN yum update
RUN yum install -y vim net-tools curl

COPY ./styles /home/webtob/docs/
COPY ./http.m /home/webtob/config/http.m&lt;/code&gt;&lt;/pre&gt;
&lt;h3 data-ke-size=&quot;size23&quot;&gt;docker build &amp;amp; run&lt;/h3&gt;
&lt;p&gt;&lt;figure class=&quot;imageblock alignCenter&quot; data-ke-mobileStyle=&quot;widthOrigin&quot; data-origin-width=&quot;707&quot; data-origin-height=&quot;198&quot;&gt;&lt;span data-url=&quot;https://blog.kakaocdn.net/dn/czczbt/btrUYpRg4yH/8KXLzvnYSj1B7vsuBs8qXK/img.png&quot; data-phocus=&quot;https://blog.kakaocdn.net/dn/czczbt/btrUYpRg4yH/8KXLzvnYSj1B7vsuBs8qXK/img.png&quot; data-alt=&quot;현재 작업한 위치의 파일리스트&quot;&gt;&lt;img src=&quot;https://blog.kakaocdn.net/dn/czczbt/btrUYpRg4yH/8KXLzvnYSj1B7vsuBs8qXK/img.png&quot; srcset=&quot;https://img1.daumcdn.net/thumb/R1280x0/?scode=mtistory2&amp;fname=https%3A%2F%2Fblog.kakaocdn.net%2Fdn%2Fczczbt%2FbtrUYpRg4yH%2F8KXLzvnYSj1B7vsuBs8qXK%2Fimg.png&quot; onerror=&quot;this.onerror=null; this.src='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png'; this.srcset='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png';&quot; loading=&quot;lazy&quot; width=&quot;707&quot; height=&quot;198&quot; data-origin-width=&quot;707&quot; data-origin-height=&quot;198&quot;/&gt;&lt;/span&gt;&lt;figcaption&gt;현재 작업한 위치의 파일리스트&lt;/figcaption&gt;
&lt;/figure&gt;
&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;도커이미지를 생성하고, 서비스포트 80과 jsv포트 9900을 포트포워딩 해주자.&lt;/p&gt;
&lt;pre id=&quot;code_1672366814963&quot; class=&quot;shell&quot; data-ke-language=&quot;shell&quot; data-ke-type=&quot;codeblock&quot;&gt;&lt;code&gt;docker build -t webtob-my:0.1 .
docker run -d -p 80:80 -p 9900:9900 --name webtob webtob-my:0.1
docker logs -f webtob&lt;/code&gt;&lt;/pre&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;h2 data-ke-size=&quot;size26&quot;&gt;Jeus&lt;/h2&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;Jeus(제우스)는 티맥스소프트에서 제공하는 유료 와스(WAS) 이다.&lt;/p&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;WebSphere&lt;/b&gt;를 1달정도 사수를 따라다니며 유지보수를 했던 기억이 있는데, 유료 Was 솔루션들은 &lt;b&gt;모두 비슷한 포맷으로 운용&lt;/b&gt;되는 것 같다. 당시에도 &lt;u&gt;WebSphere 내부에서 서버/애플리케이션 등의 개념&lt;/u&gt;으로 &lt;b&gt;여러 서비스를 올리기 위해 분리&lt;/b&gt;해둔 개념이 있었고, &lt;u&gt;제우스도 관리서버, 노드서버, 매니지드서버&lt;/u&gt;의 명칭으로 사용되고 있었다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;참고로 제우스에는 자체적으로 웹서버가 있다고 하지만, 결국 성능적 최적화를 위해 별도의 웹서버 분리를 지향한다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;h3 data-ke-size=&quot;size23&quot;&gt;클라우드 아키텍처&lt;/h3&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;먼저 TmaxSoft에서 제공하고 있는 클라우드 환경에서의 아키텍처를 보자.&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;1785&quot; data-origin-height=&quot;738&quot;&gt;&lt;span data-url=&quot;https://blog.kakaocdn.net/dn/HZpFU/btrURo0KeVc/Mq3uCYjdt6S9JWCcsbako0/img.jpg&quot; data-phocus=&quot;https://blog.kakaocdn.net/dn/HZpFU/btrURo0KeVc/Mq3uCYjdt6S9JWCcsbako0/img.jpg&quot; data-alt=&quot;Jeus 클라우드 아키텍처&quot;&gt;&lt;img src=&quot;https://blog.kakaocdn.net/dn/HZpFU/btrURo0KeVc/Mq3uCYjdt6S9JWCcsbako0/img.jpg&quot; srcset=&quot;https://img1.daumcdn.net/thumb/R1280x0/?scode=mtistory2&amp;fname=https%3A%2F%2Fblog.kakaocdn.net%2Fdn%2FHZpFU%2FbtrURo0KeVc%2FMq3uCYjdt6S9JWCcsbako0%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;1785&quot; height=&quot;738&quot; data-origin-width=&quot;1785&quot; data-origin-height=&quot;738&quot;/&gt;&lt;/span&gt;&lt;figcaption&gt;Jeus 클라우드 아키텍처&lt;/figcaption&gt;
&lt;/figure&gt;
&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;DAS(Domain Admin Server)라 불리는 관리서버가 각각의 애플리케이션인 매니지드서버를 총 관리하고 있었는데,&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;독립적인 os를 가지는 클라우드환경에서의 오토 스케일링 때문에 관리서버가 필요없게 되었고, 이로인해 모든 도메인은 별도 네이밍으로 운영되야한다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;h3 data-ke-size=&quot;size23&quot;&gt;기동 프로세스 분석&lt;/h3&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;jeus 컨테이너를 기동한 뒤 ps -ef 로 프로세스를 분석해보았다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;{homeDirectory}/script/&lt;b&gt;start.sh&lt;/b&gt; 를 실행하는데,&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;이때 &lt;b&gt;env.sh&lt;/b&gt;을 실행해 시스템변수들을 체크하여 &lt;b&gt;domain.xml&lt;/b&gt; 파일의 내용을 변경한다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;그리고 /root/jeus8/bin/&lt;b&gt;startCloudServer&amp;nbsp;&lt;/b&gt;파일을 실행하여 서비스를 기동하게 된다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;위 내용을 기준으로 우리가 해야할 작업은 아래와 같다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;- 첫번째, 환경변수를 세팅&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;- 두번째, domain.xml 에 webtob 연동내용 삽입&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;- 세번째, domain.xml 에 배포할 애플리케이션 정보&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;h3 data-ke-size=&quot;size23&quot;&gt;환경변수세팅&lt;/h3&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;프로세스 분석 시 env.sh 을 보았는데 아래내용을 참고해보자.&lt;/p&gt;
&lt;pre id=&quot;code_1672368176636&quot; class=&quot;bash&quot; data-ke-language=&quot;bash&quot; data-ke-type=&quot;codeblock&quot;&gt;&lt;code&gt;#!/bin/bash

export PATH=&quot;$JEUS_HOME/bin:$JEUS_HOME/lib:$PATH&quot;

# JEUS properties option(ex: -Djeus.scf.group-id=prozone-1 #######
ADD_START_OPT=&quot;${ADD_START_OPT}&quot;
export ADD_START_OPT

# Set SCF ID NAME ################################################
if [ -z $SCF_ID ]; then SCF_ID=&quot;prozone-1&quot;; fi
SCF_GROUP_ID=&quot;-Djeus.scf.group-id=${SCF_ID}&quot;
export SCF_ID
export SCF_GROUP_ID
##################################################################

# Set JVM-Option #################################################
export JAVA_VM_PROPERTIES=&quot;${JAVA_VM_PROPERTIES} -Xms1024m -Xmx1024m &quot;
##################################################################

# DOMAIN ID set ##################################################
#BASE_ADDRESS=`grep &quot;${HOSTNAME}&quot; /etc/hosts | awk '{print $1}'`
if [ -z $BASE_ADDRESS ]; then export BASE_ADDRESS=0.0.0.0; fi
sed -i &quot;s/%BASE_ADDRESS%/${BASE_ADDRESS}/g&quot; ${DOMAIN_HOME}/config/domain.xml

if [ -z $SERVERNAME ]; then export SERVERNAME=`hostname`; fi
sed -i &quot;s/%SERVERNAME%/${SERVERNAME}/g&quot; ${DOMAIN_HOME}/config/domain.xml

if [ -z $BASE_PORT ]; then export BASE_PORT=9736; fi
sed -i &quot;s/%BASE_PORT%/${BASE_PORT}/g&quot; ${DOMAIN_HOME}/config/domain.xml

if [ -z $HTTP_PORT ]; then export HTTP_PORT=8080; fi
sed -i &quot;s/%HTTP_PORT%/${HTTP_PORT}/g&quot; ${DOMAIN_HOME}/config/domain.xml

if [ -z $HTTP_THREAD_MIN ]; then export HTTP_THREAD_MIN=10; fi
sed -i &quot;s/%HTTP_THREAD_MIN%/${HTTP_THREAD_MIN}/g&quot; ${DOMAIN_HOME}/config/domain.xml

if [ -z $HTTP_THREAD_MAX ]; then export HTTP_THREAD_MAX=20; fi
sed -i &quot;s/%HTTP_THREAD_MAX%/${HTTP_THREAD_MAX}/g&quot; ${DOMAIN_HOME}/config/domain.xml

if [ -z $APP_DIR ]; then export APP_DIR=&quot;${INSTALL_HOME}/app&quot;; fi
sed -i &quot;s+%APP_DIR%+${APP_DIR}+g&quot; ${DOMAIN_HOME}/config/domain.xml

if [ -z $APP_NAME_1 ]; then export APP_NAME_1=examples; fi
sed -i &quot;s/%APP_NAME_1%/${APP_NAME_1}/g&quot; ${DOMAIN_HOME}/config/domain.xml

if [ -z $APPLICATION_PATH_1 ]; then export APPLICATION_PATH_1=examples.ear; fi
sed -i &quot;s/%APPLICATION_PATH_1%/${APPLICATION_PATH_1}/g&quot; ${DOMAIN_HOME}/config/domain.xml

if [ -z $APP_TYPE_1 ]; then export APP_TYPE_1=EAR; fi
sed -i &quot;s/%APP_TYPE_1%/${APP_TYPE_1}/g&quot; ${DOMAIN_HOME}/config/domain.xml

if [ -z $NODE_JAVA_1 ]; then export NODE_JAVA_1=false; fi
sed -i &quot;s/%NODE_JAVA_1%/${NODE_JAVA_1}/g&quot; ${DOMAIN_HOME}/config/domain.xml

# JEUS admin IP/PW
if [ -z $JEUS_USER ]; then export JEUS_USER=jeus; fi
if [ -z $PASSWORD ]; then export PASSWORD=jeus; fi&lt;/code&gt;&lt;/pre&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;내용은 아주 쉬웠다. 환경변수가 없으면 default 값을 세팅하고 domain.xml에 문자열을 치환하는 것.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;필자는 여기서 애플리케이션 정보 관련 정보를 docker run 에서 넣어줄 것이다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;- APP_NAME_1&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;- APPLICATION_PATH_1&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;- APP_TYPE_1&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;h3 data-ke-size=&quot;size23&quot;&gt;domain.xml&lt;/h3&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;해당 파일의 위치는 /root/jeus8/domains/domain1/config/&lt;b&gt;domain.xml&lt;/b&gt; 이다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;스크립트가 돌기전 파일을 추출하기 위해 sleep 명령어를 준 상태로 이미지를 기동하여 파일을 가져왔다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;h4 data-ke-size=&quot;size20&quot;&gt;Webtob Listener&lt;/h4&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;이제 단순한 xml 작업인데, jeus domain.xml 을 검색해보면 어떤 태그가 존재하는지 설명이 나오니 참고바란다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;servers&amp;gt;server&amp;gt;web-engine 태그 하위에 &lt;b&gt;webtob-connector&lt;/b&gt; 라는 태그를 작성하여 webtob 에서 설정한 jsv에 관한 내용을 작성할 것이다. (필자는 DAS화면에서 직접 webtob-connector를 만들어 생성된 xml을 이용하였다)&lt;/p&gt;
&lt;pre id=&quot;code_1672368722483&quot; class=&quot;xml&quot; data-ke-language=&quot;highlight.js&quot; data-ke-type=&quot;codeblock&quot;&gt;&lt;code&gt;               &amp;lt;webtob-connector&amp;gt;
                  &amp;lt;name&amp;gt;my-webtob&amp;lt;/name&amp;gt;
                  &amp;lt;postdata-read-timeout&amp;gt;30000&amp;lt;/postdata-read-timeout&amp;gt;
                  &amp;lt;max-post-size&amp;gt;-1&amp;lt;/max-post-size&amp;gt;
                  &amp;lt;max-parameter-count&amp;gt;-1&amp;lt;/max-parameter-count&amp;gt;
                  &amp;lt;max-header-count&amp;gt;-1&amp;lt;/max-header-count&amp;gt;
                  &amp;lt;max-header-size&amp;gt;-1&amp;lt;/max-header-size&amp;gt;
                  &amp;lt;max-querystring-size&amp;gt;8192&amp;lt;/max-querystring-size&amp;gt;
                  &amp;lt;wjp-version&amp;gt;2&amp;lt;/wjp-version&amp;gt;
                  &amp;lt;registration-id&amp;gt;MyGroup&amp;lt;/registration-id&amp;gt;
                  &amp;lt;network-address&amp;gt;
                     &amp;lt;port&amp;gt;9900&amp;lt;/port&amp;gt;
                     &amp;lt;ip-address&amp;gt;20.41.116.235&amp;lt;/ip-address&amp;gt;
                  &amp;lt;/network-address&amp;gt;
                  &amp;lt;thread-pool&amp;gt;
                     &amp;lt;number&amp;gt;1000&amp;lt;/number&amp;gt;
                     &amp;lt;thread-state-notify&amp;gt;
                        &amp;lt;max-thread-active-time&amp;gt;0&amp;lt;/max-thread-active-time&amp;gt;
                        &amp;lt;interrupt-thread&amp;gt;false&amp;lt;/interrupt-thread&amp;gt;
                        &amp;lt;active-timeout-notification&amp;gt;false&amp;lt;/active-timeout-notification&amp;gt;
                        &amp;lt;notify-threshold-ratio&amp;gt;0.0&amp;lt;/notify-threshold-ratio&amp;gt;
                        &amp;lt;restart-threshold-ratio&amp;gt;0.0&amp;lt;/restart-threshold-ratio&amp;gt;
                     &amp;lt;/thread-state-notify&amp;gt;
                  &amp;lt;/thread-pool&amp;gt;
                  &amp;lt;hth-count&amp;gt;1&amp;lt;/hth-count&amp;gt;
                  &amp;lt;request-prefetch&amp;gt;false&amp;lt;/request-prefetch&amp;gt;
                  &amp;lt;read-timeout&amp;gt;120000&amp;lt;/read-timeout&amp;gt;
                  &amp;lt;reconnect-interval&amp;gt;5000&amp;lt;/reconnect-interval&amp;gt;
                  &amp;lt;reconnect-count-for-backup&amp;gt;12&amp;lt;/reconnect-count-for-backup&amp;gt;
               &amp;lt;/webtob-connector&amp;gt;&lt;/code&gt;&lt;/pre&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;webtob *SERVER에 정의 하였던 MyGroup 을 &lt;b&gt;registration-id&lt;/b&gt; 태그에 넣어주고,&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;접근 포트와 webtob ip를 작성해주면 완성된다.&lt;/p&gt;
&lt;h4 data-ke-size=&quot;size20&quot;&gt;deployed-application&lt;/h4&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;이부분은 기본적으로 시스템변수로 변경이 되는데,&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;배포되는 애플리케이션의 root context가 기본으로 /{app name}으로 설정되어 '/' 로 변경하는 태그를 넣어주었다.&lt;/p&gt;
&lt;pre id=&quot;code_1672368950071&quot; class=&quot;xml&quot; data-ke-language=&quot;highlight.js&quot; data-ke-type=&quot;codeblock&quot;&gt;&lt;code&gt;    &amp;lt;deployed-applications&amp;gt;
        &amp;lt;deployed-application&amp;gt;
        	...
            &amp;lt;context-path&amp;gt;/&amp;lt;/context-path&amp;gt;
            ...
        &amp;lt;/deployed-application&amp;gt;
    &amp;lt;/deployed-applications&amp;gt;&lt;/code&gt;&lt;/pre&gt;
&lt;h3 data-ke-size=&quot;size23&quot;&gt;Dockerfile&lt;/h3&gt;
&lt;pre id=&quot;code_1672369040568&quot; class=&quot;dockerfile&quot; data-ke-language=&quot;highlight.js&quot; data-ke-type=&quot;codeblock&quot;&gt;&lt;code&gt;FROM tmaxsoftofficial/jeus:8.1.105067.1-jdk8-openjdk-ubuntu

RUN apt update
RUN apt install -y vim net-tools curl

COPY ./web-0.0.1-SNAPSHOT.war /root/app
COPY ./new-domain.xml /root/jeus8/domains/domain1/config/domain.xml&lt;/code&gt;&lt;/pre&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;필요한 패키지를 apt install을 통해 다운하고,&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;배포할 애플리케이션을 deploy 경로에, 작성한 domain.xml 파일을 복사한다.&lt;/p&gt;
&lt;h3 data-ke-size=&quot;size23&quot;&gt;docker build &amp;amp; run&lt;/h3&gt;
&lt;p&gt;&lt;figure class=&quot;imageblock alignCenter&quot; data-ke-mobileStyle=&quot;widthOrigin&quot; data-origin-width=&quot;941&quot; data-origin-height=&quot;227&quot;&gt;&lt;span data-url=&quot;https://blog.kakaocdn.net/dn/cJkNjn/btrUZXGZxOY/xIyTDslUX785kWTF2sCio0/img.png&quot; data-phocus=&quot;https://blog.kakaocdn.net/dn/cJkNjn/btrUZXGZxOY/xIyTDslUX785kWTF2sCio0/img.png&quot; data-alt=&quot;Jeus 작업디렉토리 파일정보&quot;&gt;&lt;img src=&quot;https://blog.kakaocdn.net/dn/cJkNjn/btrUZXGZxOY/xIyTDslUX785kWTF2sCio0/img.png&quot; srcset=&quot;https://img1.daumcdn.net/thumb/R1280x0/?scode=mtistory2&amp;fname=https%3A%2F%2Fblog.kakaocdn.net%2Fdn%2FcJkNjn%2FbtrUZXGZxOY%2FxIyTDslUX785kWTF2sCio0%2Fimg.png&quot; onerror=&quot;this.onerror=null; this.src='//t1.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;619&quot; height=&quot;149&quot; data-origin-width=&quot;941&quot; data-origin-height=&quot;227&quot;/&gt;&lt;/span&gt;&lt;figcaption&gt;Jeus 작업디렉토리 파일정보&lt;/figcaption&gt;
&lt;/figure&gt;
&lt;/p&gt;
&lt;pre id=&quot;code_1672369221973&quot; class=&quot;bash&quot; data-ke-language=&quot;bash&quot; data-ke-type=&quot;codeblock&quot;&gt;&lt;code&gt;docker build -t jeus-my:0.1
docker run -d  -p 8080:8080 -p 9736:9736 \
 -e APP_NAME_1=web-0.0.1-SNAPSHOT \
 -e APPLICATION_PATH_1=web-0.0.1-SNAPSHOT.war \
 -e APP_TYPE_1=WAR \
 --name jeus-my jeus-my:0.1

docker logs &amp;ndash;f jeus-my&lt;/code&gt;&lt;/pre&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;9736 포트는 admin 콘솔인데 UI로 확인차 열어주었고, 앞서말한 환경변수를 다음과 같이 세팅해주었다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;h2 data-ke-size=&quot;size26&quot;&gt;테스트&lt;/h2&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;이제 모든 설정이 완료되었다&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;webtob 80 port '/'로 접근하면 jsv 설정을 따라 jeus서비스를 연결하여 webtob를 통해 client가 응답을 받게 된다.&lt;/p&gt;
&lt;p&gt;&lt;figure class=&quot;imageblock alignCenter&quot; data-ke-mobileStyle=&quot;widthOrigin&quot; data-origin-width=&quot;727&quot; data-origin-height=&quot;311&quot;&gt;&lt;span data-url=&quot;https://blog.kakaocdn.net/dn/be0nUR/btrU0tTloHq/p4vMhsFDHHu8mKFireick0/img.png&quot; data-phocus=&quot;https://blog.kakaocdn.net/dn/be0nUR/btrU0tTloHq/p4vMhsFDHHu8mKFireick0/img.png&quot;&gt;&lt;img src=&quot;https://blog.kakaocdn.net/dn/be0nUR/btrU0tTloHq/p4vMhsFDHHu8mKFireick0/img.png&quot; srcset=&quot;https://img1.daumcdn.net/thumb/R1280x0/?scode=mtistory2&amp;fname=https%3A%2F%2Fblog.kakaocdn.net%2Fdn%2Fbe0nUR%2FbtrU0tTloHq%2Fp4vMhsFDHHu8mKFireick0%2Fimg.png&quot; onerror=&quot;this.onerror=null; this.src='//t1.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;727&quot; height=&quot;311&quot; data-origin-width=&quot;727&quot; data-origin-height=&quot;311&quot;/&gt;&lt;/span&gt;&lt;/figure&gt;
&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;이 때 webtob access log를 확인하면 아래와 같이 &lt;b&gt;.css&lt;/b&gt; 파일은 webtob로 요청이 오게된다.&lt;/p&gt;
&lt;p&gt;&lt;figure class=&quot;imageblock alignCenter&quot; data-ke-mobileStyle=&quot;widthOrigin&quot; data-origin-width=&quot;767&quot; data-origin-height=&quot;36&quot;&gt;&lt;span data-url=&quot;https://blog.kakaocdn.net/dn/8aDlt/btrU0udEdMb/GfvQ30R0o1F6fPLC133tAK/img.png&quot; data-phocus=&quot;https://blog.kakaocdn.net/dn/8aDlt/btrU0udEdMb/GfvQ30R0o1F6fPLC133tAK/img.png&quot;&gt;&lt;img src=&quot;https://blog.kakaocdn.net/dn/8aDlt/btrU0udEdMb/GfvQ30R0o1F6fPLC133tAK/img.png&quot; srcset=&quot;https://img1.daumcdn.net/thumb/R1280x0/?scode=mtistory2&amp;fname=https%3A%2F%2Fblog.kakaocdn.net%2Fdn%2F8aDlt%2FbtrU0udEdMb%2FGfvQ30R0o1F6fPLC133tAK%2Fimg.png&quot; onerror=&quot;this.onerror=null; this.src='//t1.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;767&quot; height=&quot;36&quot; data-origin-width=&quot;767&quot; data-origin-height=&quot;36&quot;/&gt;&lt;/span&gt;&lt;/figure&gt;
&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;확실한 테스트를 위해 해당 파일을 없애도 보고(Status Code 404)&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;해당파일 내용을 변경해가며 페이지 스타일의 변경을 확인하였다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;h2 data-ke-size=&quot;size26&quot;&gt;마치며&lt;/h2&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;전통적인 3Tier 구조는 MSA 아키텍처가 생기며 점차 사용하지 않고 관심도 없었는데, 아직도 3Tier 구조를 사용하는 수많은 기업들이 남아있다. 뭐 고객사는 대부분 UNIX 빵빵한 서버에 WEB-WAS로 거의죽지않는 서비스를 하고있으니, ㅋㅋㅋ 그리고 대고객서비스가 아닌 이용자가 많지 않은 서비스는 필요가 없겠지만... 그래도 난 컨테이너화된 쿠버네티스가 너무 재밌고 좋다.&amp;nbsp;&lt;/p&gt;
&lt;figure contenteditable=&quot;false&quot; data-ke-type=&quot;emoticon&quot; data-ke-align=&quot;alignCenter&quot; data-emoticon-type=&quot;friends2&quot; data-emoticon-name=&quot;016&quot; data-emoticon-isanimation=&quot;false&quot; data-emoticon-src=&quot;https://t1.daumcdn.net/keditor/emoticon/friends2/large/016.png&quot;&gt;&lt;img src=&quot;https://t1.daumcdn.net/keditor/emoticon/friends2/large/016.png&quot; width=&quot;150&quot; /&gt;&lt;/figure&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;추가로 WAS에서 db connection을 관리하는 설정도 있는데 커뮤니케이션이 어떻게 될지 모르겠으나 나중에 해봐야겠다.&lt;/p&gt;</description>
      <category>엔지니어링/CI-CD</category>
      <category>Container</category>
      <category>docker</category>
      <category>IMAGE</category>
      <category>jeus</category>
      <category>webtob</category>
      <author>joon95</author>
      <guid isPermaLink="true">https://flowlog.tistory.com/101</guid>
      <comments>https://flowlog.tistory.com/101#entry101comment</comments>
      <pubDate>Fri, 30 Dec 2022 12:24:34 +0900</pubDate>
    </item>
    <item>
      <title>[MSA] SAGA pattern</title>
      <link>https://flowlog.tistory.com/100</link>
      <description>&lt;p data-ke-size=&quot;size16&quot;&gt;지난번 MSA 포스팅에 사용한 Outbox pattern글에 이어 SAGA pattern을 작성하려한다.&lt;/p&gt;
&lt;figure id=&quot;og_1671412515059&quot; contenteditable=&quot;false&quot; data-ke-type=&quot;opengraph&quot; data-ke-align=&quot;alignCenter&quot; data-og-type=&quot;article&quot; data-og-title=&quot;MSA : Outbox Pattern&quot; data-og-description=&quot;MSA 아키텍처? 마이크로서비스아키텍처에 대한 화두가 널리 퍼진지 한 8년정도 된 것 같다. 대학교 1학년 때(2014년도)에 쿠버네티스에 대해 알게되어 도커컨테이너에 대해 공부했었던 기억이 있&quot; data-og-host=&quot;flowlog.tistory.com&quot; data-og-source-url=&quot;https://flowlog.tistory.com/91&quot; data-og-url=&quot;https://flowlog.tistory.com/91&quot; data-og-image=&quot;https://scrap.kakaocdn.net/dn/bw2HB6/hyQWsD2mow/ADIfkk9usaNieH6zhOZwy1/img.jpg?width=360&amp;amp;height=360&amp;amp;face=0_0_360_360,https://scrap.kakaocdn.net/dn/bYT02p/hyQWAB5zwd/ynMcgdXkOncIp3KnH0S831/img.jpg?width=360&amp;amp;height=360&amp;amp;face=0_0_360_360,https://scrap.kakaocdn.net/dn/w4bAC/hyQWH2hxkL/bONtxIoioBwViYTUKGmgdk/img.png?width=1484&amp;amp;height=881&amp;amp;face=0_0_1484_881&quot;&gt;&lt;a href=&quot;https://flowlog.tistory.com/91&quot; target=&quot;_blank&quot; rel=&quot;noopener&quot; data-source-url=&quot;https://flowlog.tistory.com/91&quot;&gt;
&lt;div class=&quot;og-image&quot; style=&quot;background-image: url('https://scrap.kakaocdn.net/dn/bw2HB6/hyQWsD2mow/ADIfkk9usaNieH6zhOZwy1/img.jpg?width=360&amp;amp;height=360&amp;amp;face=0_0_360_360,https://scrap.kakaocdn.net/dn/bYT02p/hyQWAB5zwd/ynMcgdXkOncIp3KnH0S831/img.jpg?width=360&amp;amp;height=360&amp;amp;face=0_0_360_360,https://scrap.kakaocdn.net/dn/w4bAC/hyQWH2hxkL/bONtxIoioBwViYTUKGmgdk/img.png?width=1484&amp;amp;height=881&amp;amp;face=0_0_1484_881');&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;MSA : Outbox Pattern&lt;/p&gt;
&lt;p class=&quot;og-desc&quot; data-ke-size=&quot;size16&quot;&gt;MSA 아키텍처? 마이크로서비스아키텍처에 대한 화두가 널리 퍼진지 한 8년정도 된 것 같다. 대학교 1학년 때(2014년도)에 쿠버네티스에 대해 알게되어 도커컨테이너에 대해 공부했었던 기억이 있&lt;/p&gt;
&lt;p class=&quot;og-host&quot; data-ke-size=&quot;size16&quot;&gt;flowlog.tistory.com&lt;/p&gt;
&lt;/div&gt;
&lt;/a&gt;&lt;/figure&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;h2 data-ke-size=&quot;size26&quot;&gt;Saga패턴&lt;/h2&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;Saga 패턴은 간단히 말해 이벤트에 대한 &lt;b&gt;보상트랜잭션&lt;/b&gt;을 발행할 수 있는 것이다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;가장 흔한 예로 &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;p data-ke-size=&quot;size16&quot;&gt;이 때,&amp;nbsp;기존 프로세스라면 재고-&amp;gt;주문-&amp;gt;결제를 정상적으로 수행하는데 만약 결제에서 잔액부족의 이유로 결제취소가 이루어진다면 해당 주문건에 대한 트랜잭션 롤백을 할 수 없게 된다. 재고는 이미 -1 상태이고, 주문도 정상적으로 들어가고, 결제쪽에서만 실패인 상황. 이렇게되면 이미 재고가 -1 상태임으로 주문실패건에 대한 회복이 불가하기 떄문에 &lt;span&gt;다른 사용자마저 구매를 할 수 없게 된다. &lt;/span&gt;&lt;span&gt;이를 개선하고자 MSA 에서 saga패턴 개념이 들어온 것 같다.&lt;/span&gt;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;span&gt;위 프로세스에서 &lt;b&gt;saga패턴&lt;/b&gt;이 들어간다면 &lt;b&gt;재고&lt;/b&gt;-&lt;b&gt;주문&lt;/b&gt;-&lt;b&gt;결제&lt;/b&gt; 라는 하위 이벤트는 각 서비스가 되어 상태를 메지시큐(kafka)에 저장하여 주문에 대한 롤백이 가능하게 된다.&lt;/span&gt;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;span&gt;Saga 패턴에는 2가지 형식이 존재한다.&lt;/span&gt;&lt;span&gt;&lt;/span&gt;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;span&gt;- Choreograph(코레오그래피) : 각 서비스들은 개별 event Queue 를 사용하는 형태&lt;/span&gt;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;span&gt;- Orchestration(오케스트레이션) : 중앙관리 별도 서비스(app)를 통해 2개의 event Queue를 사용하는 형태&lt;/span&gt;&lt;span&gt;&lt;/span&gt;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;span&gt;더 상세하게 알아보자.&lt;/span&gt;&lt;/p&gt;
&lt;h3 data-ke-size=&quot;size23&quot;&gt;&lt;span&gt;Choreograph (코레오그래피)&lt;/span&gt;&lt;/h3&gt;
&lt;p&gt;&lt;figure class=&quot;imageblock alignCenter&quot; data-ke-mobileStyle=&quot;widthOrigin&quot; data-origin-width=&quot;1673&quot; data-origin-height=&quot;733&quot;&gt;&lt;span data-url=&quot;https://blog.kakaocdn.net/dn/beQlNL/btrTRpNvvbt/X2Nr2oNo46VKR7SFnCvgs1/img.png&quot; data-phocus=&quot;https://blog.kakaocdn.net/dn/beQlNL/btrTRpNvvbt/X2Nr2oNo46VKR7SFnCvgs1/img.png&quot; data-alt=&quot;Sagapattern Choreograph&quot;&gt;&lt;img src=&quot;https://blog.kakaocdn.net/dn/beQlNL/btrTRpNvvbt/X2Nr2oNo46VKR7SFnCvgs1/img.png&quot; srcset=&quot;https://img1.daumcdn.net/thumb/R1280x0/?scode=mtistory2&amp;fname=https%3A%2F%2Fblog.kakaocdn.net%2Fdn%2FbeQlNL%2FbtrTRpNvvbt%2FX2Nr2oNo46VKR7SFnCvgs1%2Fimg.png&quot; onerror=&quot;this.onerror=null; this.src='//t1.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;1673&quot; height=&quot;733&quot; data-origin-width=&quot;1673&quot; data-origin-height=&quot;733&quot;/&gt;&lt;/span&gt;&lt;figcaption&gt;Sagapattern Choreograph&lt;/figcaption&gt;
&lt;/figure&gt;
&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;span&gt;코레오그래피 방식은 위에서 설명한대로 마이크로서비스가 &lt;span&gt;각각의&lt;span&gt; &lt;/span&gt;&lt;/span&gt;event를 가지게 되어 &lt;/span&gt;&lt;span&gt;kafka의 &lt;b&gt;Topic 관리포인트가 매우 많아지게&amp;nbsp;&lt;/b&gt;되므로 운영자는 메시지큐 솔루션의 이해가 많아야 한다. 그리고 무조건 kafka를 통해 하지만 각 &lt;b&gt;마이크로서비스는 port를 점유하고 있지 않기&lt;/b&gt; 때문에 전체적인 네트워크 관점에서 자유로운(?) 것 같은 느낌이 든다...ㅋㅋㅋ&lt;/span&gt;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;그럼 각 서비스의 역할을 보자.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;b&gt;주문서비스&lt;/b&gt;는 &lt;u&gt;주문이벤트&lt;/u&gt;라는 토픽을 발행한 뒤, 각 마이크로서비스의 event를 listen 하여 주문상태를 체크하게 된다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;span&gt;서비스A,B...은 주문이벤트를 읽어 각 프로세스를 수행한 뒤 각자의 event 토픽에 수행내역을 저장한다.&lt;/span&gt;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;span&gt;추가로 이런 프로세스들이 진행되며 RDBS 보다는 &lt;b&gt;NOSQL&lt;/b&gt;을 사용한 사례가 몇몇 보인다. 이유를 생각해보면 서비스A,B..는 &lt;u&gt;실제 테이블 반영전에 별도의 주문상태를 테이블에 저장&lt;/u&gt;하게 되는데 외부테이블에 종속적인게 아닌 nosql 성향의 데이터를 정의하기 때문이다.&amp;nbsp;&lt;/span&gt;&lt;span&gt;&lt;/span&gt;&lt;span&gt;&lt;/span&gt;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;span&gt;상세한 프로세스를 예를 들어보자. (이해를 돕기위해 Topic 로그를 확인)&lt;/span&gt;&lt;/p&gt;
&lt;h4 data-ke-size=&quot;size20&quot;&gt;&lt;span&gt;프로세스&lt;/span&gt;&lt;/h4&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;1. 주문서비스에서 주문이벤트가 들어온다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;여기서 주문상태(orderStatus)가 최초 생성상태이고, 이후 각서비스들의 토픽상태를 통해 변경된다.&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;- 최초:ORDER_CREATED&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;- 완료:ORDER_COMPLETED&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;- 실패:ORDER_CANCELLED&lt;/p&gt;
&lt;pre id=&quot;code_1671416502228&quot; class=&quot;json&quot; data-ke-language=&quot;highlight.js&quot; data-ke-type=&quot;codeblock&quot;&gt;&lt;code&gt;{
  &quot;eventId&quot;:&quot;9de11486-99c7-450c-b215-885df191f876&quot;,
  &quot;date&quot;:&quot;2022-10-27T01:52:51.461+00:00&quot;,
  &quot;purchaseOrder&quot;:
    {
      &quot;orderId&quot;:&quot;00516b17-63c5-4018-8168-861f23c9cfc4&quot;,
      &quot;productId&quot;:2,
      &quot;price&quot;:200,
      &quot;userId&quot;:1
    },
  &quot;orderStatus&quot;:&quot;ORDER_CREATED&quot;
}&lt;/code&gt;&lt;/pre&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;span&gt;2. 주문이벤트 기다리던 서비스A,B...가 프로세스를 수행 한뒤 각자의 토픽에 데이터를 전송&lt;/span&gt;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;span&gt;인벤토리 프로세스가 &lt;b&gt;정상처리되&lt;/b&gt;었으면 &lt;b&gt;status:RESERVED&lt;/b&gt; 를 리턴한다.&lt;/span&gt;&lt;/p&gt;
&lt;pre id=&quot;code_1671416743534&quot; class=&quot;json&quot; data-ke-language=&quot;highlight.js&quot; data-ke-type=&quot;codeblock&quot;&gt;&lt;code&gt;{
  &quot;eventId&quot;:&quot;39d0c4e1-a79b-4682-bc18-730d32232c9b&quot;,
  &quot;date&quot;:&quot;2022-10-27T01:52:51.490+00:00&quot;,
  &quot;inventory&quot;:
    {
      &quot;orderId&quot;:&quot;00516b17-63c5-4018-8168-861f23c9cfc4&quot;,
      &quot;productId&quot;:2
    },
  &quot;status&quot;:&quot;RESERVED&quot;
}&lt;/code&gt;&lt;/pre&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;결제 프로세스가 만약 &lt;b&gt;비정상처리&lt;/b&gt;되었으면 &lt;b&gt;status:REJECTED&lt;/b&gt; 를 리턴한다.&lt;/p&gt;
&lt;pre id=&quot;code_1671416748492&quot; class=&quot;json&quot; data-ke-language=&quot;highlight.js&quot; data-ke-type=&quot;codeblock&quot;&gt;&lt;code&gt;{
  &quot;eventId&quot;:&quot;50ab7508-0f12-4f3d-9659-6287e82403ba&quot;,
  &quot;date&quot;:&quot;2022-10-27T01:52:51.478+00:00&quot;,
  &quot;payment&quot;:
    {
      &quot;orderId&quot;:&quot;00516b17-63c5-4018-8168-861f23c9cfc4&quot;,
      &quot;userId&quot;:1,
      &quot;amount&quot;:200
    },
  &quot;paymentStatus&quot;:&quot;REJECTED&quot;
}&lt;/code&gt;&lt;/pre&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;3. 각 서비스들의 상태체크 및 롤백&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;모든 마이크로서비스 프로세스의 event 토픽을 기다리고 정상인지 체크하는데 &lt;b&gt;정상일 경우&lt;/b&gt; 주문 상태를 '&lt;b&gt;ORDER_COMPLETED&lt;/b&gt;' 로 변경하게 되고, 위와 같이 특정서비스가 &lt;b&gt;REJECTED&lt;/b&gt; 상태를 내보냈다면 주문상태를 '&lt;b&gt;ORDER_CANCELLED&lt;/b&gt;' 로 변경하여 각 마이크로서비스는 해당 주문건을 rollback 하는 프로세스를 진행하게 된다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;rollback 프로세스는 어렵지 않다. -count 한 내용을 거꾸로 +count 해주면 된다.&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;(필자는 rollback 이라하여 database transaction 발생 이전으로 돌리는 줄알았으나, 샘플코드에서 간단히 update로 구현하여 롤백을 구현하였음)&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;Orchestration (오케스트레이션)&lt;/h3&gt;
&lt;p&gt;&lt;figure class=&quot;imageblock alignCenter&quot; data-ke-mobileStyle=&quot;widthOrigin&quot; data-origin-width=&quot;1432&quot; data-origin-height=&quot;737&quot;&gt;&lt;span data-url=&quot;https://blog.kakaocdn.net/dn/bKGrPQ/btrTR9QXaca/WIAyewWdFlggpg1pHtN9r0/img.png&quot; data-phocus=&quot;https://blog.kakaocdn.net/dn/bKGrPQ/btrTR9QXaca/WIAyewWdFlggpg1pHtN9r0/img.png&quot; data-alt=&quot;Saga Pattern Orchestration&quot;&gt;&lt;img src=&quot;https://blog.kakaocdn.net/dn/bKGrPQ/btrTR9QXaca/WIAyewWdFlggpg1pHtN9r0/img.png&quot; srcset=&quot;https://img1.daumcdn.net/thumb/R1280x0/?scode=mtistory2&amp;fname=https%3A%2F%2Fblog.kakaocdn.net%2Fdn%2FbKGrPQ%2FbtrTR9QXaca%2FWIAyewWdFlggpg1pHtN9r0%2Fimg.png&quot; onerror=&quot;this.onerror=null; this.src='//t1.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;1432&quot; height=&quot;737&quot; data-origin-width=&quot;1432&quot; data-origin-height=&quot;737&quot;/&gt;&lt;/span&gt;&lt;figcaption&gt;Saga Pattern Orchestration&lt;/figcaption&gt;
&lt;/figure&gt;
&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;오케스트레이션 방식은 별도의 관리서비스를 통한다는 부분이 다르다. 코레오그래피와 달리 관리자는 메시지큐(kafka)에서 딱&lt;b&gt; 2개 토픽&lt;/b&gt;만 모니터링하면 되어 &lt;b&gt;관리포인트가 줄어들었고&lt;/b&gt;, 대신 &lt;b&gt;별도의 중앙관리서비스를 직접 구현 및 관리를 필요&lt;/b&gt;로 한다. 이 방식은 아무래도 MSA 아키텍처를 제대로 이해한 AA급의 개발자가 있어야 현실적으로 가능해보인다. 그래도 back-end 서비스는 중앙관리서비스와 tcp 통신을 하기 때문에 기존과 동일한 개발로직을 가져가면 되어 편하다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;h4 data-ke-size=&quot;size20&quot;&gt;프로세스&lt;/h4&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;이전에 설명한 코레오그래피와 &lt;u&gt;다른 점만&lt;/u&gt; 짚고 넘어가보자.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;주문서비스에서 주문 토픽이 생성되면 &lt;b&gt;중앙관리서비스에서 모든 제어를 담당&lt;/b&gt;하게되어 각 백엔드 서비스들에게 tcp 통신을 요청하게 되고 모든 서비스의 상태를 체크한뒤 주문성공여부에 대한 내용을 업데이트한다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;h2 data-ke-size=&quot;size26&quot;&gt;마치며&lt;/h2&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;MSA(마이크로서비스아키텍처)를 설계하며 Saga패턴에 대해 알아보았다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;배달의민족 같은 트래픽이 많은 대고객 서비스에서 충분히 사용되고 있는데, 금융이나 실제 현업의 IT담당자들은 이렇게까지 MSA 구조를 설계하고 수용하려하지 않는 것 같다...&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;서비스가 어마어마하게 커져가는 기업만... 가능할 것 같은(?)&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;뭐, 작은서비스에 이렇게까지 들어가면 오버스펙이겠지만 ㅎㅎㅎㅎ&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;결국 차세대, 차세대해도... 비즈니스를 설계하는 사람이 잘 파악해야 가능할듯&lt;/p&gt;
&lt;figure contenteditable=&quot;false&quot; data-ke-type=&quot;emoticon&quot; data-ke-align=&quot;alignCenter&quot; data-emoticon-type=&quot;niniz&quot; data-emoticon-name=&quot;043&quot; data-emoticon-isanimation=&quot;false&quot; data-emoticon-src=&quot;https://t1.daumcdn.net/keditor/emoticon/niniz/large/043.gif&quot;&gt;&lt;img src=&quot;https://t1.daumcdn.net/keditor/emoticon/niniz/large/043.gif&quot; width=&quot;150&quot; /&gt;&lt;/figure&gt;</description>
      <category>개발/Spring</category>
      <category>aa</category>
      <category>MSA</category>
      <category>SAGA</category>
      <category>마이크로서비스아키텍처</category>
      <author>joon95</author>
      <guid isPermaLink="true">https://flowlog.tistory.com/100</guid>
      <comments>https://flowlog.tistory.com/100#entry100comment</comments>
      <pubDate>Mon, 19 Dec 2022 13:44:57 +0900</pubDate>
    </item>
    <item>
      <title>안녕하세요, 제 블로그 방문을 환영합니다.</title>
      <link>https://flowlog.tistory.com/notice/99</link>
      <description>&lt;p data-ke-size=&quot;size16&quot;&gt;동양미래대학교 컴퓨터소프트웨어공학(학)과 (3+1년) 졸업&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;19년10월~24년12월&amp;nbsp;&lt;b&gt;메타넷티플랫폼&lt;/b&gt;에서 Devops&amp;amp;Develop&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;24년12월~현재&amp;nbsp;&lt;b&gt;메타넷디지털&lt;/b&gt;에서 Develop&amp;amp;Devops&lt;/p&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;- OCP, ARO, NKS, AKS, RKE, kubespray, kubesphere&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;MSA 아키텍처 설계/구현(SpringCloud기반)&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;CI-CD 파트&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;- jenkins, Github Action, AzureDevops Pipeline, ArgoCD, Harbor, ArgoCD, Nexus, Jfrog&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;전체적인 아키텍처를 알고 그릴 줄 아는 것&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;u&gt;&lt;b&gt;&lt;i&gt;마지막으로 제 블로그에 글을 읽고 이상한 부분이나 궁금한 점 &lt;span style=&quot;color: #ee2323;&quot;&gt;댓글로 소통&lt;/span&gt; 부탁드립니다.&lt;/i&gt;&lt;/b&gt;&lt;/u&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;+사내추천 환영입니다. 연락주십쇼_-_(dkttkemf@gmail.com)&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;--- Blog History ---&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;2022.12.14 berry 스킨적용(이상한 현상이 있을 수 있습니다.)&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;2022.08.01 hLLOW 스킨적용&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;2022.07.29 블로그 개설&lt;/p&gt;</description>
      <author>joon95</author>
      <guid isPermaLink="true">https://flowlog.tistory.com/notice/99</guid>
      <pubDate>Wed, 14 Dec 2022 01:21:43 +0900</pubDate>
    </item>
    <item>
      <title>[Springboot] 대용량 Select Query OOM 방지를 위한 스트림</title>
      <link>https://flowlog.tistory.com/98</link>
      <description>&lt;h2 data-ke-size=&quot;size26&quot;&gt;서론&lt;/h2&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;대량의 select 쿼리를 날릴 때 대부분의 개발자들은 Memory 요소를 파악하지 못하는 것 같다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;이번에도 몇십만건의 데이터를 한번에 load 해서 front에 뿌리는 프로세스에서 was가 후두둑 죽어가는게 아닌가.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;이전 프로젝트에서도 배치개발을 하던 과장급 프리랜서가 있었는데,&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;운영에 올리자마자 터지고 난리도 아니였다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;그들의 답변은 항상 &lt;b&gt;'개발환경에선 잘된다, 단지 데이터가 많을 뿐 -&amp;gt; 인프라문제다.'&lt;/b&gt;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;제발 메모리 이슈좀 알아서 해결해라!!!!!!!!!!!!&lt;/p&gt;
&lt;figure contenteditable=&quot;false&quot; data-ke-type=&quot;emoticon&quot; data-ke-align=&quot;alignCenter&quot; data-emoticon-type=&quot;friends1&quot; data-emoticon-name=&quot;040&quot; data-emoticon-isanimation=&quot;false&quot; data-emoticon-src=&quot;https://t1.daumcdn.net/keditor/emoticon/friends1/large/040.gif&quot;&gt;&lt;img src=&quot;https://t1.daumcdn.net/keditor/emoticon/friends1/large/040.gif&quot; width=&quot;150&quot; /&gt;&lt;/figure&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;h2 data-ke-size=&quot;size26&quot;&gt;테스트 목적&lt;/h2&gt;
&lt;blockquote data-ke-style=&quot;style1&quot;&gt;&lt;span style=&quot;font-family: 'Noto Serif KR';&quot;&gt;'직접'해보자&lt;/span&gt;&lt;/blockquote&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;Q. 수십~수백만 건의 데이터를 어떻게 사용자에게 보여줄 것인가???&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;검색해보니 제일먼저 Mybatis fetchSize 조정이 있었다.(default 10)&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;- 만약 1000개의 데이터를 select 한다면 fetchSize만큼 짤라서 함, 1000/10=100번의 db요청.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;- 성능개선이 어마어마하다고 함, 보통 1000 사용하는듯)&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;다음은 Mybatis resultHandler를 이용한 컨트롤 제일 정확한 핸들러인거 같다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;- 추가적으로 excel 파일 생성 시, 이 핸들러로 oom 방지를 많이 한다고 한다.(나중에 해봐야지)&lt;/p&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;Stream 방식이 있다.(본 블로그는 이걸 사용할 것)&lt;/b&gt;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;- Stream 방식은 이전에 tail -f 기능을 springboot로 구현하기 에 포스팅한 SSE와 비슷한 개념인 것 같다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;figure id=&quot;og_1670854733902&quot; contenteditable=&quot;false&quot; data-ke-type=&quot;opengraph&quot; data-ke-align=&quot;alignCenter&quot; data-og-type=&quot;article&quot; data-og-title=&quot;springboot Sse(Server Send Event) 단방향 통신을 이용해 tail -f 기능 구현&quot; data-og-description=&quot;1. 난 이걸 왜 쓰게되었나? OCP 웹콘솔에 보면 pod의 log를 지속적으로 호출하는 페이지가 있는데, 말 그대로 서버가 클라이언트 쪽에 로그를 일방적으로 보내는 방식인거 같았고, 이걸 구현해보고 &quot; data-og-host=&quot;flowlog.tistory.com&quot; data-og-source-url=&quot;https://flowlog.tistory.com/87&quot; data-og-url=&quot;https://flowlog.tistory.com/87&quot; data-og-image=&quot;https://scrap.kakaocdn.net/dn/b27rfN/hyQRRddail/RbRwFJN4hBl2cj85YptNfK/img.png?width=256&amp;amp;height=256&amp;amp;face=0_0_256_256,https://scrap.kakaocdn.net/dn/739Bc/hyQROngqTO/flmgJcoZLy0FW6KsBkI7HK/img.png?width=256&amp;amp;height=256&amp;amp;face=0_0_256_256,https://scrap.kakaocdn.net/dn/cKG1lX/hyQRP7xb2g/5S5WnQlpk3ZLHVWN2ZJOGK/img.png?width=700&amp;amp;height=832&amp;amp;face=0_0_700_832&quot;&gt;&lt;a href=&quot;https://flowlog.tistory.com/87&quot; target=&quot;_blank&quot; rel=&quot;noopener&quot; data-source-url=&quot;https://flowlog.tistory.com/87&quot;&gt;
&lt;div class=&quot;og-image&quot; style=&quot;background-image: url('https://scrap.kakaocdn.net/dn/b27rfN/hyQRRddail/RbRwFJN4hBl2cj85YptNfK/img.png?width=256&amp;amp;height=256&amp;amp;face=0_0_256_256,https://scrap.kakaocdn.net/dn/739Bc/hyQROngqTO/flmgJcoZLy0FW6KsBkI7HK/img.png?width=256&amp;amp;height=256&amp;amp;face=0_0_256_256,https://scrap.kakaocdn.net/dn/cKG1lX/hyQRP7xb2g/5S5WnQlpk3ZLHVWN2ZJOGK/img.png?width=700&amp;amp;height=832&amp;amp;face=0_0_700_832');&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;springboot Sse(Server Send Event) 단방향 통신을 이용해 tail -f 기능 구현&lt;/p&gt;
&lt;p class=&quot;og-desc&quot; data-ke-size=&quot;size16&quot;&gt;1. 난 이걸 왜 쓰게되었나? OCP 웹콘솔에 보면 pod의 log를 지속적으로 호출하는 페이지가 있는데, 말 그대로 서버가 클라이언트 쪽에 로그를 일방적으로 보내는 방식인거 같았고, 이걸 구현해보고&lt;/p&gt;
&lt;p class=&quot;og-host&quot; data-ke-size=&quot;size16&quot;&gt;flowlog.tistory.com&lt;/p&gt;
&lt;/div&gt;
&lt;/a&gt;&lt;/figure&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;sse&amp;nbsp;는 접속한 브라우저가 구독을 해놓아서 구독을 해지할 때까지 지속적으로 데이터를 뿌리는 역할이였지만,&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;여기서 사용할 Stream은 그냥 ResponseBodyEmitter 로 설정한 시간 동안 전송하고 완료처리를 하게 된다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;h2 data-ke-size=&quot;size26&quot;&gt;대용량 데이터 샘플&lt;/h2&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;수백만건 데이터를 어떻게 해볼까 하다가 검색해보니 샘플 데이터가 바로나왔다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;아래 링크에서 다운해서 mysql에 올리면 2,838,426건의 데이터가 있는 테이블이 있다.&lt;/p&gt;
&lt;p&gt;&lt;figure class=&quot;imageblock alignCenter&quot; data-ke-mobileStyle=&quot;widthOrigin&quot; data-origin-width=&quot;236&quot; data-origin-height=&quot;183&quot;&gt;&lt;span data-url=&quot;https://blog.kakaocdn.net/dn/7DQX5/btrTmZaD3E7/kPodn9n8vgseXAKA0Sqlyk/img.png&quot; data-phocus=&quot;https://blog.kakaocdn.net/dn/7DQX5/btrTmZaD3E7/kPodn9n8vgseXAKA0Sqlyk/img.png&quot;&gt;&lt;img src=&quot;https://blog.kakaocdn.net/dn/7DQX5/btrTmZaD3E7/kPodn9n8vgseXAKA0Sqlyk/img.png&quot; srcset=&quot;https://img1.daumcdn.net/thumb/R1280x0/?scode=mtistory2&amp;fname=https%3A%2F%2Fblog.kakaocdn.net%2Fdn%2F7DQX5%2FbtrTmZaD3E7%2FkPodn9n8vgseXAKA0Sqlyk%2Fimg.png&quot; onerror=&quot;this.onerror=null; this.src='//t1.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;236&quot; height=&quot;183&quot; data-origin-width=&quot;236&quot; data-origin-height=&quot;183&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;필자는 이 database의 salaries 테이블을 조회하려 한다.&lt;/p&gt;
&lt;figure id=&quot;og_1670855754745&quot; contenteditable=&quot;false&quot; data-ke-type=&quot;opengraph&quot; data-ke-align=&quot;alignCenter&quot; data-og-type=&quot;object&quot; data-og-title=&quot;GitHub - datacharmer/test_db: A sample MySQL database with an integrated test suite, used to test your applications and database&quot; data-og-description=&quot;A sample MySQL database with an integrated test suite, used to test your applications and database servers - GitHub - datacharmer/test_db: A sample MySQL database with an integrated test suite, use...&quot; data-og-host=&quot;github.com&quot; data-og-source-url=&quot;https://github.com/datacharmer/test_db&quot; data-og-url=&quot;https://github.com/datacharmer/test_db&quot; data-og-image=&quot;https://scrap.kakaocdn.net/dn/dck50M/hyQRHIwrX6/ADNkNhQouNOVB0He3vqax1/img.png?width=1200&amp;amp;height=600&amp;amp;face=975_129_1057_219&quot;&gt;&lt;a href=&quot;https://github.com/datacharmer/test_db&quot; target=&quot;_blank&quot; rel=&quot;noopener&quot; data-source-url=&quot;https://github.com/datacharmer/test_db&quot;&gt;
&lt;div class=&quot;og-image&quot; style=&quot;background-image: url('https://scrap.kakaocdn.net/dn/dck50M/hyQRHIwrX6/ADNkNhQouNOVB0He3vqax1/img.png?width=1200&amp;amp;height=600&amp;amp;face=975_129_1057_219');&quot;&gt;&amp;nbsp;&lt;/div&gt;
&lt;div class=&quot;og-text&quot;&gt;
&lt;p class=&quot;og-title&quot; data-ke-size=&quot;size16&quot;&gt;GitHub - datacharmer/test_db: A sample MySQL database with an integrated test suite, used to test your applications and database&lt;/p&gt;
&lt;p class=&quot;og-desc&quot; data-ke-size=&quot;size16&quot;&gt;A sample MySQL database with an integrated test suite, used to test your applications and database servers - GitHub - datacharmer/test_db: A sample MySQL database with an integrated test suite, use...&lt;/p&gt;
&lt;p class=&quot;og-host&quot; data-ke-size=&quot;size16&quot;&gt;github.com&lt;/p&gt;
&lt;/div&gt;
&lt;/a&gt;&lt;/figure&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;h2 data-ke-size=&quot;size26&quot;&gt;Springboot&lt;/h2&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;자, 이제 stream 이 되도록 소스를 구현하자.&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;Spring initializr&lt;/h3&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;spring init 사이트에서 mybatis, mysql, lombok, web, devTools 를 선택해 가져왔다.&lt;/p&gt;
&lt;p&gt;&lt;figure class=&quot;imageblock alignCenter&quot; data-ke-mobileStyle=&quot;widthOrigin&quot; data-origin-width=&quot;973&quot; data-origin-height=&quot;932&quot;&gt;&lt;span data-url=&quot;https://blog.kakaocdn.net/dn/dYGH2s/btrTuAGTlAc/tyZl4nH7pabFCqibBODT0K/img.png&quot; data-phocus=&quot;https://blog.kakaocdn.net/dn/dYGH2s/btrTuAGTlAc/tyZl4nH7pabFCqibBODT0K/img.png&quot;&gt;&lt;img src=&quot;https://blog.kakaocdn.net/dn/dYGH2s/btrTuAGTlAc/tyZl4nH7pabFCqibBODT0K/img.png&quot; srcset=&quot;https://img1.daumcdn.net/thumb/R1280x0/?scode=mtistory2&amp;fname=https%3A%2F%2Fblog.kakaocdn.net%2Fdn%2FdYGH2s%2FbtrTuAGTlAc%2FtyZl4nH7pabFCqibBODT0K%2Fimg.png&quot; onerror=&quot;this.onerror=null; this.src='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png'; this.srcset='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png';&quot; loading=&quot;lazy&quot; width=&quot;668&quot; height=&quot;640&quot; data-origin-width=&quot;973&quot; data-origin-height=&quot;932&quot;/&gt;&lt;/span&gt;&lt;/figure&gt;
&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;알아서 다운받고 maven project 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;귀찮아할 수 있으니 pom.xml도 첨부하겠다..&lt;/p&gt;
&lt;pre id=&quot;code_1670856686654&quot; class=&quot;xml&quot; data-ke-language=&quot;highlight.js&quot; data-ke-type=&quot;codeblock&quot;&gt;&lt;code&gt;&amp;lt;?xml version=&quot;1.0&quot; encoding=&quot;UTF-8&quot;?&amp;gt;
&amp;lt;project xmlns=&quot;http://maven.apache.org/POM/4.0.0&quot; xmlns:xsi=&quot;http://www.w3.org/2001/XMLSchema-instance&quot;
	xsi:schemaLocation=&quot;http://maven.apache.org/POM/4.0.0 https://maven.apache.org/xsd/maven-4.0.0.xsd&quot;&amp;gt;
	&amp;lt;modelVersion&amp;gt;4.0.0&amp;lt;/modelVersion&amp;gt;
	&amp;lt;parent&amp;gt;
		&amp;lt;groupId&amp;gt;org.springframework.boot&amp;lt;/groupId&amp;gt;
		&amp;lt;artifactId&amp;gt;spring-boot-starter-parent&amp;lt;/artifactId&amp;gt;
		&amp;lt;version&amp;gt;3.0.0&amp;lt;/version&amp;gt;
		&amp;lt;relativePath/&amp;gt; &amp;lt;!-- lookup parent from repository --&amp;gt;
	&amp;lt;/parent&amp;gt;
	&amp;lt;groupId&amp;gt;com.example&amp;lt;/groupId&amp;gt;
	&amp;lt;artifactId&amp;gt;demo&amp;lt;/artifactId&amp;gt;
	&amp;lt;version&amp;gt;0.0.1-SNAPSHOT&amp;lt;/version&amp;gt;
	&amp;lt;name&amp;gt;demo&amp;lt;/name&amp;gt;
	&amp;lt;description&amp;gt;Demo project for Spring Boot&amp;lt;/description&amp;gt;
	&amp;lt;properties&amp;gt;
		&amp;lt;java.version&amp;gt;17&amp;lt;/java.version&amp;gt;
	&amp;lt;/properties&amp;gt;
	&amp;lt;dependencies&amp;gt;
		&amp;lt;dependency&amp;gt;
			&amp;lt;groupId&amp;gt;org.springframework.boot&amp;lt;/groupId&amp;gt;
			&amp;lt;artifactId&amp;gt;spring-boot-starter-web&amp;lt;/artifactId&amp;gt;
		&amp;lt;/dependency&amp;gt;
		&amp;lt;dependency&amp;gt;
			&amp;lt;groupId&amp;gt;org.mybatis.spring.boot&amp;lt;/groupId&amp;gt;
			&amp;lt;artifactId&amp;gt;mybatis-spring-boot-starter&amp;lt;/artifactId&amp;gt;
			&amp;lt;version&amp;gt;3.0.0&amp;lt;/version&amp;gt;
		&amp;lt;/dependency&amp;gt;

		&amp;lt;dependency&amp;gt;
			&amp;lt;groupId&amp;gt;org.springframework.boot&amp;lt;/groupId&amp;gt;
			&amp;lt;artifactId&amp;gt;spring-boot-devtools&amp;lt;/artifactId&amp;gt;
			&amp;lt;scope&amp;gt;runtime&amp;lt;/scope&amp;gt;
			&amp;lt;optional&amp;gt;true&amp;lt;/optional&amp;gt;
		&amp;lt;/dependency&amp;gt;
		&amp;lt;dependency&amp;gt;
			&amp;lt;groupId&amp;gt;com.mysql&amp;lt;/groupId&amp;gt;
			&amp;lt;artifactId&amp;gt;mysql-connector-j&amp;lt;/artifactId&amp;gt;
			&amp;lt;scope&amp;gt;runtime&amp;lt;/scope&amp;gt;
		&amp;lt;/dependency&amp;gt;
		&amp;lt;dependency&amp;gt;
			&amp;lt;groupId&amp;gt;org.projectlombok&amp;lt;/groupId&amp;gt;
			&amp;lt;artifactId&amp;gt;lombok&amp;lt;/artifactId&amp;gt;
			&amp;lt;optional&amp;gt;true&amp;lt;/optional&amp;gt;
		&amp;lt;/dependency&amp;gt;
		&amp;lt;dependency&amp;gt;
			&amp;lt;groupId&amp;gt;org.springframework.boot&amp;lt;/groupId&amp;gt;
			&amp;lt;artifactId&amp;gt;spring-boot-starter-test&amp;lt;/artifactId&amp;gt;
			&amp;lt;scope&amp;gt;test&amp;lt;/scope&amp;gt;
		&amp;lt;/dependency&amp;gt;
	&amp;lt;/dependencies&amp;gt;

	&amp;lt;build&amp;gt;
		&amp;lt;plugins&amp;gt;
			&amp;lt;plugin&amp;gt;
				&amp;lt;groupId&amp;gt;org.springframework.boot&amp;lt;/groupId&amp;gt;
				&amp;lt;artifactId&amp;gt;spring-boot-maven-plugin&amp;lt;/artifactId&amp;gt;
				&amp;lt;configuration&amp;gt;
					&amp;lt;excludes&amp;gt;
						&amp;lt;exclude&amp;gt;
							&amp;lt;groupId&amp;gt;org.projectlombok&amp;lt;/groupId&amp;gt;
							&amp;lt;artifactId&amp;gt;lombok&amp;lt;/artifactId&amp;gt;
						&amp;lt;/exclude&amp;gt;
					&amp;lt;/excludes&amp;gt;
				&amp;lt;/configuration&amp;gt;
			&amp;lt;/plugin&amp;gt;
		&amp;lt;/plugins&amp;gt;
	&amp;lt;/build&amp;gt;

&amp;lt;/project&amp;gt;&lt;/code&gt;&lt;/pre&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;h3 data-ke-size=&quot;size23&quot;&gt;project 구조&lt;/h3&gt;
&lt;p&gt;&lt;figure class=&quot;imageblock alignCenter&quot; data-ke-mobileStyle=&quot;widthOrigin&quot; data-origin-width=&quot;237&quot; data-origin-height=&quot;367&quot;&gt;&lt;span data-url=&quot;https://blog.kakaocdn.net/dn/bkdsle/btrTtnatXLK/7BTIXGkzQX8RD3t3XFgMok/img.png&quot; data-phocus=&quot;https://blog.kakaocdn.net/dn/bkdsle/btrTtnatXLK/7BTIXGkzQX8RD3t3XFgMok/img.png&quot;&gt;&lt;img src=&quot;https://blog.kakaocdn.net/dn/bkdsle/btrTtnatXLK/7BTIXGkzQX8RD3t3XFgMok/img.png&quot; srcset=&quot;https://img1.daumcdn.net/thumb/R1280x0/?scode=mtistory2&amp;fname=https%3A%2F%2Fblog.kakaocdn.net%2Fdn%2Fbkdsle%2FbtrTtnatXLK%2F7BTIXGkzQX8RD3t3XFgMok%2Fimg.png&quot; onerror=&quot;this.onerror=null; this.src='//t1.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;237&quot; height=&quot;367&quot; data-origin-width=&quot;237&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;간단하게 만들어질 프로젝트 구조에 대해 확인해보자.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;mybatis 사용 시 늘 보던 구조^^&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;(service interface는 귀찮아서 뺏다ㅋ)&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;application.properties&lt;/h3&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;간단히 로그레벨, datasource, mybatis 설정을 넣어주었다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;(대학생 때는 항상 root-context.xml 만들고 난리였는데, 이제 이거만해도 잘된당)&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;(springboot-mybatis-starter 가 생겨서 그렇다나 뭐라나..?)&lt;/p&gt;
&lt;pre id=&quot;code_1670855468985&quot; class=&quot;yaml&quot; data-ke-language=&quot;highligh.js&quot; data-ke-type=&quot;codeblock&quot;&gt;&lt;code&gt;logging.level.root: INFO
# database
spring.datasource.url: jdbc:mysql://localhost:3306/employees?characterEncoding=utf8
spring.datasource.username: root
spring.datasource.password: admin1234
spring.datasource.driver-class-name: com.mysql.cj.jdbc.Driver
spring.datasource.hikari.maximum-pool-size: 30
# mybatis
mybatis.type-aliases-package: com.example.demo.model
mybatis.mapper-locations: classpath:mybatis/mapper/**/*.xml&lt;/code&gt;&lt;/pre&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;근데 datasource.hikari 로 다 선언해야되는거로 알고잇었는데 url 오류가 계속 나서 이거로했다.(아는 사람 댓글 좀요)&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;debug 로그를 확인해보니 pool 갯수가 잘 변경되고 있음;&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;SalaryMapper.xml&lt;/h3&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;resources/mybatis/mapper 아래 위치하는 파일이다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;mybatis 에서 모든 쿼리들을 바로 볼 수 있는 xml ㅎㅎ&lt;/p&gt;
&lt;pre id=&quot;code_1670855888506&quot; class=&quot;xml&quot; data-ke-language=&quot;highlight.js&quot; data-ke-type=&quot;codeblock&quot;&gt;&lt;code&gt;&amp;lt;?xml version=&quot;1.0&quot; encoding=&quot;UTF-8&quot;?&amp;gt;
&amp;lt;!DOCTYPE mapper PUBLIC &quot;-//mybatis.org//DTD Mapper 3.0//EN&quot; &quot;http://mybatis.org/dtd/mybatis-3-mapper.dtd&quot;&amp;gt;
&amp;lt;mapper namespace=&quot;com.example.demo.mapper.SalaryMapper&quot;&amp;gt;
  &amp;lt;select id=&quot;getSalaryOri&quot; resultType=&quot;SalaryModel&quot;&amp;gt;
    SELECT * FROM salaries;
  &amp;lt;/select&amp;gt;
  &amp;lt;select id=&quot;getSalary&quot; resultType=&quot;SalaryModel&quot; fetchSize=&quot;1000&quot;&amp;gt;
    SELECT * FROM salaries;
  &amp;lt;/select&amp;gt;
  &amp;lt;select id=&quot;getAllSalary&quot; parameterType=&quot;map&quot; resultType=&quot;map&quot; fetchSize=&quot;-2147483648&quot;&amp;gt;
    SELECT * FROM salaries;
  &amp;lt;/select&amp;gt;
&amp;lt;/mapper&amp;gt;&lt;/code&gt;&lt;/pre&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;fetchSize가 있는 경우 / 없는 경우 와 stream 용도로 사용할 All을 작성하였다.&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;Model(DTO)&lt;/h3&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;샘플데이터로 사용할 Salary Table 구조를 확인한 뒤 작성하였다.&lt;/p&gt;
&lt;p&gt;&lt;figure class=&quot;imageblock alignCenter&quot; data-ke-mobileStyle=&quot;widthOrigin&quot; data-origin-width=&quot;487&quot; data-origin-height=&quot;111&quot;&gt;&lt;span data-url=&quot;https://blog.kakaocdn.net/dn/dxBeNo/btrTukRHqCq/9O9s7uiMt7PYYGwkSrHFj1/img.png&quot; data-phocus=&quot;https://blog.kakaocdn.net/dn/dxBeNo/btrTukRHqCq/9O9s7uiMt7PYYGwkSrHFj1/img.png&quot;&gt;&lt;img src=&quot;https://blog.kakaocdn.net/dn/dxBeNo/btrTukRHqCq/9O9s7uiMt7PYYGwkSrHFj1/img.png&quot; srcset=&quot;https://img1.daumcdn.net/thumb/R1280x0/?scode=mtistory2&amp;fname=https%3A%2F%2Fblog.kakaocdn.net%2Fdn%2FdxBeNo%2FbtrTukRHqCq%2F9O9s7uiMt7PYYGwkSrHFj1%2Fimg.png&quot; onerror=&quot;this.onerror=null; this.src='//t1.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;487&quot; height=&quot;111&quot; data-origin-width=&quot;487&quot; data-origin-height=&quot;111&quot;/&gt;&lt;/span&gt;&lt;/figure&gt;
&lt;/p&gt;
&lt;pre id=&quot;code_1670856099198&quot; class=&quot;java&quot; data-ke-language=&quot;java&quot; data-ke-type=&quot;codeblock&quot;&gt;&lt;code&gt;package com.example.demo.model;

import lombok.Builder;
import lombok.Data;

@Builder
@Data
public class SalaryModel {
	private int emp_no;
	private int salary;
	private String from_date;
	private String to_date;
}&lt;/code&gt;&lt;/pre&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;h3 data-ke-size=&quot;size23&quot;&gt;Mapper&lt;/h3&gt;
&lt;pre id=&quot;code_1670856003021&quot; class=&quot;java&quot; data-ke-language=&quot;highligh.js&quot; data-ke-type=&quot;codeblock&quot;&gt;&lt;code&gt;package com.example.demo.mapper;

import java.util.List;
import org.apache.ibatis.annotations.Mapper;
import org.apache.ibatis.session.ResultHandler;
import com.example.demo.model.SalaryModel;

@Mapper
public interface SalaryMapper {
	List&amp;lt;SalaryModel&amp;gt; getSalaryOri();
	List&amp;lt;SalaryModel&amp;gt; getSalary();
	void getAllSalary(ResultHandler handler);
}&lt;/code&gt;&lt;/pre&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;stream 용도로 사용할 mapper는 void 형식으로 작성해야한다고 함&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;Handler&lt;/h3&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;stream 을 위한 핸들러이다. (deprecated 된 라이브러리이니 주의바람)&lt;/p&gt;
&lt;pre id=&quot;code_1670856213748&quot; class=&quot;java&quot; data-ke-language=&quot;java&quot; data-ke-type=&quot;codeblock&quot;&gt;&lt;code&gt;package com.example.demo.handler;

import java.util.Observable;
import org.apache.ibatis.session.ResultContext;
import org.apache.ibatis.session.ResultHandler;

public class RowHandler extends Observable implements ResultHandler{
	@Override
	public void handleResult(ResultContext resultContext) {
		// TODO Auto-generated method stub
		super.setChanged();
		super.notifyObservers(resultContext.getResultObject());
	}
}&lt;/code&gt;&lt;/pre&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;h3 data-ke-size=&quot;size23&quot;&gt;Service&lt;/h3&gt;
&lt;pre id=&quot;code_1670856317617&quot; class=&quot;java&quot; data-ke-language=&quot;java&quot; data-ke-type=&quot;codeblock&quot;&gt;&lt;code&gt;package com.example.demo.service;

import java.util.List;
import java.util.Observer;

import org.apache.ibatis.session.ResultContext;
import org.apache.ibatis.session.ResultHandler;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.stereotype.Service;

import com.example.demo.handler.RowHandler;
import com.example.demo.mapper.SalaryMapper;
import com.example.demo.model.SalaryModel;

@Service
public class SalaryService {
	@Autowired
	public SalaryMapper mapper;
	public List&amp;lt;SalaryModel&amp;gt; getSalaryOri(){
		return mapper.getSalaryOri();
	}
	public List&amp;lt;SalaryModel&amp;gt; getSalary(){
		return mapper.getSalary();
	}
	public void getAllSalary(Observer observer) {
		RowHandler resultHandler = new RowHandler();
		resultHandler.addObserver(observer);
		mapper.getAllSalary(resultHandler);
	}
}&lt;/code&gt;&lt;/pre&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;h3 data-ke-size=&quot;size23&quot;&gt;Controller&lt;/h3&gt;
&lt;pre id=&quot;code_1670856351273&quot; class=&quot;java&quot; data-ke-language=&quot;java&quot; data-ke-type=&quot;codeblock&quot;&gt;&lt;code&gt;package com.example.demo.controller;

import java.io.IOException;
import java.util.List;
import java.util.concurrent.ExecutorService;
import java.util.concurrent.Executors;

import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.web.bind.annotation.GetMapping;
import org.springframework.web.bind.annotation.RestController;
import org.springframework.web.servlet.mvc.method.annotation.ResponseBodyEmitter;

import com.example.demo.model.SalaryModel;
import com.example.demo.service.SalaryService;

import lombok.extern.slf4j.Slf4j;

@Slf4j
@RestController
public class JoonController {

	@Autowired
	SalaryService salaryService;
	@GetMapping(value=&quot;/ori&quot;)
	private List&amp;lt;SalaryModel&amp;gt; salaryOri() {
		List&amp;lt;SalaryModel&amp;gt; salaryList = salaryService.getSalaryOri();
    	return salaryList;
	}
	@GetMapping(value=&quot;/sal&quot;)
	private List&amp;lt;SalaryModel&amp;gt; salary() {
		List&amp;lt;SalaryModel&amp;gt; salaryList = salaryService.getSalary();
    	return salaryList;
	}
	@GetMapping(value=&quot;/stream&quot;)
	private ResponseBodyEmitter stream() {
		// timeout 10분
		final ResponseBodyEmitter emitter = new ResponseBodyEmitter(600000L);

		ExecutorService executorService = Executors.newSingleThreadExecutor();
		executorService.execute(() -&amp;gt; {
			salaryService.getAllSalary((observer, args) -&amp;gt; {
			  try {
			    emitter.send(args.toString() + &quot;\n&quot;);
			  } catch (IOException e) {
			    log.error(&quot;### Stream 방식으로 목록을 전달하던 중 에러 발생&quot;, e);
			  }
			});
			emitter.complete();
		});
		return emitter;
	}	
}&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;이렇게 총 3개의 api 가 완성되었다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;GET /ori : default&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;GET /sal : fetchSize 1000 세팅&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;GET /stream : stream 핸들러&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;h2 data-ke-size=&quot;size26&quot;&gt;모니터링 환경 세팅&lt;/h2&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;필자는 jconsole 을 이용하여 모니터링 할 것이다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;그리고 eclipse 에서 바로 run 과 동시에 모니터링을 하고 싶다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;그렇게 하기 위해선 eclipse 에 jconsole 이 모니터링할 포트를 설정해주어야한다.&lt;/p&gt;
&lt;h3 data-ke-size=&quot;size23&quot;&gt;eclipse&lt;/h3&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;eclipse 상단 메뉴 중 run&amp;gt;run configurations 에 들어가 Arguments에 아래내용을 추가하자.&lt;/p&gt;
&lt;p&gt;&lt;figure class=&quot;imageblock alignCenter&quot; data-ke-mobileStyle=&quot;widthOrigin&quot; data-origin-width=&quot;782&quot; data-origin-height=&quot;714&quot;&gt;&lt;span data-url=&quot;https://blog.kakaocdn.net/dn/VEPxk/btrTwBSQDVb/LKckwJqsoIbo5IQwsaYCUk/img.png&quot; data-phocus=&quot;https://blog.kakaocdn.net/dn/VEPxk/btrTwBSQDVb/LKckwJqsoIbo5IQwsaYCUk/img.png&quot;&gt;&lt;img src=&quot;https://blog.kakaocdn.net/dn/VEPxk/btrTwBSQDVb/LKckwJqsoIbo5IQwsaYCUk/img.png&quot; srcset=&quot;https://img1.daumcdn.net/thumb/R1280x0/?scode=mtistory2&amp;fname=https%3A%2F%2Fblog.kakaocdn.net%2Fdn%2FVEPxk%2FbtrTwBSQDVb%2FLKckwJqsoIbo5IQwsaYCUk%2Fimg.png&quot; onerror=&quot;this.onerror=null; this.src='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png'; this.srcset='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png';&quot; loading=&quot;lazy&quot; width=&quot;618&quot; height=&quot;564&quot; data-origin-width=&quot;782&quot; data-origin-height=&quot;714&quot;/&gt;&lt;/span&gt;&lt;/figure&gt;
&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;위 3개만 있으면 되고 추가적으로 메모리지정 및 gclog 도 추가해줬음.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;port는 임의로 9611 로 지정하였으니 jconsole에서 이쪽으로 연결해주면 된다.&lt;/p&gt;
&lt;pre id=&quot;code_1670856826642&quot; class=&quot;typescript&quot; data-ke-language=&quot;typescript&quot; data-ke-type=&quot;codeblock&quot;&gt;&lt;code&gt;-Dcom.sun.management.jmxremote.port=9611
-Dcom.sun.management.jmxremote.rmi.port=9611
-Dcom.sun.management.jmxremote.ssl=false
-XX:+HeapDumpOnOutOfMemoryError
-XX:HeapDumpPath=C:\sts-workspace
-verbose:gc
-Xms2048m
-Xmx2048m&lt;/code&gt;&lt;/pre&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;h3 data-ke-size=&quot;size23&quot;&gt;jconsole&lt;/h3&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;jconsole 은 java에 기본적으로 설치되어있따!&lt;/p&gt;
&lt;p&gt;&lt;figure class=&quot;imageblock alignCenter&quot; data-ke-mobileStyle=&quot;widthOrigin&quot; data-origin-width=&quot;691&quot; data-origin-height=&quot;118&quot;&gt;&lt;span data-url=&quot;https://blog.kakaocdn.net/dn/uqx7P/btrTtAAHnkO/GBzLOLoka4su0GbgWUdEg1/img.png&quot; data-phocus=&quot;https://blog.kakaocdn.net/dn/uqx7P/btrTtAAHnkO/GBzLOLoka4su0GbgWUdEg1/img.png&quot;&gt;&lt;img src=&quot;https://blog.kakaocdn.net/dn/uqx7P/btrTtAAHnkO/GBzLOLoka4su0GbgWUdEg1/img.png&quot; srcset=&quot;https://img1.daumcdn.net/thumb/R1280x0/?scode=mtistory2&amp;fname=https%3A%2F%2Fblog.kakaocdn.net%2Fdn%2Fuqx7P%2FbtrTtAAHnkO%2FGBzLOLoka4su0GbgWUdEg1%2Fimg.png&quot; onerror=&quot;this.onerror=null; this.src='//t1.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;691&quot; height=&quot;118&quot; data-origin-width=&quot;691&quot; data-origin-height=&quot;118&quot;/&gt;&lt;/span&gt;&lt;/figure&gt;
&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;더블클릭해서 실행한 뒤, localhost:port 로 연결하면 모니터링된다.&lt;/p&gt;
&lt;p&gt;&lt;figure class=&quot;imageblock alignCenter&quot; data-ke-mobileStyle=&quot;widthOrigin&quot; data-origin-width=&quot;885&quot; data-origin-height=&quot;746&quot;&gt;&lt;span data-url=&quot;https://blog.kakaocdn.net/dn/RQ6Tc/btrTti1jr5k/2slvkiGaqjD8PUPP1lYjz1/img.png&quot; data-phocus=&quot;https://blog.kakaocdn.net/dn/RQ6Tc/btrTti1jr5k/2slvkiGaqjD8PUPP1lYjz1/img.png&quot;&gt;&lt;img src=&quot;https://blog.kakaocdn.net/dn/RQ6Tc/btrTti1jr5k/2slvkiGaqjD8PUPP1lYjz1/img.png&quot; srcset=&quot;https://img1.daumcdn.net/thumb/R1280x0/?scode=mtistory2&amp;fname=https%3A%2F%2Fblog.kakaocdn.net%2Fdn%2FRQ6Tc%2FbtrTti1jr5k%2F2slvkiGaqjD8PUPP1lYjz1%2Fimg.png&quot; onerror=&quot;this.onerror=null; this.src='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png'; this.srcset='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png';&quot; loading=&quot;lazy&quot; width=&quot;700&quot; height=&quot;590&quot; data-origin-width=&quot;885&quot; data-origin-height=&quot;746&quot;/&gt;&lt;/span&gt;&lt;/figure&gt;
&lt;figure class=&quot;imageblock alignCenter&quot; data-ke-mobileStyle=&quot;widthOrigin&quot; data-origin-width=&quot;886&quot; data-origin-height=&quot;740&quot;&gt;&lt;span data-url=&quot;https://blog.kakaocdn.net/dn/FzzsA/btrTqfjOr9C/D05aqU6pCtl3e2pOOS8utk/img.png&quot; data-phocus=&quot;https://blog.kakaocdn.net/dn/FzzsA/btrTqfjOr9C/D05aqU6pCtl3e2pOOS8utk/img.png&quot;&gt;&lt;img src=&quot;https://blog.kakaocdn.net/dn/FzzsA/btrTqfjOr9C/D05aqU6pCtl3e2pOOS8utk/img.png&quot; srcset=&quot;https://img1.daumcdn.net/thumb/R1280x0/?scode=mtistory2&amp;fname=https%3A%2F%2Fblog.kakaocdn.net%2Fdn%2FFzzsA%2FbtrTqfjOr9C%2FD05aqU6pCtl3e2pOOS8utk%2Fimg.png&quot; onerror=&quot;this.onerror=null; this.src='//t1.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;769&quot; height=&quot;642&quot; data-origin-width=&quot;886&quot; data-origin-height=&quot;740&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;/h2&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;정확한 히스토리를 남기면서 하고싶어서 Postman을 중점으로 해보았다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;response 되는 Body Size가 195MB 정도 되어 postman에&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;Error: Maximum response size reached 가 발생하였다.&lt;/p&gt;
&lt;p&gt;&lt;figure class=&quot;imageblock alignCenter&quot; data-ke-mobileStyle=&quot;widthOrigin&quot; data-origin-width=&quot;394&quot; data-origin-height=&quot;276&quot;&gt;&lt;span data-url=&quot;https://blog.kakaocdn.net/dn/bX9Chq/btrTmu2Q99I/KuzirPVMZYN3VutgjKzEh1/img.png&quot; data-phocus=&quot;https://blog.kakaocdn.net/dn/bX9Chq/btrTmu2Q99I/KuzirPVMZYN3VutgjKzEh1/img.png&quot;&gt;&lt;img src=&quot;https://blog.kakaocdn.net/dn/bX9Chq/btrTmu2Q99I/KuzirPVMZYN3VutgjKzEh1/img.png&quot; srcset=&quot;https://img1.daumcdn.net/thumb/R1280x0/?scode=mtistory2&amp;fname=https%3A%2F%2Fblog.kakaocdn.net%2Fdn%2FbX9Chq%2FbtrTmu2Q99I%2FKuzirPVMZYN3VutgjKzEh1%2Fimg.png&quot; onerror=&quot;this.onerror=null; this.src='//t1.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;394&quot; height=&quot;276&quot; data-origin-width=&quot;394&quot; data-origin-height=&quot;276&quot;/&gt;&lt;/span&gt;&lt;/figure&gt;
&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;찾아보니 default로 설정된 값이 50MB 여서 settings 에서 0으로 잠시 세팅해주었다.&lt;/p&gt;
&lt;p&gt;&lt;figure class=&quot;imageblock alignCenter&quot; data-ke-mobileStyle=&quot;widthOrigin&quot; data-origin-width=&quot;729&quot; data-origin-height=&quot;648&quot;&gt;&lt;span data-url=&quot;https://blog.kakaocdn.net/dn/cGEojU/btrTt1rdGZG/PzXl3aDweBDTV4DyQhAdB0/img.png&quot; data-phocus=&quot;https://blog.kakaocdn.net/dn/cGEojU/btrTt1rdGZG/PzXl3aDweBDTV4DyQhAdB0/img.png&quot;&gt;&lt;img src=&quot;https://blog.kakaocdn.net/dn/cGEojU/btrTt1rdGZG/PzXl3aDweBDTV4DyQhAdB0/img.png&quot; srcset=&quot;https://img1.daumcdn.net/thumb/R1280x0/?scode=mtistory2&amp;fname=https%3A%2F%2Fblog.kakaocdn.net%2Fdn%2FcGEojU%2FbtrTt1rdGZG%2FPzXl3aDweBDTV4DyQhAdB0%2Fimg.png&quot; onerror=&quot;this.onerror=null; this.src='//t1.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;729&quot; height=&quot;648&quot; data-origin-width=&quot;729&quot; data-origin-height=&quot;648&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;/h2&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;먼저 필자는 fetchSize 에 대해 놀라운 변화가 없었다. 아마 Column 들의 Size가 작아서 그런거 같기도하다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;그럼 Stream과 비교하면 어느정도의 차이가 났을까?&lt;/p&gt;
&lt;p&gt;&lt;figure class=&quot;imageblock alignCenter&quot; data-ke-mobileStyle=&quot;widthOrigin&quot; data-origin-width=&quot;987&quot; data-origin-height=&quot;982&quot;&gt;&lt;span data-url=&quot;https://blog.kakaocdn.net/dn/cOCEon/btrTwAl6oxj/l36IaeAPppxgMSLvUXmFDK/img.png&quot; data-phocus=&quot;https://blog.kakaocdn.net/dn/cOCEon/btrTwAl6oxj/l36IaeAPppxgMSLvUXmFDK/img.png&quot;&gt;&lt;img src=&quot;https://blog.kakaocdn.net/dn/cOCEon/btrTwAl6oxj/l36IaeAPppxgMSLvUXmFDK/img.png&quot; srcset=&quot;https://img1.daumcdn.net/thumb/R1280x0/?scode=mtistory2&amp;fname=https%3A%2F%2Fblog.kakaocdn.net%2Fdn%2FcOCEon%2FbtrTwAl6oxj%2Fl36IaeAPppxgMSLvUXmFDK%2Fimg.png&quot; onerror=&quot;this.onerror=null; this.src='//t1.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;722&quot; height=&quot;718&quot; data-origin-width=&quot;987&quot; data-origin-height=&quot;982&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;메모리에 가지고 한번에 return 해주면&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;- 크롬에서 Memory 오류 발생하는 현상 간혹 발생&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;- 소요시간 5분19초~49초 사이&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;- Heap 메모리 사용률은 최대 1.7GB, 평균 1.5GB&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;반면에 Stream 테스트는&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;- 유저가 지속적으로 update 되는 데이터를 보기때문에 사용성 우수&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;- 소요시간 1분24초로 &lt;b&gt;(4배의 속도상승)&lt;/b&gt;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;- Heap 메모리 사용률 최대 1.3GB, 평균 0.7GB&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;h2 data-ke-size=&quot;size26&quot;&gt;마무리&lt;/h2&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;물론 Stream이 정답은 아닐 것이다.&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;그리고 gc.log 는 뭐 기본적으로 보게되는데,&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;HeapDump 설정은 그냥 생각만 하고 귀찮아서 거의 방치한 상태였다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;개발자들의 실수를 support 하기 위해 다음부턴 꼭. ㅋ챙겨보자..ㅋ&lt;/p&gt;
&lt;figure contenteditable=&quot;false&quot; data-ke-type=&quot;emoticon&quot; data-ke-align=&quot;alignCenter&quot; data-emoticon-type=&quot;friends1&quot; data-emoticon-name=&quot;006&quot; data-emoticon-isanimation=&quot;false&quot; data-emoticon-src=&quot;https://t1.daumcdn.net/keditor/emoticon/friends1/large/006.gif&quot;&gt;&lt;img src=&quot;https://t1.daumcdn.net/keditor/emoticon/friends1/large/006.gif&quot; width=&quot;150&quot; /&gt;&lt;/figure&gt;
&lt;h2 data-ke-size=&quot;size26&quot;&gt;Github&lt;/h2&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;해당 테스트에 사용한 소스코드는 github에 업로드하였다.&lt;/p&gt;
&lt;figure id=&quot;og_1671500761727&quot; contenteditable=&quot;false&quot; data-ke-type=&quot;opengraph&quot; data-ke-align=&quot;alignCenter&quot; data-og-type=&quot;object&quot; data-og-title=&quot;GitHub - joonhyeok95/spring-stream-mysql: 280만건 데이터를 화면에 뿌릴 때 stream 방식으로 테스트하는 샘플&quot; data-og-description=&quot;280만건 데이터를 화면에 뿌릴 때 stream 방식으로 테스트하는 샘플. Contribute to joonhyeok95/spring-stream-mysql development by creating an account on GitHub.&quot; data-og-host=&quot;github.com&quot; data-og-source-url=&quot;https://github.com/joonhyeok95/spring-stream-mysql&quot; data-og-url=&quot;https://github.com/joonhyeok95/spring-stream-mysql&quot; data-og-image=&quot;https://scrap.kakaocdn.net/dn/chOTHh/hyQWA30tto/vKCqDi42lUpKvFKhh3eOEk/img.png?width=1200&amp;amp;height=600&amp;amp;face=0_0_1200_600&quot;&gt;&lt;a href=&quot;https://github.com/joonhyeok95/spring-stream-mysql&quot; target=&quot;_blank&quot; rel=&quot;noopener&quot; data-source-url=&quot;https://github.com/joonhyeok95/spring-stream-mysql&quot;&gt;
&lt;div class=&quot;og-image&quot; style=&quot;background-image: url('https://scrap.kakaocdn.net/dn/chOTHh/hyQWA30tto/vKCqDi42lUpKvFKhh3eOEk/img.png?width=1200&amp;amp;height=600&amp;amp;face=0_0_1200_600');&quot;&gt;&amp;nbsp;&lt;/div&gt;
&lt;div class=&quot;og-text&quot;&gt;
&lt;p class=&quot;og-title&quot; data-ke-size=&quot;size16&quot;&gt;GitHub - joonhyeok95/spring-stream-mysql: 280만건 데이터를 화면에 뿌릴 때 stream 방식으로 테스트하는 샘플&lt;/p&gt;
&lt;p class=&quot;og-desc&quot; data-ke-size=&quot;size16&quot;&gt;280만건 데이터를 화면에 뿌릴 때 stream 방식으로 테스트하는 샘플. Contribute to joonhyeok95/spring-stream-mysql development by creating an account on GitHub.&lt;/p&gt;
&lt;p class=&quot;og-host&quot; data-ke-size=&quot;size16&quot;&gt;github.com&lt;/p&gt;
&lt;/div&gt;
&lt;/a&gt;&lt;/figure&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;h2 data-ke-size=&quot;size26&quot;&gt;참고 사이트&lt;/h2&gt;
&lt;figure id=&quot;og_1670857847732&quot; contenteditable=&quot;false&quot; data-ke-type=&quot;opengraph&quot; data-ke-align=&quot;alignCenter&quot; data-og-type=&quot;article&quot; data-og-title=&quot;[Spring] Stream 형태로 Response 응답 주기 (feat. MyBatis, Observer)&quot; data-og-description=&quot;환경 Spring Version : 4.2 이상 MySQL + MyBatis 문서 Http Stream Return은 총 3가지가 있는듯하다. (Spring 4.2부터) - ResponseBodyEmitter : https://docs.spring.io/autorepo/docs/spring-framework/4.3.5.RELEASE/javadoc-api/org/springframework/w&quot; data-og-host=&quot;seongtak-yoon.tistory.com&quot; data-og-source-url=&quot;https://seongtak-yoon.tistory.com/68&quot; data-og-url=&quot;https://seongtak-yoon.tistory.com/68&quot; data-og-image=&quot;https://scrap.kakaocdn.net/dn/ccHOG4/hyQRIHqDBU/yapFkk1P1rcbdsM5jLJ7uk/img.png?width=800&amp;amp;height=800&amp;amp;face=0_0_800_800,https://scrap.kakaocdn.net/dn/W94TW/hyQRHolNBq/FjJdJ2KF9vzGFVC2njd84k/img.png?width=800&amp;amp;height=800&amp;amp;face=0_0_800_800&quot;&gt;&lt;a href=&quot;https://seongtak-yoon.tistory.com/68&quot; target=&quot;_blank&quot; rel=&quot;noopener&quot; data-source-url=&quot;https://seongtak-yoon.tistory.com/68&quot;&gt;
&lt;div class=&quot;og-image&quot; style=&quot;background-image: url('https://scrap.kakaocdn.net/dn/ccHOG4/hyQRIHqDBU/yapFkk1P1rcbdsM5jLJ7uk/img.png?width=800&amp;amp;height=800&amp;amp;face=0_0_800_800,https://scrap.kakaocdn.net/dn/W94TW/hyQRHolNBq/FjJdJ2KF9vzGFVC2njd84k/img.png?width=800&amp;amp;height=800&amp;amp;face=0_0_800_800');&quot;&gt;&amp;nbsp;&lt;/div&gt;
&lt;div class=&quot;og-text&quot;&gt;
&lt;p class=&quot;og-title&quot; data-ke-size=&quot;size16&quot;&gt;[Spring] Stream 형태로 Response 응답 주기 (feat. MyBatis, Observer)&lt;/p&gt;
&lt;p class=&quot;og-desc&quot; data-ke-size=&quot;size16&quot;&gt;환경 Spring Version : 4.2 이상 MySQL + MyBatis 문서 Http Stream Return은 총 3가지가 있는듯하다. (Spring 4.2부터) - ResponseBodyEmitter : https://docs.spring.io/autorepo/docs/spring-framework/4.3.5.RELEASE/javadoc-api/org/springframework/w&lt;/p&gt;
&lt;p class=&quot;og-host&quot; data-ke-size=&quot;size16&quot;&gt;seongtak-yoon.tistory.com&lt;/p&gt;
&lt;/div&gt;
&lt;/a&gt;&lt;/figure&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;</description>
      <category>개발/Spring</category>
      <category>GC</category>
      <category>heap</category>
      <category>jconsole</category>
      <category>jvm</category>
      <category>Memory</category>
      <category>SpringBoot</category>
      <category>Stream</category>
      <author>joon95</author>
      <guid isPermaLink="true">https://flowlog.tistory.com/98</guid>
      <comments>https://flowlog.tistory.com/98#entry98comment</comments>
      <pubDate>Tue, 13 Dec 2022 00:12:08 +0900</pubDate>
    </item>
    <item>
      <title>[Trouble Shooting] Spring HeapDump 설정 및 분석</title>
      <link>https://flowlog.tistory.com/97</link>
      <description>&lt;p data-ke-size=&quot;size16&quot;&gt;신규 구축 프로젝트 중 erp application 서비스가 자꾸 죽는다고, ..&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;환경은 쿠버네티스 기반인데 &lt;b&gt;OOM 떨어지게 개발&lt;/b&gt;해놓고 '나'한테 전화가 쏟아진다.&lt;/p&gt;
&lt;figure contenteditable=&quot;false&quot; data-ke-type=&quot;emoticon&quot; data-ke-align=&quot;alignCenter&quot; data-emoticon-type=&quot;friends1&quot; data-emoticon-name=&quot;045&quot; data-emoticon-isanimation=&quot;false&quot; data-emoticon-src=&quot;https://t1.daumcdn.net/keditor/emoticon/friends1/large/045.gif&quot;&gt;&lt;img src=&quot;https://t1.daumcdn.net/keditor/emoticon/friends1/large/045.gif&quot; width=&quot;150&quot; /&gt;&lt;/figure&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;인프라 명목을 단정짓기위해 분석을 시작하고 팩트로 조져보자!&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;h2 data-ke-size=&quot;size26&quot;&gt;dump 경로 및 gc로그 설정&lt;/h2&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;먼저 heapdump 옵션과 gc log 설정을 하여 모니터링 하기 위해&amp;nbsp;deployment yaml 에 설정을 추가해주었다.&lt;/p&gt;
&lt;pre id=&quot;code_1670847638240&quot; class=&quot;yaml&quot; data-ke-language=&quot;highlight.js&quot; data-ke-type=&quot;codeblock&quot;&gt;&lt;code&gt;spec:
  template:
    containers:
    - args:
      - -XX:+HeapDumpOnOutOfMemoryError
      - -XX:HeapDumpPath=/덤프파일경로/
      - -XX:+PrintGCDetails
      - -verbose:gc
      - -Xloggc:/가비지컬렉터로그경로/gc.log:time,level,tags
      - -jar
      - -Dspring.profiles.active=prod
      - 실행될애플리케이션.jar
      - -javaagent:dd-java-agent.jar
      command:
      - java&lt;/code&gt;&lt;/pre&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;h2 data-ke-size=&quot;size26&quot;&gt;gc.log 모니터링&lt;/h2&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;개발사에게 대용량 쿼리요청시 잘 처리해달라고 그렇게 말했으나... 역시나.... 여러 페이지에서 옵션으로 [전체조회] 버튼이 있었는데&amp;nbsp;29만 row의 데이터를 조회하는 페이지에서 눌러보니&amp;nbsp;&lt;b&gt;memory가 미친듯이 올라가며&lt;i&gt;&lt;u&gt; full gc&lt;/u&gt;&lt;/i&gt;가 수행되는걸&lt;/b&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;HeapDump 분석&lt;/h2&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;heap dump가 떨어져 바로 파일을 옮겨 분석을 해보았다.(파일 사이즈는 약 4.2GB)&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;분석 프로그램은 &lt;b&gt;Eclipse MemoryAnalyzer&lt;/b&gt; 를 이용하였다.&lt;/p&gt;
&lt;p&gt;&lt;figure class=&quot;imageblock alignCenter&quot; data-ke-mobileStyle=&quot;widthOrigin&quot; data-origin-width=&quot;1220&quot; data-origin-height=&quot;697&quot;&gt;&lt;span data-url=&quot;https://blog.kakaocdn.net/dn/cBNyHN/btrTuByYlvi/ZZyL15RPlbkqoBXANntSZk/img.png&quot; data-phocus=&quot;https://blog.kakaocdn.net/dn/cBNyHN/btrTuByYlvi/ZZyL15RPlbkqoBXANntSZk/img.png&quot;&gt;&lt;img src=&quot;https://blog.kakaocdn.net/dn/cBNyHN/btrTuByYlvi/ZZyL15RPlbkqoBXANntSZk/img.png&quot; srcset=&quot;https://img1.daumcdn.net/thumb/R1280x0/?scode=mtistory2&amp;fname=https%3A%2F%2Fblog.kakaocdn.net%2Fdn%2FcBNyHN%2FbtrTuByYlvi%2FZZyL15RPlbkqoBXANntSZk%2Fimg.png&quot; onerror=&quot;this.onerror=null; this.src='//t1.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;692&quot; height=&quot;395&quot; data-origin-width=&quot;1220&quot; data-origin-height=&quot;697&quot;/&gt;&lt;/span&gt;&lt;/figure&gt;
&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;tomcat threads 1.6 G / mysql Result Set 1.5 GB 영역을 사용했다.&lt;/p&gt;
&lt;p&gt;&lt;figure class=&quot;imageblock alignCenter&quot; data-ke-mobileStyle=&quot;widthOrigin&quot; data-origin-width=&quot;928&quot; data-origin-height=&quot;1255&quot;&gt;&lt;span data-url=&quot;https://blog.kakaocdn.net/dn/bZvT4R/btrTu3vkxNl/zX57JhCe7yUQphbL7o6vQk/img.png&quot; data-phocus=&quot;https://blog.kakaocdn.net/dn/bZvT4R/btrTu3vkxNl/zX57JhCe7yUQphbL7o6vQk/img.png&quot;&gt;&lt;img src=&quot;https://blog.kakaocdn.net/dn/bZvT4R/btrTu3vkxNl/zX57JhCe7yUQphbL7o6vQk/img.png&quot; srcset=&quot;https://img1.daumcdn.net/thumb/R1280x0/?scode=mtistory2&amp;fname=https%3A%2F%2Fblog.kakaocdn.net%2Fdn%2FbZvT4R%2FbtrTu3vkxNl%2FzX57JhCe7yUQphbL7o6vQk%2Fimg.png&quot; onerror=&quot;this.onerror=null; this.src='//t1.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;575&quot; height=&quot;778&quot; data-origin-width=&quot;928&quot; data-origin-height=&quot;1255&quot;/&gt;&lt;/span&gt;&lt;/figure&gt;
&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;각 영역의 설명을 보니 결국 select에서 문제인 것 같다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;Hint 1 은 1번 문제가 2번과 관련이 있을 수 있다는 내용이다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;그럼 이제 더 상세하게 봐보자.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;최상위 Object에서 &lt;b&gt;List Objects &amp;gt; with outgoing&lt;/b&gt;을 눌러보니 아래와 같은 정보가 나온다&lt;/p&gt;
&lt;p&gt;&lt;figure class=&quot;imageblock alignCenter&quot; data-ke-mobileStyle=&quot;widthOrigin&quot; data-origin-width=&quot;1247&quot; data-origin-height=&quot;1473&quot;&gt;&lt;span data-url=&quot;https://blog.kakaocdn.net/dn/ox8UW/btrTu2b6DHG/S0ccP5AKlw5yrmVaTErVxk/img.png&quot; data-phocus=&quot;https://blog.kakaocdn.net/dn/ox8UW/btrTu2b6DHG/S0ccP5AKlw5yrmVaTErVxk/img.png&quot;&gt;&lt;img src=&quot;https://blog.kakaocdn.net/dn/ox8UW/btrTu2b6DHG/S0ccP5AKlw5yrmVaTErVxk/img.png&quot; srcset=&quot;https://img1.daumcdn.net/thumb/R1280x0/?scode=mtistory2&amp;fname=https%3A%2F%2Fblog.kakaocdn.net%2Fdn%2Fox8UW%2FbtrTu2b6DHG%2FS0ccP5AKlw5yrmVaTErVxk%2Fimg.png&quot; onerror=&quot;this.onerror=null; this.src='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png'; this.srcset='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png';&quot; loading=&quot;lazy&quot; width=&quot;677&quot; height=&quot;800&quot; data-origin-width=&quot;1247&quot; data-origin-height=&quot;1473&quot;/&gt;&lt;/span&gt;&lt;/figure&gt;
&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;rowData들을 몇개 찍어보면 테이블명과 컬럼명들도 확인할 수 있다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;h2 data-ke-size=&quot;size26&quot;&gt;마무리&lt;/h2&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;1.5기가의 heap을 사용하게 된 경위를 이렇게 찾을 수 있었다.&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;mybatis를 사용할 때&amp;nbsp;어떻게 개선할 수 있는지 개인적으로 프로젝트를 해봐야겠다.&lt;/p&gt;</description>
      <category>엔지니어링/기타</category>
      <category>heap</category>
      <category>HeapDump</category>
      <category>spring</category>
      <category>덤프분석</category>
      <category>분석</category>
      <category>트러블슈팅</category>
      <author>joon95</author>
      <guid isPermaLink="true">https://flowlog.tistory.com/97</guid>
      <comments>https://flowlog.tistory.com/97#entry97comment</comments>
      <pubDate>Mon, 12 Dec 2022 21:39:51 +0900</pubDate>
    </item>
    <item>
      <title>[Gradle] build Error - validity check failed</title>
      <link>https://flowlog.tistory.com/96</link>
      <description>&lt;p data-ke-size=&quot;size16&quot;&gt;얼마전에 NCP(Naver Cloud Platform) 프로젝트 중 개발사에서 Gradle 기반의 springMVC &lt;b&gt;이미지를 빌드&lt;/b&gt; 에러가 난다고 연락이 왔다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;u&gt;&lt;b&gt;&lt;span style=&quot;background-color: #ffffff; color: #222222;&quot;&gt;PKIX path validation failed: java.security.cert.CertPathValidatorException: validity check failed&lt;/span&gt;&lt;/b&gt;&lt;/u&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;개발사 말로는 local 환경에서는 정상적으로 라이브러리를 가져올 수 있으나, Naver 빌드시 보안상 오류가 아니냐고 해서 확인 요청이 온 것이다.&lt;/p&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;적으로는 라이브러리 사이트에서 tls 인증서가 만료가 된 상황이였다.&lt;/p&gt;
&lt;p&gt;&lt;figure class=&quot;imageblock alignCenter&quot; data-ke-mobileStyle=&quot;widthOrigin&quot; data-origin-width=&quot;917&quot; data-origin-height=&quot;820&quot;&gt;&lt;span data-url=&quot;https://blog.kakaocdn.net/dn/kUCVt/btrThkFgL6R/r51k8X3EQypeEvJ4tJ1G30/img.png&quot; data-phocus=&quot;https://blog.kakaocdn.net/dn/kUCVt/btrThkFgL6R/r51k8X3EQypeEvJ4tJ1G30/img.png&quot;&gt;&lt;img src=&quot;https://blog.kakaocdn.net/dn/kUCVt/btrThkFgL6R/r51k8X3EQypeEvJ4tJ1G30/img.png&quot; srcset=&quot;https://img1.daumcdn.net/thumb/R1280x0/?scode=mtistory2&amp;fname=https%3A%2F%2Fblog.kakaocdn.net%2Fdn%2FkUCVt%2FbtrThkFgL6R%2Fr51k8X3EQypeEvJ4tJ1G30%2Fimg.png&quot; onerror=&quot;this.onerror=null; this.src='//t1.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;520&quot; height=&quot;465&quot; data-origin-width=&quot;917&quot; data-origin-height=&quot;820&quot;/&gt;&lt;/span&gt;&lt;/figure&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;필자도 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;보통 개발자들이 로컬환경에서 tls 인증을 false 상태로 하니... 이런일도 생길 수 있다는 것을 새삼 느꼈다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;물론 지금까지 Nexus 를 구축한 폐쇄망 환경에서만 프로젝트를 했어서 감히 생각도 못했던 부분이다..&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;그리고 에러를 찾던 도중, 실제 gradle 명령어가 날린 vm의 인증서를 체크하라는 타 블로그 들도 있어 NCP쪽에 문의해보았는데&amp;nbsp;NCP의 SourceBuild는 cloud에 있는 ubuntu 이미지로 기동하기 때문에 따로 인증서를 등록하는 부분이 없다고 한다. (괘니 NCP욕만했음^^; 이부분은 AzureDevops나 GithubAction의 cloud runner 를 이용한다면 동일한 것 같다.)&lt;/p&gt;</description>
      <category>엔지니어링/기타</category>
      <category>failed</category>
      <category>gradle</category>
      <category>spring</category>
      <category>SpringBoot</category>
      <author>joon95</author>
      <guid isPermaLink="true">https://flowlog.tistory.com/96</guid>
      <comments>https://flowlog.tistory.com/96#entry96comment</comments>
      <pubDate>Sat, 10 Dec 2022 23:08:29 +0900</pubDate>
    </item>
    <item>
      <title>[AzureDevops] CI-CD Pipeline 구축 테스트</title>
      <link>https://flowlog.tistory.com/95</link>
      <description>&lt;p data-ke-size=&quot;size16&quot;&gt;11월 3주간 Github Actions 과 AzureDevops 두 개의 CI-CD Pipeline 구축테스트를 진행하였고&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;Rest 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;AzureDevops 도 GHES와 같이 Private 용도의 Server를 제공하며,&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;별도의 Self-Hosted Runner를 이용할 수있다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;AzureDevops URL은 dev.azure.com/{&lt;b&gt;Organization&lt;/b&gt;}/{&lt;b&gt;Project&lt;/b&gt;} 로 들어갈 수 있다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;h2 data-ke-size=&quot;size26&quot;&gt;기초적인 파이프라인 생성방법&lt;/h2&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;Pipelines 메뉴에 들어가 repository, 배포위치 등을 손쉽게 선택하여 파이프라인의 뼈대를 쉽게 생성할 수 있다.&lt;/p&gt;
&lt;p&gt;&lt;figure class=&quot;imageblock alignCenter&quot; data-ke-mobileStyle=&quot;widthOrigin&quot; data-origin-width=&quot;897&quot; data-origin-height=&quot;599&quot;&gt;&lt;span data-url=&quot;https://blog.kakaocdn.net/dn/bU9umj/btrS1eD5TUb/2pd2efQZl73AKXrcwddacK/img.png&quot; data-phocus=&quot;https://blog.kakaocdn.net/dn/bU9umj/btrS1eD5TUb/2pd2efQZl73AKXrcwddacK/img.png&quot; data-alt=&quot;pipeline Create Git Repository Target&quot;&gt;&lt;img src=&quot;https://blog.kakaocdn.net/dn/bU9umj/btrS1eD5TUb/2pd2efQZl73AKXrcwddacK/img.png&quot; srcset=&quot;https://img1.daumcdn.net/thumb/R1280x0/?scode=mtistory2&amp;fname=https%3A%2F%2Fblog.kakaocdn.net%2Fdn%2FbU9umj%2FbtrS1eD5TUb%2F2pd2efQZl73AKXrcwddacK%2Fimg.png&quot; onerror=&quot;this.onerror=null; this.src='//t1.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;897&quot; height=&quot;599&quot; data-origin-width=&quot;897&quot; data-origin-height=&quot;599&quot;/&gt;&lt;/span&gt;&lt;figcaption&gt;pipeline Create Git Repository Target&lt;/figcaption&gt;
&lt;/figure&gt;
&lt;figure class=&quot;imageblock alignCenter&quot; data-ke-mobileStyle=&quot;widthOrigin&quot; data-origin-width=&quot;677&quot; data-origin-height=&quot;335&quot;&gt;&lt;span data-url=&quot;https://blog.kakaocdn.net/dn/bugQTw/btrS09Jzp4u/0W33Bnzc94VZ8jWYWhw6bk/img.png&quot; data-phocus=&quot;https://blog.kakaocdn.net/dn/bugQTw/btrS09Jzp4u/0W33Bnzc94VZ8jWYWhw6bk/img.png&quot; data-alt=&quot;repository select&quot;&gt;&lt;img src=&quot;https://blog.kakaocdn.net/dn/bugQTw/btrS09Jzp4u/0W33Bnzc94VZ8jWYWhw6bk/img.png&quot; srcset=&quot;https://img1.daumcdn.net/thumb/R1280x0/?scode=mtistory2&amp;fname=https%3A%2F%2Fblog.kakaocdn.net%2Fdn%2FbugQTw%2FbtrS09Jzp4u%2F0W33Bnzc94VZ8jWYWhw6bk%2Fimg.png&quot; onerror=&quot;this.onerror=null; this.src='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png'; this.srcset='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png';&quot; loading=&quot;lazy&quot; width=&quot;677&quot; height=&quot;335&quot; data-origin-width=&quot;677&quot; data-origin-height=&quot;335&quot;/&gt;&lt;/span&gt;&lt;figcaption&gt;repository select&lt;/figcaption&gt;
&lt;/figure&gt;
&lt;figure class=&quot;imageblock alignCenter&quot; data-ke-mobileStyle=&quot;widthOrigin&quot; data-origin-width=&quot;833&quot; data-origin-height=&quot;688&quot;&gt;&lt;span data-url=&quot;https://blog.kakaocdn.net/dn/cW6QIV/btrS0eq4c67/WpiHZjDgTTgq5xOFSG9nj1/img.png&quot; data-phocus=&quot;https://blog.kakaocdn.net/dn/cW6QIV/btrS0eq4c67/WpiHZjDgTTgq5xOFSG9nj1/img.png&quot; data-alt=&quot;Choice Deploy Target&quot;&gt;&lt;img src=&quot;https://blog.kakaocdn.net/dn/cW6QIV/btrS0eq4c67/WpiHZjDgTTgq5xOFSG9nj1/img.png&quot; srcset=&quot;https://img1.daumcdn.net/thumb/R1280x0/?scode=mtistory2&amp;fname=https%3A%2F%2Fblog.kakaocdn.net%2Fdn%2FcW6QIV%2FbtrS0eq4c67%2FWpiHZjDgTTgq5xOFSG9nj1%2Fimg.png&quot; onerror=&quot;this.onerror=null; this.src='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png'; this.srcset='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png';&quot; loading=&quot;lazy&quot; width=&quot;833&quot; height=&quot;688&quot; data-origin-width=&quot;833&quot; data-origin-height=&quot;688&quot;/&gt;&lt;/span&gt;&lt;figcaption&gt;Choice Deploy Target&lt;/figcaption&gt;
&lt;/figure&gt;
&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;사용자의 구독에 운영중인 자원들을 손쉽게 선택할 수 있다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;필자는 AKS 에 배포할 것으로 Deploy to Azure Kubernetes Service 를 선택한 뒤, 구독정보, 클러스터이름, 네임스페이스 지정, 이미지이름 등을 선택을 하면, Review 화면에서 자동생성된 pipeline을 볼 수 있게된다.&lt;/p&gt;
&lt;p&gt;&lt;figure class=&quot;imageblock alignCenter&quot; data-ke-mobileStyle=&quot;widthOrigin&quot; data-origin-width=&quot;982&quot; data-origin-height=&quot;1180&quot;&gt;&lt;span data-url=&quot;https://blog.kakaocdn.net/dn/blvVpn/btrS0LhRhoo/n1P1kzYv0KR2AZmI8TEB81/img.png&quot; data-phocus=&quot;https://blog.kakaocdn.net/dn/blvVpn/btrS0LhRhoo/n1P1kzYv0KR2AZmI8TEB81/img.png&quot; data-alt=&quot;Auto Configuration Pipeline&quot;&gt;&lt;img src=&quot;https://blog.kakaocdn.net/dn/blvVpn/btrS0LhRhoo/n1P1kzYv0KR2AZmI8TEB81/img.png&quot; srcset=&quot;https://img1.daumcdn.net/thumb/R1280x0/?scode=mtistory2&amp;fname=https%3A%2F%2Fblog.kakaocdn.net%2Fdn%2FblvVpn%2FbtrS0LhRhoo%2Fn1P1kzYv0KR2AZmI8TEB81%2Fimg.png&quot; onerror=&quot;this.onerror=null; this.src='//t1.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;982&quot; height=&quot;1180&quot; data-origin-width=&quot;982&quot; data-origin-height=&quot;1180&quot;/&gt;&lt;/span&gt;&lt;figcaption&gt;Auto Configuration Pipeline&lt;/figcaption&gt;
&lt;/figure&gt;
&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;이렇게 기본적으로 소스 빌드/푸쉬가 프로세스를 확인할 수 있다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;이제 입맛에 맞게 수정해보자.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;h2 data-ke-size=&quot;size26&quot;&gt;파일 위치&lt;/h2&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;생성을 하면 기본적으로 최상단('/')에 &lt;b&gt;azure-pipelines.yml &lt;/b&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;실행 조건 (trigger)&lt;/h2&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;소스 코드 상단에 trigger 옵션이 자동으로 지정되어 main 브랜치에 소스변경 이벤트가 감지되면 자동으로 파이프라인이 실행된다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;추가적으로 save를 한 뒤 Edit Pipeline을 누른 후, 우측 상단에 [...] 버튼을 클릭하여 Triggers 를 눌러보면,&lt;/p&gt;
&lt;p&gt;&lt;figure class=&quot;imageblock alignCenter&quot; data-ke-mobileStyle=&quot;widthOrigin&quot; data-origin-width=&quot;257&quot; data-origin-height=&quot;233&quot;&gt;&lt;span data-url=&quot;https://blog.kakaocdn.net/dn/cD52JG/btrSWykhhKE/wbBuda0mvcOIRQqXLZKoS1/img.png&quot; data-phocus=&quot;https://blog.kakaocdn.net/dn/cD52JG/btrSWykhhKE/wbBuda0mvcOIRQqXLZKoS1/img.png&quot;&gt;&lt;img src=&quot;https://blog.kakaocdn.net/dn/cD52JG/btrSWykhhKE/wbBuda0mvcOIRQqXLZKoS1/img.png&quot; srcset=&quot;https://img1.daumcdn.net/thumb/R1280x0/?scode=mtistory2&amp;fname=https%3A%2F%2Fblog.kakaocdn.net%2Fdn%2FcD52JG%2FbtrSWykhhKE%2FwbBuda0mvcOIRQqXLZKoS1%2Fimg.png&quot; onerror=&quot;this.onerror=null; this.src='//t1.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;257&quot; height=&quot;233&quot; data-origin-width=&quot;257&quot; data-origin-height=&quot;233&quot;/&gt;&lt;/span&gt;&lt;/figure&gt;
&lt;figure class=&quot;imageblock alignCenter&quot; data-ke-mobileStyle=&quot;widthOrigin&quot; data-origin-width=&quot;509&quot; data-origin-height=&quot;232&quot;&gt;&lt;span data-url=&quot;https://blog.kakaocdn.net/dn/cm3Eu0/btrS09W9yoo/EXWXNiSq70jxquk1KLUl9k/img.png&quot; data-phocus=&quot;https://blog.kakaocdn.net/dn/cm3Eu0/btrS09W9yoo/EXWXNiSq70jxquk1KLUl9k/img.png&quot;&gt;&lt;img src=&quot;https://blog.kakaocdn.net/dn/cm3Eu0/btrS09W9yoo/EXWXNiSq70jxquk1KLUl9k/img.png&quot; srcset=&quot;https://img1.daumcdn.net/thumb/R1280x0/?scode=mtistory2&amp;fname=https%3A%2F%2Fblog.kakaocdn.net%2Fdn%2Fcm3Eu0%2FbtrS09W9yoo%2FEXWXNiSq70jxquk1KLUl9k%2Fimg.png&quot; onerror=&quot;this.onerror=null; this.src='//t1.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;509&quot; height=&quot;232&quot; data-origin-width=&quot;509&quot; data-origin-height=&quot;232&quot;/&gt;&lt;/span&gt;&lt;/figure&gt;
&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;기본적으로 Triggers가 활성화 되어 있기 때문에 불필요하면 여기서 Disable 해주면된다.&lt;/p&gt;
&lt;p&gt;&lt;figure class=&quot;imageblock alignCenter&quot; data-ke-mobileStyle=&quot;widthOrigin&quot; data-origin-width=&quot;961&quot; data-origin-height=&quot;186&quot;&gt;&lt;span data-url=&quot;https://blog.kakaocdn.net/dn/cd7PUb/btrSYB1R9Dr/xcStwEURkxDVlcuz6dyd0k/img.png&quot; data-phocus=&quot;https://blog.kakaocdn.net/dn/cd7PUb/btrSYB1R9Dr/xcStwEURkxDVlcuz6dyd0k/img.png&quot; data-alt=&quot;Trigger Disable&quot;&gt;&lt;img src=&quot;https://blog.kakaocdn.net/dn/cd7PUb/btrSYB1R9Dr/xcStwEURkxDVlcuz6dyd0k/img.png&quot; srcset=&quot;https://img1.daumcdn.net/thumb/R1280x0/?scode=mtistory2&amp;fname=https%3A%2F%2Fblog.kakaocdn.net%2Fdn%2Fcd7PUb%2FbtrSYB1R9Dr%2FxcStwEURkxDVlcuz6dyd0k%2Fimg.png&quot; onerror=&quot;this.onerror=null; this.src='//t1.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;961&quot; height=&quot;186&quot; data-origin-width=&quot;961&quot; data-origin-height=&quot;186&quot;/&gt;&lt;/span&gt;&lt;figcaption&gt;Trigger Disable&lt;/figcaption&gt;
&lt;/figure&gt;
&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;h2 data-ke-size=&quot;size26&quot;&gt;실행시 변수 입력&lt;/h2&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;파이프라인 실행 당연히 배포할 태그명이나 브랜치정보들을 변수화하여 받아와야하니 설정해보자.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;우측 상단에 Variables 버튼을 누르면 변수를 정의할 수 있는 우측 창이 하나 나온다.&lt;/p&gt;
&lt;p&gt;&lt;figure class=&quot;imageblock alignCenter&quot; data-ke-mobileStyle=&quot;widthOrigin&quot; data-origin-width=&quot;216&quot; data-origin-height=&quot;304&quot;&gt;&lt;span data-url=&quot;https://blog.kakaocdn.net/dn/bY04X0/btrS0840zRS/Rh6sZeDFZKuCTkqk0M0lC1/img.png&quot; data-phocus=&quot;https://blog.kakaocdn.net/dn/bY04X0/btrS0840zRS/Rh6sZeDFZKuCTkqk0M0lC1/img.png&quot;&gt;&lt;img src=&quot;https://blog.kakaocdn.net/dn/bY04X0/btrS0840zRS/Rh6sZeDFZKuCTkqk0M0lC1/img.png&quot; srcset=&quot;https://img1.daumcdn.net/thumb/R1280x0/?scode=mtistory2&amp;fname=https%3A%2F%2Fblog.kakaocdn.net%2Fdn%2FbY04X0%2FbtrS0840zRS%2FRh6sZeDFZKuCTkqk0M0lC1%2Fimg.png&quot; onerror=&quot;this.onerror=null; this.src='//t1.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;216&quot; height=&quot;304&quot; data-origin-width=&quot;216&quot; data-origin-height=&quot;304&quot;/&gt;&lt;/span&gt;&lt;/figure&gt;
&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;New variable 을 클릭해 변수를 생성해보자.&lt;/p&gt;
&lt;p&gt;&lt;figure class=&quot;imageblock alignCenter&quot; data-ke-mobileStyle=&quot;widthOrigin&quot; data-origin-width=&quot;465&quot; data-origin-height=&quot;513&quot;&gt;&lt;span data-url=&quot;https://blog.kakaocdn.net/dn/nHzsu/btrSZZVfx80/mUDey7Q5STkFFFWNt3lNg1/img.png&quot; data-phocus=&quot;https://blog.kakaocdn.net/dn/nHzsu/btrSZZVfx80/mUDey7Q5STkFFFWNt3lNg1/img.png&quot;&gt;&lt;img src=&quot;https://blog.kakaocdn.net/dn/nHzsu/btrSZZVfx80/mUDey7Q5STkFFFWNt3lNg1/img.png&quot; srcset=&quot;https://img1.daumcdn.net/thumb/R1280x0/?scode=mtistory2&amp;fname=https%3A%2F%2Fblog.kakaocdn.net%2Fdn%2FnHzsu%2FbtrSZZVfx80%2FmUDey7Q5STkFFFWNt3lNg1%2Fimg.png&quot; onerror=&quot;this.onerror=null; this.src='//t1.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;465&quot; height=&quot;513&quot; data-origin-width=&quot;465&quot; data-origin-height=&quot;513&quot;/&gt;&lt;/span&gt;&lt;/figure&gt;
&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;변수명은 'MY_TAG'&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;default 값은 sample-001 로 주었다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;그리고 Let users override this value when running this pipeline 버튼을 클릭해 파이프라인 실행시 값을 덮어쓸 수 있게 해주자.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;추가로 밑에 script에서 사용할 수 있는 방법이 나오니 변수 확인용으로 찍어봐도 된다.&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;CI pipeline (Spring App)&lt;/h2&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;이후 파이프라인 구성은 정말 쉽게 할 수 있도록 되어있다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;예를 들면 필자가 maven 기반의 Springboot Application 을 git에 올려두어서 소스를 package 하는 명령어를 사용하고 싶다면, 우측 Tasks에서 검색해보면 거의 모든(?) 파이프라인이 이미 구성되어있어 이름만 맞춰주면 된다.&lt;/p&gt;
&lt;p&gt;&lt;figure class=&quot;imageblock alignCenter&quot; data-ke-mobileStyle=&quot;widthOrigin&quot; data-origin-width=&quot;358&quot; data-origin-height=&quot;223&quot;&gt;&lt;span data-url=&quot;https://blog.kakaocdn.net/dn/cjdfRS/btrSZSWnnM6/rH4aU9Zc80sle09cze3xcK/img.png&quot; data-phocus=&quot;https://blog.kakaocdn.net/dn/cjdfRS/btrSZSWnnM6/rH4aU9Zc80sle09cze3xcK/img.png&quot; data-alt=&quot;AzureDevops Pipelien Tasks&quot;&gt;&lt;img src=&quot;https://blog.kakaocdn.net/dn/cjdfRS/btrSZSWnnM6/rH4aU9Zc80sle09cze3xcK/img.png&quot; srcset=&quot;https://img1.daumcdn.net/thumb/R1280x0/?scode=mtistory2&amp;fname=https%3A%2F%2Fblog.kakaocdn.net%2Fdn%2FcjdfRS%2FbtrSZSWnnM6%2FrH4aU9Zc80sle09cze3xcK%2Fimg.png&quot; onerror=&quot;this.onerror=null; this.src='//t1.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;358&quot; height=&quot;223&quot; data-origin-width=&quot;358&quot; data-origin-height=&quot;223&quot;/&gt;&lt;/span&gt;&lt;figcaption&gt;AzureDevops Pipelien Tasks&lt;/figcaption&gt;
&lt;/figure&gt;
&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;# 만들어진 샘플&lt;/p&gt;
&lt;pre id=&quot;code_1670327700128&quot; class=&quot;yaml&quot; data-ke-language=&quot;highlight.js&quot; data-ke-type=&quot;codeblock&quot;&gt;&lt;code&gt;resources:
- repo: self

variables:
  dockerRegistryServiceConnection: '*************'
  imageRepository: 'spring-app'
  containerRegistry: '**********.azurecr.io'
  dockerfilePath: '**/Dockerfile'
  imagePullSecret: '************acr34208459-auth'
  vmImageName: 'ubuntu-latest'
  branch: 'spring'

stages:
- stage: Build
  displayName: Build stage
  jobs:
  - job: Build
    displayName: Build
    pool:
      vmImage: $(vmImageName)
    steps:
    
    - task: Maven@4
      displayName: Maven Build
      inputs:
        mavenPomFile: 'pom.xml'
        publishJUnitResults: true
        testResultsFiles: '**/surefire-reports/TEST-*.xml'
        javaHomeOption: 'JDKVersion'
        mavenVersionOption: 'Default'
        mavenAuthenticateFeed: false
        effectivePomSkip: false
        sonarQubeRunAnalysis: false
        
    - task: Docker@2
      displayName: Build and push an image to container registry
      inputs:
        command: buildAndPush
        repository: $(imageRepository)
        dockerfile: $(dockerfilePath)
        containerRegistry: $(dockerRegistryServiceConnection)
        tags: |
          $(MY_TAG)&lt;/code&gt;&lt;/pre&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;h2 data-ke-size=&quot;size26&quot;&gt;파이프라인 실행&lt;/h2&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;이제 파이프라인을 실행해보자!&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;run 즉시엔 클라우드서버의 image를 사용하기에 잠시 Not started 상태였다가 기다리면 시작된다.&lt;/p&gt;
&lt;p&gt;&lt;figure class=&quot;imageblock alignCenter&quot; data-ke-mobileStyle=&quot;widthOrigin&quot; data-origin-width=&quot;228&quot; data-origin-height=&quot;79&quot;&gt;&lt;span data-url=&quot;https://blog.kakaocdn.net/dn/cfHzhP/btrSWxloEVG/aNJbRrdFkeVrkIxBrxtlu1/img.png&quot; data-phocus=&quot;https://blog.kakaocdn.net/dn/cfHzhP/btrSWxloEVG/aNJbRrdFkeVrkIxBrxtlu1/img.png&quot;&gt;&lt;img src=&quot;https://blog.kakaocdn.net/dn/cfHzhP/btrSWxloEVG/aNJbRrdFkeVrkIxBrxtlu1/img.png&quot; srcset=&quot;https://img1.daumcdn.net/thumb/R1280x0/?scode=mtistory2&amp;fname=https%3A%2F%2Fblog.kakaocdn.net%2Fdn%2FcfHzhP%2FbtrSWxloEVG%2FaNJbRrdFkeVrkIxBrxtlu1%2Fimg.png&quot; onerror=&quot;this.onerror=null; this.src='//t1.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;228&quot; height=&quot;79&quot; data-origin-width=&quot;228&quot; data-origin-height=&quot;79&quot;/&gt;&lt;/span&gt;&lt;/figure&gt;
&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;Stage를 클릭하여 들어가보면 각 job마다 log가 찍힌다.&lt;/p&gt;
&lt;p&gt;&lt;figure class=&quot;imageblock alignCenter&quot; data-ke-mobileStyle=&quot;widthOrigin&quot; data-origin-width=&quot;266&quot; data-origin-height=&quot;369&quot;&gt;&lt;span data-url=&quot;https://blog.kakaocdn.net/dn/wcTcw/btrS09ixBmL/OkeFCX8vnS7gby1Bh0ULB1/img.png&quot; data-phocus=&quot;https://blog.kakaocdn.net/dn/wcTcw/btrS09ixBmL/OkeFCX8vnS7gby1Bh0ULB1/img.png&quot;&gt;&lt;img src=&quot;https://blog.kakaocdn.net/dn/wcTcw/btrS09ixBmL/OkeFCX8vnS7gby1Bh0ULB1/img.png&quot; srcset=&quot;https://img1.daumcdn.net/thumb/R1280x0/?scode=mtistory2&amp;fname=https%3A%2F%2Fblog.kakaocdn.net%2Fdn%2FwcTcw%2FbtrS09ixBmL%2FOkeFCX8vnS7gby1Bh0ULB1%2Fimg.png&quot; onerror=&quot;this.onerror=null; this.src='//t1.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;266&quot; height=&quot;369&quot; data-origin-width=&quot;266&quot; data-origin-height=&quot;369&quot;/&gt;&lt;/span&gt;&lt;/figure&gt;
&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;필자가 변수에 집어넣은 값으로 실제 이미지가 ACR(Azure Container Registry)에 푸쉬되었다.&lt;/p&gt;
&lt;p&gt;&lt;figure class=&quot;imageblock alignCenter&quot; data-ke-mobileStyle=&quot;widthOrigin&quot; data-origin-width=&quot;676&quot; data-origin-height=&quot;346&quot;&gt;&lt;span data-url=&quot;https://blog.kakaocdn.net/dn/zA8ui/btrSYC0PTT2/Y7bhP9A8wYVIKZWSAdUJok/img.png&quot; data-phocus=&quot;https://blog.kakaocdn.net/dn/zA8ui/btrSYC0PTT2/Y7bhP9A8wYVIKZWSAdUJok/img.png&quot;&gt;&lt;img src=&quot;https://blog.kakaocdn.net/dn/zA8ui/btrSYC0PTT2/Y7bhP9A8wYVIKZWSAdUJok/img.png&quot; srcset=&quot;https://img1.daumcdn.net/thumb/R1280x0/?scode=mtistory2&amp;fname=https%3A%2F%2Fblog.kakaocdn.net%2Fdn%2FzA8ui%2FbtrSYC0PTT2%2FY7bhP9A8wYVIKZWSAdUJok%2Fimg.png&quot; onerror=&quot;this.onerror=null; this.src='//t1.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;676&quot; height=&quot;346&quot; data-origin-width=&quot;676&quot; data-origin-height=&quot;346&quot;/&gt;&lt;/span&gt;&lt;/figure&gt;
&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;h2 data-ke-size=&quot;size26&quot;&gt;Rest API&lt;/h2&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;span&gt;azureDevOps &lt;/span&gt;는 &lt;span&gt;api &lt;/span&gt;버전이 변경되기 때문에 &lt;span&gt;rest api &lt;/span&gt;사용시 꼭 &lt;b&gt;버전을 지정&lt;/b&gt;해야한다&lt;span&gt;.&lt;/span&gt;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;h3 data-ke-size=&quot;size23&quot;&gt;PAT(Personal Access Token)&lt;/h3&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;먼저 PAT(Personal Access Token)을 발급 받아야 한다.&lt;/p&gt;
&lt;p&gt;&lt;figure class=&quot;imageblock alignCenter&quot; data-ke-mobileStyle=&quot;widthOrigin&quot; data-origin-width=&quot;425&quot; data-origin-height=&quot;633&quot;&gt;&lt;span data-url=&quot;https://blog.kakaocdn.net/dn/cFzmFN/btrS1Lu9vtB/nkFvk2nrwDfdOgn515yen1/img.png&quot; data-phocus=&quot;https://blog.kakaocdn.net/dn/cFzmFN/btrS1Lu9vtB/nkFvk2nrwDfdOgn515yen1/img.png&quot;&gt;&lt;img src=&quot;https://blog.kakaocdn.net/dn/cFzmFN/btrS1Lu9vtB/nkFvk2nrwDfdOgn515yen1/img.png&quot; srcset=&quot;https://img1.daumcdn.net/thumb/R1280x0/?scode=mtistory2&amp;fname=https%3A%2F%2Fblog.kakaocdn.net%2Fdn%2FcFzmFN%2FbtrS1Lu9vtB%2FnkFvk2nrwDfdOgn515yen1%2Fimg.png&quot; onerror=&quot;this.onerror=null; this.src='//t1.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;425&quot; height=&quot;633&quot; data-origin-width=&quot;425&quot; data-origin-height=&quot;633&quot;/&gt;&lt;/span&gt;&lt;/figure&gt;
&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;토큰을 발행 해준다. pipeline 쪽 권한을 부여하였다.&lt;/p&gt;
&lt;p&gt;&lt;figure class=&quot;imageblock alignCenter&quot; data-ke-mobileStyle=&quot;widthOrigin&quot; data-origin-width=&quot;1763&quot; data-origin-height=&quot;884&quot;&gt;&lt;span data-url=&quot;https://blog.kakaocdn.net/dn/pZHUw/btrSW6g0v34/khI7FXwPBZoIKiWRwGexjk/img.png&quot; data-phocus=&quot;https://blog.kakaocdn.net/dn/pZHUw/btrSW6g0v34/khI7FXwPBZoIKiWRwGexjk/img.png&quot;&gt;&lt;img src=&quot;https://blog.kakaocdn.net/dn/pZHUw/btrSW6g0v34/khI7FXwPBZoIKiWRwGexjk/img.png&quot; srcset=&quot;https://img1.daumcdn.net/thumb/R1280x0/?scode=mtistory2&amp;fname=https%3A%2F%2Fblog.kakaocdn.net%2Fdn%2FpZHUw%2FbtrSW6g0v34%2FkhI7FXwPBZoIKiWRwGexjk%2Fimg.png&quot; onerror=&quot;this.onerror=null; this.src='//t1.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;1763&quot; height=&quot;884&quot; data-origin-width=&quot;1763&quot; data-origin-height=&quot;884&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;호출 Data 작성&lt;/h3&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;span&gt;URL : dev.azure.com/{&lt;/span&gt;&lt;b&gt;조직&lt;/b&gt;&lt;span&gt;}/{&lt;/span&gt;&lt;b&gt;프로젝트명&lt;/b&gt;&lt;span&gt;}/_apis/pipelines/{&lt;b&gt;pipeline_id&lt;/b&gt;}/runs?api-version=7.0&lt;/span&gt;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;span&gt;Header Authorization&lt;/span&gt;은 &lt;span&gt;basic &lt;/span&gt;방식으로 &lt;b&gt;&lt;span&gt;username:password &lt;/span&gt;&lt;/b&gt;를 &lt;span&gt;base64 &lt;/span&gt;인코딩한 값이다&lt;span&gt;.&lt;/span&gt;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;body 에는 branch, variable 을 json 규격에 맞게 보내면 된다.&lt;/p&gt;
&lt;pre id=&quot;code_1670370898367&quot; class=&quot;java&quot; data-ke-language=&quot;java&quot; data-ke-type=&quot;codeblock&quot;&gt;&lt;code&gt;curl --location --request POST \
'https://dev.azure.com/joon95/**************-poc/_apis/pipelines/4/runs?api-version=7.0' \
--header 'Content-Type: application/json' \
--header 'Authorization: Basic ***************************' \
--data-raw '{
    &quot;resources&quot;: {
        &quot;repositories&quot;: {
            &quot;self&quot;: {
                &quot;refName&quot;: &quot;refs/heads/main&quot;
            }
        }
    },
    &quot;variables&quot;: {
        &quot;MY_TAG&quot;: {
            &quot;isSecret&quot;: false,
            &quot;value&quot;: &quot;AZ-DEVOPS-POSTMAN-22.11.28&quot;
        }
    }
}'&lt;/code&gt;&lt;/pre&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;이렇게 작성하여 요청을해보면 내가 요청한 MY_TAG 값으로 이미지가 빌드/푸쉬 되는걸 확인할 수 있다.&lt;/p&gt;
&lt;p&gt;&lt;figure class=&quot;imageblock alignCenter&quot; data-ke-mobileStyle=&quot;widthOrigin&quot; data-filename=&quot;blob&quot; data-origin-width=&quot;404&quot; data-origin-height=&quot;368&quot;&gt;&lt;span data-url=&quot;https://blog.kakaocdn.net/dn/bKKi6q/btrS1eRQWz4/JgKAV4RvWKkbf5tiNhXHF1/img.png&quot; data-phocus=&quot;https://blog.kakaocdn.net/dn/bKKi6q/btrS1eRQWz4/JgKAV4RvWKkbf5tiNhXHF1/img.png&quot;&gt;&lt;img src=&quot;https://blog.kakaocdn.net/dn/bKKi6q/btrS1eRQWz4/JgKAV4RvWKkbf5tiNhXHF1/img.png&quot; srcset=&quot;https://img1.daumcdn.net/thumb/R1280x0/?scode=mtistory2&amp;fname=https%3A%2F%2Fblog.kakaocdn.net%2Fdn%2FbKKi6q%2FbtrS1eRQWz4%2FJgKAV4RvWKkbf5tiNhXHF1%2Fimg.png&quot; onerror=&quot;this.onerror=null; this.src='//t1.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;404&quot; height=&quot;368&quot; data-filename=&quot;blob&quot; data-origin-width=&quot;404&quot; data-origin-height=&quot;368&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;/h2&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;이번 설명엔 azureDevops 캡처 이미지가 너무 많아서&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;파이프라인 중간에 &lt;b&gt;사용자가 개입하여 승인/거절 (approve) 프로세스&lt;/b&gt;는 다음 포스팅에 정리하려 한다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;Blue/Green 배포도 잘 정리해 두었으니 곧 포스팅하겠다.&lt;/p&gt;</description>
      <category>엔지니어링/CI-CD</category>
      <category>AzureDevOps</category>
      <category>CI-CD</category>
      <category>DevOps</category>
      <category>pipeline</category>
      <author>joon95</author>
      <guid isPermaLink="true">https://flowlog.tistory.com/95</guid>
      <comments>https://flowlog.tistory.com/95#entry95comment</comments>
      <pubDate>Tue, 6 Dec 2022 21:08:26 +0900</pubDate>
    </item>
    <item>
      <title>[AzureDevops] Image Tag Not working to Pipeline Create</title>
      <link>https://flowlog.tistory.com/94</link>
      <description>&lt;p data-ke-size=&quot;size16&quot;&gt;휴... azureDevops Pipeline 사용 중 이미지버전(Tag)를 변수로 받아와서 배포하는 시나리오에서&lt;br /&gt;자꾸 태그를 가져오지 못하는 현상이 생겼다...&lt;/p&gt;
&lt;p&gt;&lt;figure class=&quot;imageblock alignCenter&quot; data-ke-mobileStyle=&quot;widthOrigin&quot; data-origin-width=&quot;1289&quot; data-origin-height=&quot;42&quot;&gt;&lt;span data-url=&quot;https://blog.kakaocdn.net/dn/bmkBS3/btrSOwTpQJR/yKwm7x2ApSwsnMnPA30rD1/img.png&quot; data-phocus=&quot;https://blog.kakaocdn.net/dn/bmkBS3/btrSOwTpQJR/yKwm7x2ApSwsnMnPA30rD1/img.png&quot;&gt;&lt;img src=&quot;https://blog.kakaocdn.net/dn/bmkBS3/btrSOwTpQJR/yKwm7x2ApSwsnMnPA30rD1/img.png&quot; srcset=&quot;https://img1.daumcdn.net/thumb/R1280x0/?scode=mtistory2&amp;fname=https%3A%2F%2Fblog.kakaocdn.net%2Fdn%2FbmkBS3%2FbtrSOwTpQJR%2FyKwm7x2ApSwsnMnPA30rD1%2Fimg.png&quot; onerror=&quot;this.onerror=null; this.src='//t1.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;1289&quot; height=&quot;42&quot; data-origin-width=&quot;1289&quot; data-origin-height=&quot;42&quot;/&gt;&lt;/span&gt;&lt;/figure&gt;
&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;대체 왜이럴까!!!!!!!!!!&lt;/p&gt;
&lt;figure data-ke-type=&quot;emoticon&quot; data-ke-align=&quot;alignCenter&quot; data-emoticon-type=&quot;friends1&quot; data-emoticon-name=&quot;040&quot; data-emoticon-isanimation=&quot;false&quot; data-emoticon-src=&quot;https://t1.daumcdn.net/keditor/emoticon/friends1/large/040.gif&quot;&gt;&lt;img src=&quot;https://t1.daumcdn.net/keditor/emoticon/friends1/large/040.gif&quot; width=&quot;150&quot; /&gt;&lt;/figure&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;계속 찾던 와중에... 발견해버렸따....&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;azure Devops 에서 pipeline을 생성할 때 데이터를 구독에서 연결되어 있는 자원을 선택할 수 있는데&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;Image Name 부분에 '-' 가 들어간 경우 실제 pipeline.yml 에서 &lt;b&gt;'-'가 삭제되는 현상&lt;/b&gt;....&lt;/p&gt;
&lt;p&gt;&lt;figure class=&quot;imageblock alignCenter&quot; data-ke-mobileStyle=&quot;widthOrigin&quot; data-origin-width=&quot;462&quot; data-origin-height=&quot;558&quot;&gt;&lt;span data-url=&quot;https://blog.kakaocdn.net/dn/bnLb86/btrSXqYYLMY/RfZioeckYh0vGfa4RCUkIK/img.png&quot; data-phocus=&quot;https://blog.kakaocdn.net/dn/bnLb86/btrSXqYYLMY/RfZioeckYh0vGfa4RCUkIK/img.png&quot;&gt;&lt;img src=&quot;https://blog.kakaocdn.net/dn/bnLb86/btrSXqYYLMY/RfZioeckYh0vGfa4RCUkIK/img.png&quot; srcset=&quot;https://img1.daumcdn.net/thumb/R1280x0/?scode=mtistory2&amp;fname=https%3A%2F%2Fblog.kakaocdn.net%2Fdn%2FbnLb86%2FbtrSXqYYLMY%2FRfZioeckYh0vGfa4RCUkIK%2Fimg.png&quot; onerror=&quot;this.onerror=null; this.src='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png'; this.srcset='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png';&quot; loading=&quot;lazy&quot; width=&quot;462&quot; height=&quot;558&quot; data-origin-width=&quot;462&quot; data-origin-height=&quot;558&quot;/&gt;&lt;/span&gt;&lt;/figure&gt;
&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;이렇게 spring-app 이미지이름을 지정했는데&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;b&gt;실제 pipeline.yml 에 선언된 변수는 - 가 자동으로 제거됨;&lt;/b&gt;;;&lt;/p&gt;
&lt;p&gt;&lt;figure class=&quot;imageblock alignCenter&quot; data-ke-mobileStyle=&quot;widthOrigin&quot; data-origin-width=&quot;615&quot; data-origin-height=&quot;180&quot;&gt;&lt;span data-url=&quot;https://blog.kakaocdn.net/dn/RqAoK/btrSUTAXiGB/n1SdLJhIZ1DcZtUs7chX6k/img.png&quot; data-phocus=&quot;https://blog.kakaocdn.net/dn/RqAoK/btrSUTAXiGB/n1SdLJhIZ1DcZtUs7chX6k/img.png&quot;&gt;&lt;img src=&quot;https://blog.kakaocdn.net/dn/RqAoK/btrSUTAXiGB/n1SdLJhIZ1DcZtUs7chX6k/img.png&quot; srcset=&quot;https://img1.daumcdn.net/thumb/R1280x0/?scode=mtistory2&amp;fname=https%3A%2F%2Fblog.kakaocdn.net%2Fdn%2FRqAoK%2FbtrSUTAXiGB%2Fn1SdLJhIZ1DcZtUs7chX6k%2Fimg.png&quot; onerror=&quot;this.onerror=null; this.src='//t1.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;615&quot; height=&quot;180&quot; data-origin-width=&quot;615&quot; data-origin-height=&quot;180&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;figure contenteditable=&quot;false&quot; data-ke-type=&quot;emoticon&quot; data-ke-align=&quot;alignCenter&quot; data-emoticon-type=&quot;friends1&quot; data-emoticon-name=&quot;045&quot; data-emoticon-isanimation=&quot;false&quot; data-emoticon-src=&quot;https://t1.daumcdn.net/keditor/emoticon/friends1/large/045.gif&quot;&gt;&lt;img src=&quot;https://t1.daumcdn.net/keditor/emoticon/friends1/large/045.gif&quot; width=&quot;150&quot; /&gt;&lt;/figure&gt;</description>
      <category>엔지니어링/CI-CD</category>
      <category>AzureDevOps</category>
      <category>cd</category>
      <category>pipeline</category>
      <author>joon95</author>
      <guid isPermaLink="true">https://flowlog.tistory.com/94</guid>
      <comments>https://flowlog.tistory.com/94#entry94comment</comments>
      <pubDate>Mon, 5 Dec 2022 14:06:53 +0900</pubDate>
    </item>
    <item>
      <title>[Github Actions] CI-CD Pipeline 구축 테스트</title>
      <link>https://flowlog.tistory.com/93</link>
      <description>&lt;p data-ke-size=&quot;size16&quot;&gt;11월 3주간 Github Actions과 AzureDevop Pipeline 을 테스트 해보았고&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;Rest API 호출 테스트까지 케이스를 정리해보려 한다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;추가로 &lt;b&gt;Github Enterprise (일명 GHES)&lt;/b&gt; 도 구축하여 이것저것 사용해 보았는데&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;Actions 사용을 위해 별도의 &lt;b&gt;Runner를 기동하는 Host&lt;/b&gt;가 필요했고,&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;기존 Github Marketplace 에서 사용하던 라이브러리를 못 쓰게되어&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;모든 것을 shell script 로 작성해야했다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;(GHES는 폐쇄망 기준으로 만들어져 깃헙 마켓플레이스를 사용하려면&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;GHES서버, 러너서버 모두 아웃바운드 트래픽을 열어주어야 한다고함)&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;자 그럼 그동안 했던 내용을 정리해보자.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;h2 data-ke-size=&quot;size26&quot;&gt;파일 위치&lt;/h2&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;기본적으로 레파지토리 최상위 경로기준 &lt;b&gt;/.github/workflows/*.yml&amp;nbsp;&lt;/b&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;workflow 실행 조건 (on:)&lt;/h2&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;action을 정의하려고 하면 제일먼저 어떤 상황에 사용될지 정의하는 부분이 있다.&lt;/p&gt;
&lt;pre id=&quot;code_1670066244995&quot; class=&quot;yaml&quot; data-ke-language=&quot;highlight.js&quot; data-ke-type=&quot;codeblock&quot;&gt;&lt;code&gt;on:
  pull_request:
    branches: 
      - main
  push:
    branches: 
      - main
  repository_dispatch:
    types: [CI-001]
  workflow_dispatch:
    inputs:
      VERSION:
        required: false
        default: &quot;1.0&quot;
        type: string&lt;/code&gt;&lt;/pre&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;b&gt;push&lt;/b&gt;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;의미 그대로 repository 에 push가 일어났을 때&lt;/p&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;pull_request&lt;/b&gt;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;의미 그대로 repository branch merge 가 일어났을 때&lt;/p&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;repository_dispatch&lt;/b&gt;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;Rest API 를 사용하기 위한 설정.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;필자는 webhook 기능을 작성하기 위해 repository_dispatch 를 사용했고,&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;여기서 정한 types 를 통해 이름을 지정하여 실행하도록 되어 있다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;이때 json 규격의 데이타를 통해 변수를 정의하여 사용할 수 있다.&lt;/p&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;workflow_dispatch &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;이 때 각 변수를 inputs 을 통해 지정할 수 있는데 &lt;b&gt;필수여부, 기본값, 타입&lt;/b&gt;을 지정하여 사용한다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;Actions&amp;gt;작성한액션에 들어가보면 Run workflow 버튼이 생기고,&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;inputs에 정의한 변수를 재정의 할 수 있도록 창이 띄워진다.&lt;/p&gt;
&lt;p&gt;&lt;figure class=&quot;imageblock alignCenter&quot; data-ke-mobileStyle=&quot;widthOrigin&quot; data-origin-width=&quot;1234&quot; data-origin-height=&quot;524&quot;&gt;&lt;span data-url=&quot;https://blog.kakaocdn.net/dn/0La99/btrSJawkUzp/vlTeX72CvJoafJZ5hJSDf1/img.png&quot; data-phocus=&quot;https://blog.kakaocdn.net/dn/0La99/btrSJawkUzp/vlTeX72CvJoafJZ5hJSDf1/img.png&quot; data-alt=&quot;workflow_dispatch 수동 조작&quot;&gt;&lt;img src=&quot;https://blog.kakaocdn.net/dn/0La99/btrSJawkUzp/vlTeX72CvJoafJZ5hJSDf1/img.png&quot; srcset=&quot;https://img1.daumcdn.net/thumb/R1280x0/?scode=mtistory2&amp;fname=https%3A%2F%2Fblog.kakaocdn.net%2Fdn%2F0La99%2FbtrSJawkUzp%2FvlTeX72CvJoafJZ5hJSDf1%2Fimg.png&quot; onerror=&quot;this.onerror=null; this.src='//t1.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;1234&quot; height=&quot;524&quot; data-origin-width=&quot;1234&quot; data-origin-height=&quot;524&quot;/&gt;&lt;/span&gt;&lt;figcaption&gt;workflow_dispatch 수동 조작&lt;/figcaption&gt;
&lt;/figure&gt;
&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;h2 data-ke-size=&quot;size26&quot;&gt;jobs 정의&lt;/h2&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;이제 실행한 job을 정의해야한다.&lt;/p&gt;
&lt;h3 data-ke-size=&quot;size23&quot;&gt;jobs&lt;/h3&gt;
&lt;pre id=&quot;code_1670067813034&quot; class=&quot;yaml&quot; data-ke-language=&quot;highlight.js&quot; data-ke-type=&quot;codeblock&quot;&gt;&lt;code&gt;jobs:
  deployapp:
    runs-on: ubuntu-latest
    steps:
      - uses: actions/checkout@master
        with:
          ref: ${{ github.event.inputs.BRANCH }}
  approveapp:
    runs-on: ubuntu-latest
    needs: deployapp
    environment: joon
    steps:
      - run: echo asked for approval joonhyeok
  promotereject:
    runs-on: ubuntu-latest
    needs: approveapp
    steps:
      - name: Promote App
        run: echo '다음 프로세스~'&lt;/code&gt;&lt;/pre&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;jobs는 큰 실행 단위이며, 나중에 Action을 실행했을 경우 아래그림처럼 step이 보인다.&lt;/p&gt;
&lt;p&gt;&lt;figure class=&quot;imageblock alignCenter&quot; data-ke-mobileStyle=&quot;widthOrigin&quot; data-origin-width=&quot;809&quot; data-origin-height=&quot;54&quot;&gt;&lt;span data-url=&quot;https://blog.kakaocdn.net/dn/byd5c8/btrSIqfgLxR/jzVOKvtdkVERSMuo6m9BL0/img.png&quot; data-phocus=&quot;https://blog.kakaocdn.net/dn/byd5c8/btrSIqfgLxR/jzVOKvtdkVERSMuo6m9BL0/img.png&quot; data-alt=&quot;job&quot;&gt;&lt;img src=&quot;https://blog.kakaocdn.net/dn/byd5c8/btrSIqfgLxR/jzVOKvtdkVERSMuo6m9BL0/img.png&quot; srcset=&quot;https://img1.daumcdn.net/thumb/R1280x0/?scode=mtistory2&amp;fname=https%3A%2F%2Fblog.kakaocdn.net%2Fdn%2Fbyd5c8%2FbtrSIqfgLxR%2FjzVOKvtdkVERSMuo6m9BL0%2Fimg.png&quot; onerror=&quot;this.onerror=null; this.src='//t1.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;809&quot; height=&quot;54&quot; data-origin-width=&quot;809&quot; data-origin-height=&quot;54&quot;/&gt;&lt;/span&gt;&lt;figcaption&gt;job&lt;/figcaption&gt;
&lt;/figure&gt;
&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;b&gt;runs-on: ubuntu-latest&lt;/b&gt;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;이 옵션은 JOB이 실행될 vm을 지정해 준다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;만약 GHES에 구축하였을 경우 self-hosted runner 를 사용하기 때문에 &lt;b&gt;runner의 label&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;steps:&lt;/b&gt;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;이 옵션은 아래에 선언되는 것들이 모두 각각의 step이라 칭하게 된다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;워크플로우를 작동해보면 아래와 같이 분류되어 각각의 항목을 볼 수 있다.&lt;/p&gt;
&lt;p&gt;&lt;figure class=&quot;imageblock alignCenter&quot; data-ke-mobileStyle=&quot;widthOrigin&quot; data-origin-width=&quot;300&quot; data-origin-height=&quot;311&quot;&gt;&lt;span data-url=&quot;https://blog.kakaocdn.net/dn/bY2863/btrSHNoty35/tGdi9U9zKppE9KUSKY4wqk/img.png&quot; data-phocus=&quot;https://blog.kakaocdn.net/dn/bY2863/btrSHNoty35/tGdi9U9zKppE9KUSKY4wqk/img.png&quot; data-alt=&quot;steps&quot;&gt;&lt;img src=&quot;https://blog.kakaocdn.net/dn/bY2863/btrSHNoty35/tGdi9U9zKppE9KUSKY4wqk/img.png&quot; srcset=&quot;https://img1.daumcdn.net/thumb/R1280x0/?scode=mtistory2&amp;fname=https%3A%2F%2Fblog.kakaocdn.net%2Fdn%2FbY2863%2FbtrSHNoty35%2FtGdi9U9zKppE9KUSKY4wqk%2Fimg.png&quot; onerror=&quot;this.onerror=null; this.src='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png'; this.srcset='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png';&quot; loading=&quot;lazy&quot; width=&quot;300&quot; height=&quot;311&quot; data-origin-width=&quot;300&quot; data-origin-height=&quot;311&quot;/&gt;&lt;/span&gt;&lt;figcaption&gt;steps&lt;/figcaption&gt;
&lt;/figure&gt;
&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;b&gt;needs:&lt;/b&gt;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;이 옵션은 선처리 되어야할 job에 의존성을 부여하여 해당 job이 완료되었을 경우 실행하라는 의미이다.&lt;/p&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;enviroment:&lt;/b&gt;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;이 옵션을 지정하면 job 실행을 위한 승인 또는 딜레이 타임을 지정할 수 있다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;구성 방법은 레포지토리&amp;gt;Settings&amp;gt;Enviroments 에서 생성할 수 있다.&lt;/p&gt;
&lt;p&gt;&lt;figure class=&quot;imageblock alignCenter&quot; data-ke-mobileStyle=&quot;widthOrigin&quot; data-origin-width=&quot;873&quot; data-origin-height=&quot;523&quot;&gt;&lt;span data-url=&quot;https://blog.kakaocdn.net/dn/uyQ1t/btrSLZN0x6v/IIk1emczs4wymJtdMNEto0/img.png&quot; data-phocus=&quot;https://blog.kakaocdn.net/dn/uyQ1t/btrSLZN0x6v/IIk1emczs4wymJtdMNEto0/img.png&quot; data-alt=&quot;Github Action Environments Create&quot;&gt;&lt;img src=&quot;https://blog.kakaocdn.net/dn/uyQ1t/btrSLZN0x6v/IIk1emczs4wymJtdMNEto0/img.png&quot; srcset=&quot;https://img1.daumcdn.net/thumb/R1280x0/?scode=mtistory2&amp;fname=https%3A%2F%2Fblog.kakaocdn.net%2Fdn%2FuyQ1t%2FbtrSLZN0x6v%2FIIk1emczs4wymJtdMNEto0%2Fimg.png&quot; onerror=&quot;this.onerror=null; this.src='//t1.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;873&quot; height=&quot;523&quot; data-origin-width=&quot;873&quot; data-origin-height=&quot;523&quot;/&gt;&lt;/span&gt;&lt;figcaption&gt;Github Action Environments Create&lt;/figcaption&gt;
&lt;/figure&gt;
&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;reviewer 에는 사용자의 full name을 적어주어야 뜬다...ㅋ&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;Wait timer는 기다리는 시간이다.&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;Secret&lt;/h3&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;스크립트 작성 시 암호화가 필요한 내용은 Settings&amp;gt;Secrets 기능을 사용하면 된다.&lt;/p&gt;
&lt;p&gt;&lt;figure class=&quot;imageblock alignCenter&quot; data-ke-mobileStyle=&quot;widthOrigin&quot; data-origin-width=&quot;1110&quot; data-origin-height=&quot;805&quot;&gt;&lt;span data-url=&quot;https://blog.kakaocdn.net/dn/cnriS0/btrSHWS7rde/bdEVwkdKvPrntkJZIrYXO0/img.png&quot; data-phocus=&quot;https://blog.kakaocdn.net/dn/cnriS0/btrSHWS7rde/bdEVwkdKvPrntkJZIrYXO0/img.png&quot; data-alt=&quot;github secret&quot;&gt;&lt;img src=&quot;https://blog.kakaocdn.net/dn/cnriS0/btrSHWS7rde/bdEVwkdKvPrntkJZIrYXO0/img.png&quot; srcset=&quot;https://img1.daumcdn.net/thumb/R1280x0/?scode=mtistory2&amp;fname=https%3A%2F%2Fblog.kakaocdn.net%2Fdn%2FcnriS0%2FbtrSHWS7rde%2FbdEVwkdKvPrntkJZIrYXO0%2Fimg.png&quot; onerror=&quot;this.onerror=null; this.src='//t1.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;1110&quot; height=&quot;805&quot; data-origin-width=&quot;1110&quot; data-origin-height=&quot;805&quot;/&gt;&lt;/span&gt;&lt;figcaption&gt;github secret&lt;/figcaption&gt;
&lt;/figure&gt;
&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;필자는 Azure 서비스 중 ACR, AKS 을 이용하고 있기 때문에 관련 접근 값을 지정하였다.&lt;/p&gt;
&lt;p&gt;&lt;figure class=&quot;imageblock alignCenter&quot; data-ke-mobileStyle=&quot;widthOrigin&quot; data-origin-width=&quot;777&quot; data-origin-height=&quot;641&quot;&gt;&lt;span data-url=&quot;https://blog.kakaocdn.net/dn/bUuqOs/btrSKu1T9Fg/4Y69xXnXqOUrefRAE1pUt0/img.png&quot; data-phocus=&quot;https://blog.kakaocdn.net/dn/bUuqOs/btrSKu1T9Fg/4Y69xXnXqOUrefRAE1pUt0/img.png&quot; data-alt=&quot;github secret list&quot;&gt;&lt;img src=&quot;https://blog.kakaocdn.net/dn/bUuqOs/btrSKu1T9Fg/4Y69xXnXqOUrefRAE1pUt0/img.png&quot; srcset=&quot;https://img1.daumcdn.net/thumb/R1280x0/?scode=mtistory2&amp;fname=https%3A%2F%2Fblog.kakaocdn.net%2Fdn%2FbUuqOs%2FbtrSKu1T9Fg%2F4Y69xXnXqOUrefRAE1pUt0%2Fimg.png&quot; onerror=&quot;this.onerror=null; this.src='//t1.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;777&quot; height=&quot;641&quot; data-origin-width=&quot;777&quot; data-origin-height=&quot;641&quot;/&gt;&lt;/span&gt;&lt;figcaption&gt;github secret list&lt;/figcaption&gt;
&lt;/figure&gt;
&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;지정한 secret 을 사용할 때는 &lt;b&gt;&lt;span style=&quot;background-color: #ffffff; color: #0a3069;&quot;&gt;${{ secrets.acr_password }}&lt;/span&gt;&lt;/b&gt;&lt;span style=&quot;background-color: #ffffff; color: #0a3069;&quot;&gt; 을 쓰면 된다.&lt;/span&gt;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;h3 data-ke-size=&quot;size23&quot;&gt;승인전략&lt;/h3&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;enviroments 설정 하면 아래와 같은 프로세스가 진행된다.&lt;/p&gt;
&lt;p&gt;&lt;figure class=&quot;imageblock alignCenter&quot; data-ke-mobileStyle=&quot;widthOrigin&quot; data-origin-width=&quot;911&quot; data-origin-height=&quot;467&quot;&gt;&lt;span data-url=&quot;https://blog.kakaocdn.net/dn/BwOrU/btrSJmXJyL9/xyEDLQKdKfm4ljKdYw7li0/img.png&quot; data-phocus=&quot;https://blog.kakaocdn.net/dn/BwOrU/btrSJmXJyL9/xyEDLQKdKfm4ljKdYw7li0/img.png&quot; data-alt=&quot;reviewer waiting&quot;&gt;&lt;img src=&quot;https://blog.kakaocdn.net/dn/BwOrU/btrSJmXJyL9/xyEDLQKdKfm4ljKdYw7li0/img.png&quot; srcset=&quot;https://img1.daumcdn.net/thumb/R1280x0/?scode=mtistory2&amp;fname=https%3A%2F%2Fblog.kakaocdn.net%2Fdn%2FBwOrU%2FbtrSJmXJyL9%2FxyEDLQKdKfm4ljKdYw7li0%2Fimg.png&quot; onerror=&quot;this.onerror=null; this.src='//t1.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;911&quot; height=&quot;467&quot; data-origin-width=&quot;911&quot; data-origin-height=&quot;467&quot;/&gt;&lt;/span&gt;&lt;figcaption&gt;reviewer waiting&lt;/figcaption&gt;
&lt;/figure&gt;
&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;생성된 Review deployments 를 눌러&lt;/p&gt;
&lt;p&gt;&lt;figure class=&quot;imageblock alignCenter&quot; data-ke-mobileStyle=&quot;widthOrigin&quot; data-origin-width=&quot;643&quot; data-origin-height=&quot;422&quot;&gt;&lt;span data-url=&quot;https://blog.kakaocdn.net/dn/kCWjw/btrSLJYKidL/MItgCyHZF20K88PRzbizA1/img.png&quot; data-phocus=&quot;https://blog.kakaocdn.net/dn/kCWjw/btrSLJYKidL/MItgCyHZF20K88PRzbizA1/img.png&quot;&gt;&lt;img src=&quot;https://blog.kakaocdn.net/dn/kCWjw/btrSLJYKidL/MItgCyHZF20K88PRzbizA1/img.png&quot; srcset=&quot;https://img1.daumcdn.net/thumb/R1280x0/?scode=mtistory2&amp;fname=https%3A%2F%2Fblog.kakaocdn.net%2Fdn%2FkCWjw%2FbtrSLJYKidL%2FMItgCyHZF20K88PRzbizA1%2Fimg.png&quot; onerror=&quot;this.onerror=null; this.src='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png'; this.srcset='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png';&quot; loading=&quot;lazy&quot; width=&quot;643&quot; height=&quot;422&quot; data-origin-width=&quot;643&quot; data-origin-height=&quot;422&quot;/&gt;&lt;/span&gt;&lt;/figure&gt;
&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;가벼운 코맨트를 적어 Approve and deploy를 누르면 다음 스텝을 진행하라는 의미이다.&lt;/p&gt;
&lt;p&gt;&lt;figure class=&quot;imageblock alignCenter&quot; data-ke-mobileStyle=&quot;widthOrigin&quot; data-origin-width=&quot;915&quot; data-origin-height=&quot;582&quot;&gt;&lt;span data-url=&quot;https://blog.kakaocdn.net/dn/biPUUX/btrSJn3pRlL/jxTNbPcYBzckMq1kf6uPE1/img.png&quot; data-phocus=&quot;https://blog.kakaocdn.net/dn/biPUUX/btrSJn3pRlL/jxTNbPcYBzckMq1kf6uPE1/img.png&quot;&gt;&lt;img src=&quot;https://blog.kakaocdn.net/dn/biPUUX/btrSJn3pRlL/jxTNbPcYBzckMq1kf6uPE1/img.png&quot; srcset=&quot;https://img1.daumcdn.net/thumb/R1280x0/?scode=mtistory2&amp;fname=https%3A%2F%2Fblog.kakaocdn.net%2Fdn%2FbiPUUX%2FbtrSJn3pRlL%2FjxTNbPcYBzckMq1kf6uPE1%2Fimg.png&quot; onerror=&quot;this.onerror=null; this.src='//t1.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;915&quot; height=&quot;582&quot; data-origin-width=&quot;915&quot; data-origin-height=&quot;582&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;취소를 누르면 거기서 모든 job이 중단&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;예로 blue/green 배포를 구현하려 해보았는데,&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;신규pod를 배포하고 나서 github 유저가 승인/거절 프로세스를 넣으려했는데,&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;거절할 경우 신규pod를 제거해야하는데, 모든 job이 중단되어 버리는 현상이 발생하였다.&lt;/p&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;AzureDevops에서는 거절할 경우 프로세스를 정의하여 script가 실행&lt;/b&gt;&lt;/u&gt;되게 하였는데&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;u&gt;&lt;b&gt;Aithub Action에선 if{{ failure }} 옵션을 가지 못하고 바로 끝나버리는 것&lt;/b&gt;&lt;/u&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;p data-ke-size=&quot;size16&quot;&gt;github 의 의도가 겨우 이거뿐인지..ㅜㅜ&lt;/p&gt;
&lt;figure contenteditable=&quot;false&quot; data-ke-type=&quot;emoticon&quot; data-ke-align=&quot;alignCenter&quot; data-emoticon-type=&quot;friends1&quot; data-emoticon-name=&quot;035&quot; data-emoticon-isanimation=&quot;false&quot; data-emoticon-src=&quot;https://t1.daumcdn.net/keditor/emoticon/friends1/large/035.gif&quot;&gt;&lt;img src=&quot;https://t1.daumcdn.net/keditor/emoticon/friends1/large/035.gif&quot; width=&quot;150&quot; /&gt;&lt;/figure&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;h3 data-ke-size=&quot;size23&quot;&gt;Sample CI-CD.yaml&lt;/h3&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;지금까지 간단한 변수 데이터들을 사용하는 방법을 설명했다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;이해의 도움을 주기 위해 필자가 작성한 샘플을 하나 보여주려한다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;워크로드 내용은&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;1. 변수화(레포 브랜치, 이미지이름, 이미지버전, 배포할 네임스페이스)&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;2.&amp;nbsp;Springboot (Maven) packge&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;3. Azure Container Registry push&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;4. Azure Kubernetes Service Deploy (사전 작성한 deployment.yml)&lt;/p&gt;
&lt;pre id=&quot;code_1670077673704&quot; class=&quot;yaml&quot; data-ke-language=&quot;highlight.js&quot; data-ke-type=&quot;codeblock&quot;&gt;&lt;code&gt;name: spring-manual-CI/CD

on:
  workflow_dispatch:
    inputs:
      VERSION:
        required: false
        default: &quot;1.0&quot;
        type: string
      BRANCH:
        required: false
        default: &quot;main&quot;
        type: string
      IMAGENAME:
        required: false
        default: &quot;php&quot;
        type: string
      NAMESPACE:
        required: false
        default: &quot;default&quot;
        type: string
        
jobs:
  build-and-deploy:
    runs-on: ubuntu-latest
    steps:
      - uses: actions/checkout@master
        with:
          ref: ${{ github.event.inputs.BRANCH }}
      - uses: azure/docker-login@v1
        with:
          login-server: lottechemicalacr.azurecr.io
          username: ${{ secrets.acr_username }}
          password: ${{ secrets.acr_password }}
          
      - name: Set up JDK 11
        uses: actions/setup-java@v2
        with:
          java-version: '11'
          distribution: 'adopt'
          
      - name: Build with Maven
        run: mvn --batch-mode --update-snapshots package
        
      - name: Build and push image to ACR
        id: build-image
        run: |
          echo ls -al
          docker build &quot;$GITHUB_WORKSPACE/&quot; -f  &quot;Dockerfile&quot; -t lottechemicalacr.azurecr.io/${{ github.event.inputs.IMAGENAME }}:${{ github.event.inputs.VERSION }} --label dockerfile-path=Dockerfile
          docker push lottechemicalacr.azurecr.io/${{ github.event.inputs.IMAGENAME }}:${{ github.event.inputs.VERSION }}
      - uses: azure/k8s-set-context@v1
        with:
          kubeconfig: ${{ secrets.aks_kubeconfig }}
        id: login

      - name: Create namespace
        run: |
          namespacePresent=`kubectl get namespace | grep ${{ github.event.inputs.NAMESPACE }} | wc -l`
          if [ $namespacePresent -eq 0 ]
          then
              echo `kubectl create namespace ${{ github.event.inputs.NAMESPACE }}`
          fi
      - uses: azure/k8s-deploy@v1.2
        with:
          namespace: ${{ github.event.inputs.NAMESPACE }}
          manifests: |
            manifests/deployment.yml
            manifests/service.yml
          images: |
            lottechemicalacr.azurecr.io/${{ github.event.inputs.IMAGENAME }}:${{ github.event.inputs.VERSION }}&lt;/code&gt;&lt;/pre&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;ㅎㅎ 적당히 참고하시라..&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;h2 data-ke-size=&quot;size26&quot;&gt;Rest API 사용&lt;/h2&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;당연히 github action 페이지에 와서 굳이 워크로드 승인 절차를 &lt;u&gt;&lt;b&gt;수동으로&lt;/b&gt;&lt;/u&gt; 할 일은 없어야 되기 때문에 Rest API를 사용해보자.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;br /&gt;먼저 PAT(Personal Access Token)을 발급받아야한다.&lt;/p&gt;
&lt;h3 data-ke-size=&quot;size23&quot;&gt;PAT(Personal Access Token)&lt;/h3&gt;
&lt;p&gt;&lt;figure class=&quot;imageblock alignCenter&quot; data-ke-mobileStyle=&quot;widthOrigin&quot; data-origin-width=&quot;940&quot; data-origin-height=&quot;668&quot;&gt;&lt;span data-url=&quot;https://blog.kakaocdn.net/dn/bh1ORA/btrSIsKUhoc/bKshsQT5EAzV4z903XLgSK/img.png&quot; data-phocus=&quot;https://blog.kakaocdn.net/dn/bh1ORA/btrSIsKUhoc/bKshsQT5EAzV4z903XLgSK/img.png&quot; data-alt=&quot;PAT 발급&quot;&gt;&lt;img src=&quot;https://blog.kakaocdn.net/dn/bh1ORA/btrSIsKUhoc/bKshsQT5EAzV4z903XLgSK/img.png&quot; srcset=&quot;https://img1.daumcdn.net/thumb/R1280x0/?scode=mtistory2&amp;fname=https%3A%2F%2Fblog.kakaocdn.net%2Fdn%2Fbh1ORA%2FbtrSIsKUhoc%2FbKshsQT5EAzV4z903XLgSK%2Fimg.png&quot; onerror=&quot;this.onerror=null; this.src='//t1.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;940&quot; height=&quot;668&quot; data-origin-width=&quot;940&quot; data-origin-height=&quot;668&quot;/&gt;&lt;/span&gt;&lt;figcaption&gt;PAT 발급&lt;/figcaption&gt;
&lt;/figure&gt;
&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;간단하게 workflow 권한을 주었더니 repo 이하 권한이 default로 선택되었다.&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;호출 data 작성&lt;/h3&gt;
&lt;table style=&quot;border-collapse: collapse; width: 100%;&quot; border=&quot;1&quot; data-ke-align=&quot;alignLeft&quot;&gt;
&lt;tbody&gt;
&lt;tr&gt;
&lt;td style=&quot;width: 50%;&quot;&gt;&lt;b&gt;Type&lt;/b&gt;&lt;/td&gt;
&lt;td style=&quot;width: 50%;&quot;&gt;&lt;b&gt;Value&lt;/b&gt;&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td style=&quot;width: 50%;&quot;&gt;Method&lt;/td&gt;
&lt;td style=&quot;width: 50%;&quot;&gt;POST&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td style=&quot;width: 50%;&quot;&gt;URL (Public Github)&lt;/td&gt;
&lt;td style=&quot;width: 50%;&quot;&gt;&lt;b&gt;&lt;span&gt;api&lt;/span&gt;&lt;/b&gt;&lt;span&gt;.github.com/repos/{&lt;b&gt;Owner&lt;/b&gt;}/{&lt;b&gt;ReposName&lt;/b&gt;}/dispatches&lt;/span&gt;&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td style=&quot;width: 50%;&quot;&gt;URL (Enterprise Github)&lt;/td&gt;
&lt;td style=&quot;width: 50%;&quot;&gt;&lt;span&gt;{domain}/api/v3/repos/{&lt;b&gt;owner&lt;/b&gt;}/{&lt;b&gt;reposName&lt;/b&gt;}/dispatches&lt;/span&gt;&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td style=&quot;width: 50%;&quot;&gt;Header &lt;span&gt;Authorization&lt;/span&gt;&lt;/td&gt;
&lt;td style=&quot;width: 50%;&quot;&gt;Bearer {Token}&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td style=&quot;width: 50%;&quot;&gt;Header Accept&lt;/td&gt;
&lt;td style=&quot;width: 50%;&quot;&gt;application/vnd.github+json&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td style=&quot;width: 50%;&quot;&gt;Header Content-Type&lt;/td&gt;
&lt;td style=&quot;width: 50%;&quot;&gt;application/json&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td style=&quot;width: 50%;&quot;&gt;Body (Json)&lt;/td&gt;
&lt;td style=&quot;width: 50%;&quot;&gt;{ event_type: String, client_payload: Object }&lt;/td&gt;
&lt;/tr&gt;
&lt;/tbody&gt;
&lt;/table&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;Enterprise Github 와 같이 테스트를 진행하였기에 URL을 두개 정리하였고,&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;github 에서 API 사용시 Accept를 vnd.github+json 으로 지정하는게 가이드였다.&lt;/p&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 호출에 대한 응답값은 204에 No Content 이다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;이렇게 데이터를 쭉~ 해서 curl 명령어로 보자면 아래와 같은&amp;nbsp;형태가 된다.&lt;/p&gt;
&lt;pre id=&quot;code_1670076317784&quot; class=&quot;java&quot; data-ke-language=&quot;java&quot; data-ke-type=&quot;codeblock&quot;&gt;&lt;code&gt;curl --location --request POST 'https://api.github.com/repos/joonhyeok95/php-new-app/dispatches' \
--header 'Content-Type: application/json' \
--header 'Authorization: Bearer ghp_************************' \
--header 'Accept: application/vnd.github+json' \
--data-raw '{
  &quot;event_type&quot;: &quot;CI-001&quot;,
  &quot;client_payload&quot;: {
    &quot;passed&quot;: false,
    &quot;message&quot;: &quot;Error: timeout&quot;,
    &quot;tag&quot;: &quot;PUB-POSTMAN-0.1&quot;
  }
}'&lt;/code&gt;&lt;/pre&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;payload 부분을 보자면&lt;/p&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;event_type&lt;/b&gt;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;이벤트타입은 on&amp;gt;repository_dispatch&amp;gt;types 에 적은 이름을 호출한다.&lt;/p&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;client_payload&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;작성한 워크로드 파일에서 &lt;b&gt;&lt;span style=&quot;background-color: #ffffff; color: #0a3069;&quot;&gt;${{ github.event.client_payload.변수명 }}&lt;/span&gt;&lt;/b&gt;&lt;span style=&quot;background-color: #ffffff; color: #0a3069;&quot;&gt; 으로 사용할 수 있다.&lt;/span&gt;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;h2 data-ke-size=&quot;size26&quot;&gt;마치며,&lt;/h2&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;Github Enterprise 때문에 해당 부분을 테스트하게 되었는데,&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;역시나 &lt;b&gt;public에 비하면 제한사항이 많아 불편했다&lt;/b&gt;.(물론 스크립트로 다 할 수 있는데, Github에서 공식적으로 라이브러리를 제공하니 그쪽을 사용함에도 무리없이 이용할 수 있어야지..ㅎㅎ)&lt;/p&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;/u&gt; &lt;span&gt;&lt;b&gt;Gitlab&lt;/b&gt;을 사용해 왔기 때문에, Github가 매-우 불편하다....(개인적)&lt;/span&gt;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;span&gt;gitlab 빠가 되어버렷...&lt;/span&gt;&lt;/p&gt;
&lt;figure contenteditable=&quot;false&quot; data-ke-type=&quot;emoticon&quot; data-ke-align=&quot;alignCenter&quot; data-emoticon-type=&quot;friends1&quot; data-emoticon-name=&quot;046&quot; data-emoticon-isanimation=&quot;false&quot; data-emoticon-src=&quot;https://t1.daumcdn.net/keditor/emoticon/friends1/large/046.gif&quot;&gt;&lt;img src=&quot;https://t1.daumcdn.net/keditor/emoticon/friends1/large/046.gif&quot; width=&quot;150&quot; /&gt;&lt;/figure&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;다음은 AzureDevops Pipeline 구축 후기를 올릴 예정이다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;Devops 여러분 다들 파이팅^^&lt;/p&gt;</description>
      <category>엔지니어링/CI-CD</category>
      <category>actions</category>
      <category>CI-CD</category>
      <category>github</category>
      <category>githubactions</category>
      <category>GithubEnterprise</category>
      <category>pipeline</category>
      <author>joon95</author>
      <guid isPermaLink="true">https://flowlog.tistory.com/93</guid>
      <comments>https://flowlog.tistory.com/93#entry93comment</comments>
      <pubDate>Sat, 3 Dec 2022 23:29:41 +0900</pubDate>
    </item>
    <item>
      <title>[NKS] velero 백업 및 복원하기</title>
      <link>https://flowlog.tistory.com/92</link>
      <description>&lt;p data-ke-size=&quot;size16&quot;&gt;네이버 쿠버네티스 서비스에서 velero를 통한 클러스터 백업&amp;amp;복원을 가이드하고 있다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;velero 는 vmware-tanzu에서 업데이트하고 있는 github 링크를 통해 사용하면 된다.&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;1. VELERO 셋업&lt;/h2&gt;
&lt;h3 data-ke-size=&quot;size23&quot;&gt;1-1. velero 다운로드&lt;/h3&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;22년 11월 7일 기준으로 현재 1.8.1 버전이 최신이다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;github 에서 다운로드 받고 압축을 푼뒤 실행할 수 있는 폴더로 이동시켜주자.&lt;/p&gt;
&lt;pre id=&quot;code_1667803240127&quot; class=&quot;bash&quot; data-ke-language=&quot;bash&quot; data-ke-type=&quot;codeblock&quot;&gt;&lt;code&gt;$ wget https://github.com/vmware-tanzu/velero/releases/download/v1.8.1/velero-v1.8.1-linux-amd64.tar.gz
$ tar -xvzf velero-v1.8.1-linux-amd64.tar.gz
$ sudo mv velero-v1.8.1-linux-amd64/velero /usr/local/bin/velero&lt;/code&gt;&lt;/pre&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;h3 data-ke-size=&quot;size23&quot;&gt;1-2. 클라우드 인증정보(s3)&lt;/h3&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;velero 의 저장소는 S3기반의 오브젝트 스토리지이기 때문에 정보를 지정해주자.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;필자는 네이버클라우드의 오브젝트스토리지를 사용하며, 지역은 KR 이다.&lt;/p&gt;
&lt;pre id=&quot;code_1667803370747&quot; class=&quot;shell&quot; data-ke-language=&quot;shell&quot; data-ke-type=&quot;codeblock&quot;&gt;&lt;code&gt;$ cat cloud-credential

[default]
aws_access_key_id=본인의엑세스키
aws_secret_access_key=본인의시크릿키
region=KR
server_api_uri=https://ncloud.apigw.ntruss.com/vserver/v2&lt;/code&gt;&lt;/pre&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;h3 data-ke-size=&quot;size23&quot;&gt;1-3. Object Storage 생성&lt;/h3&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;다음은 NCP 콘솔에서 오브젝트스토리지를 하나 만들어주자.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;필자는 joon95backup 이란 버킷을 생성하였다.&lt;/p&gt;
&lt;p&gt;&lt;figure class=&quot;imageblock alignCenter&quot; data-ke-mobileStyle=&quot;widthOrigin&quot; data-origin-width=&quot;894&quot; data-origin-height=&quot;425&quot;&gt;&lt;span data-url=&quot;https://blog.kakaocdn.net/dn/oXv0o/btrQEajttMg/8UXZ9TYoLVApZQ5Edy2Xkk/img.png&quot; data-phocus=&quot;https://blog.kakaocdn.net/dn/oXv0o/btrQEajttMg/8UXZ9TYoLVApZQ5Edy2Xkk/img.png&quot;&gt;&lt;img src=&quot;https://blog.kakaocdn.net/dn/oXv0o/btrQEajttMg/8UXZ9TYoLVApZQ5Edy2Xkk/img.png&quot; srcset=&quot;https://img1.daumcdn.net/thumb/R1280x0/?scode=mtistory2&amp;fname=https%3A%2F%2Fblog.kakaocdn.net%2Fdn%2FoXv0o%2FbtrQEajttMg%2F8UXZ9TYoLVApZQ5Edy2Xkk%2Fimg.png&quot; onerror=&quot;this.onerror=null; this.src='//t1.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;894&quot; height=&quot;425&quot; data-origin-width=&quot;894&quot; data-origin-height=&quot;425&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;1-4. Velero 서버 설치&lt;/h3&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;이제 쿠버네티스에 velero를 올릴 것이다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;여기서 사용되는 velero 계정(service account)은 cluster-admin 권한으로 생성되 클러스터의 모든 권한을 얻어 자유롭게 백업을 할 수 있게 된다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;여기에 쓰인 nks의 경로들은 모두 ncp 사용가이드에 적혀있는데로 사용하면 된다.&lt;/p&gt;
&lt;pre id=&quot;code_1667803592453&quot; class=&quot;shell&quot; data-ke-language=&quot;shell&quot; data-ke-type=&quot;codeblock&quot;&gt;&lt;code&gt;$ velero install \
 --kubeconfig $KUBE_CONFIG \
 --provider velero.io/aws \
 --bucket joon95bucket \
 --image nks.kr.private-ncr.ntruss.com/velero:v1.8.1 \
 --plugins nks.kr.private-ncr.ntruss.com/velero-plugin-for-aws:v1.0.0,nks.kr.private-ncr.ntruss.com/velero-plugin-for-ncloud:v0.0.6 \
 --backup-location-config region=kr,s3ForcePathStyle=&quot;true&quot;,s3Url=https://kr.object.ncloudstorage.com \
 --use-volume-snapshots=false \
 --secret-file=./cloud-credential&lt;/code&gt;&lt;/pre&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;h3 data-ke-size=&quot;size23&quot;&gt;1-5. 설치 확인&lt;/h3&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;간단히 velero 네임스페이스에 리소스들에 정상적으로 돌아가는 것을 확인하면 된다.&lt;/p&gt;
&lt;pre id=&quot;code_1667803644494&quot; class=&quot;shell&quot; data-ke-language=&quot;shell&quot; data-ke-type=&quot;codeblock&quot;&gt;&lt;code&gt;$ kubectl get all --namespace velero&lt;/code&gt;&lt;/pre&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;h3 data-ke-size=&quot;size23&quot;&gt;1-6. 스냅샷 구성&lt;/h3&gt;
&lt;pre id=&quot;code_1667803684993&quot; class=&quot;shell&quot; data-ke-language=&quot;shell&quot; data-ke-type=&quot;codeblock&quot;&gt;&lt;code&gt;$ velero --kubeconfig $KUBE_CONFIG snapshot-location create default --provider ncloud/volume-snapshotter-plugin&lt;/code&gt;&lt;/pre&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;h2 data-ke-size=&quot;size26&quot;&gt;2. 백업&lt;/h2&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;이제 velero 명령어를 통해 백업을 해보자.&lt;/p&gt;
&lt;pre id=&quot;code_1667803766535&quot; class=&quot;shell&quot; data-ke-language=&quot;shell&quot; data-ke-type=&quot;codeblock&quot;&gt;&lt;code&gt;$ velero backup  --kubeconfig $KUBE_CONFIG create ns-joon-backup --include-namespaces joon&lt;/code&gt;&lt;/pre&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;진행이 정상적인지 체크해보자.&lt;/p&gt;
&lt;pre id=&quot;code_1667803866273&quot; class=&quot;shell&quot; data-ke-language=&quot;shell&quot; data-ke-type=&quot;codeblock&quot;&gt;&lt;code&gt;velero backup  --kubeconfig $KUBE_CONFIG get ns-joon-backup
velero backup  --kubeconfig $KUBE_CONFIG describe ns-joon-backup&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;&lt;figure class=&quot;imageblock alignCenter&quot; data-ke-mobileStyle=&quot;widthOrigin&quot; data-origin-width=&quot;437&quot; data-origin-height=&quot;631&quot;&gt;&lt;span data-url=&quot;https://blog.kakaocdn.net/dn/OE81w/btrQDFcXadQ/mw3dbtSKx5WSAnKe0KVVJ0/img.png&quot; data-phocus=&quot;https://blog.kakaocdn.net/dn/OE81w/btrQDFcXadQ/mw3dbtSKx5WSAnKe0KVVJ0/img.png&quot; data-alt=&quot;describe 명령어 결과&quot;&gt;&lt;img src=&quot;https://blog.kakaocdn.net/dn/OE81w/btrQDFcXadQ/mw3dbtSKx5WSAnKe0KVVJ0/img.png&quot; srcset=&quot;https://img1.daumcdn.net/thumb/R1280x0/?scode=mtistory2&amp;fname=https%3A%2F%2Fblog.kakaocdn.net%2Fdn%2FOE81w%2FbtrQDFcXadQ%2Fmw3dbtSKx5WSAnKe0KVVJ0%2Fimg.png&quot; onerror=&quot;this.onerror=null; this.src='//t1.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;437&quot; height=&quot;631&quot; data-origin-width=&quot;437&quot; data-origin-height=&quot;631&quot;/&gt;&lt;/span&gt;&lt;figcaption&gt;describe 명령어 결과&lt;/figcaption&gt;
&lt;/figure&gt;
&lt;figure class=&quot;imageblock alignCenter&quot; data-ke-mobileStyle=&quot;widthOrigin&quot; data-origin-width=&quot;834&quot; data-origin-height=&quot;46&quot;&gt;&lt;span data-url=&quot;https://blog.kakaocdn.net/dn/boMy7E/btrQChjEhgK/Npz0yjKe9kq8vwN2z3ol80/img.png&quot; data-phocus=&quot;https://blog.kakaocdn.net/dn/boMy7E/btrQChjEhgK/Npz0yjKe9kq8vwN2z3ol80/img.png&quot; data-alt=&quot;velero backup get&quot;&gt;&lt;img src=&quot;https://blog.kakaocdn.net/dn/boMy7E/btrQChjEhgK/Npz0yjKe9kq8vwN2z3ol80/img.png&quot; srcset=&quot;https://img1.daumcdn.net/thumb/R1280x0/?scode=mtistory2&amp;fname=https%3A%2F%2Fblog.kakaocdn.net%2Fdn%2FboMy7E%2FbtrQChjEhgK%2FNpz0yjKe9kq8vwN2z3ol80%2Fimg.png&quot; onerror=&quot;this.onerror=null; this.src='//t1.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;834&quot; height=&quot;46&quot; data-origin-width=&quot;834&quot; data-origin-height=&quot;46&quot;/&gt;&lt;/span&gt;&lt;figcaption&gt;velero backup get&lt;/figcaption&gt;
&lt;/figure&gt;
&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;실제 s3 버킷에 접근해보면 아래와 같이 백업이 되어있다.&lt;/p&gt;
&lt;p&gt;&lt;figure class=&quot;imageblock alignCenter&quot; data-ke-mobileStyle=&quot;widthOrigin&quot; data-origin-width=&quot;851&quot; data-origin-height=&quot;244&quot;&gt;&lt;span data-url=&quot;https://blog.kakaocdn.net/dn/bHDx0N/btrQDcvsg94/6tYaE7z08hSKl2r5b7fj9k/img.png&quot; data-phocus=&quot;https://blog.kakaocdn.net/dn/bHDx0N/btrQDcvsg94/6tYaE7z08hSKl2r5b7fj9k/img.png&quot; data-alt=&quot;bucket velero backup list&quot;&gt;&lt;img src=&quot;https://blog.kakaocdn.net/dn/bHDx0N/btrQDcvsg94/6tYaE7z08hSKl2r5b7fj9k/img.png&quot; srcset=&quot;https://img1.daumcdn.net/thumb/R1280x0/?scode=mtistory2&amp;fname=https%3A%2F%2Fblog.kakaocdn.net%2Fdn%2FbHDx0N%2FbtrQDcvsg94%2F6tYaE7z08hSKl2r5b7fj9k%2Fimg.png&quot; onerror=&quot;this.onerror=null; this.src='//t1.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;851&quot; height=&quot;244&quot; data-origin-width=&quot;851&quot; data-origin-height=&quot;244&quot;/&gt;&lt;/span&gt;&lt;figcaption&gt;bucket velero backup list&lt;/figcaption&gt;
&lt;/figure&gt;
&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;h2 data-ke-size=&quot;size26&quot;&gt;3. 복원&lt;/h2&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;네임스페이스를 날리기전 상태를 캡처해놓고~&lt;/p&gt;
&lt;p&gt;&lt;figure class=&quot;imageblock alignCenter&quot; data-ke-mobileStyle=&quot;widthOrigin&quot; data-origin-width=&quot;596&quot; data-origin-height=&quot;317&quot;&gt;&lt;span data-url=&quot;https://blog.kakaocdn.net/dn/d8oePr/btrQChYhAO5/fTcCerFCoZJa9xB5JKnhqk/img.png&quot; data-phocus=&quot;https://blog.kakaocdn.net/dn/d8oePr/btrQChYhAO5/fTcCerFCoZJa9xB5JKnhqk/img.png&quot;&gt;&lt;img src=&quot;https://blog.kakaocdn.net/dn/d8oePr/btrQChYhAO5/fTcCerFCoZJa9xB5JKnhqk/img.png&quot; srcset=&quot;https://img1.daumcdn.net/thumb/R1280x0/?scode=mtistory2&amp;fname=https%3A%2F%2Fblog.kakaocdn.net%2Fdn%2Fd8oePr%2FbtrQChYhAO5%2FfTcCerFCoZJa9xB5JKnhqk%2Fimg.png&quot; onerror=&quot;this.onerror=null; this.src='//t1.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;596&quot; height=&quot;317&quot; data-origin-width=&quot;596&quot; data-origin-height=&quot;317&quot;/&gt;&lt;/span&gt;&lt;/figure&gt;
&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;해당 네임스페이스를 모두 날리고 진행한다.&lt;/p&gt;
&lt;p&gt;&lt;figure class=&quot;imageblock alignCenter&quot; data-ke-mobileStyle=&quot;widthOrigin&quot; data-origin-width=&quot;387&quot; data-origin-height=&quot;59&quot;&gt;&lt;span data-url=&quot;https://blog.kakaocdn.net/dn/bhcz6L/btrQAwH7mee/BkmQxV3VwhTUb6sAHgeav1/img.png&quot; data-phocus=&quot;https://blog.kakaocdn.net/dn/bhcz6L/btrQAwH7mee/BkmQxV3VwhTUb6sAHgeav1/img.png&quot;&gt;&lt;img src=&quot;https://blog.kakaocdn.net/dn/bhcz6L/btrQAwH7mee/BkmQxV3VwhTUb6sAHgeav1/img.png&quot; srcset=&quot;https://img1.daumcdn.net/thumb/R1280x0/?scode=mtistory2&amp;fname=https%3A%2F%2Fblog.kakaocdn.net%2Fdn%2Fbhcz6L%2FbtrQAwH7mee%2FBkmQxV3VwhTUb6sAHgeav1%2Fimg.png&quot; onerror=&quot;this.onerror=null; this.src='//t1.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;387&quot; height=&quot;59&quot; data-origin-width=&quot;387&quot; data-origin-height=&quot;59&quot;/&gt;&lt;/span&gt;&lt;/figure&gt;
&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;삭제를 잘 확인하였다면 restore create 를 통해 복원을 실행한다.&lt;/p&gt;
&lt;pre id=&quot;code_1667804060839&quot; class=&quot;shell&quot; data-ke-language=&quot;shell&quot; data-ke-type=&quot;codeblock&quot;&gt;&lt;code&gt;$ velero restore --kubeconfig $KUBE_CONFIG create --from-backup ns-joon-backup&lt;/code&gt;&lt;/pre&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;blob&quot; data-origin-width=&quot;612&quot; data-origin-height=&quot;317&quot;&gt;&lt;span data-url=&quot;https://blog.kakaocdn.net/dn/eyYDPs/btrQz6QmNZB/K4lKm1mnk82JGlNHfI7YuK/img.png&quot; data-phocus=&quot;https://blog.kakaocdn.net/dn/eyYDPs/btrQz6QmNZB/K4lKm1mnk82JGlNHfI7YuK/img.png&quot;&gt;&lt;img src=&quot;https://blog.kakaocdn.net/dn/eyYDPs/btrQz6QmNZB/K4lKm1mnk82JGlNHfI7YuK/img.png&quot; srcset=&quot;https://img1.daumcdn.net/thumb/R1280x0/?scode=mtistory2&amp;fname=https%3A%2F%2Fblog.kakaocdn.net%2Fdn%2FeyYDPs%2FbtrQz6QmNZB%2FK4lKm1mnk82JGlNHfI7YuK%2Fimg.png&quot; onerror=&quot;this.onerror=null; this.src='//t1.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;612&quot; height=&quot;317&quot; data-filename=&quot;blob&quot; data-origin-width=&quot;612&quot; data-origin-height=&quot;317&quot;/&gt;&lt;/span&gt;&lt;/figure&gt;
&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;h2 data-ke-size=&quot;size26&quot;&gt;4. Velero 삭제&lt;/h2&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;먼저 생성한 backup 을 삭제하는 방법이다.&lt;/p&gt;
&lt;pre id=&quot;code_1667804374035&quot; class=&quot;shell&quot; data-ke-language=&quot;shell&quot; data-ke-type=&quot;codeblock&quot;&gt;&lt;code&gt;$ velero backup  --kubeconfig $KUBE_CONFIG delete ns-joon-backup&lt;/code&gt;&lt;/pre&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;다음은 쿠버네티스에 올라간 velero 서버를 삭제하는 명령어이다.&lt;/p&gt;
&lt;pre id=&quot;code_1667804408010&quot; class=&quot;shell&quot; data-ke-language=&quot;shell&quot; data-ke-type=&quot;codeblock&quot;&gt;&lt;code&gt;$ kubectl delete namespace/velero clusterrolebinding/velero
$ kubectl delete crds -l component=velero&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;&lt;figure class=&quot;imageblock alignCenter&quot; data-ke-mobileStyle=&quot;widthOrigin&quot; data-origin-width=&quot;510&quot; data-origin-height=&quot;199&quot;&gt;&lt;span data-url=&quot;https://blog.kakaocdn.net/dn/bxOSsA/btrQw1ILoqq/KdvmtODmQ9r57NYZMu2wc0/img.png&quot; data-phocus=&quot;https://blog.kakaocdn.net/dn/bxOSsA/btrQw1ILoqq/KdvmtODmQ9r57NYZMu2wc0/img.png&quot; data-alt=&quot;velero crds 리스트&quot;&gt;&lt;img src=&quot;https://blog.kakaocdn.net/dn/bxOSsA/btrQw1ILoqq/KdvmtODmQ9r57NYZMu2wc0/img.png&quot; srcset=&quot;https://img1.daumcdn.net/thumb/R1280x0/?scode=mtistory2&amp;fname=https%3A%2F%2Fblog.kakaocdn.net%2Fdn%2FbxOSsA%2FbtrQw1ILoqq%2FKdvmtODmQ9r57NYZMu2wc0%2Fimg.png&quot; onerror=&quot;this.onerror=null; this.src='//t1.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;510&quot; height=&quot;199&quot; data-origin-width=&quot;510&quot; data-origin-height=&quot;199&quot;/&gt;&lt;/span&gt;&lt;figcaption&gt;velero crds 리스트&lt;/figcaption&gt;
&lt;/figure&gt;
&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;h2 data-ke-size=&quot;size26&quot;&gt;내용 추가 (22.11.08)&lt;/h2&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;위 내용으로 네임스페이스별 백업&amp;amp;복원테스트를 완료하였는데 문제가 발생했다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;바로 &lt;b&gt;NAS pv 를 붙여보니&lt;/b&gt; &lt;b&gt;404 Not Found 에러&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;알아보니 pv를 백업하려면 &lt;b&gt;무결성보장을 위한 fsfreeze&lt;/b&gt; 명령어를 실행하여 백업을 뜨는데,&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;NAS는 프리징을 지원하지 않아서 &lt;b&gt;NAS를 사용하는 PV는 백업을 못한다는 것&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;추가로 PV를 백업하려면 &lt;b&gt;restic을 활성화&lt;/b&gt; 해서 &lt;b&gt;오브젝트스토리지로 백업&lt;/b&gt;할 수 있다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;velero install --use-restic 옵션을 통해 활성화 할 수 있다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;그리고 restic은 오브젝트 스토리지에 접근 시 암호에 특수문자가 포함되어 있으면 에러가 발생한다고 하니 패스워드설정 시 참고하자.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;h2 data-ke-size=&quot;size26&quot;&gt;마치며..&lt;/h2&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;이렇게 쿠버네티스 클러스터를 백업 복원하는 실습을 해보았다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;첨에 버킷을 찾을 수 없다는 에러메시지가 계속 나와서 해멧는데...&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;access key, secret key, url 정보를 눈이 빠지게 쳐다봤지만... 똑같앗앗다...&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;그래서 그냥 velero 서버를 다시 설치하니.. 잘됨...(아놔)..&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;뭐. 전체적인 방법은 간단하니 좋다..^^&lt;/p&gt;
&lt;figure contenteditable=&quot;false&quot; data-ke-type=&quot;emoticon&quot; data-ke-align=&quot;alignCenter&quot; data-emoticon-type=&quot;friends1&quot; data-emoticon-name=&quot;027&quot; data-emoticon-isanimation=&quot;false&quot; data-emoticon-src=&quot;https://t1.daumcdn.net/keditor/emoticon/friends1/large/027.gif&quot;&gt;&lt;img src=&quot;https://t1.daumcdn.net/keditor/emoticon/friends1/large/027.gif&quot; width=&quot;150&quot; /&gt;&lt;/figure&gt;</description>
      <category>엔지니어링/NCP</category>
      <category>backup</category>
      <category>K8S</category>
      <category>Kubernetes</category>
      <category>NKS</category>
      <category>Restore</category>
      <category>velero</category>
      <category>클러스터백업</category>
      <author>joon95</author>
      <guid isPermaLink="true">https://flowlog.tistory.com/92</guid>
      <comments>https://flowlog.tistory.com/92#entry92comment</comments>
      <pubDate>Mon, 7 Nov 2022 16:03:51 +0900</pubDate>
    </item>
    <item>
      <title>[MSA] Outbox Pattern</title>
      <link>https://flowlog.tistory.com/91</link>
      <description>&lt;h2 data-ke-size=&quot;size26&quot;&gt;MSA 아키텍처?&lt;/h2&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;마이크로서비스아키텍처에 대한 화두가 널리 퍼진지 한 8년정도 된 것 같다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;대학교 1학년 때(2014년도)에 쿠버네티스에 대해 알게되어 도커컨테이너에 대해 공부했었던 기억이 있는데,&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;b&gt;인프라(infrastructure)영역은 쿠버네티스, 오픈시프트(redhat), 탄주(vmware)&lt;/b&gt; 등의 오케스트레이터를 통해 널리 사용하고 있는 것 같다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;그럼 이제 어플리케이션영역이 마이크로서비스화 되어야한다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;필자는 작년 한 해동안 마이데이터 제공자 API 프로젝트를 openshift 기반 환경에서 진행하였는데,&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;redhat 기반이라 해당 플랫폼에서 제공하는 3scale(API G/W), rhsso(인증), Fuse, Jboss 를 사용하여 컨테이너를 운용했다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;하지만 마이데이터 제공자는 단순히 Read API를 제공하는 것이기 때문에 복잡한 비즈니스 로직을 가져가진 않았다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;그럼 어플리케이션영역의 마이크로서비스는 어떻게 해야할까?&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;먼저 비즈니스 로직을 도메인별로 분류하고 구분지어야 한다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;실제 MSA 서비스로의 전환 사례는 배달의민족 기술블로그 글에 잘 설명되어 있으니 한번 읽어보길 바랍니다.&lt;/p&gt;
&lt;figure id=&quot;og_1667452213904&quot; contenteditable=&quot;false&quot; data-ke-type=&quot;opengraph&quot; data-ke-align=&quot;alignCenter&quot; data-og-type=&quot;article&quot; data-og-title=&quot;회원시스템 이벤트기반 아키텍처 구축하기 | 우아한형제들 기술블로그&quot; data-og-description=&quot;{{item.name}} 최초의 배달의민족은 하나의 프로젝트로 만들어졌습니다. 배달의민족의 주문수는 J 커브를 그리는 빠른 속도로 성장했고, 주문수가 커지면서 자연스럽게 트래픽 또한 매우 커졌습니&quot; data-og-host=&quot;techblog.woowahan.com&quot; data-og-source-url=&quot;https://techblog.woowahan.com/7835/&quot; data-og-url=&quot;https://techblog.woowahan.com/7835/&quot; data-og-image=&quot;https://scrap.kakaocdn.net/dn/baZOd8/hyQshIXJFn/85w7SO1ox7bUpA2e1D2QHk/img.jpg?width=1640&amp;amp;height=856&amp;amp;face=0_0_1640_856,https://scrap.kakaocdn.net/dn/fhUy8/hyQseFspMC/qRPCilkHdlTo4Uj6pK1GUk/img.jpg?width=1640&amp;amp;height=856&amp;amp;face=0_0_1640_856,https://scrap.kakaocdn.net/dn/UcZEK/hyQqNW1tkM/cxeUKT7CoZzbPnqzTLW9k0/img.png?width=945&amp;amp;height=505&amp;amp;face=0_0_945_505&quot;&gt;&lt;a href=&quot;https://techblog.woowahan.com/7835/&quot; target=&quot;_blank&quot; rel=&quot;noopener&quot; data-source-url=&quot;https://techblog.woowahan.com/7835/&quot;&gt;
&lt;div class=&quot;og-image&quot; style=&quot;background-image: url('https://scrap.kakaocdn.net/dn/baZOd8/hyQshIXJFn/85w7SO1ox7bUpA2e1D2QHk/img.jpg?width=1640&amp;amp;height=856&amp;amp;face=0_0_1640_856,https://scrap.kakaocdn.net/dn/fhUy8/hyQseFspMC/qRPCilkHdlTo4Uj6pK1GUk/img.jpg?width=1640&amp;amp;height=856&amp;amp;face=0_0_1640_856,https://scrap.kakaocdn.net/dn/UcZEK/hyQqNW1tkM/cxeUKT7CoZzbPnqzTLW9k0/img.png?width=945&amp;amp;height=505&amp;amp;face=0_0_945_505');&quot;&gt;&amp;nbsp;&lt;/div&gt;
&lt;div class=&quot;og-text&quot;&gt;
&lt;p class=&quot;og-title&quot; data-ke-size=&quot;size16&quot;&gt;회원시스템 이벤트기반 아키텍처 구축하기 | 우아한형제들 기술블로그&lt;/p&gt;
&lt;p class=&quot;og-desc&quot; data-ke-size=&quot;size16&quot;&gt;{{item.name}} 최초의 배달의민족은 하나의 프로젝트로 만들어졌습니다. 배달의민족의 주문수는 J 커브를 그리는 빠른 속도로 성장했고, 주문수가 커지면서 자연스럽게 트래픽 또한 매우 커졌습니&lt;/p&gt;
&lt;p class=&quot;og-host&quot; data-ke-size=&quot;size16&quot;&gt;techblog.woowahan.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;h2 data-ke-size=&quot;size26&quot;&gt;MSA 중 Outbox 패턴&lt;/h2&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;마이크로 서비스 아키텍처의 종류는 여러가지가 있다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;- SAGA 패턴(Choreography, Orchestration)&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;- CQRS 패턴&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;- OUTBOX 패턴&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;- Event Sourcing&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;...&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;여러개가 나오는데 필자는 이번에 Outbox 패턴과 SAGA패턴 2가지를 해보았고,&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;이 포스팅에선 Outbox 패턴에 대한 포스팅을 할 것이다.&lt;/p&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;Outbox 패턴에서의 핵심은 kafka connector&lt;/b&gt; 라는 놈이다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;이 친구가 database의 트랜잭션 로그를 읽어들여 카프카 토픽에 정보를 보내고, 나머지 마이크로서비스가 토픽을 읽어 back 작업을 하게 된다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;카프카 커넥터는 Confluent 라는 유료제품과 Debezium 무료제품이 있다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;필자는 &lt;b&gt;Debezium(데브지움)&lt;/b&gt;을 사용하였고 사이트에서 설명하는 아키텍처는 아래와 같다.&lt;/p&gt;
&lt;p&gt;&lt;figure class=&quot;imageblock alignCenter&quot; data-ke-mobileStyle=&quot;widthOrigin&quot; data-origin-width=&quot;1025&quot; data-origin-height=&quot;490&quot;&gt;&lt;span data-url=&quot;https://blog.kakaocdn.net/dn/AXg59/btrQkGdwKXG/pTKyyb9NWgaQ4ofdu4q2b1/img.jpg&quot; data-phocus=&quot;https://blog.kakaocdn.net/dn/AXg59/btrQkGdwKXG/pTKyyb9NWgaQ4ofdu4q2b1/img.jpg&quot; data-alt=&quot;Debezium Kafka Connector Architecture&quot;&gt;&lt;img src=&quot;https://blog.kakaocdn.net/dn/AXg59/btrQkGdwKXG/pTKyyb9NWgaQ4ofdu4q2b1/img.jpg&quot; srcset=&quot;https://img1.daumcdn.net/thumb/R1280x0/?scode=mtistory2&amp;fname=https%3A%2F%2Fblog.kakaocdn.net%2Fdn%2FAXg59%2FbtrQkGdwKXG%2FpTKyyb9NWgaQ4ofdu4q2b1%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;1025&quot; height=&quot;490&quot; data-origin-width=&quot;1025&quot; data-origin-height=&quot;490&quot;/&gt;&lt;/span&gt;&lt;figcaption&gt;Debezium Kafka Connector Architecture&lt;/figcaption&gt;
&lt;/figure&gt;
&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;말 그대로 db 트랜잭션 로그를 읽어 다른곳에 연결해주는 역할이다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;h2 data-ke-size=&quot;size26&quot;&gt;Outbox 아키텍처&lt;/h2&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;예제 소스는 &lt;a href=&quot;https://fullstackdeveloper.guru/2022/05/19/how-to-implement-transactional-outbox-design-pattern-in-spring-boot-microservices/&quot; target=&quot;_blank&quot; rel=&quot;noopener&quot;&gt;링크&lt;/a&gt;에서 가져왔는데,&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;springboot application까지 image로 만들어 1개의 docker network 안에서 통신하는 예제로&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;그래서 개발자의 pc에서 docker network에 어떻게 접근하여 통신이 이루어지는지에 대한 그림을 그렸다.&lt;/p&gt;
&lt;p&gt;&lt;figure class=&quot;imageblock alignCenter&quot; data-ke-mobileStyle=&quot;widthOrigin&quot; data-origin-width=&quot;1484&quot; data-origin-height=&quot;881&quot;&gt;&lt;span data-url=&quot;https://blog.kakaocdn.net/dn/pTkFY/btrQkuxC9gI/WRL9bizSXkoFgtdt8YQtk1/img.png&quot; data-phocus=&quot;https://blog.kakaocdn.net/dn/pTkFY/btrQkuxC9gI/WRL9bizSXkoFgtdt8YQtk1/img.png&quot; data-alt=&quot;MSA outbox pattern&quot;&gt;&lt;img src=&quot;https://blog.kakaocdn.net/dn/pTkFY/btrQkuxC9gI/WRL9bizSXkoFgtdt8YQtk1/img.png&quot; srcset=&quot;https://img1.daumcdn.net/thumb/R1280x0/?scode=mtistory2&amp;fname=https%3A%2F%2Fblog.kakaocdn.net%2Fdn%2FpTkFY%2FbtrQkuxC9gI%2FWRL9bizSXkoFgtdt8YQtk1%2Fimg.png&quot; onerror=&quot;this.onerror=null; this.src='//t1.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;1484&quot; height=&quot;881&quot; data-origin-width=&quot;1484&quot; data-origin-height=&quot;881&quot;/&gt;&lt;/span&gt;&lt;figcaption&gt;MSA outbox pattern&lt;/figcaption&gt;
&lt;/figure&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;2. 주문서비스는 주문(customer_order), 주문이벤트(outbox) 테이블에 insert 한다.(이때 outbox 테이블은 바로 delete 함)&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;3. kafka 커넥터는 database의 특정 테이블(oubox)의 트랜잭션로그를 &lt;b&gt;연결(connect)&lt;/b&gt;하고 있기 때문에 변화를 감지한다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;4. kafka 커넥터는 특정 테이블(outbox)의 트랜잭션을 kafka에 날린다.(이 때 보내는 토픽명은 orders_server.orders.outbox : &lt;b&gt;커넥터에정의한db명.db명.table명&lt;/b&gt;)&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;5. 배달서비스는 위 토픽을 구독하고 있기 때문에 데이터를 가져가 처리한다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;h2 data-ke-size=&quot;size26&quot;&gt;docker-compose&lt;/h2&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;위에서 그린 그림을 docker 컨테이너로 구현해 보자.!!!&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;- mysql : 데이터베이스&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;- zookeeper : 카프카관리용&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;- kafka : 카프카서버&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;- kafka maanger : 모니터링용&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;- kafka connector : 카프카커넥터&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;b&gt;총 5개의 컨테이너를 구동&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;docker network bridge(&lt;span&gt;default)&lt;/span&gt;&lt;/b&gt;로 사용하면 &lt;b&gt;dns 구성이 안되어 container 끼리 통신이&lt;/b&gt; 안되었다. 그래서 network 를 새로 만들면 auto dns 부분이 적용되 컨테이너끼리 통신이 된다고 한다.&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;그래서 docker compose 를 사용하면 어떻게 되나 찾아보니 &lt;b&gt;bridge &lt;span&gt;network를&lt;/span&gt;&amp;nbsp;자동으로 생성&lt;/b&gt;하여 각 도커서비스의 이름으로 dns를 이름에 맞게 구성할 수 있다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;또, 이번에 알았는데 window docker는 &lt;b&gt;host 모드&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;카프카, 주키퍼 이미지를 debezium 꺼로 하니 연결이 잘안되어 &lt;b&gt;bitnami 이미지&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;그리고 depends_on 옵션을 사용해 먼저 기동되야 할 컨테이너의 의존성을 걸 수 있다.(docker --link 옵션)&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;kafka 쪽은 docker 네트워크 안에선 kafka:29092 로 통신하도록하고, 외부에선 localhost:9092로 접근하도록 옵션을 준다.&lt;/p&gt;
&lt;pre id=&quot;code_1667528582278&quot; class=&quot;yaml&quot; data-ke-language=&quot;highlight.js&quot; data-ke-type=&quot;codeblock&quot;&gt;&lt;code&gt;version: '3'
services:
  zookeeper:
    hostname: zoo
    image: 'bitnami/zookeeper:latest'
    ports:
      - '2181:2181'
    environment:
      - ALLOW_ANONYMOUS_LOGIN=yes
  kafka:
    hostname: kafka
    image: 'bitnami/kafka:latest'
    ports:
      - '9092:9092'
    privileged: true
    environment:
      - KAFKA_BROKER_ID=1
      - KAFKA_LISTENER_SECURITY_PROTOCOL_MAP=PLAINTEXT:PLAINTEXT,PLAINTEXT_HOST:PLAINTEXT
      - KAFKA_LISTENERS=PLAINTEXT://kafka:29092,PLAINTEXT_HOST://kafka:9092
      - KAFKA_ADVERTISED_LISTENERS=PLAINTEXT://kafka:29092,PLAINTEXT_HOST://localhost:9092
      - KAFKA_ZOOKEEPER_CONNECT=zookeeper:2181
      - ALLOW_PLAINTEXT_LISTENER=yes
    depends_on:
      - zookeeper
  mysql:
    image: mysql
    hostname: mysql
    ports:
      - &quot;3307:3306&quot;
    environment:
      MYSQL_ROOT_PASSWORD: root
  manager:
    image: sheepkiller/kafka-manager
    ports:
      - 9000:9000
    environment:
      - ZK_HOSTS=zookeeper:2181
    depends_on:
      - zookeeper
  
  connect:
    links:
      - zookeeper
      - kafka
    ports:
      - 8083:8083 
    environment:
      - GROUP_ID=1 
      - CONFIG_STORAGE_TOPIC=my_connect_configs 
      - OFFSET_STORAGE_TOPIC=my_connect_offsets 
      - STATUS_STORAGE_TOPIC=my_connect_statuses 
      - BOOTSTRAP_SERVERS=kafka:29092
    image: quay.io/debezium/connect:1.9
    depends_on:
      - mysql
      - kafka
      - zookeeper&lt;/code&gt;&lt;/pre&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;h2 data-ke-size=&quot;size26&quot;&gt;kafka Connector 연결&lt;/h2&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;connect 컨테이너에 접근해 POST /connectors 에 생성요청을 해야한다.&lt;/p&gt;
&lt;pre id=&quot;code_1667535218017&quot; class=&quot;bash&quot; data-ke-language=&quot;bash&quot; data-ke-type=&quot;codeblock&quot;&gt;&lt;code&gt;curl -i -X POST -H &quot;Accept:application/json&quot; \
-H  &quot;Content-Type:application/json&quot; http://localhost:8083/connectors \
-d '{
    &quot;name&quot;: &quot;orders-connecter&quot;,
    &quot;config&quot;: {
        &quot;connector.class&quot;: &quot;io.debezium.connector.mysql.MySqlConnector&quot;,
        &quot;tasks.max&quot;: &quot;1&quot;,
        &quot;database.hostname&quot;: &quot;mysql&quot;,
        &quot;database.port&quot;: &quot;3306&quot;,
        &quot;database.user&quot;: &quot;root&quot;,
        &quot;database.password&quot;: &quot;root&quot;,
        &quot;database.server.id&quot;: &quot;100&quot;,
        &quot;database.server.name&quot;: &quot;orders_server&quot;,
        &quot;database.include.list&quot;: &quot;orders&quot;,
        &quot;table.include.list&quot;:&quot;orders.outbox&quot;,
        &quot;database.history.kafka.bootstrap.servers&quot;: &quot;kafka:29092&quot;,
        &quot;database.history.kafka.topic&quot;: &quot;schema_changes.orders&quot;,
        &quot;transforms&quot;: &quot;unwrap&quot;,
        &quot;transforms.unwrap.type&quot;: &quot;io.debezium.transforms.ExtractNewRecordState&quot;
    }
}'&lt;/code&gt;&lt;/pre&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;docker compose 를 이용해 각 컨테이너의 dns를 세팅해주었기 때문에 데이터베이스와 카프카서버의 접근정보를 다음과 같이 기입하였고, 사용할 커넥터명, 연결 할 데이터베이스, 테이블명, 트랜잭션변경을 기록할 토픽을 정의해주면 된다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;연결된 커넥터 확인은 동일한 주소에 GET 으로 요청하면된다.&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;kafka Manager&lt;/h2&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;카프카 클러스터의 정보를 GUI 환경으로 보기 위한 툴 manager를 설정해보자.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;localhost:9000 에 접근한 뒤 Add Cluster 버튼을 눌러 추가한다.&lt;/p&gt;
&lt;p&gt;&lt;figure class=&quot;imageblock alignCenter&quot; data-ke-mobileStyle=&quot;widthOrigin&quot; data-origin-width=&quot;570&quot; data-origin-height=&quot;875&quot;&gt;&lt;span data-url=&quot;https://blog.kakaocdn.net/dn/z56tO/btrQo2l1Y5k/znxzDwEEwk91McOjTSjJ61/img.png&quot; data-phocus=&quot;https://blog.kakaocdn.net/dn/z56tO/btrQo2l1Y5k/znxzDwEEwk91McOjTSjJ61/img.png&quot; data-alt=&quot;kafka manager cluster add&quot;&gt;&lt;img src=&quot;https://blog.kakaocdn.net/dn/z56tO/btrQo2l1Y5k/znxzDwEEwk91McOjTSjJ61/img.png&quot; srcset=&quot;https://img1.daumcdn.net/thumb/R1280x0/?scode=mtistory2&amp;fname=https%3A%2F%2Fblog.kakaocdn.net%2Fdn%2Fz56tO%2FbtrQo2l1Y5k%2FznxzDwEEwk91McOjTSjJ61%2Fimg.png&quot; onerror=&quot;this.onerror=null; this.src='//t1.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;570&quot; height=&quot;875&quot; data-origin-width=&quot;570&quot; data-origin-height=&quot;875&quot;/&gt;&lt;/span&gt;&lt;figcaption&gt;kafka manager cluster add&lt;/figcaption&gt;
&lt;/figure&gt;
&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;host 정보는 zookeeper 를 입력하거나 host.docker.internal 을 사용해야한다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;왜냐하면 매니저컨테이너에서 주키퍼서버로 연결을 하기 때문이다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;가볍게 localhost:2181 을 써주면 매니저컨테이너에서 자신의 컨테이너의 2181로 binding 하기때문에 error가 발생한다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;이렇게 등록된 클러스터를 보면 토픽정보를 볼 수 있다.&lt;/p&gt;
&lt;p&gt;&lt;figure class=&quot;imageblock alignCenter&quot; data-ke-mobileStyle=&quot;widthOrigin&quot; data-origin-width=&quot;1140&quot; data-origin-height=&quot;780&quot;&gt;&lt;span data-url=&quot;https://blog.kakaocdn.net/dn/ZmVnJ/btrQlNKewyp/BNkbRNQZqhVCNt2AcOuOLK/img.png&quot; data-phocus=&quot;https://blog.kakaocdn.net/dn/ZmVnJ/btrQlNKewyp/BNkbRNQZqhVCNt2AcOuOLK/img.png&quot; data-alt=&quot;kafka manager dashboard&quot;&gt;&lt;img src=&quot;https://blog.kakaocdn.net/dn/ZmVnJ/btrQlNKewyp/BNkbRNQZqhVCNt2AcOuOLK/img.png&quot; srcset=&quot;https://img1.daumcdn.net/thumb/R1280x0/?scode=mtistory2&amp;fname=https%3A%2F%2Fblog.kakaocdn.net%2Fdn%2FZmVnJ%2FbtrQlNKewyp%2FBNkbRNQZqhVCNt2AcOuOLK%2Fimg.png&quot; onerror=&quot;this.onerror=null; this.src='//t1.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;780&quot; data-origin-width=&quot;1140&quot; data-origin-height=&quot;780&quot;/&gt;&lt;/span&gt;&lt;figcaption&gt;kafka manager dashboard&lt;/figcaption&gt;
&lt;/figure&gt;
&lt;/p&gt;
&lt;h2 data-ke-size=&quot;size26&quot;&gt;테스트&lt;/h2&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;자 스프링부트를 기동하고 주문을 요청해보자.&lt;/p&gt;
&lt;pre id=&quot;code_1667536257475&quot; class=&quot;bash&quot; data-ke-language=&quot;bash&quot; data-ke-type=&quot;codeblock&quot;&gt;&lt;code&gt;curl --location --request POST 'http://localhost:8080/order' \
 --header 'Content-Type: application/json' \
 --data-raw '{
               &quot;name&quot;: &quot;joon&quot;,
               &quot;quantity&quot;: 95
             }'&lt;/code&gt;&lt;/pre&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;데이터베이스에 접근해 생성된 데이터를 확인하면.&lt;/p&gt;
&lt;pre id=&quot;code_1667536433965&quot; class=&quot;bash&quot; data-ke-language=&quot;bash&quot; data-ke-type=&quot;codeblock&quot;&gt;&lt;code&gt;show databases;
use orders;
show tables;

select * from customer_order;
select * from outbox;&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;&lt;figure class=&quot;imageblock alignCenter&quot; data-ke-mobileStyle=&quot;widthOrigin&quot; data-origin-width=&quot;184&quot; data-origin-height=&quot;89&quot;&gt;&lt;span data-url=&quot;https://blog.kakaocdn.net/dn/CxEgq/btrQlqaLSBE/0GLzj7xpGfpmgMZcwmafl0/img.png&quot; data-phocus=&quot;https://blog.kakaocdn.net/dn/CxEgq/btrQlqaLSBE/0GLzj7xpGfpmgMZcwmafl0/img.png&quot; data-alt=&quot;customer_order table&quot;&gt;&lt;img src=&quot;https://blog.kakaocdn.net/dn/CxEgq/btrQlqaLSBE/0GLzj7xpGfpmgMZcwmafl0/img.png&quot; srcset=&quot;https://img1.daumcdn.net/thumb/R1280x0/?scode=mtistory2&amp;fname=https%3A%2F%2Fblog.kakaocdn.net%2Fdn%2FCxEgq%2FbtrQlqaLSBE%2F0GLzj7xpGfpmgMZcwmafl0%2Fimg.png&quot; onerror=&quot;this.onerror=null; this.src='//t1.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;184&quot; height=&quot;89&quot; data-origin-width=&quot;184&quot; data-origin-height=&quot;89&quot;/&gt;&lt;/span&gt;&lt;figcaption&gt;customer_order table&lt;/figcaption&gt;
&lt;/figure&gt;
&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;customer_order 테이블과 outbox 테이블에 insert 한 뒤&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;outbox 테이블은 delete 를 날린다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;(주문서비스에서 stdout 로그를 남기고 있음.)&lt;/p&gt;
&lt;pre id=&quot;code_1667537243947&quot; class=&quot;bash&quot; data-ke-language=&quot;bash&quot; data-ke-type=&quot;codeblock&quot;&gt;&lt;code&gt;Outbox [id=4, event=order_created, eventId=3, payload={name=joon, quantity=95}, createdAt=2022-11-03T10:50:33.784361600]&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;이 때 kafka connector 는 outbox 테이블에 들어온 트랜잭션로그를 읽어 kafka에 보내는데&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;토픽의 내용을 보면 아래와 같다.&lt;/p&gt;
&lt;pre id=&quot;code_1667536592538&quot; class=&quot;bash&quot; data-ke-language=&quot;bash&quot; data-ke-type=&quot;codeblock&quot;&gt;&lt;code&gt;# order_server.orders.outbox 
{&quot;schema&quot;:{&quot;type&quot;:&quot;struct&quot;,&quot;fields&quot;:[{&quot;type&quot;:&quot;int32&quot;,&quot;optional&quot;:false,&quot;field&quot;:&quot;id&quot;},{&quot;type&quot;:&quot;string&quot;,&quot;optional&quot;:true,&quot;field&quot;:&quot;event&quot;},{&quot;type&quot;:&quot;int32&quot;,&quot;optional&quot;:true,&quot;field&quot;:&quot;event_id&quot;},{&quot;type&quot;:&quot;string&quot;,&quot;optional&quot;:true,&quot;name&quot;:&quot;io.debezium.data.Json&quot;,&quot;version&quot;:1,&quot;field&quot;:&quot;payload&quot;},{&quot;type&quot;:&quot;string&quot;,&quot;optional&quot;:true,&quot;name&quot;:&quot;io.debezium.time.ZonedTimestamp&quot;,&quot;version&quot;:1,&quot;field&quot;:&quot;created_at&quot;}],&quot;optional&quot;:false,&quot;name&quot;:&quot;orders_server.orders.outbox.Value&quot;},&quot;payload&quot;:{&quot;id&quot;:3,&quot;event&quot;:&quot;order_created&quot;,&quot;event_id&quot;:3,&quot;payload&quot;:&quot;{\&quot;name\&quot;:\&quot;joon\&quot;,\&quot;quantity\&quot;:95}&quot;,&quot;created_at&quot;:&quot;2022-11-03T10:50:34Z&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;이제 딜리버리서비스는 해당 토픽을 읽어 Payload 값을 출력한다.&lt;/p&gt;
&lt;pre id=&quot;code_1667537400197&quot; class=&quot;bash&quot; data-ke-language=&quot;bash&quot; data-ke-type=&quot;codeblock&quot;&gt;&lt;code&gt;KafkaMessage [payload=PayLoad [id=3, event=order_created, eventId=3, payload={&quot;name&quot;:&quot;joon&quot;,&quot;quantity&quot;:95}, createdAt=2022-11-03T10:50:34Z]]&lt;/code&gt;&lt;/pre&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;h2 data-ke-size=&quot;size26&quot;&gt;마치며&lt;/h2&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;마이크로서비스아키텍처를 그동안 들어만 봤지 어떻게 구성하고 유지하는지에 대해 감이 잡히지 않았는데&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;SAGA패턴과 outbox 패턴을 구현해보면서 조금 알것 같다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;현재 MSA 프로젝트에서 이러한 패턴을 정의하고 비즈니스 로직을 수립해 나아가고 있는데&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;결국 다른 개발자들의 역량이 어느정도 받쳐주어야 가능할 것 같다는 생각이 든다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;조만간 SAGA 패턴 2가지에 대해 포스팅을 남길 예정이다.&lt;/p&gt;</description>
      <category>개발/Spring</category>
      <category>connector</category>
      <category>debezium</category>
      <category>KAFKA</category>
      <category>MSA</category>
      <category>outbox pattern</category>
      <author>joon95</author>
      <guid isPermaLink="true">https://flowlog.tistory.com/91</guid>
      <comments>https://flowlog.tistory.com/91#entry91comment</comments>
      <pubDate>Fri, 4 Nov 2022 13:58:18 +0900</pubDate>
    </item>
    <item>
      <title>[JAVA] 간단한 .java 파일을 실행 가능한 .jar 파일로 만들고 도커 이미지로 쿠버네티스 cronjob 생성하기</title>
      <link>https://flowlog.tistory.com/90</link>
      <description>&lt;p data-ke-size=&quot;size16&quot;&gt;어제 간단한 특정시간이 지난 뒤 프로세스가 종료되는 자바파일을 구현해야하는 상황이 생겼다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;그래서 jar 파일을 만들고 Dockerfile을 작성한 뒤 Docker image를 생성하고,&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;쿠버네티스 cronjob 에서 가져오는 것 까지해보았다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;먼저 .jar 파일은 그동안 maven, gradle을 통해 자동으로 쭈루룩 만들어줬었는데,&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;그냥 test.java main 메소드 하나 있는 것을 &lt;b&gt;컴파일&lt;/b&gt;하여 나온 &lt;b&gt;.class 파일과 매니패스트파일을 합치는 작업을&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;(문자열 끝에 엔터가 필요하다고...)&lt;/p&gt;
&lt;figure contenteditable=&quot;false&quot; data-ke-type=&quot;emoticon&quot; data-ke-align=&quot;alignCenter&quot; data-emoticon-type=&quot;friends1&quot; data-emoticon-name=&quot;022&quot; data-emoticon-isanimation=&quot;false&quot; data-emoticon-src=&quot;https://t1.daumcdn.net/keditor/emoticon/friends1/large/022.gif&quot;&gt;&lt;img src=&quot;https://t1.daumcdn.net/keditor/emoticon/friends1/large/022.gif&quot; width=&quot;150&quot; /&gt;&lt;/figure&gt;
&lt;h2 data-ke-size=&quot;size26&quot;&gt;test.java&lt;/h2&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;가장먼저 개발을 할 test.java 를 만들자.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;간단히 Thread를 통해 3초의 대기를 준 뒤 프로세스가 종료되게 하였다.&lt;/p&gt;
&lt;pre id=&quot;code_1667446947631&quot; class=&quot;java&quot; data-ke-language=&quot;java&quot; data-ke-type=&quot;codeblock&quot;&gt;&lt;code&gt;import java.text.SimpleDateFormat;
import java.util.Calendar;
class joon_test {
    public static void main(String args[]) throws InterruptedException {
        System.out.println(&quot;Metanet java run...&quot;);
		int num = 3 ;
		// time output format
		SimpleDateFormat fmt = new SimpleDateFormat(&quot;HH:mm:ss&quot;);
		for(int i=0; i &amp;lt; num; i++){
			Calendar cal = Calendar.getInstance() ;
			// print (now times + i )
			System.out.println(&quot;Metanet ... &quot; + fmt.format(cal.getTime()) + &quot;=&quot; + i) ;
			// 1sec sleeping
			Thread.sleep(1000);
		}
        System.out.println(&quot;Metanet java stop.&quot;);
    }
}&lt;/code&gt;&lt;/pre&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;h2 data-ke-size=&quot;size26&quot;&gt;manifest.txt&lt;/h2&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;매니패스트파일에선 .jar가 실행될 때 메인이 되는 클래스가 무엇인지 정의해 준다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;(꼭 라인 끝에 enter를 쳐서 1줄을 띄어 줘야한다. 안 그럼 에러가 난다..)&lt;/p&gt;
&lt;pre id=&quot;code_1667447004542&quot; class=&quot;scala&quot; data-ke-language=&quot;scala&quot; data-ke-type=&quot;codeblock&quot;&gt;&lt;code&gt;Main-class: joon_test&lt;/code&gt;&lt;/pre&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;h2 data-ke-size=&quot;size26&quot;&gt;컴파일 &amp;amp; 합치기&lt;/h2&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;제일 먼저 .java 를 컴파일하여 .class를 생성하자. (파일은 클래스명.class 가 생성된다)&lt;/p&gt;
&lt;pre id=&quot;code_1667447149407&quot; class=&quot;shell&quot; data-ke-language=&quot;shell&quot; data-ke-type=&quot;codeblock&quot;&gt;&lt;code&gt;$ javac .\test.java&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;(이때 옵션의 mf 순서에 따라 뒤에 인자를 맞춰주어야함 매니패스트파일과 클래스파일)&lt;/p&gt;
&lt;pre id=&quot;code_1667447158383&quot; class=&quot;shell&quot; data-ke-language=&quot;shell&quot; data-ke-type=&quot;codeblock&quot;&gt;&lt;code&gt;$ jar -cvmf manifest.txt joon.jar joon_test.class&lt;/code&gt;&lt;/pre&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;이제 .jar가 생성되었다.아래 명령어로 기동해보자.&lt;/p&gt;
&lt;pre id=&quot;code_1667447163461&quot; class=&quot;shell&quot; data-ke-language=&quot;shell&quot; data-ke-type=&quot;codeblock&quot;&gt;&lt;code&gt;$ java -jar joon.jar
Metanet java run...
Metanet ... 12:49:40=0
Metanet ... 12:49:41=1
Metanet ... 12:49:42=2
Metanet java stop.&lt;/code&gt;&lt;/pre&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;h2 data-ke-size=&quot;size26&quot;&gt;Dockerfile 작성&lt;/h2&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;이제 docker image 생성을 위한 파일을 작성해야한다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;가벼운 openjdk alpine 이미지를 가져와 실행하도록 했다.&lt;/p&gt;
&lt;pre id=&quot;code_1667447475406&quot; class=&quot;dockerfile&quot; data-ke-language=&quot;highlight.js&quot; data-ke-type=&quot;codeblock&quot;&gt;&lt;code&gt;FROM openjdk:17-alpine

LABEL maintainer=&quot;Lim joon hyeok&quot;
LABEL title=&quot;job sample image&quot;
LABEL version=&quot;1.0&quot;
LABEL description=&quot;sampling&quot;

COPY ./joon.jar /

# Start the service
CMD [&quot;java&quot;,&quot;-jar&quot;,&quot;/joon.jar&quot;]&lt;/code&gt;&lt;/pre&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;h2 data-ke-size=&quot;size26&quot;&gt;Image Build &amp;amp; Push&lt;/h2&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;위 Dockerfile을 빌드하자&lt;/p&gt;
&lt;pre id=&quot;code_1667447593570&quot; class=&quot;shell&quot; data-ke-language=&quot;shell&quot; data-ke-type=&quot;codeblock&quot;&gt;&lt;code&gt;$ docker build -t job-test:1.0 .
[+] Building 21.4s (7/7) FINISHED
 =&amp;gt; [internal] load build definition from Dockerfile                                                                                                                                         0.0s 
 =&amp;gt; =&amp;gt; transferring dockerfile: 269B                                                                                                                                                         0.0s 
 =&amp;gt; [internal] load .dockerignore                                                                                                                                                            0.1s 
 =&amp;gt; =&amp;gt; transferring context: 2B                                                                                                                                                              0.0s 
 =&amp;gt; [internal] load metadata for docker.io/library/openjdk:17-alpine                                                                                                                         3.7s 
 =&amp;gt; [internal] load build context                                                                                                                                                            0.1s 
 =&amp;gt; =&amp;gt; transferring context: 1.31kB                                                                                                                                                          0.0s 
 =&amp;gt; [1/2] FROM docker.io/library/openjdk:17-alpine@sha256:4b6abae565492dbe9e7a894137c966a7485154238902f2f25e9dbd9784383d81                                                                  16.2s 
 =&amp;gt; =&amp;gt; resolve docker.io/library/openjdk:17-alpine@sha256:4b6abae565492dbe9e7a894137c966a7485154238902f2f25e9dbd9784383d81                                                                   0.1s 
 =&amp;gt; =&amp;gt; sha256:53c9466125e464fed5626bde7b7a0f91aab09905f0a07e9ad4e930ae72e0fc63 928.44kB / 928.44kB                                                                                           0.8s 
 =&amp;gt; =&amp;gt; sha256:d8d715783b80cab158f5bf9726bcada5265c1624b64ca2bb46f42f94998d4662 186.80MB / 186.80MB                                                                                           9.7s 
 =&amp;gt; =&amp;gt; sha256:4b6abae565492dbe9e7a894137c966a7485154238902f2f25e9dbd9784383d81 319B / 319B                                                                                                   0.0s 
 =&amp;gt; =&amp;gt; sha256:a996cdcc040704ec6badaf5fecf1e144c096e00231a29188596c784bcf858d05 951B / 951B                                                                                                   0.0s 
 =&amp;gt; =&amp;gt; sha256:264c9bdce361556ba6e685e401662648358980c01151c3d977f0fdf77f7c26ab 3.48kB / 3.48kB                                                                                               0.0s 
 =&amp;gt; =&amp;gt; sha256:5843afab387455b37944e709ee8c78d7520df80f8d01cf7f861aae63beeddb6b 2.81MB / 2.81MB                                                                                               0.5s 
 =&amp;gt; =&amp;gt; extracting sha256:5843afab387455b37944e709ee8c78d7520df80f8d01cf7f861aae63beeddb6b                                                                                                    0.6s 
 =&amp;gt; =&amp;gt; extracting sha256:53c9466125e464fed5626bde7b7a0f91aab09905f0a07e9ad4e930ae72e0fc63                                                                                                    0.3s 
 =&amp;gt; =&amp;gt; extracting sha256:d8d715783b80cab158f5bf9726bcada5265c1624b64ca2bb46f42f94998d4662                                                                                                    6.2s 
 =&amp;gt; [2/2] COPY ./mtp.jar /                                                                                                                                                                   0.9s 
 =&amp;gt; exporting to image                                                                                                                                                                       0.2s 
 =&amp;gt; =&amp;gt; exporting layers                                                                                                                                                                      0.1s 
 =&amp;gt; =&amp;gt; writing image sha256:f4884aad8840a6bc1fd5acd9578d391d1d09d14428f4788835b4ddcfd0d63fbc                                                                                                 0.0s 
 =&amp;gt; =&amp;gt; naming to docker.io/library/joon-test:1.0&lt;/code&gt;&lt;/pre&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;생성된 이미지 조회&lt;/p&gt;
&lt;pre id=&quot;code_1667447746977&quot; class=&quot;shell&quot; data-ke-language=&quot;shell&quot; data-ke-type=&quot;codeblock&quot;&gt;&lt;code&gt;$ docker images joon-test
REPOSITORY   TAG       IMAGE ID       CREATED              SIZE
joon-test    1.0       f4884aad8840   About a minute ago   326MB&lt;/code&gt;&lt;/pre&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;이제 생성한 이미지를 원격 레지스트리 서버에 올려야한다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;필자는 Naver Container Registry 를 사용중이다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;docker tag 명령어로 각자 레지스트리 도메인을 가지는 이미지로 변경 한 뒤&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;docker login 후에 원격컨테이너레지스트리에 푸쉬하자.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;(임시로 url을 기입하였습니다.)&lt;/p&gt;
&lt;pre id=&quot;code_1667447939631&quot; class=&quot;shell&quot; data-ke-language=&quot;shell&quot; data-ke-type=&quot;codeblock&quot;&gt;&lt;code&gt;$ registry_url=joon95-sample-url.kr.ncr.ntruss.com
$ docker tag mtp-job-test:0.1 $registry_url/job-test:1.0
$ docker login -u 유저네임 $registry_url
password: 패스워드입력
$ docker push $registry_url/job-test:1.0&lt;/code&gt;&lt;/pre&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;h2 data-ke-size=&quot;size26&quot;&gt;secret&lt;/h2&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;쿠버네티스에서 private image에 접근하기 위한 secret을 생성해야한다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;생성될 namespace는 default에 생성하였다.&lt;/p&gt;
&lt;pre id=&quot;code_1667448128874&quot; class=&quot;shell&quot; data-ke-language=&quot;shell&quot; data-ke-type=&quot;codeblock&quot;&gt;&lt;code&gt;$ kubectl create secret docker-registry 생성할secret명 \
    --docker-server=컨테이너레지스트리URL 
    --docker-username=유저명 
    --docker-password=패스워드 
    -n default&lt;/code&gt;&lt;/pre&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;h2 data-ke-size=&quot;size26&quot;&gt;cronjob&lt;/h2&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;이제 1분마다 자동으로 실행될 크론잡을 작성하자.&lt;/p&gt;
&lt;pre id=&quot;code_1667448356105&quot; class=&quot;yaml&quot; data-ke-language=&quot;highlight.js&quot; data-ke-type=&quot;codeblock&quot;&gt;&lt;code&gt;apiVersion: batch/v1
kind: CronJob
metadata:
  namespace: default
  name: sample-cronjob
spec:
  schedule: &quot;*/1 * * * *&quot;
  concurrencyPolicy: &quot;Forbid&quot;
  #startingDeadlineSeconds: 200
  #suspend: false
  successfulJobsHistoryLimit: 2
  failedJobsHistoryLimit: 1
  jobTemplate:
    spec:
      backoffLimit: 0
      template:
        metadata:
          labels:
            parent: &quot;sample-cronjob&quot;
        spec:
          imagePullSecrets:
            - name: 생성한시크릿명
          containers:
            - command: [&quot;java&quot;, &quot;-jar&quot;, &quot;joon.jar&quot;]
              image: 컨테이너레지스트리서버URL/job-test:1.0
              imagePullPolicy: Always
              name: job-sample
              resources: {}
          dnsPolicy: ClusterFirst
          schedulerName: default-scheduler
          securityContext: {}
          terminationGracePeriodSeconds: 30
          restartPolicy: Never&lt;/code&gt;&lt;/pre&gt;
&lt;pre id=&quot;code_1667448426613&quot; class=&quot;shell&quot; data-ke-language=&quot;shell&quot; data-ke-type=&quot;codeblock&quot;&gt;&lt;code&gt;$ kubectl apply -f cronjob.yaml -n default&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;이제 1분마다 job pod가 기동됩니다. 로그를 보면 정상적으로 출력되고요 ㅎㅎ&lt;/p&gt;
&lt;p&gt;&lt;figure class=&quot;imageblock alignCenter&quot; data-ke-mobileStyle=&quot;widthOrigin&quot; data-origin-width=&quot;216&quot; data-origin-height=&quot;98&quot;&gt;&lt;span data-url=&quot;https://blog.kakaocdn.net/dn/7dyiP/btrQf4LV3x3/fZNkwfO2GrIFs3AiybLpDK/img.png&quot; data-phocus=&quot;https://blog.kakaocdn.net/dn/7dyiP/btrQf4LV3x3/fZNkwfO2GrIFs3AiybLpDK/img.png&quot;&gt;&lt;img src=&quot;https://blog.kakaocdn.net/dn/7dyiP/btrQf4LV3x3/fZNkwfO2GrIFs3AiybLpDK/img.png&quot; srcset=&quot;https://img1.daumcdn.net/thumb/R1280x0/?scode=mtistory2&amp;fname=https%3A%2F%2Fblog.kakaocdn.net%2Fdn%2F7dyiP%2FbtrQf4LV3x3%2FfZNkwfO2GrIFs3AiybLpDK%2Fimg.png&quot; onerror=&quot;this.onerror=null; this.src='//t1.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;216&quot; height=&quot;98&quot; data-origin-width=&quot;216&quot; data-origin-height=&quot;98&quot;/&gt;&lt;/span&gt;&lt;/figure&gt;
&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;현재시간을 stoud log 찍은 뒤 3초 후 종료(Completed) 되는 프로세스가 완료되었습니다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;이전에 포스팅했던 3scale 백업 고도화에서 dockerfile / cronjob 에 대한 설정을 미리 검토해놓아서 쉽게 적용할 수 있었습니다.&lt;/p&gt;</description>
      <category>개발/JAVA</category>
      <category>cronjob</category>
      <category>docker</category>
      <category>dockerfile</category>
      <category>JAR</category>
      <category>Java</category>
      <category>job</category>
      <category>Kubernetes</category>
      <category>배치</category>
      <category>스케줄링</category>
      <author>joon95</author>
      <guid isPermaLink="true">https://flowlog.tistory.com/90</guid>
      <comments>https://flowlog.tistory.com/90#entry90comment</comments>
      <pubDate>Thu, 3 Nov 2022 13:35:17 +0900</pubDate>
    </item>
    <item>
      <title>[Springboot] Fabric8 kubernetes 리소스 컨트롤하기</title>
      <link>https://flowlog.tistory.com/89</link>
      <description>&lt;p data-ke-size=&quot;size16&quot;&gt;스프링부트에서 쿠버네티스 리소스를 컨트롤하기 위해 제공되는 라이브러리가 2개 있다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;하나는 kubernetes에서 제공하는 것이고&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;또 하나는 Fabric8 에서 제공하는 라이브러리이다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;span&gt;이 두개의 차이는 이 &lt;a href=&quot;https://itnext.io/difference-between-fabric8-and-official-kubernetes-java-client-3e0a994fd4af&quot;&gt;링크&lt;/a&gt;에서 아주 잘 설명하고 있다.&lt;/span&gt;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;위 링크에서 대충 보자면 kubernetes는 객체를 선언하고 하는 작업들이 많고&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;Fabric8은 웹소켓을 이용한다는 것이다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;(소스 구현부도 Fabric이 편해보임..)&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;이제 간단히 k8s 리소스를 가져와보자.&lt;/p&gt;
&lt;h2 data-ke-size=&quot;size26&quot;&gt;Pom.xml&lt;/h2&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;k8s / ocp 라이브러리가 따로 존재한다.(필자의 현재 코드는 k8s용)&lt;/p&gt;
&lt;pre id=&quot;code_1666795952710&quot; class=&quot;xml&quot; data-ke-language=&quot;java&quot; data-ke-type=&quot;codeblock&quot;&gt;&lt;code&gt;&amp;lt;!-- k8s client --&amp;gt;
&amp;lt;dependency&amp;gt;
    &amp;lt;groupId&amp;gt;io.fabric8&amp;lt;/groupId&amp;gt;
    &amp;lt;artifactId&amp;gt;kubernetes-client&amp;lt;/artifactId&amp;gt;
    &amp;lt;version&amp;gt;6.1.1&amp;lt;/version&amp;gt;
&amp;lt;/dependency&amp;gt;
&amp;lt;!-- ocp https://mvnrepository.com/artifact/io.fabric8/openshift-client --&amp;gt;
&amp;lt;dependency&amp;gt;
    &amp;lt;groupId&amp;gt;io.fabric8&amp;lt;/groupId&amp;gt;
    &amp;lt;artifactId&amp;gt;openshift-client&amp;lt;/artifactId&amp;gt;
    &amp;lt;version&amp;gt;6.1.1&amp;lt;/version&amp;gt;
&amp;lt;/dependency&amp;gt;&lt;/code&gt;&lt;/pre&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;h2 data-ke-size=&quot;size26&quot;&gt;Controller&lt;/h2&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;간단히 GET /k8s/namespace 에 접근하면 모든 namespace를 출력해준다.&lt;/p&gt;
&lt;pre id=&quot;code_1666796028301&quot; class=&quot;java&quot; data-ke-language=&quot;java&quot; data-ke-type=&quot;codeblock&quot;&gt;&lt;code&gt;@RequestMapping(&quot;/k8s&quot;)
public class K8sTest {
    @GetMapping(value =&quot;/namespace&quot;, produces = &quot;application/json&quot;)
    public @ResponseBody NamespaceList testsetsetset() {
        NamespaceList ns = null;
        try (KubernetesClient client = new KubernetesClientBuilder().build()) {
            client.pods().inAnyNamespace().list()
                .getItems()
                .stream()
                .map(Pod::getMetadata)
                .map(ObjectMeta::getName)
                .forEach(System.out::println);

            NamespaceList ns = client.namespaces().list();
            ns.getItems().forEach((d) -&amp;gt; { 
                log.info(&quot;namespace : &quot; + d.getMetadata().getName());
            });
        }
        return ns;
    }
}&lt;/code&gt;&lt;/pre&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;여기서 쿠버네티스 client 객체는 아래 우선순위에 따라 적용된다.&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;2. 환경 변수&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;3. 큐브 구성파일&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;4. 서비스 계정 토큰 및 탑재된 CA인증서&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;필자는 스프링부트가 기동되고 있는 host에 k8s 접근용 큐브 구성파일이 존재한다(~/.kube/config)&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;h2 data-ke-size=&quot;size26&quot;&gt;테스트&lt;/h2&gt;
&lt;p&gt;&lt;figure class=&quot;imageblock alignCenter&quot; data-ke-mobileStyle=&quot;widthOrigin&quot; data-origin-width=&quot;940&quot; data-origin-height=&quot;1171&quot;&gt;&lt;span data-url=&quot;https://blog.kakaocdn.net/dn/b3YOSu/btrPCCpXZXe/VFC71PiwQBn50oTke72Fpk/img.png&quot; data-phocus=&quot;https://blog.kakaocdn.net/dn/b3YOSu/btrPCCpXZXe/VFC71PiwQBn50oTke72Fpk/img.png&quot; data-alt=&quot;브라우저에서 호출&quot;&gt;&lt;img src=&quot;https://blog.kakaocdn.net/dn/b3YOSu/btrPCCpXZXe/VFC71PiwQBn50oTke72Fpk/img.png&quot; srcset=&quot;https://img1.daumcdn.net/thumb/R1280x0/?scode=mtistory2&amp;fname=https%3A%2F%2Fblog.kakaocdn.net%2Fdn%2Fb3YOSu%2FbtrPCCpXZXe%2FVFC71PiwQBn50oTke72Fpk%2Fimg.png&quot; onerror=&quot;this.onerror=null; this.src='//t1.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;940&quot; height=&quot;1171&quot; data-origin-width=&quot;940&quot; data-origin-height=&quot;1171&quot;/&gt;&lt;/span&gt;&lt;figcaption&gt;브라우저에서 호출&lt;/figcaption&gt;
&lt;/figure&gt;
&lt;figure class=&quot;imageblock alignCenter&quot; data-ke-mobileStyle=&quot;widthOrigin&quot; data-origin-width=&quot;940&quot; data-origin-height=&quot;358&quot;&gt;&lt;span data-url=&quot;https://blog.kakaocdn.net/dn/bdjfoq/btrPDArk1bE/aUdj4bV8niO86QoIRYpk2K/img.png&quot; data-phocus=&quot;https://blog.kakaocdn.net/dn/bdjfoq/btrPDArk1bE/aUdj4bV8niO86QoIRYpk2K/img.png&quot; data-alt=&quot;호출시 로그 출력&quot;&gt;&lt;img src=&quot;https://blog.kakaocdn.net/dn/bdjfoq/btrPDArk1bE/aUdj4bV8niO86QoIRYpk2K/img.png&quot; srcset=&quot;https://img1.daumcdn.net/thumb/R1280x0/?scode=mtistory2&amp;fname=https%3A%2F%2Fblog.kakaocdn.net%2Fdn%2Fbdjfoq%2FbtrPDArk1bE%2FaUdj4bV8niO86QoIRYpk2K%2Fimg.png&quot; onerror=&quot;this.onerror=null; this.src='//t1.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;940&quot; height=&quot;358&quot; data-origin-width=&quot;940&quot; data-origin-height=&quot;358&quot;/&gt;&lt;/span&gt;&lt;figcaption&gt;호출시 로그 출력&lt;/figcaption&gt;
&lt;/figure&gt;
&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;h2 data-ke-size=&quot;size26&quot;&gt;마치며&lt;/h2&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;Fabric8 라이브러리를 처음알게 되었는데, 정말 쉽게 자원을 컨트롤할 수 있었다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;이렇게 쿠버네티스 대사보드나 ocp 웹콘솔 같은 걸 만들어보는 프로젝트를 진행해 보아도 좋을 것 같다.&lt;/p&gt;</description>
      <category>개발/Spring</category>
      <category>fabric8</category>
      <category>K8S</category>
      <category>Kubernetes</category>
      <category>SpringBoot</category>
      <author>joon95</author>
      <guid isPermaLink="true">https://flowlog.tistory.com/89</guid>
      <comments>https://flowlog.tistory.com/89#entry89comment</comments>
      <pubDate>Wed, 26 Oct 2022 23:59:08 +0900</pubDate>
    </item>
    <item>
      <title>[Spring Cloud] Zipkin(집킨) 분산 추적 해보기</title>
      <link>https://flowlog.tistory.com/88</link>
      <description>&lt;p data-ke-size=&quot;size16&quot;&gt;Zipkin(집킨)은 스프링클라우드에서 제공하는 &lt;b&gt;분산추적 서비스&lt;/b&gt;이다&lt;span&gt;.&lt;/span&gt;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;span&gt;&lt;b&gt;Zipkin&lt;/b&gt;(&lt;/span&gt;집킨&lt;span&gt;)&lt;/span&gt;서버와 &lt;span&gt;&lt;b&gt;sleuth&lt;/b&gt;(&lt;/span&gt;슬루스&lt;span&gt;)&lt;/span&gt;가 있으며&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;집킨서버는 &lt;b&gt;데이터를 보관 및 웹대시보드&lt;/b&gt;를 제공하며&lt;span&gt;,&lt;/span&gt;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;슬루스는 &lt;b&gt;로그 데이터들을 잘 처리&lt;/b&gt;하여 집킨에 보내는 역할이다&lt;span&gt;.&lt;/span&gt;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;span&gt;&amp;nbsp;&lt;/span&gt;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;집킨서버의 데이터 저장소는&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;- 인메모리&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;span&gt;- mysql&lt;/span&gt;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;- 카산드라&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;span&gt;- elasticSearch&lt;/span&gt;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;로 설정할 수 있으며 별다른 설정이 없을 경우 인메모리&lt;span&gt;(WAS)&lt;/span&gt;에 저장하게 된다&lt;span&gt;.&lt;/span&gt;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;h2 data-ke-size=&quot;size26&quot;&gt;&lt;span&gt;Zipkin 구동&lt;/span&gt;&lt;/h2&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;span&gt;간단히 도커를 통해 기동하며 port는 default 9411 을 사용한다.&lt;/span&gt;&lt;/p&gt;
&lt;pre id=&quot;code_1666794629492&quot; class=&quot;java&quot; data-ke-language=&quot;java&quot; data-ke-type=&quot;codeblock&quot;&gt;&lt;code&gt;docker run -d -p 9411:9411 openzipkin/zipkin&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;&lt;figure class=&quot;imageblock alignCenter&quot; data-ke-mobileStyle=&quot;widthOrigin&quot; data-origin-width=&quot;940&quot; data-origin-height=&quot;480&quot;&gt;&lt;span data-url=&quot;https://blog.kakaocdn.net/dn/nOBrC/btrPFy0o5Uu/jv81VpCk1IQPYiIKONiAI1/img.png&quot; data-phocus=&quot;https://blog.kakaocdn.net/dn/nOBrC/btrPFy0o5Uu/jv81VpCk1IQPYiIKONiAI1/img.png&quot; data-alt=&quot;Zipkin index page&quot;&gt;&lt;img src=&quot;https://blog.kakaocdn.net/dn/nOBrC/btrPFy0o5Uu/jv81VpCk1IQPYiIKONiAI1/img.png&quot; srcset=&quot;https://img1.daumcdn.net/thumb/R1280x0/?scode=mtistory2&amp;fname=https%3A%2F%2Fblog.kakaocdn.net%2Fdn%2FnOBrC%2FbtrPFy0o5Uu%2Fjv81VpCk1IQPYiIKONiAI1%2Fimg.png&quot; onerror=&quot;this.onerror=null; this.src='//t1.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;940&quot; height=&quot;480&quot; data-origin-width=&quot;940&quot; data-origin-height=&quot;480&quot;/&gt;&lt;/span&gt;&lt;figcaption&gt;Zipkin index page&lt;/figcaption&gt;
&lt;/figure&gt;
&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;h2 data-ke-size=&quot;size26&quot;&gt;Pom.xml&lt;/h2&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;zipkin 사용을 위한 라이브러리 추가&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;위에서 설명했듯이 슬루스란 친구를 통해 데이터를 zipkin서버에 보낸다.&lt;/p&gt;
&lt;pre id=&quot;code_1666794735850&quot; class=&quot;xml&quot; data-ke-language=&quot;highlight.js&quot; data-ke-type=&quot;codeblock&quot;&gt;&lt;code&gt;&amp;lt;!-- zipkin --&amp;gt;
&amp;lt;dependency&amp;gt;
	&amp;lt;groupId&amp;gt;org.springframework.cloud&amp;lt;/groupId&amp;gt;
	&amp;lt;artifactId&amp;gt;spring-cloud-sleuth-zipkin&amp;lt;/artifactId&amp;gt;
    &amp;lt;version&amp;gt;3.1.4&amp;lt;/version&amp;gt;
&amp;lt;/dependency&amp;gt;
&amp;lt;!-- https://mvnrepository.com/artifact/org.springframework.cloud/spring-cloud-sleuth-zipkin --&amp;gt;
&amp;lt;dependency&amp;gt;
	&amp;lt;groupId&amp;gt;org.springframework.cloud&amp;lt;/groupId&amp;gt;
	&amp;lt;artifactId&amp;gt;spring-cloud-starter-sleuth&amp;lt;/artifactId&amp;gt;
    &amp;lt;version&amp;gt;3.1.4&amp;lt;/version&amp;gt;
&amp;lt;/dependency&amp;gt;&lt;/code&gt;&lt;/pre&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;h2 data-ke-size=&quot;size26&quot;&gt;properties&lt;/h2&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;집킨 서버와 슬루스의 트랜잭션 전송 비율을 정의한다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;(probability: 1.0은 100%를 의미함)&lt;/p&gt;
&lt;pre id=&quot;code_1666794884815&quot; class=&quot;java&quot; data-ke-language=&quot;java&quot; data-ke-type=&quot;codeblock&quot;&gt;&lt;code&gt;spring.zipkin.base-url=http://localhost:9411
spring.sleuth.sampler.probability=1.0&lt;/code&gt;&lt;/pre&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;h2 data-ke-size=&quot;size26&quot;&gt;테스트&lt;/h2&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;설정 후 &lt;span&gt;springboot &lt;/span&gt;를 실행하면&lt;span&gt;&lt;span&gt;&amp;nbsp; &lt;/span&gt;Log&lt;/span&gt;가 달라진 것을 확인할 수 있다&lt;span&gt;.&lt;/span&gt;&lt;/p&gt;
&lt;p&gt;&lt;figure class=&quot;imageblock alignCenter&quot; data-ke-mobileStyle=&quot;widthOrigin&quot; data-origin-width=&quot;940&quot; data-origin-height=&quot;258&quot;&gt;&lt;span data-url=&quot;https://blog.kakaocdn.net/dn/bkfvT4/btrPFyTBf6K/63qMccSpPEZikY2XbQo9Hk/img.png&quot; data-phocus=&quot;https://blog.kakaocdn.net/dn/bkfvT4/btrPFyTBf6K/63qMccSpPEZikY2XbQo9Hk/img.png&quot;&gt;&lt;img src=&quot;https://blog.kakaocdn.net/dn/bkfvT4/btrPFyTBf6K/63qMccSpPEZikY2XbQo9Hk/img.png&quot; srcset=&quot;https://img1.daumcdn.net/thumb/R1280x0/?scode=mtistory2&amp;fname=https%3A%2F%2Fblog.kakaocdn.net%2Fdn%2FbkfvT4%2FbtrPFyTBf6K%2F63qMccSpPEZikY2XbQo9Hk%2Fimg.png&quot; onerror=&quot;this.onerror=null; this.src='//t1.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;940&quot; height=&quot;258&quot; data-origin-width=&quot;940&quot; data-origin-height=&quot;258&quot;/&gt;&lt;/span&gt;&lt;/figure&gt;
&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;span&gt;Zipkin &lt;/span&gt;대시보드를 보면 트레이스들이 잡힌다&lt;span&gt;.&lt;/span&gt;&lt;/p&gt;
&lt;p&gt;&lt;figure class=&quot;imageblock alignCenter&quot; data-ke-mobileStyle=&quot;widthOrigin&quot; data-origin-width=&quot;940&quot; data-origin-height=&quot;559&quot;&gt;&lt;span data-url=&quot;https://blog.kakaocdn.net/dn/bf28q8/btrPFdvkbDK/bpIzAsRpnuYsDrhHGBqLq0/img.png&quot; data-phocus=&quot;https://blog.kakaocdn.net/dn/bf28q8/btrPFdvkbDK/bpIzAsRpnuYsDrhHGBqLq0/img.png&quot; data-alt=&quot;zipkin trace list&quot;&gt;&lt;img src=&quot;https://blog.kakaocdn.net/dn/bf28q8/btrPFdvkbDK/bpIzAsRpnuYsDrhHGBqLq0/img.png&quot; srcset=&quot;https://img1.daumcdn.net/thumb/R1280x0/?scode=mtistory2&amp;fname=https%3A%2F%2Fblog.kakaocdn.net%2Fdn%2Fbf28q8%2FbtrPFdvkbDK%2FbpIzAsRpnuYsDrhHGBqLq0%2Fimg.png&quot; onerror=&quot;this.onerror=null; this.src='//t1.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;940&quot; height=&quot;559&quot; data-origin-width=&quot;940&quot; data-origin-height=&quot;559&quot;/&gt;&lt;/span&gt;&lt;figcaption&gt;zipkin trace list&lt;/figcaption&gt;
&lt;/figure&gt;
&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;트레이스를 하나 클릭해보면 상세하게 소요시간 스텝이 보이게된다.&lt;/p&gt;
&lt;p&gt;&lt;figure class=&quot;imageblock alignCenter&quot; data-ke-mobileStyle=&quot;widthOrigin&quot; data-origin-width=&quot;940&quot; data-origin-height=&quot;406&quot;&gt;&lt;span data-url=&quot;https://blog.kakaocdn.net/dn/NIEDS/btrPEDul6wY/Lz7lpr02VRHVpbBqsSnbbK/img.png&quot; data-phocus=&quot;https://blog.kakaocdn.net/dn/NIEDS/btrPEDul6wY/Lz7lpr02VRHVpbBqsSnbbK/img.png&quot; data-alt=&quot;zipkin trace detail view&quot;&gt;&lt;img src=&quot;https://blog.kakaocdn.net/dn/NIEDS/btrPEDul6wY/Lz7lpr02VRHVpbBqsSnbbK/img.png&quot; srcset=&quot;https://img1.daumcdn.net/thumb/R1280x0/?scode=mtistory2&amp;fname=https%3A%2F%2Fblog.kakaocdn.net%2Fdn%2FNIEDS%2FbtrPEDul6wY%2FLz7lpr02VRHVpbBqsSnbbK%2Fimg.png&quot; onerror=&quot;this.onerror=null; this.src='//t1.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;940&quot; height=&quot;406&quot; data-origin-width=&quot;940&quot; data-origin-height=&quot;406&quot;/&gt;&lt;/span&gt;&lt;figcaption&gt;zipkin trace detail view&lt;/figcaption&gt;
&lt;/figure&gt;
&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;추가로 &lt;span&gt;spring.application.name &lt;/span&gt;을 지정하면&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;span style=&quot;background-color: #e8f2fe; color: #000000;&quot;&gt;spring.application.name=&lt;/span&gt;&lt;span style=&quot;background-color: #e8f2fe; color: #2aa198;&quot;&gt;my-app-joon95&lt;/span&gt;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;로그부분과 대시보드의 serviceName이 변경된다&lt;span&gt;.&lt;/span&gt;&lt;/p&gt;
&lt;p&gt;&lt;figure class=&quot;imageblock alignCenter&quot; data-ke-mobileStyle=&quot;widthOrigin&quot; data-origin-width=&quot;940&quot; data-origin-height=&quot;97&quot;&gt;&lt;span data-url=&quot;https://blog.kakaocdn.net/dn/oE7yV/btrPFdPDFRz/8knIRRDFY83yn7tt12zp11/img.png&quot; data-phocus=&quot;https://blog.kakaocdn.net/dn/oE7yV/btrPFdPDFRz/8knIRRDFY83yn7tt12zp11/img.png&quot;&gt;&lt;img src=&quot;https://blog.kakaocdn.net/dn/oE7yV/btrPFdPDFRz/8knIRRDFY83yn7tt12zp11/img.png&quot; srcset=&quot;https://img1.daumcdn.net/thumb/R1280x0/?scode=mtistory2&amp;fname=https%3A%2F%2Fblog.kakaocdn.net%2Fdn%2FoE7yV%2FbtrPFdPDFRz%2F8knIRRDFY83yn7tt12zp11%2Fimg.png&quot; onerror=&quot;this.onerror=null; this.src='//t1.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;940&quot; height=&quot;97&quot; data-origin-width=&quot;940&quot; data-origin-height=&quot;97&quot;/&gt;&lt;/span&gt;&lt;/figure&gt;
&lt;figure class=&quot;imageblock alignCenter&quot; data-ke-mobileStyle=&quot;widthOrigin&quot; data-origin-width=&quot;697&quot; data-origin-height=&quot;450&quot;&gt;&lt;span data-url=&quot;https://blog.kakaocdn.net/dn/ld2tO/btrPDfnIiqs/3pjnkeBbZK6dnZpz76Tfek/img.png&quot; data-phocus=&quot;https://blog.kakaocdn.net/dn/ld2tO/btrPDfnIiqs/3pjnkeBbZK6dnZpz76Tfek/img.png&quot;&gt;&lt;img src=&quot;https://blog.kakaocdn.net/dn/ld2tO/btrPDfnIiqs/3pjnkeBbZK6dnZpz76Tfek/img.png&quot; srcset=&quot;https://img1.daumcdn.net/thumb/R1280x0/?scode=mtistory2&amp;fname=https%3A%2F%2Fblog.kakaocdn.net%2Fdn%2Fld2tO%2FbtrPDfnIiqs%2F3pjnkeBbZK6dnZpz76Tfek%2Fimg.png&quot; onerror=&quot;this.onerror=null; this.src='//t1.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;697&quot; height=&quot;450&quot; data-origin-width=&quot;697&quot; data-origin-height=&quot;450&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;/h2&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;MSA 아키텍처에서는 서비스A에서 B,C,D.. 많은 네트워크 구간들을 통해 서비스를 제공하게되는데, 이때 발생되는 loss를 이러한 Zipkin(집킨)을 통해 파악할 수 있다. 여기서 적용되어있는 redis를 통한 캐시 정책 이 외에도 kafka를 통한 메시지큐서비스로 SAGA패턴을 구성해 볼 예정이다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;스프링클라우드가 얼마나 많은 고민들을 하여 라이브러리를 제공하게 되었는지 그 노력은 정말 대단하다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;</description>
      <category>개발/Spring</category>
      <category>MSA</category>
      <category>Zipkin</category>
      <category>분산추적서비스</category>
      <category>집킨</category>
      <author>joon95</author>
      <guid isPermaLink="true">https://flowlog.tistory.com/88</guid>
      <comments>https://flowlog.tistory.com/88#entry88comment</comments>
      <pubDate>Wed, 26 Oct 2022 23:43:11 +0900</pubDate>
    </item>
    <item>
      <title>[Springboot] Sse(Server Send Event) 단방향 통신을 이용해 tail -f 기능 구현</title>
      <link>https://flowlog.tistory.com/87</link>
      <description>&lt;h2 data-ke-size=&quot;size26&quot;&gt;난 이걸 왜 쓰게되었나?&lt;/h2&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;span&gt;OCP &lt;/span&gt;웹콘솔에 보면 &lt;span&gt;pod&lt;/span&gt;의 &lt;span&gt;log&lt;/span&gt;를 지속적으로 호출하는 페이지가 있는데&lt;span&gt;, &lt;/span&gt;말 그대로 서버가 클라이언트 쪽에 로그를 일방적으로 보내는 방식인거 같았고&lt;span&gt;, &lt;/span&gt;이걸 구현해보고 싶다는 생각이 들어 통신모듈을 찾아보다가 &lt;span&gt;SSE(Server Send Event)&lt;/span&gt;라는 라이브러리를 알게되어 찾아보니&lt;span&gt;&amp;nbsp;&lt;/span&gt;보통 접속중인 사용자에게 &lt;span&gt;push &lt;/span&gt;알림을 보내는 용도&lt;span&gt;, &lt;/span&gt;스포츠 중계서비스에 이용할 수 있다&lt;span&gt;. &lt;/span&gt;필자는 리눅스에서 로그 파일을 볼 때 이용하는 &lt;b&gt;&lt;span&gt;tail -f &lt;/span&gt;기능으로 활용&lt;/b&gt;해보려한다&lt;span&gt;.&lt;/span&gt;&lt;/p&gt;
&lt;h2 data-ke-size=&quot;size26&quot;&gt;단방향 통신을 위한 모듈&lt;/h2&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;span&gt;SeeEmitter &lt;/span&gt;클래스는 &lt;span&gt;2015&lt;/span&gt;년&lt;span&gt;(Spring Framework 4.2)&lt;/span&gt;부터 사용할 수 있게 되었다&lt;span&gt;.&lt;/span&gt;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;span&gt;Internet Exporer&lt;/span&gt;를 제외한 모든 브라우저를 지원한다&lt;span&gt;.&lt;/span&gt;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;span&gt;HTTP/1.1 &lt;/span&gt;프로토콜 사용시 브라우저에서 &lt;span&gt;1&lt;/span&gt;개 도메인에 대해 생성할 수 있는 &lt;span&gt;EventStream&lt;/span&gt;은 최대 &lt;span&gt;6&lt;/span&gt;개이다&lt;span&gt;.&lt;/span&gt;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;span&gt;HTTP/2 &lt;/span&gt;&lt;span&gt;프로토콜 사용시 브라우저와 서버간 최대 &lt;span&gt;100&lt;/span&gt;개까지 유지 가능하다.&lt;/span&gt;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;h2 data-ke-size=&quot;size26&quot;&gt;&lt;span&gt;주의사항&lt;/span&gt;&lt;/h2&gt;
&lt;h3 data-ke-size=&quot;size23&quot;&gt;&lt;span&gt;JPA 설정 시&lt;/span&gt;&lt;/h3&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;span&gt;SSE &lt;/span&gt;통신을 사용하는 동안 &lt;b&gt;&lt;span&gt;HTTP Connection&lt;/span&gt;&lt;/b&gt;이 계속 열려있으므로 응답 &lt;span&gt;API&lt;/span&gt;쪽에서 &lt;span&gt;JAP&lt;/span&gt;를 사용하고 &lt;b&gt;&lt;span&gt;open-in-view &lt;/span&gt;&lt;/b&gt;속성이 &lt;span&gt;true&lt;/span&gt;로 되어있다면&lt;span&gt;, DB Connection&lt;/span&gt;도 같이 열려있게되니 반드시 해당 속성을 &lt;b&gt;&lt;span&gt;false&lt;/span&gt;&lt;/b&gt;로 해야한다&lt;span&gt;&lt;/span&gt;&lt;/p&gt;
&lt;h3 data-ke-size=&quot;size23&quot;&gt;&lt;span&gt;Nginx 리버스 프록시&lt;/span&gt;&lt;/h3&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;위 설정을 사용한다면 아래와 같은 세팅이 적용된다&lt;span&gt;.&lt;/span&gt;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;span&gt;&lt;span&gt;1.&lt;span&gt; &lt;/span&gt;&lt;/span&gt;&lt;/span&gt;기본적으로 &lt;span&gt;Upstream &lt;/span&gt;요청 시 &lt;span&gt;HTTP/1.0 &lt;/span&gt;버전을 사용함&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;span&gt;&lt;span&gt;2.&lt;span&gt; &lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span&gt;Nnginx&lt;/span&gt;에서&lt;span&gt; Connectiob: close &lt;/span&gt;헤더를 사용함&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;이를 해결하기 위해 아래설정을 추가하자&lt;span&gt;.&lt;/span&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 width=&quot;601&quot;&gt;&lt;span&gt;&lt;span&gt;proxy_set_header Connection '';&lt;/span&gt;&lt;span&gt;&lt;/span&gt;&lt;/span&gt;&lt;br /&gt;&lt;span&gt;&lt;span&gt;proxy_http_version 1.1;&lt;/span&gt;&lt;span&gt;&lt;/span&gt;&lt;/span&gt;&lt;/td&gt;
&lt;/tr&gt;
&lt;/tbody&gt;
&lt;/table&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;그 외에도 &lt;span&gt;proxy buffering &lt;/span&gt;기능을 비활성화하여 &lt;span&gt;SSE &lt;/span&gt;응답에 대해 즉각적인 대응이 되도록 해야한다&lt;span&gt;. &lt;/span&gt;이 부분은 &lt;span&gt;SSE &lt;/span&gt;응답 &lt;span&gt;API &lt;/span&gt;쪽에서 헤더에 &lt;b&gt;&lt;span&gt;X-Accel-Buffering: no &lt;/span&gt;&lt;/b&gt;를 붙여주면 &lt;span&gt;SSE &lt;/span&gt;응답만 버퍼링을 하지 않도록 할 수 있다고 한다&lt;span&gt;. (&lt;/span&gt;자세한 설명은 참고 링크를 확인&lt;span&gt;)&lt;/span&gt;&lt;/p&gt;
&lt;h3 data-ke-size=&quot;size23&quot;&gt;&lt;span&gt;Scale out &lt;/span&gt;시 문제&lt;/h3&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;span&gt;SseEmitter &lt;/span&gt;객체는 서버 메모리에 저장되어 있어 인스턴스를 늘리는 경우 커넥션을 해당 컨테이너에 유지시키는 방식을 써야한다&lt;span&gt;. &lt;/span&gt;해싱방식을 쓸 순 있지만&lt;span&gt;, Redis &lt;/span&gt;같은 메시지 브로커나 분산 캐시를 통해 문제를 해결할 수 있다고 한다&lt;span&gt;.&lt;/span&gt;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;h2 data-ke-size=&quot;size26&quot;&gt;구현&lt;/h2&gt;
&lt;h3 data-ke-size=&quot;size23&quot;&gt;application.properties&lt;/h3&gt;
&lt;p data-ke-size=&quot;size18&quot;&gt;로그 파일을 파일로 저장하고 롤링, 보관주기를 설정하자.&lt;/p&gt;
&lt;p data-ke-size=&quot;size18&quot;&gt;스프링 application 실행 위치에 myapp.log 로 생성되게 하였다.&lt;/p&gt;
&lt;pre id=&quot;code_1666793433674&quot; class=&quot;bash&quot; data-ke-language=&quot;bash&quot; data-ke-type=&quot;codeblock&quot;&gt;&lt;code&gt;logging.file.path=.
logging.file.name=myapp.log
logging.pattern.rolling-file-name=myapp-%d{yyyy-MM-dd}.%i.log
logging.file.max-history=2
logging.file.max-size=1MB
logging.file.total-size-cap=10MB
logging.file.clean-history-on-start=true&lt;/code&gt;&lt;/pre&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;h3 data-ke-size=&quot;size23&quot;&gt;sse.html&lt;/h3&gt;
&lt;pre id=&quot;code_1666793485412&quot; class=&quot;html xml&quot; data-ke-language=&quot;html&quot; data-ke-type=&quot;codeblock&quot;&gt;&lt;code&gt;&amp;lt;!DOCTYPE html&amp;gt;
&amp;lt;html lang=&quot;en&quot;&amp;gt;
&amp;lt;head&amp;gt;
    &amp;lt;meta charset=&quot;UTF-8&quot;&amp;gt;
    &amp;lt;title&amp;gt;Title&amp;lt;/title&amp;gt;
&amp;lt;/head&amp;gt;
&amp;lt;body&amp;gt;
&amp;lt;input id=&quot;input&quot;/&amp;gt;
&amp;lt;button id=&quot;send&quot;&amp;gt;로그파일 읽기&amp;lt;/button&amp;gt;
&amp;lt;div id=&quot;messages&quot;&amp;gt;&amp;lt;/div&amp;gt;
&amp;lt;script&amp;gt;
    const eventSource = new EventSource(`/subscribe?id=${Math.random()}`);
    eventSource.onopen = (e) =&amp;gt; { console.log(e); };
    eventSource.onerror = (e) =&amp;gt; { console.log(e); };
    eventSource.onmessage = (e) =&amp;gt; {
        document.querySelector(&quot;#messages&quot;).appendChild(document.createTextNode(e.data + &quot;\n&quot;));
    };
    document.querySelector(&quot;#send&quot;).addEventListener(&quot;click&quot;, () =&amp;gt; {
        fetch(`/publish?message=${document.querySelector(&quot;#input&quot;).value}`);
    });
&amp;lt;/script&amp;gt;
&amp;lt;/body&amp;gt;
&amp;lt;/html&amp;gt;&lt;/code&gt;&lt;/pre&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;페이지가 로딩되면 바로 /&lt;span&gt;subscribe?&lt;/span&gt;&lt;span&gt;id=&lt;/span&gt;랜덤값을 보내어 파일쓰레드생성을 요청할 것이다.&lt;span&gt;.&lt;/span&gt;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;span&gt;&lt;b&gt;Input&lt;/b&gt; &lt;/span&gt;태그를 넣어두었는데 이부분을 활용하면 접속한 세션 &lt;b&gt;전체에 메시지를 전달&lt;/b&gt;하도록 할 수 있다&lt;span&gt;.&lt;/span&gt;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;h3 data-ke-size=&quot;size23&quot;&gt;&lt;span&gt;파일 읽기&lt;/span&gt;&lt;/h3&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;파일을 읽어 sse 객체에 send 해준다.&lt;/p&gt;
&lt;pre id=&quot;code_1666793614266&quot; class=&quot;java&quot; data-ke-language=&quot;java&quot; data-ke-type=&quot;codeblock&quot;&gt;&lt;code&gt;@Slf4j
public class FileThread extends Thread{
    private static final int DELAY_MILLIS = 1000;
    private boolean isRun;
    //대상 파일
    private final File file;
    private final Map&amp;lt;String, SseEmitter&amp;gt; sse;
    
    public FileThread(File file, Map&amp;lt;String, SseEmitter&amp;gt; sse) {
        this.file = file;
        this.sse = sse;
    }

    @Override
    public void run() {
        isRun = true;
        if (!file.exists()) {
            System.out.println(&quot;Failed to find a file - &quot; + file.getPath());
        }
        //try 문에서 Stream을 열면 블럭이 끝났을 때 close를 해줌
        try (BufferedReader br = new BufferedReader(new InputStreamReader(new FileInputStream(file), StandardCharsets.UTF_8))) {
            while (isRun) {
                //readLine(): 파일의 한 line을 읽어오는 메소드
                final String le = br.readLine();
                if (le != null) {
//                  System.out.println(&quot;New line added - &quot; + le);
                    sse.forEach((id, emitter) -&amp;gt; {
                        try {
                            emitter.send(le, MediaType.TEXT_PLAIN);
                        } catch (Exception e) {
//                          deadIds.add(id);
                            log.info(&quot;disconnected id : &quot; + id);
                        }
                    });            	
                } else {
                    Thread.sleep(DELAY_MILLIS);
                }
            }
        } catch (Exception e) {
            System.out.println(&quot;Failed to tail a file - &quot; + file.getPath());
        }
        System.out.println(&quot;Stop to tail a file - &quot; + file.getName());
    }
}&lt;/code&gt;&lt;/pre&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;h3 data-ke-size=&quot;size23&quot;&gt;Sse Controller&lt;/h3&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;GET /tails url 에 접근하면 위에서 생성한 sse.html 을 출력하고&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;GET /subscribe?id=랜덤값 을 통해 sse객체에 id 값을 맵핑한다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;GET /publish 는 파일쓰레드를 생성하여 지속적으로 클라이언트에게 메시지를 보내게 된다.&lt;/p&gt;
&lt;pre id=&quot;code_1666793838266&quot; class=&quot;java&quot; data-ke-language=&quot;java&quot; data-ke-type=&quot;codeblock&quot;&gt;&lt;code&gt;@Slf4j
@Controller
public class LogsSse {

    private static final Map&amp;lt;String, SseEmitter&amp;gt; CLIENTS = new ConcurrentHashMap&amp;lt;&amp;gt;();

    @GetMapping(value=&quot;/tails&quot;)
    public String page() {
    	log.info(&quot;springboot application log tails -f page&quot;);
        return &quot;html/sse&quot;;
    }

    
    @GetMapping(value=&quot;/subscribe&quot;, produces = MediaType.TEXT_EVENT_STREAM_VALUE)
    public SseEmitter subscribe(String id) throws IOException {
        SseEmitter emitter = new SseEmitter();
        CLIENTS.put(id, emitter);
        emitter.onTimeout(() -&amp;gt; CLIENTS.remove(id));
        emitter.onCompletion(() -&amp;gt; CLIENTS.remove(id));

        emitter.send(&quot;서버와 연결되었습니다. ID(&quot; + id + &quot;)&quot;, MediaType.TEXT_PLAIN);
        log.info(&quot;서버와 연결되었습니다. ID(&quot; + id + &quot;)&quot;);

        return emitter;
    }

    @GetMapping(value=&quot;/publish&quot;)
    public @ResponseBody HashMap&amp;lt;String, Object&amp;gt; publish(String message) {
        HashMap&amp;lt;String,Object&amp;gt; map = new HashMap&amp;lt;String,Object&amp;gt;();
        Set&amp;lt;String&amp;gt; deadIds = new HashSet&amp;lt;&amp;gt;();

        CLIENTS.forEach((id, emitter) -&amp;gt; {
            try {
//                emitter.send(message, MediaType.TEXT_PLAIN);
                log.info(&quot;전송ID : &quot; + id + &quot;, 내용 : &quot; + message);
                fileGet();
            } catch (Exception e) {
                deadIds.add(id);
                log.info(&quot;disconnected id : &quot; + id);
                thread.interrupt();
            }
        });
        
        map.put(&quot;status&quot;,&quot;success&quot;);
        deadIds.forEach(CLIENTS::remove);
        return map;
    }

    FileThread watcher = null;
    Thread thread = null;
    // 스프링부트 로그를 읽어오기
    public void fileGet() {
    	if(thread==null) {
	    	File file = new File(&quot;myapp.log&quot;);    
	    	watcher = new FileThread(file, CLIENTS);
	    	thread = new Thread(watcher);
	    	thread.setDaemon(true);
	        thread.start();
    	}
    }
}&lt;/code&gt;&lt;/pre&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;h2 data-ke-size=&quot;size26&quot;&gt;테스트&lt;/h2&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;작성한 /tails 페이지에 접근해보자.&lt;/p&gt;
&lt;p&gt;&lt;figure class=&quot;imageblock alignCenter&quot; data-ke-mobileStyle=&quot;widthOrigin&quot; data-origin-width=&quot;940&quot; data-origin-height=&quot;436&quot;&gt;&lt;span data-url=&quot;https://blog.kakaocdn.net/dn/Rt4rW/btrPFyeZIz9/AbWotvS25kmPqxqdpJ8nEK/img.png&quot; data-phocus=&quot;https://blog.kakaocdn.net/dn/Rt4rW/btrPFyeZIz9/AbWotvS25kmPqxqdpJ8nEK/img.png&quot;&gt;&lt;img src=&quot;https://blog.kakaocdn.net/dn/Rt4rW/btrPFyeZIz9/AbWotvS25kmPqxqdpJ8nEK/img.png&quot; srcset=&quot;https://img1.daumcdn.net/thumb/R1280x0/?scode=mtistory2&amp;fname=https%3A%2F%2Fblog.kakaocdn.net%2Fdn%2FRt4rW%2FbtrPFyeZIz9%2FAbWotvS25kmPqxqdpJ8nEK%2Fimg.png&quot; onerror=&quot;this.onerror=null; this.src='//t1.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;940&quot; height=&quot;436&quot; data-origin-width=&quot;940&quot; data-origin-height=&quot;436&quot;/&gt;&lt;/span&gt;&lt;/figure&gt;
&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;페이지접근 시 &lt;span&gt;sse.html &lt;/span&gt;의 &lt;span&gt;eventSource&lt;/span&gt;를 통해 &lt;span&gt;/subscribe &lt;/span&gt;페이지에 랜덤한 &lt;span&gt;id&lt;/span&gt;값을 보내어 &lt;span&gt;sseEmitter &lt;/span&gt;객체를 생성하게 되고&lt;span&gt;, [&lt;/span&gt;로그파일 읽기&lt;span&gt;] &lt;/span&gt;버튼을 누르면 쓰레드를 생성해 &lt;span&gt;file&lt;/span&gt;을 지속적으로 읽어 &lt;span&gt;sseEmitter &lt;/span&gt;객체에 들어온 클라이언트들에게 로그를 보낸다&lt;span&gt;.&lt;/span&gt;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p&gt;&lt;figure class=&quot;imageblock alignCenter&quot; data-ke-mobileStyle=&quot;widthOrigin&quot; data-origin-width=&quot;940&quot; data-origin-height=&quot;475&quot;&gt;&lt;span data-url=&quot;https://blog.kakaocdn.net/dn/E7RLC/btrPGe1BTSo/VkEtzaEGksdJJkxaEHuB81/img.png&quot; data-phocus=&quot;https://blog.kakaocdn.net/dn/E7RLC/btrPGe1BTSo/VkEtzaEGksdJJkxaEHuB81/img.png&quot;&gt;&lt;img src=&quot;https://blog.kakaocdn.net/dn/E7RLC/btrPGe1BTSo/VkEtzaEGksdJJkxaEHuB81/img.png&quot; srcset=&quot;https://img1.daumcdn.net/thumb/R1280x0/?scode=mtistory2&amp;fname=https%3A%2F%2Fblog.kakaocdn.net%2Fdn%2FE7RLC%2FbtrPGe1BTSo%2FVkEtzaEGksdJJkxaEHuB81%2Fimg.png&quot; onerror=&quot;this.onerror=null; this.src='//t1.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;940&quot; height=&quot;475&quot; data-origin-width=&quot;940&quot; data-origin-height=&quot;475&quot;/&gt;&lt;/span&gt;&lt;/figure&gt;
&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;여러 브라우저를 동시에 켜서 확인할 수 있고&lt;span&gt;, &lt;/span&gt;접속한 랜덤한 &lt;span&gt;ID &lt;/span&gt;별로 데이터를 전달하게된다&lt;span&gt;.&lt;/span&gt;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;h2 data-ke-size=&quot;size26&quot;&gt;&lt;span&gt;마치며&lt;/span&gt;&lt;/h2&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;span&gt;통신에 대한 부분은 항상 소켓만 생각했었고, 소켓으로만 구현해보았었는데,&lt;/span&gt;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;span&gt;찾아보니 역시 용도에 따라 라이브러리가 이미 존재했다..ㅋ&lt;/span&gt;&lt;/p&gt;
&lt;figure contenteditable=&quot;false&quot; data-ke-type=&quot;emoticon&quot; data-ke-align=&quot;alignCenter&quot; data-emoticon-type=&quot;friends1&quot; data-emoticon-name=&quot;031&quot; data-emoticon-isanimation=&quot;false&quot; data-emoticon-src=&quot;https://t1.daumcdn.net/keditor/emoticon/friends1/large/031.gif&quot;&gt;&lt;img src=&quot;https://t1.daumcdn.net/keditor/emoticon/friends1/large/031.gif&quot; width=&quot;150&quot; /&gt;&lt;/figure&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;span&gt;ocp 콘솔을 쓰면서 항상 logs 메뉴를 자주 눌렀었는데 이번 기회를 통해 재밌는 기능을 구현해봐서 재밌었다.&lt;/span&gt;&lt;span&gt;&lt;/span&gt;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;h2 data-ke-size=&quot;size26&quot;&gt;참고블로그&lt;/h2&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;span&gt;&lt;a href=&quot;https://jsonobject.tistory.com/558&quot;&gt;https://jsonobject.tistory.com/558&lt;/a&gt;&lt;/span&gt;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;span&gt;&lt;a href=&quot;https://tecoble.techcourse.co.kr/post/2022-10-11-server-sent-events/&quot;&gt;https://tecoble.techcourse.co.kr/post/2022-10-11-server-sent-events/&lt;/a&gt;&lt;/span&gt;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;span&gt;&lt;a href=&quot;https://jinseongsoft.tistory.com/146&quot;&gt;https://jinseongsoft.tistory.com/146&lt;/a&gt;&lt;/span&gt;&lt;/p&gt;</description>
      <category>개발/Spring</category>
      <category>SpringBoot</category>
      <category>SSE</category>
      <category>sseEmitter</category>
      <category>tail -f</category>
      <category>기능구현</category>
      <author>joon95</author>
      <guid isPermaLink="true">https://flowlog.tistory.com/87</guid>
      <comments>https://flowlog.tistory.com/87#entry87comment</comments>
      <pubDate>Wed, 26 Oct 2022 23:27:41 +0900</pubDate>
    </item>
    <item>
      <title>[Kubernetes] docker Desktop Kubernetes PV 붙이기</title>
      <link>https://flowlog.tistory.com/86</link>
      <description>&lt;p data-ke-size=&quot;size16&quot;&gt;도커데스크톱으로 쿠버네티스를 올리고 pv를 붙이려면 hostpath로 하면 된다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;참고로&amp;nbsp; hostpath는 node의 storeage를 사용하는 것으로,&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;도커데스크톱은 pc 위에 1개의 노드풀로 올라가 있기 때문에 사용할 수 있다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;필자는 C:/kubernetes/storage/ 경로를 스토리지로 사용하고&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;샘플 pod에 toolbox 폴더라는 pv를 붙였다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;먼저 deployment.yaml 에 volume을 작성하자.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;경로는 &lt;b&gt;/run/desktop/mnt/host/c/&lt;/b&gt; 뒤에 작성하면 된다.&lt;/p&gt;
&lt;pre id=&quot;code_1666767428268&quot; class=&quot;yaml&quot; data-ke-language=&quot;highlight.js&quot; data-ke-type=&quot;codeblock&quot;&gt;&lt;code&gt;apiVersion: apps/v1
kind: Deployment
metadata:
  name: toolbox-deployment
spec:
  selector:
    matchLabels:
      app: toolbox
  replicas: 1 # tells deployment to run 2 pods matching the template
  template:
    metadata:
      labels:
        app: toolbox
    spec:
      containers:
        - name: toolbox
          image: webdevops/toolbox:latest
          ports:
            - containerPort: 80
          command: [&quot;/bin/sh&quot;, &quot;-c&quot;]
          args: [&quot;sleep 600&quot;]
          volumeMounts:
            - mountPath: /local-dir
              name: vol
      volumes:
        - name: vol
          hostPath:
            path: /run/desktop/mnt/host/c/kubernetes/storage/toolbox
            type: Directory&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;p data-ke-size=&quot;size16&quot;&gt;폴더를 미리 만들어 주지 않았을 경우 아래와 같은 에러가 발생함!&lt;/p&gt;
&lt;pre id=&quot;code_1666767513432&quot; class=&quot;bash&quot; data-ke-language=&quot;bash&quot; data-ke-type=&quot;codeblock&quot;&gt;&lt;code&gt;MountVolume.SetUp failed for volume &quot;vol&quot; : hostPath type check failed: /run/desktop/mnt/host/c/kubernetes/storage/toolbox is not a directory&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;배포 후 pod 에 들어가 df 명령어를 쳐보면 마운트가 된것을 확인할 수 있다.&lt;/p&gt;
&lt;p&gt;&lt;figure class=&quot;imageblock alignCenter&quot; data-ke-mobileStyle=&quot;widthOrigin&quot; data-origin-width=&quot;783&quot; data-origin-height=&quot;382&quot;&gt;&lt;span data-url=&quot;https://blog.kakaocdn.net/dn/bTvprT/btrPCDO1dDK/erNdOzbiB2P7ZyKcajcNX1/img.png&quot; data-phocus=&quot;https://blog.kakaocdn.net/dn/bTvprT/btrPCDO1dDK/erNdOzbiB2P7ZyKcajcNX1/img.png&quot;&gt;&lt;img src=&quot;https://blog.kakaocdn.net/dn/bTvprT/btrPCDO1dDK/erNdOzbiB2P7ZyKcajcNX1/img.png&quot; srcset=&quot;https://img1.daumcdn.net/thumb/R1280x0/?scode=mtistory2&amp;fname=https%3A%2F%2Fblog.kakaocdn.net%2Fdn%2FbTvprT%2FbtrPCDO1dDK%2FerNdOzbiB2P7ZyKcajcNX1%2Fimg.png&quot; onerror=&quot;this.onerror=null; this.src='//t1.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;783&quot; height=&quot;382&quot; data-origin-width=&quot;783&quot; data-origin-height=&quot;382&quot;/&gt;&lt;/span&gt;&lt;/figure&gt;
&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;해당 경로로 이동해 파일을 생성하면 잘된다.&lt;/p&gt;
&lt;p&gt;&lt;figure class=&quot;imageblock alignCenter&quot; data-ke-mobileStyle=&quot;widthOrigin&quot; data-origin-width=&quot;453&quot; data-origin-height=&quot;229&quot;&gt;&lt;span data-url=&quot;https://blog.kakaocdn.net/dn/6TJid/btrPEjhFmQj/pd0htTRQ6lnvGsfxj9K6nK/img.png&quot; data-phocus=&quot;https://blog.kakaocdn.net/dn/6TJid/btrPEjhFmQj/pd0htTRQ6lnvGsfxj9K6nK/img.png&quot;&gt;&lt;img src=&quot;https://blog.kakaocdn.net/dn/6TJid/btrPEjhFmQj/pd0htTRQ6lnvGsfxj9K6nK/img.png&quot; srcset=&quot;https://img1.daumcdn.net/thumb/R1280x0/?scode=mtistory2&amp;fname=https%3A%2F%2Fblog.kakaocdn.net%2Fdn%2F6TJid%2FbtrPEjhFmQj%2Fpd0htTRQ6lnvGsfxj9K6nK%2Fimg.png&quot; onerror=&quot;this.onerror=null; this.src='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png'; this.srcset='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png';&quot; loading=&quot;lazy&quot; width=&quot;453&quot; height=&quot;229&quot; data-origin-width=&quot;453&quot; data-origin-height=&quot;229&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;</description>
      <category>엔지니어링/Kubernetes</category>
      <category>hostpath</category>
      <category>PV</category>
      <category>storage</category>
      <author>joon95</author>
      <guid isPermaLink="true">https://flowlog.tistory.com/86</guid>
      <comments>https://flowlog.tistory.com/86#entry86comment</comments>
      <pubDate>Wed, 26 Oct 2022 16:04:43 +0900</pubDate>
    </item>
    <item>
      <title>[Kubernetes] nginx-ingress로 들어온 path를 삭제하고 백엔드에 전달하기</title>
      <link>https://flowlog.tistory.com/85</link>
      <description>&lt;p data-ke-size=&quot;size16&quot;&gt;쿠버네티스 ingress 설정 중&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;path에 따라 서비스를 연결시켜주는 방법&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;metadata&amp;gt;annotaions 안에 rewrite 를 넣어주고&lt;/p&gt;
&lt;pre id=&quot;code_1666766205395&quot; class=&quot;bash&quot; data-ke-language=&quot;bash&quot; data-ke-type=&quot;codeblock&quot;&gt;&lt;code&gt;nginx.ingress.kubernetes.io/rewrite-target: /$2&lt;/code&gt;&lt;/pre&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;spec&amp;gt;rules&amp;gt;http&amp;gt;paths 안에 아래와 같이 적용하면 된다.&lt;/p&gt;
&lt;pre id=&quot;code_1666766301317&quot; class=&quot;bash&quot; data-ke-language=&quot;bash&quot; data-ke-type=&quot;codeblock&quot;&gt;&lt;code&gt;path: /외부접근패스(/|$)(.*)&lt;/code&gt;&lt;/pre&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;이렇게하면 사용자가 도메인/외부접근패스 로 들어올 경우&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;nginx 에 / 로 설정된다. &lt;b&gt;&quot;GET&amp;nbsp;/&amp;nbsp;HTTP/1.1&quot;&amp;nbsp;200&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;도메인/외부접근패스/패스1/패스2 로 들어온다면&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;nginx 에 /패스1/패스2 로 전달된다. &lt;b&gt;&quot;GET /패스1/패스2 HTTP/1.1&quot; 200&lt;/b&gt;&lt;/p&gt;</description>
      <category>엔지니어링/Kubernetes</category>
      <category>Ingress</category>
      <category>Kubernetes</category>
      <category>nginx-ingress</category>
      <category>rewrite</category>
      <author>joon95</author>
      <guid isPermaLink="true">https://flowlog.tistory.com/85</guid>
      <comments>https://flowlog.tistory.com/85#entry85comment</comments>
      <pubDate>Wed, 26 Oct 2022 15:41:34 +0900</pubDate>
    </item>
    <item>
      <title>[Springboot] kafka JSON 통신하기</title>
      <link>https://flowlog.tistory.com/84</link>
      <description>&lt;p data-ke-size=&quot;size16&quot;&gt;그동안 메시지 큐에 대해 알고만 있지 사용은 해보지 않았다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;메시지 큐는 어떤 서비스A가 서비스B에게 데이터를 전송할 때 서비스B가 만약 메모리등의 이슈로 정상적인 응답을 하지 못할 경우 해당 요청에 대한 데이터에 유실이 발생할 수 있어서 그 중간 매개체로 사용하게 된다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;실제로 작년에 ELK, EFK 를 구축할 때 메시지 큐를 포함한 아키텍처를 많이 보았었다. filebeat 에서 Logstash로 데이터를 전송할 때나, Logstash에서 Elasticsearch 에 데이터를 전송할 때 메시지큐를 사용하는 것이다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;메시지 큐는 카프카(Kafka), 레빗엠큐(RabbitMQ) 등의 솔루션이 있고, 이 글은 카프카를 사용하여 JSON 타입의 메시지 전송을 해볼 것이다.&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;h2 data-ke-size=&quot;size26&quot;&gt;카프카(Kafka)&lt;/h2&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;카프카는 도커로 올려두었다. 도커컴포즈를 이용한 다양한 글들은 바로 찾을 수 있으니 패스.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;간단히 사용 포트만 확인하자면&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;- 카프카 port : 9092&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;- 주키퍼 port : 2181&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;참고로 주키퍼는 간단히 설명하자면.. 카프카서버 관리를 위한 서비스이고, 위키백과에선 다음과 같이 설명한다.&lt;/p&gt;
&lt;blockquote data-ke-style=&quot;style1&quot;&gt;&lt;span style=&quot;font-family: 'Noto Serif KR';&quot;&gt;&lt;span style=&quot;background-color: #ffffff; color: #4d5156;&quot;&gt;아파치 주키퍼는 아파치 소프트웨어 재단 프로젝트중의 한 소프트웨어 프로젝트로서 공개 분산형 구성 서비스, 동기 서비스 및 대용량 분산 시스템을 위한 네이밍 레지스트리를 제공한다.&lt;/span&gt;&lt;/span&gt;&lt;/blockquote&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;h2 data-ke-size=&quot;size26&quot;&gt;Pom.xml&lt;/h2&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;kafka를 사용하기 위한 디펜던시를 추가하자.&lt;/p&gt;
&lt;pre id=&quot;code_1666540890086&quot; class=&quot;xml&quot; data-ke-language=&quot;xml&quot; data-ke-type=&quot;codeblock&quot;&gt;&lt;code&gt;&amp;lt;!-- kafka --&amp;gt;
&amp;lt;dependency&amp;gt;
    &amp;lt;groupId&amp;gt;org.springframework.kafka&amp;lt;/groupId&amp;gt;
    &amp;lt;artifactId&amp;gt;spring-kafka&amp;lt;/artifactId&amp;gt;
&amp;lt;/dependency&amp;gt;&lt;/code&gt;&lt;/pre&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;h2 data-ke-size=&quot;size26&quot;&gt;application.properties&lt;/h2&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;카프카서버와 offset 설정에 대해 설정한다.&lt;/p&gt;
&lt;pre id=&quot;code_1666540978439&quot; class=&quot;scala&quot; data-ke-language=&quot;scala&quot; data-ke-type=&quot;codeblock&quot;&gt;&lt;code&gt;spring.kafka.bootstrap-servers=localhost:9092
spring.kafka.consumer.auto-offset-reset=earliest&lt;/code&gt;&lt;/pre&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;auto-offset-reset 설정은 카프카가 컨슈머(Consumer)를 새로 생성하여 토픽에서 데이터를 가져올 때 offset 정보가 존재하지 않을 때 디폴트값을 설정하는 사항이다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;옵션은 3개로 아래를 참고하자.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;- 'latest' : 가장 마지막 offset&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;- 'earliest' : 가장 처음 offset&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;- 'none' : exception 발생&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;KafkaMsgVO&lt;/h2&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;json 데이터를 보낼 VO를 작성하자. 간단히 name, msg 2개의 String 데이터를 작성하였다.&lt;/p&gt;
&lt;pre id=&quot;code_1666541268486&quot; class=&quot;java&quot; data-ke-language=&quot;java&quot; data-ke-type=&quot;codeblock&quot;&gt;&lt;code&gt;@Getter
@NoArgsConstructor
@AllArgsConstructor
public class KafkaMsgVO {
	private String name;
	private String msg;
}&lt;/code&gt;&lt;/pre&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;h2 data-ke-size=&quot;size26&quot;&gt;KakfaConfig&lt;/h2&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;DefaultKafkaProducerFactory 를 Map&amp;lt;String, Object&amp;gt; 형식으로 정의해주고&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;group_id 를 foo 로 하였다. consumer에서 메시지를 가져올 때 groupId를 정의하니 참고.&lt;/p&gt;
&lt;pre id=&quot;code_1666541339836&quot; class=&quot;java&quot; data-ke-language=&quot;java&quot; data-ke-type=&quot;codeblock&quot;&gt;&lt;code&gt;@EnableKafka
@Configuration
public class KafkaConfig {

	@Value(&quot;${spring.kafka.bootstrap-servers}&quot;)
	private String servers;
	
	@Bean
	public ProducerFactory&amp;lt;String, KafkaMsgVO&amp;gt; msgProducerFactory() {
	    Map&amp;lt;String, Object&amp;gt; config = new HashMap&amp;lt;&amp;gt;();
	    config.put(ProducerConfig.BOOTSTRAP_SERVERS_CONFIG, servers);
	    config.put(ProducerConfig.KEY_SERIALIZER_CLASS_CONFIG, StringSerializer.class);
	    config.put(ProducerConfig.VALUE_SERIALIZER_CLASS_CONFIG, JsonSerializer.class);
	    return new DefaultKafkaProducerFactory&amp;lt;&amp;gt;(config);
	}

	@Bean
	public KafkaTemplate&amp;lt;String, KafkaMsgVO&amp;gt; msgKafkaTemplate(){
		return new KafkaTemplate&amp;lt;String, KafkaMsgVO&amp;gt;(msgProducerFactory());
	}

	@Bean
	public ConsumerFactory&amp;lt;String, KafkaMsgVO&amp;gt; consumerFactory() {
        Map&amp;lt;String, Object&amp;gt; props = new HashMap&amp;lt;&amp;gt;();
        props.put(ConsumerConfig.BOOTSTRAP_SERVERS_CONFIG, servers);
        props.put(ConsumerConfig.GROUP_ID_CONFIG, &quot;foo&quot;);
        return new DefaultKafkaConsumerFactory&amp;lt;&amp;gt;(props, new StringDeserializer(), new JsonDeserializer&amp;lt;&amp;gt;(KafkaMsgVO.class));
    }

    @Bean
    public ConcurrentKafkaListenerContainerFactory&amp;lt;String, KafkaMsgVO&amp;gt; kafkaListenerContainerFactory() {
        ConcurrentKafkaListenerContainerFactory&amp;lt;String, KafkaMsgVO&amp;gt; factory = new ConcurrentKafkaListenerContainerFactory&amp;lt;&amp;gt;();
        factory.setConsumerFactory(consumerFactory());
        return factory;
    }
}&lt;/code&gt;&lt;/pre&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;h2 data-ke-size=&quot;size26&quot;&gt;KafkaConsumer 서비스&lt;/h2&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;test_topic 토픽의 groupId는 foo 인 메시지를 대기하며 메시지가 발행되면 sysout 로그를 남긴다.&amp;nbsp;&lt;/p&gt;
&lt;pre id=&quot;code_1666700299422&quot; class=&quot;java&quot; data-ke-language=&quot;java&quot; data-ke-type=&quot;codeblock&quot;&gt;&lt;code&gt;@Service
public class KafkaConsumer {
@KafkaListener(topics = &quot;test_topic&quot;, groupId = &quot;foo&quot;)
    public void consume(KafkaMsgVO vo){
        System.out.println(&quot;name = &quot; + vo.getName());
        System.out.println(&quot;consume message = &quot; + vo.getMsg());
    }
}&lt;/code&gt;&lt;/pre&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;h2 data-ke-size=&quot;size26&quot;&gt;KafkaProducer 서비스&lt;/h2&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;메시지큐에 메시지를 전달하는 역할&lt;/p&gt;
&lt;pre id=&quot;code_1666700529599&quot; class=&quot;java&quot; data-ke-language=&quot;java&quot; data-ke-type=&quot;codeblock&quot;&gt;&lt;code&gt;@Service
public class KafkaProducer {
    private static final String TOPIC = &quot;test_topic&quot;;
    private final KafkaTemplate&amp;lt;String, KafkaMsgVO&amp;gt; kafkaTemplate;

    @Autowired
    public KafkaProducer(KafkaTemplate kafkaTemplate) {
	this.kafkaTemplate = kafkaTemplate;
    }

    public void sendMessage(KafkaMsgVO message) {
        System.out.println(String.format(&quot;Produce message(KafkaMsgVO) : %s&quot;, message));
        this.kafkaTemplate.send(TOPIC, message);
    }
}&lt;/code&gt;&lt;/pre&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;h2 data-ke-size=&quot;size26&quot;&gt;KafakController&lt;/h2&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;사용자가 메시지를 보내기 위한 컨트롤러를 작성&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;POST /kafka 를 통해 카프카에 메시지를 보내도록 하자.&lt;/p&gt;
&lt;pre id=&quot;code_1666700603523&quot; class=&quot;java&quot; data-ke-language=&quot;java&quot; data-ke-type=&quot;codeblock&quot;&gt;&lt;code&gt;@RestController
@RequestMapping(value = &quot;/kafka&quot;)
public class KafkaController {
    private final KafkaProducer producer;

    @Autowired
    KafkaController(KafkaProducer producer) {
        this.producer = producer;
    }

    @PostMapping
    public String sendMessageJson(@RequestBody KafkaMsgVO message) {
        this.producer.sendMessage(message);
        return &quot;success&quot;;
    }
}&lt;/code&gt;&lt;/pre&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;h2 data-ke-size=&quot;size26&quot;&gt;테스트&lt;/h2&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;먼저 kafka 서버에서 test_topic 토픽을 모니터링하자.&lt;/p&gt;
&lt;pre id=&quot;code_1666700671809&quot; class=&quot;bash&quot; data-ke-language=&quot;bash&quot; data-ke-type=&quot;codeblock&quot;&gt;&lt;code&gt;$ kafka-console-consumer.sh --bootstrap-server localhost:9092 --topic test_topic --from-beginning&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;&lt;figure class=&quot;imageblock alignCenter&quot; data-ke-mobileStyle=&quot;widthOrigin&quot; data-origin-width=&quot;940&quot; data-origin-height=&quot;170&quot;&gt;&lt;span data-url=&quot;https://blog.kakaocdn.net/dn/bturrm/btrPwAS9KWM/zCRYKeJreKuiDlnigXQrBK/img.png&quot; data-phocus=&quot;https://blog.kakaocdn.net/dn/bturrm/btrPwAS9KWM/zCRYKeJreKuiDlnigXQrBK/img.png&quot; data-alt=&quot;kafka consumer&quot;&gt;&lt;img src=&quot;https://blog.kakaocdn.net/dn/bturrm/btrPwAS9KWM/zCRYKeJreKuiDlnigXQrBK/img.png&quot; srcset=&quot;https://img1.daumcdn.net/thumb/R1280x0/?scode=mtistory2&amp;fname=https%3A%2F%2Fblog.kakaocdn.net%2Fdn%2Fbturrm%2FbtrPwAS9KWM%2FzCRYKeJreKuiDlnigXQrBK%2Fimg.png&quot; onerror=&quot;this.onerror=null; this.src='//t1.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;940&quot; height=&quot;170&quot; data-origin-width=&quot;940&quot; data-origin-height=&quot;170&quot;/&gt;&lt;/span&gt;&lt;figcaption&gt;kafka consumer&lt;/figcaption&gt;
&lt;/figure&gt;
&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;먼저 Kafka Producer로 메시지를 전달해보자.&lt;/p&gt;
&lt;pre id=&quot;code_1666700768761&quot; class=&quot;bash&quot; data-ke-language=&quot;bash&quot; data-ke-type=&quot;codeblock&quot;&gt;&lt;code&gt;$ kafka-console-producer.sh --broker-list localhost:9092 --topic test_topic&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;&lt;figure class=&quot;imageblock alignCenter&quot; data-ke-mobileStyle=&quot;widthOrigin&quot; data-origin-width=&quot;940&quot; data-origin-height=&quot;53&quot;&gt;&lt;span data-url=&quot;https://blog.kakaocdn.net/dn/rqslC/btrPzjI9WCo/9tWbinVAMq6eO0Qa2jXWt0/img.png&quot; data-phocus=&quot;https://blog.kakaocdn.net/dn/rqslC/btrPzjI9WCo/9tWbinVAMq6eO0Qa2jXWt0/img.png&quot; data-alt=&quot;kafka producer&quot;&gt;&lt;img src=&quot;https://blog.kakaocdn.net/dn/rqslC/btrPzjI9WCo/9tWbinVAMq6eO0Qa2jXWt0/img.png&quot; srcset=&quot;https://img1.daumcdn.net/thumb/R1280x0/?scode=mtistory2&amp;fname=https%3A%2F%2Fblog.kakaocdn.net%2Fdn%2FrqslC%2FbtrPzjI9WCo%2F9tWbinVAMq6eO0Qa2jXWt0%2Fimg.png&quot; onerror=&quot;this.onerror=null; this.src='//t1.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;940&quot; height=&quot;53&quot; data-origin-width=&quot;940&quot; data-origin-height=&quot;53&quot;/&gt;&lt;/span&gt;&lt;figcaption&gt;kafka producer&lt;/figcaption&gt;
&lt;/figure&gt;
&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;그럼 모니터링 쉘창에 전송되고 springboot console 창에도 데이터가 들어온다.&lt;/p&gt;
&lt;p&gt;&lt;figure class=&quot;imageblock alignCenter&quot; data-ke-mobileStyle=&quot;widthOrigin&quot; data-origin-width=&quot;411&quot; data-origin-height=&quot;177&quot;&gt;&lt;span data-url=&quot;https://blog.kakaocdn.net/dn/bfkq2x/btrPyKmOyQp/jvJGxidbBSmGpnbNxdthC0/img.png&quot; data-phocus=&quot;https://blog.kakaocdn.net/dn/bfkq2x/btrPyKmOyQp/jvJGxidbBSmGpnbNxdthC0/img.png&quot; data-alt=&quot;springboot consumer&quot;&gt;&lt;img src=&quot;https://blog.kakaocdn.net/dn/bfkq2x/btrPyKmOyQp/jvJGxidbBSmGpnbNxdthC0/img.png&quot; srcset=&quot;https://img1.daumcdn.net/thumb/R1280x0/?scode=mtistory2&amp;fname=https%3A%2F%2Fblog.kakaocdn.net%2Fdn%2Fbfkq2x%2FbtrPyKmOyQp%2FjvJGxidbBSmGpnbNxdthC0%2Fimg.png&quot; onerror=&quot;this.onerror=null; this.src='//t1.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;411&quot; height=&quot;177&quot; data-origin-width=&quot;411&quot; data-origin-height=&quot;177&quot;/&gt;&lt;/span&gt;&lt;figcaption&gt;springboot consumer&lt;/figcaption&gt;
&lt;/figure&gt;
&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;다음은 직접 작성한 컨트롤러를 통해 메시지를 전달해보자.&lt;/p&gt;
&lt;p&gt;&lt;figure class=&quot;imageblock alignCenter&quot; data-ke-mobileStyle=&quot;widthOrigin&quot; data-origin-width=&quot;940&quot; data-origin-height=&quot;361&quot;&gt;&lt;span data-url=&quot;https://blog.kakaocdn.net/dn/bFALXe/btrPx1XbTsM/A7kkGa15M3gom0sCXjObOK/img.png&quot; data-phocus=&quot;https://blog.kakaocdn.net/dn/bFALXe/btrPx1XbTsM/A7kkGa15M3gom0sCXjObOK/img.png&quot; data-alt=&quot;postman kafka 메시지 전달&quot;&gt;&lt;img src=&quot;https://blog.kakaocdn.net/dn/bFALXe/btrPx1XbTsM/A7kkGa15M3gom0sCXjObOK/img.png&quot; srcset=&quot;https://img1.daumcdn.net/thumb/R1280x0/?scode=mtistory2&amp;fname=https%3A%2F%2Fblog.kakaocdn.net%2Fdn%2FbFALXe%2FbtrPx1XbTsM%2FA7kkGa15M3gom0sCXjObOK%2Fimg.png&quot; onerror=&quot;this.onerror=null; this.src='//t1.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;940&quot; height=&quot;361&quot; data-origin-width=&quot;940&quot; data-origin-height=&quot;361&quot;/&gt;&lt;/span&gt;&lt;figcaption&gt;postman kafka 메시지 전달&lt;/figcaption&gt;
&lt;/figure&gt;
&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;메시지가 카프카에 전달되면 KafkaListen 을 통해 메시지를 읽어온다.&lt;/p&gt;
&lt;p&gt;&lt;figure class=&quot;imageblock alignCenter&quot; data-ke-mobileStyle=&quot;widthOrigin&quot; data-origin-width=&quot;458&quot; data-origin-height=&quot;150&quot;&gt;&lt;span data-url=&quot;https://blog.kakaocdn.net/dn/dbfiNy/btrPyGLEyW0/QLhgryCakxSGFmIKSAZkp1/img.png&quot; data-phocus=&quot;https://blog.kakaocdn.net/dn/dbfiNy/btrPyGLEyW0/QLhgryCakxSGFmIKSAZkp1/img.png&quot; data-alt=&quot;springboot consumer&quot;&gt;&lt;img src=&quot;https://blog.kakaocdn.net/dn/dbfiNy/btrPyGLEyW0/QLhgryCakxSGFmIKSAZkp1/img.png&quot; srcset=&quot;https://img1.daumcdn.net/thumb/R1280x0/?scode=mtistory2&amp;fname=https%3A%2F%2Fblog.kakaocdn.net%2Fdn%2FdbfiNy%2FbtrPyGLEyW0%2FQLhgryCakxSGFmIKSAZkp1%2Fimg.png&quot; onerror=&quot;this.onerror=null; this.src='//t1.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;150&quot; data-origin-width=&quot;458&quot; data-origin-height=&quot;150&quot;/&gt;&lt;/span&gt;&lt;figcaption&gt;springboot consumer&lt;/figcaption&gt;
&lt;/figure&gt;
&lt;figure class=&quot;imageblock alignCenter&quot; data-ke-mobileStyle=&quot;widthOrigin&quot; data-origin-width=&quot;940&quot; data-origin-height=&quot;218&quot;&gt;&lt;span data-url=&quot;https://blog.kakaocdn.net/dn/UPaMf/btrPzkOQxxL/fo7lzLz3Q3BSjkAhf7PFm1/img.png&quot; data-phocus=&quot;https://blog.kakaocdn.net/dn/UPaMf/btrPzkOQxxL/fo7lzLz3Q3BSjkAhf7PFm1/img.png&quot;&gt;&lt;img src=&quot;https://blog.kakaocdn.net/dn/UPaMf/btrPzkOQxxL/fo7lzLz3Q3BSjkAhf7PFm1/img.png&quot; srcset=&quot;https://img1.daumcdn.net/thumb/R1280x0/?scode=mtistory2&amp;fname=https%3A%2F%2Fblog.kakaocdn.net%2Fdn%2FUPaMf%2FbtrPzkOQxxL%2Ffo7lzLz3Q3BSjkAhf7PFm1%2Fimg.png&quot; onerror=&quot;this.onerror=null; this.src='//t1.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;940&quot; height=&quot;218&quot; data-origin-width=&quot;940&quot; data-origin-height=&quot;218&quot;/&gt;&lt;/span&gt;&lt;/figure&gt;
&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;h2 data-ke-size=&quot;size26&quot;&gt;마치며&lt;/h2&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;이렇게 카프카를 사용하여 메시지큐를 테스트하였다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;여기선 DefaultKafka를 건드려서 그런지 여러개의 토픽을 가져올 수 없다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;시간이 되면 다양한 topic과 group-id를 구현해보아야겠다.&lt;/p&gt;</description>
      <category>개발/Spring</category>
      <category>consumer</category>
      <category>KAFKA</category>
      <category>Producer</category>
      <category>SpringBoot</category>
      <author>joon95</author>
      <guid isPermaLink="true">https://flowlog.tistory.com/84</guid>
      <comments>https://flowlog.tistory.com/84#entry84comment</comments>
      <pubDate>Tue, 25 Oct 2022 21:32:10 +0900</pubDate>
    </item>
    <item>
      <title>[Springboot] springBoot와 springCloud 버전 맞추기</title>
      <link>https://flowlog.tistory.com/83</link>
      <description>&lt;p data-ke-size=&quot;size16&quot;&gt;스프링부트 application을 개발하던 중 스프링 클라우드 zipkin 을 사용하려다 에러가 발생하였다.&lt;/p&gt;
&lt;table style=&quot;border-collapse: collapse; width: 100%;&quot; border=&quot;1&quot; data-ke-align=&quot;alignLeft&quot;&gt;
&lt;tbody&gt;
&lt;tr&gt;
&lt;td style=&quot;width: 100%;&quot;&gt;configurationPropertiesBeans'&amp;nbsp;defined&amp;nbsp;in&amp;nbsp;class&amp;nbsp;path&amp;nbsp;resource&amp;nbsp;[org/springframework/cloud/autoconfigure/ConfigurationPropertiesRebinderAutoConfiguration.class]:&amp;nbsp;Post-processing&amp;nbsp;of&amp;nbsp;merged&amp;nbsp;bean&amp;nbsp;definition&amp;nbsp;failed;&amp;nbsp;nested&amp;nbsp;exception&amp;nbsp;is&amp;nbsp;java.lang.IllegalStateException:&amp;nbsp;Failed&amp;nbsp;to&amp;nbsp;introspect&amp;nbsp;Class&amp;nbsp;[org.springframework.cloud.context.properties.ConfigurationPropertiesBeans]&amp;nbsp;from&amp;nbsp;ClassLoader&amp;nbsp;[jdk.internal.loader.ClassLoaders$AppClassLoader@7382f612]&lt;/td&gt;
&lt;/tr&gt;
&lt;/tbody&gt;
&lt;/table&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;뭐 이런.. Bean 등록 중 에러가 발생하였는데, 찾아보니 springboot 버전이랑 맞지 않아서 그렇다고 한다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;pom.xml에 바로 추가하고 넘어가버렸더니 빨간박스를 보지못했....&lt;/p&gt;
&lt;figure contenteditable=&quot;false&quot; data-ke-type=&quot;emoticon&quot; data-ke-align=&quot;alignCenter&quot; data-emoticon-type=&quot;friends1&quot; data-emoticon-name=&quot;021&quot; data-emoticon-isanimation=&quot;false&quot; data-emoticon-src=&quot;https://t1.daumcdn.net/keditor/emoticon/friends1/large/021.gif&quot;&gt;&lt;img src=&quot;https://t1.daumcdn.net/keditor/emoticon/friends1/large/021.gif&quot; width=&quot;150&quot; /&gt;&lt;/figure&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;필자는 springboot 2.7.4 버전이였고,&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;해당버전은 springboot cloud의 2021.0.x release 이상 버전을 사용해야한다는 것이다.&lt;/p&gt;
&lt;p&gt;&lt;figure class=&quot;imageblock alignCenter&quot; data-ke-mobileStyle=&quot;widthOrigin&quot; data-origin-width=&quot;822&quot; data-origin-height=&quot;388&quot;&gt;&lt;span data-url=&quot;https://blog.kakaocdn.net/dn/rIRdV/btrPoyVfzVT/Ck05IsnMKTUS6HTh9sjpvk/img.png&quot; data-phocus=&quot;https://blog.kakaocdn.net/dn/rIRdV/btrPoyVfzVT/Ck05IsnMKTUS6HTh9sjpvk/img.png&quot;&gt;&lt;img src=&quot;https://blog.kakaocdn.net/dn/rIRdV/btrPoyVfzVT/Ck05IsnMKTUS6HTh9sjpvk/img.png&quot; srcset=&quot;https://img1.daumcdn.net/thumb/R1280x0/?scode=mtistory2&amp;fname=https%3A%2F%2Fblog.kakaocdn.net%2Fdn%2FrIRdV%2FbtrPoyVfzVT%2FCk05IsnMKTUS6HTh9sjpvk%2Fimg.png&quot; onerror=&quot;this.onerror=null; this.src='//t1.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;822&quot; height=&quot;388&quot; data-origin-width=&quot;822&quot; data-origin-height=&quot;388&quot;/&gt;&lt;/span&gt;&lt;/figure&gt;
&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;참고로 2021.0.x 중 최신은 2022.10.24 기준 아래와 같다.&lt;/p&gt;
&lt;p&gt;&lt;figure class=&quot;imageblock alignCenter&quot; data-ke-mobileStyle=&quot;widthOrigin&quot; data-origin-width=&quot;326&quot; data-origin-height=&quot;623&quot;&gt;&lt;span data-url=&quot;https://blog.kakaocdn.net/dn/bSdwIR/btrPsOXmDJ5/34gwxAtL3unlycb9r9LLQ0/img.png&quot; data-phocus=&quot;https://blog.kakaocdn.net/dn/bSdwIR/btrPsOXmDJ5/34gwxAtL3unlycb9r9LLQ0/img.png&quot;&gt;&lt;img src=&quot;https://blog.kakaocdn.net/dn/bSdwIR/btrPsOXmDJ5/34gwxAtL3unlycb9r9LLQ0/img.png&quot; srcset=&quot;https://img1.daumcdn.net/thumb/R1280x0/?scode=mtistory2&amp;fname=https%3A%2F%2Fblog.kakaocdn.net%2Fdn%2FbSdwIR%2FbtrPsOXmDJ5%2F34gwxAtL3unlycb9r9LLQ0%2Fimg.png&quot; onerror=&quot;this.onerror=null; this.src='//t1.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;326&quot; height=&quot;623&quot; data-origin-width=&quot;326&quot; data-origin-height=&quot;623&quot;/&gt;&lt;/span&gt;&lt;/figure&gt;
&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;그래서 필자는 Sleuth 3.1.4 버전을 사용하였다.&lt;/p&gt;
&lt;pre id=&quot;code_1666600472402&quot; class=&quot;xml&quot; data-ke-language=&quot;xml&quot; data-ke-type=&quot;codeblock&quot;&gt;&lt;code&gt;&amp;lt;!-- zipkin --&amp;gt;
&amp;lt;dependency&amp;gt;
	&amp;lt;groupId&amp;gt;org.springframework.cloud&amp;lt;/groupId&amp;gt;
	&amp;lt;artifactId&amp;gt;spring-cloud-sleuth-zipkin&amp;lt;/artifactId&amp;gt;
    &amp;lt;version&amp;gt;3.1.4&amp;lt;/version&amp;gt;
&amp;lt;/dependency&amp;gt;
&amp;lt;!-- https://mvnrepository.com/artifact/org.springframework.cloud/spring-cloud-sleuth-zipkin --&amp;gt;
&amp;lt;dependency&amp;gt;
	&amp;lt;groupId&amp;gt;org.springframework.cloud&amp;lt;/groupId&amp;gt;
	&amp;lt;artifactId&amp;gt;spring-cloud-starter-sleuth&amp;lt;/artifactId&amp;gt;
    &amp;lt;version&amp;gt;3.1.4&amp;lt;/version&amp;gt;
&amp;lt;/dependency&amp;gt;&lt;/code&gt;&lt;/pre&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;이&amp;nbsp;&lt;a href=&quot;https://spring.io/projects/spring-cloud&quot;&gt;링크&lt;/a&gt;를 타고 가면 springboot에 맞는 springcloud 버전을 확인할 수 있다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;</description>
      <category>개발/Spring</category>
      <category>Sleuth</category>
      <category>SpringBoot</category>
      <category>springcloud</category>
      <category>Zipkin</category>
      <author>joon95</author>
      <guid isPermaLink="true">https://flowlog.tistory.com/83</guid>
      <comments>https://flowlog.tistory.com/83#entry83comment</comments>
      <pubDate>Mon, 24 Oct 2022 17:36:13 +0900</pubDate>
    </item>
    <item>
      <title>[DockerDesktop] access permissions 에러</title>
      <link>https://flowlog.tistory.com/82</link>
      <description>&lt;p data-ke-size=&quot;size16&quot;&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 style=&quot;width: 100%;&quot;&gt;Error:&amp;nbsp;(HTTP&amp;nbsp;code&amp;nbsp;500)&amp;nbsp;server&amp;nbsp;error&amp;nbsp;-&amp;nbsp;Ports&amp;nbsp;are&amp;nbsp;not&amp;nbsp;available:&amp;nbsp;listen&amp;nbsp;tcp&amp;nbsp;0.0.0.0:2181:&amp;nbsp;bind:&amp;nbsp;An&amp;nbsp;attempt&amp;nbsp;was&amp;nbsp;made&amp;nbsp;to&amp;nbsp;access&amp;nbsp;a&amp;nbsp;socket&amp;nbsp;in&amp;nbsp;a&amp;nbsp;way&amp;nbsp;forbidden&amp;nbsp;by&amp;nbsp;its&amp;nbsp;access&amp;nbsp;permissions.&lt;/td&gt;
&lt;/tr&gt;
&lt;/tbody&gt;
&lt;/table&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;필자는 kafka zookeeper 를 도커컴포즈로 올려두고 사용하고 있었는데 위와 같은 에러가 계속 나고있었다..&lt;/p&gt;
&lt;p&gt;&lt;figure class=&quot;imageblock alignCenter&quot; data-ke-mobileStyle=&quot;widthOrigin&quot; data-origin-width=&quot;1506&quot; data-origin-height=&quot;824&quot;&gt;&lt;span data-url=&quot;https://blog.kakaocdn.net/dn/bDxQ8D/btrOEq4Irlr/ojD5ChdKrgjGLKqPAyCDx0/img.png&quot; data-phocus=&quot;https://blog.kakaocdn.net/dn/bDxQ8D/btrOEq4Irlr/ojD5ChdKrgjGLKqPAyCDx0/img.png&quot;&gt;&lt;img src=&quot;https://blog.kakaocdn.net/dn/bDxQ8D/btrOEq4Irlr/ojD5ChdKrgjGLKqPAyCDx0/img.png&quot; srcset=&quot;https://img1.daumcdn.net/thumb/R1280x0/?scode=mtistory2&amp;fname=https%3A%2F%2Fblog.kakaocdn.net%2Fdn%2FbDxQ8D%2FbtrOEq4Irlr%2FojD5ChdKrgjGLKqPAyCDx0%2Fimg.png&quot; onerror=&quot;this.onerror=null; this.src='//t1.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;1506&quot; height=&quot;824&quot; data-origin-width=&quot;1506&quot; data-origin-height=&quot;824&quot;/&gt;&lt;/span&gt;&lt;/figure&gt;
&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;로컬에 구축한 쿠버네티스에서 포트를 잡는건가? 하고&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;gt; netstat -an | findstr 2181&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;이리저리 검색하다보니 그냥 pc를 껏다키라고 .....&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;껏다키니 잘된다..ㅎ&lt;/p&gt;
&lt;figure contenteditable=&quot;false&quot; data-ke-type=&quot;emoticon&quot; data-ke-align=&quot;alignCenter&quot; data-emoticon-type=&quot;friends1&quot; data-emoticon-name=&quot;016&quot; data-emoticon-isanimation=&quot;false&quot; data-emoticon-src=&quot;https://t1.daumcdn.net/keditor/emoticon/friends1/large/016.gif&quot;&gt;&lt;img src=&quot;https://t1.daumcdn.net/keditor/emoticon/friends1/large/016.gif&quot; width=&quot;150&quot; /&gt;&lt;/figure&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;도커데스크탑쓰다가 에러가 나면 그냥 껏다 켜보자.!&lt;/p&gt;</description>
      <category>엔지니어링/Kubernetes</category>
      <category>도커데스크탑</category>
      <category>도커에러</category>
      <author>joon95</author>
      <guid isPermaLink="true">https://flowlog.tistory.com/82</guid>
      <comments>https://flowlog.tistory.com/82#entry82comment</comments>
      <pubDate>Thu, 20 Oct 2022 10:29:46 +0900</pubDate>
    </item>
    <item>
      <title>[DockerDesktop] kubernetes 설치 후 kubectl 안될 때</title>
      <link>https://flowlog.tistory.com/81</link>
      <description>&lt;p data-ke-size=&quot;size16&quot;&gt;이번에 로컬에 kubernetes를 올리고 테스트할 일이 생겨&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;docker desktop에서 클릭한번으로 구축되는 kubernetes를 해보았다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;근데 필자는 kubectl 명령어로 기존에 사용하던 cluster들 정보가 수두룩 하기때문에&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;신규 생성된 로컬 클러스터가 자동으로 바뀌지 않았다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;h2 data-ke-size=&quot;size26&quot;&gt;쿠버네티스 동작 확인&lt;/h2&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;설치는 정상적으로 되어 running 상태이고,&lt;/p&gt;
&lt;p&gt;&lt;figure class=&quot;imageblock alignCenter&quot; data-ke-mobileStyle=&quot;widthOrigin&quot; data-origin-width=&quot;1046&quot; data-origin-height=&quot;825&quot;&gt;&lt;span data-url=&quot;https://blog.kakaocdn.net/dn/d6W6BF/btrO2okQstZ/MTjj74fuYWHRiYKwfcfxak/img.png&quot; data-phocus=&quot;https://blog.kakaocdn.net/dn/d6W6BF/btrO2okQstZ/MTjj74fuYWHRiYKwfcfxak/img.png&quot;&gt;&lt;img src=&quot;https://blog.kakaocdn.net/dn/d6W6BF/btrO2okQstZ/MTjj74fuYWHRiYKwfcfxak/img.png&quot; srcset=&quot;https://img1.daumcdn.net/thumb/R1280x0/?scode=mtistory2&amp;fname=https%3A%2F%2Fblog.kakaocdn.net%2Fdn%2Fd6W6BF%2FbtrO2okQstZ%2FMTjj74fuYWHRiYKwfcfxak%2Fimg.png&quot; onerror=&quot;this.onerror=null; this.src='//t1.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;1046&quot; height=&quot;825&quot; data-origin-width=&quot;1046&quot; data-origin-height=&quot;825&quot;/&gt;&lt;/span&gt;&lt;/figure&gt;
&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;실행 중인 docker Desktop icon 우클릭&amp;gt;kubernetes 항목을 통해 생성된 자격증명을 확인할 수 있었다.&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;438&quot; data-origin-height=&quot;444&quot;&gt;&lt;span data-url=&quot;https://blog.kakaocdn.net/dn/bVkRbH/btrO35kkB0P/sVXHvoffk7JEqKSQkAZeZ0/img.png&quot; data-phocus=&quot;https://blog.kakaocdn.net/dn/bVkRbH/btrO35kkB0P/sVXHvoffk7JEqKSQkAZeZ0/img.png&quot;&gt;&lt;img src=&quot;https://blog.kakaocdn.net/dn/bVkRbH/btrO35kkB0P/sVXHvoffk7JEqKSQkAZeZ0/img.png&quot; srcset=&quot;https://img1.daumcdn.net/thumb/R1280x0/?scode=mtistory2&amp;fname=https%3A%2F%2Fblog.kakaocdn.net%2Fdn%2FbVkRbH%2FbtrO35kkB0P%2FsVXHvoffk7JEqKSQkAZeZ0%2Fimg.png&quot; onerror=&quot;this.onerror=null; this.src='//t1.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;438&quot; height=&quot;444&quot; data-origin-width=&quot;438&quot; data-origin-height=&quot;444&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;kubectl config 적용&lt;/h2&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;이제 저이름을 찾아서 kubectl의 context를 지정해주면 된다.&lt;/p&gt;
&lt;pre id=&quot;code_1666161472040&quot; class=&quot;bash&quot; data-ke-language=&quot;bash&quot; data-ke-type=&quot;codeblock&quot;&gt;&lt;code&gt;$ kubectl config get-contexts
CURRENT   NAME             CLUSTER          AUTHINFO         NAMESPACE
*         docker-desktop   docker-desktop   docker-desktop&lt;/code&gt;&lt;/pre&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;docker-desktop이라는 컨텍스트를 지정해주자.&lt;/p&gt;
&lt;pre id=&quot;code_1666161516500&quot; class=&quot;bash&quot; data-ke-language=&quot;bash&quot; data-ke-type=&quot;codeblock&quot;&gt;&lt;code&gt;$ kubectl config set-context docker-desktop
Context &quot;docker-desktop&quot; modified.&lt;/code&gt;&lt;/pre&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;h2 data-ke-size=&quot;size26&quot;&gt;테스트&lt;/h2&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;이제 정상적으로 로컬 쿠버네티스의 자격증명으로 명령어를 날릴 수 있다.&lt;/p&gt;
&lt;pre id=&quot;code_1666161549079&quot; class=&quot;bash&quot; data-ke-language=&quot;bash&quot; data-ke-type=&quot;codeblock&quot;&gt;&lt;code&gt;$ kubectl get no
NAME             STATUS   ROLES                  AGE     VERSION
docker-desktop   Ready    control-plane,master   5m12s   v1.22.5&lt;/code&gt;&lt;/pre&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;h2 data-ke-size=&quot;size26&quot;&gt;마치며&lt;/h2&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;여러 kubenetes cluster를 사용하며 config들이 엄청 쌓여있었는데&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;이번에 docker 로 올린 쿠버네티스의 이름이 무엇인지 찾다가 알게되어 기록한다&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;(첨엔 local 이라는 클러스터인줄 알았음.ㅋㅋㅋ)&lt;/p&gt;</description>
      <category>엔지니어링/Kubernetes</category>
      <category>Docker Desktop</category>
      <category>kubectl</category>
      <category>쿠버네티스 클러스터 자격증명</category>
      <category>쿠버네티스 클러스터 접근방법</category>
      <author>joon95</author>
      <guid isPermaLink="true">https://flowlog.tistory.com/81</guid>
      <comments>https://flowlog.tistory.com/81#entry81comment</comments>
      <pubDate>Wed, 19 Oct 2022 15:42:34 +0900</pubDate>
    </item>
    <item>
      <title>[성능테스트] nGrinder Script POST 해보기</title>
      <link>https://flowlog.tistory.com/80</link>
      <description>&lt;p data-ke-size=&quot;size16&quot;&gt;지난 포스팅에 nGrinder 설치 및 GET 테스트를 마쳤다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;이제 2가지 테스트를 해볼 건데,&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;로그인 시도와 로그인한 사용자만 이용할 수 있는 페이지를 요청하는 것이다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;필자가 구현해둔 application은 아래와 같다.&lt;/p&gt;
&lt;table style=&quot;border-collapse: collapse; width: 100%;&quot; border=&quot;1&quot; data-ke-align=&quot;alignLeft&quot;&gt;
&lt;tbody&gt;
&lt;tr&gt;
&lt;td style=&quot;width: 33.3333%;&quot;&gt;URL&lt;/td&gt;
&lt;td style=&quot;width: 33.3333%;&quot;&gt;Method&lt;/td&gt;
&lt;td style=&quot;width: 33.3333%;&quot;&gt;Parameter&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td style=&quot;width: 33.3333%;&quot;&gt;/&lt;/td&gt;
&lt;td style=&quot;width: 33.3333%;&quot;&gt;POST&lt;/td&gt;
&lt;td style=&quot;width: 33.3333%;&quot;&gt;email, password&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td style=&quot;width: 33.3333%;&quot;&gt;/user&lt;/td&gt;
&lt;td style=&quot;width: 33.3333%;&quot;&gt;GET&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;p data-ke-size=&quot;size16&quot;&gt;이제 nGrinder Script 작성을 해보자.&lt;/p&gt;
&lt;pre id=&quot;code_1665649385674&quot; class=&quot;go&quot; data-ke-language=&quot;go&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 = [&quot;Content-Type&quot;:&quot;application/x-www-form-urlencoded&quot;]
	public static Map&amp;lt;String, Object&amp;gt; params = [&quot;email&quot;:&quot;joon95@metanet.co.kr&quot;,&quot;password&quot;:&quot;1234&quot;]
	public static List&amp;lt;Cookie&amp;gt; cookies = []

	@BeforeProcess
	public static void beforeProcess() {
		HTTPRequestControl.setConnectionTimeout(300000)
		test = new GTest(1, &quot;ing-default-albingressst-af3c8-13072783-b03408722853.kr.lb.naverncp.com&quot;)
		request = new HTTPRequest()

		// Set header data
		headers.put(&quot;Content-Type&quot;, &quot;application/x-www-form-urlencoded&quot;)
		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.POST(&quot;http://ing-default-albingressst-af3c8-13072783-b03408722853.kr.lb.naverncp.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))
		}
		request.GET(&quot;http://ing-default-albingressst-af3c8-13072783-b03408722853.kr.lb.naverncp.com/user&quot;)
		
	}
}&lt;/code&gt;&lt;/pre&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;b&gt;headers&lt;/b&gt;와 &lt;b&gt;params&lt;/b&gt;에 데이터를 넣어준 것과&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;login 요청 이후 &lt;b&gt;GET /user&lt;/b&gt; 페이지로 넘어가는 부분이다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;이렇게 쉽게 로그인 테스트를 진행하였다.&lt;/p&gt;
&lt;p&gt;&lt;figure class=&quot;imageblock alignCenter&quot; data-ke-mobileStyle=&quot;widthOrigin&quot; data-origin-width=&quot;1230&quot; data-origin-height=&quot;813&quot;&gt;&lt;span data-url=&quot;https://blog.kakaocdn.net/dn/cya06t/btrOxoq1egZ/8Ok9QCybmJncVHKcLFmJ71/img.png&quot; data-phocus=&quot;https://blog.kakaocdn.net/dn/cya06t/btrOxoq1egZ/8Ok9QCybmJncVHKcLFmJ71/img.png&quot; data-alt=&quot;nGrinder Performance Test&quot;&gt;&lt;img src=&quot;https://blog.kakaocdn.net/dn/cya06t/btrOxoq1egZ/8Ok9QCybmJncVHKcLFmJ71/img.png&quot; srcset=&quot;https://img1.daumcdn.net/thumb/R1280x0/?scode=mtistory2&amp;fname=https%3A%2F%2Fblog.kakaocdn.net%2Fdn%2Fcya06t%2FbtrOxoq1egZ%2F8Ok9QCybmJncVHKcLFmJ71%2Fimg.png&quot; onerror=&quot;this.onerror=null; this.src='//t1.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;1230&quot; height=&quot;813&quot; data-origin-width=&quot;1230&quot; data-origin-height=&quot;813&quot;/&gt;&lt;/span&gt;&lt;figcaption&gt;nGrinder Performance Test&lt;/figcaption&gt;
&lt;/figure&gt;
&lt;/p&gt;</description>
      <category>엔지니어링/성능테스트</category>
      <category>Get</category>
      <category>ngrinder</category>
      <category>post</category>
      <category>성능테스트</category>
      <author>joon95</author>
      <guid isPermaLink="true">https://flowlog.tistory.com/80</guid>
      <comments>https://flowlog.tistory.com/80#entry80comment</comments>
      <pubDate>Thu, 13 Oct 2022 17:25:54 +0900</pubDate>
    </item>
    <item>
      <title>[성능테스트] nGrinder 사용해보기</title>
      <link>https://flowlog.tistory.com/79</link>
      <description>&lt;p data-ke-size=&quot;size16&quot;&gt;네이버클라우드에 쿠버네티스를 구축하고 springboot pod를 올렸다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;또 클라우드 서비스에서 redis, postgresql를 올렸는데,&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;redis 캐시관련해서 성능테스트를 해보고 싶어져서 몇 년 전부터 듣기만했던 &lt;b&gt;Ngrinder&lt;/b&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;Java 설치&lt;/h2&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;ngrinder 는 .war 파일로 java가 깔려있어야한다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;(아래 링크의 이전 포스팅을 참고하라.)&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;포스팅에서 설치하는 자바 버전은 jdk18 인데,&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;grinder는 &lt;b&gt;1.8&lt;/b&gt; 또는 &lt;b&gt;11&lt;/b&gt;만 &lt;b&gt;지원&lt;/b&gt;한다고 하니 버전을 잘 선택해야 한다.&lt;/p&gt;
&lt;figure id=&quot;og_1665642918642&quot; contenteditable=&quot;false&quot; data-ke-type=&quot;opengraph&quot; data-ke-align=&quot;alignCenter&quot; data-og-type=&quot;article&quot; data-og-title=&quot;우분투 20.04.3 LTS 에 openJDK 직접 설치하기&quot; data-og-description=&quot;우분투에 openjdk를 설치하려하니 잘 안되서... ppa 레포를 등록하고 패키지 찾고하면 된다하는데 자꾸 안되서 수동 설치방법을 기록한다. https://jdk.java.net/archive/ 해당 페이지에 들어가 다운할 tar.gz&quot; data-og-host=&quot;flowlog.tistory.com&quot; data-og-source-url=&quot;https://flowlog.tistory.com/78&quot; data-og-url=&quot;https://flowlog.tistory.com/78&quot; data-og-image=&quot;https://scrap.kakaocdn.net/dn/blIMQF/hyP9qlYwp9/8grgWfR39zMg6nlntw3I5K/img.png?width=200&amp;amp;height=200&amp;amp;face=0_0_200_200,https://scrap.kakaocdn.net/dn/3pwtY/hyP9j1sJls/x675OaaZFjgpLS098VIH80/img.png?width=200&amp;amp;height=200&amp;amp;face=0_0_200_200,https://scrap.kakaocdn.net/dn/ngL0T/hyP9vt3uJZ/4kUmHXmPDznW0SGPCGJbq1/img.png?width=731&amp;amp;height=341&amp;amp;face=0_0_731_341&quot;&gt;&lt;a href=&quot;https://flowlog.tistory.com/78&quot; target=&quot;_blank&quot; rel=&quot;noopener&quot; data-source-url=&quot;https://flowlog.tistory.com/78&quot;&gt;
&lt;div class=&quot;og-image&quot; style=&quot;background-image: url('https://scrap.kakaocdn.net/dn/blIMQF/hyP9qlYwp9/8grgWfR39zMg6nlntw3I5K/img.png?width=200&amp;amp;height=200&amp;amp;face=0_0_200_200,https://scrap.kakaocdn.net/dn/3pwtY/hyP9j1sJls/x675OaaZFjgpLS098VIH80/img.png?width=200&amp;amp;height=200&amp;amp;face=0_0_200_200,https://scrap.kakaocdn.net/dn/ngL0T/hyP9vt3uJZ/4kUmHXmPDznW0SGPCGJbq1/img.png?width=731&amp;amp;height=341&amp;amp;face=0_0_731_341');&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;우분투 20.04.3 LTS 에 openJDK 직접 설치하기&lt;/p&gt;
&lt;p class=&quot;og-desc&quot; data-ke-size=&quot;size16&quot;&gt;우분투에 openjdk를 설치하려하니 잘 안되서... ppa 레포를 등록하고 패키지 찾고하면 된다하는데 자꾸 안되서 수동 설치방법을 기록한다. https://jdk.java.net/archive/ 해당 페이지에 들어가 다운할 tar.gz&lt;/p&gt;
&lt;p class=&quot;og-host&quot; data-ke-size=&quot;size16&quot;&gt;flowlog.tistory.com&lt;/p&gt;
&lt;/div&gt;
&lt;/a&gt;&lt;/figure&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;h2 data-ke-size=&quot;size26&quot;&gt;Ngrinder 설치&lt;/h2&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;아래 링크로 이동하면 최신 버전을 확인할 수 있다.&lt;/p&gt;
&lt;figure id=&quot;og_1665643006798&quot; contenteditable=&quot;false&quot; data-ke-type=&quot;opengraph&quot; data-ke-align=&quot;alignCenter&quot; data-og-type=&quot;object&quot; data-og-title=&quot;Releases &amp;middot; naver/ngrinder&quot; data-og-description=&quot;enterprise level performance testing solution. Contribute to naver/ngrinder development by creating an account on GitHub.&quot; data-og-host=&quot;github.com&quot; data-og-source-url=&quot;https://github.com/naver/ngrinder/releases/&quot; data-og-url=&quot;https://github.com/naver/ngrinder/releases&quot; data-og-image=&quot;https://scrap.kakaocdn.net/dn/Ruj0v/hyP7YLoDoz/JWqNNII7YckdVd38E0V5w0/img.png?width=1200&amp;amp;height=600&amp;amp;face=0_0_1200_600&quot;&gt;&lt;a href=&quot;https://github.com/naver/ngrinder/releases/&quot; target=&quot;_blank&quot; rel=&quot;noopener&quot; data-source-url=&quot;https://github.com/naver/ngrinder/releases/&quot;&gt;
&lt;div class=&quot;og-image&quot; style=&quot;background-image: url('https://scrap.kakaocdn.net/dn/Ruj0v/hyP7YLoDoz/JWqNNII7YckdVd38E0V5w0/img.png?width=1200&amp;amp;height=600&amp;amp;face=0_0_1200_600');&quot;&gt;&amp;nbsp;&lt;/div&gt;
&lt;div class=&quot;og-text&quot;&gt;
&lt;p class=&quot;og-title&quot; data-ke-size=&quot;size16&quot;&gt;Releases &amp;middot; naver/ngrinder&lt;/p&gt;
&lt;p class=&quot;og-desc&quot; data-ke-size=&quot;size16&quot;&gt;enterprise level performance testing solution. Contribute to naver/ngrinder development by creating an account on GitHub.&lt;/p&gt;
&lt;p class=&quot;og-host&quot; data-ke-size=&quot;size16&quot;&gt;github.com&lt;/p&gt;
&lt;/div&gt;
&lt;/a&gt;&lt;/figure&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;링크를 복사 한뒤 wget으로 다운&lt;/p&gt;
&lt;pre id=&quot;code_1665643050039&quot; class=&quot;bash&quot; data-ke-language=&quot;bash&quot; data-ke-type=&quot;codeblock&quot;&gt;&lt;code&gt;$ wget https://github.com/naver/ngrinder/releases/download/ngrinder-3.5.6-20221007/ngrinder-controller-3.5.6.war
--2022-10-13 14:22:16--  https://github.com/naver/ngrinder/releases/download/ngrinder-3.5.6-20221007/ngrinder-controller-3.5.6.war
Resolving github.com (github.com)... 20.200.245.247
Connecting to github.com (github.com)|20.200.245.247|:443... connected.
HTTP request sent, awaiting response... 302 Found
Location: https://objects.githubusercontent.com/github-production-release-asset-2e65be/4709330/2130268d-0cdc-4462-8404-277959f1f84d?X-Amz-Algorithm=AWS4-HMAC-SHA256&amp;amp;X-Amz-Credential=AKIAIWNJYAX4CSVEH53A%2F20221013%2Fus-east-1%2Fs3%2Faws4_request&amp;amp;X-Amz-Date=20221013T052216Z&amp;amp;X-Amz-Expires=300&amp;amp;X-Amz-Signature=6d95bc22eb8a32da4fe196a0135dc3acbb031e3a369f545e66cc356106f4ebe4&amp;amp;X-Amz-SignedHeaders=host&amp;amp;actor_id=0&amp;amp;key_id=0&amp;amp;repo_id=4709330&amp;amp;response-content-disposition=attachment%3B%20filename%3Dngrinder-controller-3.5.6.war&amp;amp;response-content-type=application%2Foctet-stream [following]
--2022-10-13 14:22:16--  https://objects.githubusercontent.com/github-production-release-asset-2e65be/4709330/2130268d-0cdc-4462-8404-277959f1f84d?X-Amz-Algorithm=AWS4-HMAC-SHA256&amp;amp;X-Amz-Credential=AKIAIWNJYAX4CSVEH53A%2F20221013%2Fus-east-1%2Fs3%2Faws4_request&amp;amp;X-Amz-Date=20221013T052216Z&amp;amp;X-Amz-Expires=300&amp;amp;X-Amz-Signature=6d95bc22eb8a32da4fe196a0135dc3acbb031e3a369f545e66cc356106f4ebe4&amp;amp;X-Amz-SignedHeaders=host&amp;amp;actor_id=0&amp;amp;key_id=0&amp;amp;repo_id=4709330&amp;amp;response-content-disposition=attachment%3B%20filename%3Dngrinder-controller-3.5.6.war&amp;amp;response-content-type=application%2Foctet-stream
Resolving objects.githubusercontent.com (objects.githubusercontent.com)... 185.199.109.133, 185.199.111.133, 185.199.108.133, ...
Connecting to objects.githubusercontent.com (objects.githubusercontent.com)|185.199.109.133|:443... connected.
HTTP request sent, awaiting response... 200 OK
Length: 153953219 (147M) [application/octet-stream]
Saving to: &amp;lsquo;ngrinder-controller-3.5.6.war&amp;rsquo;

ngrinder-controller-3.5.6.war                      100%[==============================================================================================================&amp;gt;] 146.82M  7.74MB/s    in 15s

2022-10-13 14:22:32 (9.74 MB/s) - &amp;lsquo;ngrinder-controller-3.5.6.war&amp;rsquo; saved [153953219/153953219]&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;다운이 다 되었으면 java로 기동&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;(--port 로 실행포트를 지정할 수 있다.)&lt;/p&gt;
&lt;pre id=&quot;code_1665643109784&quot; class=&quot;bash&quot; data-ke-language=&quot;bash&quot; data-ke-type=&quot;codeblock&quot;&gt;&lt;code&gt;$ java -jar ngrinder-controller-3.5.6.war --port=8300&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;&lt;figure class=&quot;imageblock alignCenter&quot; data-ke-mobileStyle=&quot;widthOrigin&quot; data-origin-width=&quot;1516&quot; data-origin-height=&quot;979&quot;&gt;&lt;span data-url=&quot;https://blog.kakaocdn.net/dn/UBDjF/btrOtclaQFx/PYu3OcK35y3bWmHcNoSlEk/img.png&quot; data-phocus=&quot;https://blog.kakaocdn.net/dn/UBDjF/btrOtclaQFx/PYu3OcK35y3bWmHcNoSlEk/img.png&quot; data-alt=&quot;nginder controller application run&quot;&gt;&lt;img src=&quot;https://blog.kakaocdn.net/dn/UBDjF/btrOtclaQFx/PYu3OcK35y3bWmHcNoSlEk/img.png&quot; srcset=&quot;https://img1.daumcdn.net/thumb/R1280x0/?scode=mtistory2&amp;fname=https%3A%2F%2Fblog.kakaocdn.net%2Fdn%2FUBDjF%2FbtrOtclaQFx%2FPYu3OcK35y3bWmHcNoSlEk%2Fimg.png&quot; onerror=&quot;this.onerror=null; this.src='//t1.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;1516&quot; height=&quot;979&quot; data-origin-width=&quot;1516&quot; data-origin-height=&quot;979&quot;/&gt;&lt;/span&gt;&lt;figcaption&gt;nginder controller application run&lt;/figcaption&gt;
&lt;/figure&gt;
&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;springboot 로그가 쭉 뜨면서 기동된다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;각자 지정한 port 로 접속해보면 아래와 같은 페이지가 뜨고&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;최초 계정은 admin/admin 이다.&lt;/p&gt;
&lt;p&gt;&lt;figure class=&quot;imageblock alignCenter&quot; data-ke-mobileStyle=&quot;widthOrigin&quot; data-origin-width=&quot;641&quot; data-origin-height=&quot;481&quot;&gt;&lt;span data-url=&quot;https://blog.kakaocdn.net/dn/bTtF1M/btrOw5x43JT/kywrseWmVvNUGX1fT9pBg0/img.png&quot; data-phocus=&quot;https://blog.kakaocdn.net/dn/bTtF1M/btrOw5x43JT/kywrseWmVvNUGX1fT9pBg0/img.png&quot; data-alt=&quot;ngrinder main page&quot;&gt;&lt;img src=&quot;https://blog.kakaocdn.net/dn/bTtF1M/btrOw5x43JT/kywrseWmVvNUGX1fT9pBg0/img.png&quot; srcset=&quot;https://img1.daumcdn.net/thumb/R1280x0/?scode=mtistory2&amp;fname=https%3A%2F%2Fblog.kakaocdn.net%2Fdn%2FbTtF1M%2FbtrOw5x43JT%2FkywrseWmVvNUGX1fT9pBg0%2Fimg.png&quot; onerror=&quot;this.onerror=null; this.src='//t1.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;641&quot; height=&quot;481&quot; data-origin-width=&quot;641&quot; data-origin-height=&quot;481&quot;/&gt;&lt;/span&gt;&lt;figcaption&gt;ngrinder main page&lt;/figcaption&gt;
&lt;/figure&gt;
&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;h2 data-ke-size=&quot;size26&quot;&gt;계정 패스워드 수정&lt;/h2&gt;
&lt;p&gt;&lt;figure class=&quot;imageblock alignCenter&quot; data-ke-mobileStyle=&quot;widthOrigin&quot; data-origin-width=&quot;225&quot; data-origin-height=&quot;390&quot;&gt;&lt;span data-url=&quot;https://blog.kakaocdn.net/dn/b6Zd0D/btrOxoxpCyP/Oc2BVAYFaazdDBd1IkQDo0/img.png&quot; data-phocus=&quot;https://blog.kakaocdn.net/dn/b6Zd0D/btrOxoxpCyP/Oc2BVAYFaazdDBd1IkQDo0/img.png&quot;&gt;&lt;img src=&quot;https://blog.kakaocdn.net/dn/b6Zd0D/btrOxoxpCyP/Oc2BVAYFaazdDBd1IkQDo0/img.png&quot; srcset=&quot;https://img1.daumcdn.net/thumb/R1280x0/?scode=mtistory2&amp;fname=https%3A%2F%2Fblog.kakaocdn.net%2Fdn%2Fb6Zd0D%2FbtrOxoxpCyP%2FOc2BVAYFaazdDBd1IkQDo0%2Fimg.png&quot; onerror=&quot;this.onerror=null; this.src='//t1.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;225&quot; height=&quot;390&quot; data-origin-width=&quot;225&quot; data-origin-height=&quot;390&quot;/&gt;&lt;/span&gt;&lt;/figure&gt;
&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;우측 상단 admin&amp;gt;User Management 로 들어가면 기본적으로 생성된 계정이 있다.&lt;/p&gt;
&lt;p&gt;&lt;figure class=&quot;imageblock alignCenter&quot; data-ke-mobileStyle=&quot;widthOrigin&quot; data-origin-width=&quot;1220&quot; data-origin-height=&quot;429&quot;&gt;&lt;span data-url=&quot;https://blog.kakaocdn.net/dn/C3fiL/btrOwASUcEb/Uzk6zxhKav3GHdhmwvJyIk/img.png&quot; data-phocus=&quot;https://blog.kakaocdn.net/dn/C3fiL/btrOwASUcEb/Uzk6zxhKav3GHdhmwvJyIk/img.png&quot; data-alt=&quot;nGrinder default User&quot;&gt;&lt;img src=&quot;https://blog.kakaocdn.net/dn/C3fiL/btrOwASUcEb/Uzk6zxhKav3GHdhmwvJyIk/img.png&quot; srcset=&quot;https://img1.daumcdn.net/thumb/R1280x0/?scode=mtistory2&amp;fname=https%3A%2F%2Fblog.kakaocdn.net%2Fdn%2FC3fiL%2FbtrOwASUcEb%2FUzk6zxhKav3GHdhmwvJyIk%2Fimg.png&quot; onerror=&quot;this.onerror=null; this.src='//t1.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;1220&quot; height=&quot;429&quot; data-origin-width=&quot;1220&quot; data-origin-height=&quot;429&quot;/&gt;&lt;/span&gt;&lt;figcaption&gt;nGrinder default User&lt;/figcaption&gt;
&lt;/figure&gt;
&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;기본적으로 패스워드는 id와 동일하게 되어있고 edit 버튼을 통해 패스워드를 변경할 수 있다.&lt;/p&gt;
&lt;p&gt;&lt;figure class=&quot;imageblock alignCenter&quot; data-ke-mobileStyle=&quot;widthOrigin&quot; data-origin-width=&quot;508&quot; data-origin-height=&quot;613&quot;&gt;&lt;span data-url=&quot;https://blog.kakaocdn.net/dn/QNx11/btrOwCb6359/O9j2tdNt8ex33ycPF9qH10/img.png&quot; data-phocus=&quot;https://blog.kakaocdn.net/dn/QNx11/btrOwCb6359/O9j2tdNt8ex33ycPF9qH10/img.png&quot; data-alt=&quot;nGrinder User Password Change&quot;&gt;&lt;img src=&quot;https://blog.kakaocdn.net/dn/QNx11/btrOwCb6359/O9j2tdNt8ex33ycPF9qH10/img.png&quot; srcset=&quot;https://img1.daumcdn.net/thumb/R1280x0/?scode=mtistory2&amp;fname=https%3A%2F%2Fblog.kakaocdn.net%2Fdn%2FQNx11%2FbtrOwCb6359%2FO9j2tdNt8ex33ycPF9qH10%2Fimg.png&quot; onerror=&quot;this.onerror=null; this.src='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png'; this.srcset='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png';&quot; loading=&quot;lazy&quot; width=&quot;508&quot; height=&quot;613&quot; data-origin-width=&quot;508&quot; data-origin-height=&quot;613&quot;/&gt;&lt;/span&gt;&lt;figcaption&gt;nGrinder User Password Change&lt;/figcaption&gt;
&lt;/figure&gt;
&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;h2 data-ke-size=&quot;size26&quot;&gt;Agent 설치&lt;/h2&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;이제 실제 스크립트가 실행될 agent 서버를 만들어야한다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;우측 상단 admin&amp;gt;Download Agent 를 누르면 agent 파일이 다운된다.&lt;/p&gt;
&lt;p&gt;&lt;figure class=&quot;imageblock alignCenter&quot; data-ke-mobileStyle=&quot;widthOrigin&quot; data-origin-width=&quot;262&quot; data-origin-height=&quot;246&quot;&gt;&lt;span data-url=&quot;https://blog.kakaocdn.net/dn/Nop3d/btrOvhsMBnr/7TWcQzPkBJh4QF0GRZsgfK/img.png&quot; data-phocus=&quot;https://blog.kakaocdn.net/dn/Nop3d/btrOvhsMBnr/7TWcQzPkBJh4QF0GRZsgfK/img.png&quot;&gt;&lt;img src=&quot;https://blog.kakaocdn.net/dn/Nop3d/btrOvhsMBnr/7TWcQzPkBJh4QF0GRZsgfK/img.png&quot; srcset=&quot;https://img1.daumcdn.net/thumb/R1280x0/?scode=mtistory2&amp;fname=https%3A%2F%2Fblog.kakaocdn.net%2Fdn%2FNop3d%2FbtrOvhsMBnr%2F7TWcQzPkBJh4QF0GRZsgfK%2Fimg.png&quot; onerror=&quot;this.onerror=null; this.src='//t1.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;262&quot; height=&quot;246&quot; data-origin-width=&quot;262&quot; data-origin-height=&quot;246&quot;/&gt;&lt;/span&gt;&lt;/figure&gt;
&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;특정 서버에 agent 파일을 넣고 압축을 푼다.&lt;/p&gt;
&lt;pre id=&quot;code_1665643494261&quot; class=&quot;bash&quot; data-ke-language=&quot;bash&quot; data-ke-type=&quot;codeblock&quot;&gt;&lt;code&gt;$ tar -xvf ngrinder-agent-3.5.6-223.130.162.121.tar&lt;/code&gt;&lt;/pre&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;(다운받은 에이전트 파일 뒤의 223.130.162.121 은 controller 서버의 ip이다.)&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;필자는 네이버클라우드 안에 있는 서버임으로 private ip 통신을 해야하기 때문에 controller 서버 ip를 수정해야된다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;config 파일은 .ngrinder_agent 폴더 안에 있다.(기존 host정보를 주석처리하고 private ip 로 지정함)&lt;/p&gt;
&lt;pre id=&quot;code_1665643626170&quot; class=&quot;bash&quot; data-ke-language=&quot;bash&quot; data-ke-type=&quot;codeblock&quot;&gt;&lt;code&gt;$ vi ~/.ngrinder_agent/agent.conf

#agent.controller_host=223.130.162.121
agent.controller_host=10.160.227.6&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;이제 agent 를 실행해보자.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;참고로 agent 는 &lt;b&gt;JAVA_HOME 환경변수&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;export JAVA_HOME=자바설치경로/bin&lt;/p&gt;
&lt;pre id=&quot;code_1665643689929&quot; class=&quot;bash&quot; data-ke-language=&quot;bash&quot; data-ke-type=&quot;codeblock&quot;&gt;&lt;code&gt;$ ~/ngrinder-agent/run_agent.sh
2022-10-13 14:59:39,970 INFO  agent config: NGRINDER_AGENT_HOME : /root/.ngrinder_agent
2022-10-13 14:59:40,233 INFO  starter: ***************************************************
2022-10-13 14:59:40,233 INFO  starter:    Start nGrinder Agent ...
2022-10-13 14:59:40,234 INFO  starter: ***************************************************
2022-10-13 14:59:40,234 INFO  starter: JVM server mode is disabled.
2022-10-13 14:59:40,249 INFO  starter: connecting to controller 10.160.227.6:16001
2022-10-13 14:59:40,273 INFO  agent controller daemon: The agent controller daemon is started.
2022-10-13 14:59:40,337 INFO  agent controller: Connected to agent controller server at /10.160.227.6:16001
2022-10-13 14:59:40,337 INFO  agent controller: Waiting for agent controller server signal&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;gt;Agent Management 페이지로 들어가면 활성화된 agent가 목록에 뜬다.&lt;/p&gt;
&lt;p&gt;&lt;figure class=&quot;imageblock alignCenter&quot; data-ke-mobileStyle=&quot;widthOrigin&quot; data-origin-width=&quot;1220&quot; data-origin-height=&quot;307&quot;&gt;&lt;span data-url=&quot;https://blog.kakaocdn.net/dn/dLE21a/btrOsdkuump/i0RE7nWj0FS0Z0w0AkMygK/img.png&quot; data-phocus=&quot;https://blog.kakaocdn.net/dn/dLE21a/btrOsdkuump/i0RE7nWj0FS0Z0w0AkMygK/img.png&quot; data-alt=&quot;nGrinder Agent Management&quot;&gt;&lt;img src=&quot;https://blog.kakaocdn.net/dn/dLE21a/btrOsdkuump/i0RE7nWj0FS0Z0w0AkMygK/img.png&quot; srcset=&quot;https://img1.daumcdn.net/thumb/R1280x0/?scode=mtistory2&amp;fname=https%3A%2F%2Fblog.kakaocdn.net%2Fdn%2FdLE21a%2FbtrOsdkuump%2Fi0RE7nWj0FS0Z0w0AkMygK%2Fimg.png&quot; onerror=&quot;this.onerror=null; this.src='//t1.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;1220&quot; height=&quot;307&quot; data-origin-width=&quot;1220&quot; data-origin-height=&quot;307&quot;/&gt;&lt;/span&gt;&lt;figcaption&gt;nGrinder Agent Management&lt;/figcaption&gt;
&lt;/figure&gt;
&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;h2 data-ke-size=&quot;size26&quot;&gt;Test Script 작성&lt;/h2&gt;
&lt;p&gt;&lt;figure class=&quot;imageblock alignCenter&quot; data-ke-mobileStyle=&quot;widthOrigin&quot; data-origin-width=&quot;492&quot; data-origin-height=&quot;328&quot;&gt;&lt;span data-url=&quot;https://blog.kakaocdn.net/dn/dbj256/btrOwL7Oozn/xmUQtjUG6ybZot7tIwneI1/img.png&quot; data-phocus=&quot;https://blog.kakaocdn.net/dn/dbj256/btrOwL7Oozn/xmUQtjUG6ybZot7tIwneI1/img.png&quot;&gt;&lt;img src=&quot;https://blog.kakaocdn.net/dn/dbj256/btrOwL7Oozn/xmUQtjUG6ybZot7tIwneI1/img.png&quot; srcset=&quot;https://img1.daumcdn.net/thumb/R1280x0/?scode=mtistory2&amp;fname=https%3A%2F%2Fblog.kakaocdn.net%2Fdn%2Fdbj256%2FbtrOwL7Oozn%2FxmUQtjUG6ybZot7tIwneI1%2Fimg.png&quot; onerror=&quot;this.onerror=null; this.src='//t1.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;492&quot; height=&quot;328&quot; data-origin-width=&quot;492&quot; data-origin-height=&quot;328&quot;/&gt;&lt;/span&gt;&lt;/figure&gt;
&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;이제 상단메뉴의 Script를 눌러 생성해보자.&lt;/p&gt;
&lt;p&gt;&lt;figure class=&quot;imageblock alignCenter&quot; data-ke-mobileStyle=&quot;widthOrigin&quot; data-origin-width=&quot;808&quot; data-origin-height=&quot;330&quot;&gt;&lt;span data-url=&quot;https://blog.kakaocdn.net/dn/b2NYIt/btrOsaBgR0J/Ypj7Ziy9PSSiqSSseiuqc1/img.png&quot; data-phocus=&quot;https://blog.kakaocdn.net/dn/b2NYIt/btrOsaBgR0J/Ypj7Ziy9PSSiqSSseiuqc1/img.png&quot;&gt;&lt;img src=&quot;https://blog.kakaocdn.net/dn/b2NYIt/btrOsaBgR0J/Ypj7Ziy9PSSiqSSseiuqc1/img.png&quot; srcset=&quot;https://img1.daumcdn.net/thumb/R1280x0/?scode=mtistory2&amp;fname=https%3A%2F%2Fblog.kakaocdn.net%2Fdn%2Fb2NYIt%2FbtrOsaBgR0J%2FYpj7Ziy9PSSiqSSseiuqc1%2Fimg.png&quot; onerror=&quot;this.onerror=null; this.src='//t1.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;330&quot; data-origin-width=&quot;808&quot; data-origin-height=&quot;330&quot;/&gt;&lt;/span&gt;&lt;/figure&gt;
&lt;figure class=&quot;imageblock alignCenter&quot; data-ke-mobileStyle=&quot;widthOrigin&quot; data-origin-width=&quot;1238&quot; data-origin-height=&quot;660&quot;&gt;&lt;span data-url=&quot;https://blog.kakaocdn.net/dn/bOfyi0/btrOw5dNys3/RB3J53VGglUJ6jKM5EQcyK/img.png&quot; data-phocus=&quot;https://blog.kakaocdn.net/dn/bOfyi0/btrOw5dNys3/RB3J53VGglUJ6jKM5EQcyK/img.png&quot;&gt;&lt;img src=&quot;https://blog.kakaocdn.net/dn/bOfyi0/btrOw5dNys3/RB3J53VGglUJ6jKM5EQcyK/img.png&quot; srcset=&quot;https://img1.daumcdn.net/thumb/R1280x0/?scode=mtistory2&amp;fname=https%3A%2F%2Fblog.kakaocdn.net%2Fdn%2FbOfyi0%2FbtrOw5dNys3%2FRB3J53VGglUJ6jKM5EQcyK%2Fimg.png&quot; onerror=&quot;this.onerror=null; this.src='//t1.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;660&quot; data-origin-width=&quot;1238&quot; data-origin-height=&quot;660&quot;/&gt;&lt;/span&gt;&lt;/figure&gt;
&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;기본적으로 GET 방식으로 입력한 url 을 호출하는 스크립트가 생긴다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;Validate 버튼을 눌러 테스트할 수 있다.&lt;/p&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;java 버전이 &lt;b&gt;1.8 or 11&lt;/b&gt; 인지 확인하여야한다.(JAVA_HOME 환경변수 기준)&lt;/p&gt;
&lt;pre id=&quot;code_1665643725636&quot; class=&quot;bash&quot; data-ke-language=&quot;bash&quot; data-ke-type=&quot;codeblock&quot;&gt;&lt;code&gt;2022-10-13 15:07:16,788 ERROR worker-bootstrap: Error initialising worker process
net.grinder.engine.common.EngineException: Setting of Local DNS provider failed
        at net.grinder.engine.process.GrinderProcess.&amp;lt;init&amp;gt;(GrinderProcess.java:154)
        at net.grinder.engine.process.WorkerProcessEntryPoint.run(WorkerProcessEntryPoint.java:78)
        at net.grinder.engine.process.WorkerProcessEntryPoint.main(WorkerProcessEntryPoint.java:60)
Caused by: java.lang.ClassNotFoundException: sun.net.spi.nameservice.NameService
        at java.base/jdk.internal.loader.BuiltinClassLoader.loadClass(BuiltinClassLoader.java:641)
        at java.base/jdk.internal.loader.ClassLoaders$AppClassLoader.loadClass(ClassLoaders.java:188)
        at java.base/java.lang.ClassLoader.loadClass(ClassLoader.java:521)
        at java.base/java.lang.Class.forName0(Native Method)
        at java.base/java.lang.Class.forName(Class.java:383)
        at java.base/java.lang.Class.forName(Class.java:376)
        at org.ngrinder.dns.NameServiceProxy.set(NameServiceProxy.java:59)
        at net.grinder.engine.process.GrinderProcess.&amp;lt;init&amp;gt;(GrinderProcess.java:151)
        ... 2 common frames omitted&lt;/code&gt;&lt;/pre&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;h2 data-ke-size=&quot;size26&quot;&gt;Test Plan 설정 및 시작&lt;/h2&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;이제 위에서 작성한 스크립트를 어떤 role 로 실행할지 정해야한다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;상단 메뉴 Performance Test&amp;gt;Create Test&lt;/p&gt;
&lt;p&gt;&lt;figure class=&quot;imageblock alignCenter&quot; data-ke-mobileStyle=&quot;widthOrigin&quot; data-origin-width=&quot;1237&quot; data-origin-height=&quot;837&quot;&gt;&lt;span data-url=&quot;https://blog.kakaocdn.net/dn/9U6P9/btrOvggqX1r/Ptr3y3Zct1etuD3au50GJk/img.png&quot; data-phocus=&quot;https://blog.kakaocdn.net/dn/9U6P9/btrOvggqX1r/Ptr3y3Zct1etuD3au50GJk/img.png&quot;&gt;&lt;img src=&quot;https://blog.kakaocdn.net/dn/9U6P9/btrOvggqX1r/Ptr3y3Zct1etuD3au50GJk/img.png&quot; srcset=&quot;https://img1.daumcdn.net/thumb/R1280x0/?scode=mtistory2&amp;fname=https%3A%2F%2Fblog.kakaocdn.net%2Fdn%2F9U6P9%2FbtrOvggqX1r%2FPtr3y3Zct1etuD3au50GJk%2Fimg.png&quot; onerror=&quot;this.onerror=null; this.src='//t1.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;1237&quot; height=&quot;837&quot; data-origin-width=&quot;1237&quot; data-origin-height=&quot;837&quot;/&gt;&lt;/span&gt;&lt;/figure&gt;
&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;테스트이름, Agent 는 실제 기동중인 agent 대수 만큼만 선택가능, 작성한 script 선택.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;가상유저를 몇개 할지, Duration(테스트 진행 시간) 등을 설정한 후 &lt;b&gt;Save and Start &lt;/b&gt;하면된다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p&gt;&lt;figure class=&quot;imageblock alignCenter&quot; data-ke-mobileStyle=&quot;widthOrigin&quot; data-origin-width=&quot;1212&quot; data-origin-height=&quot;806&quot;&gt;&lt;span data-url=&quot;https://blog.kakaocdn.net/dn/cAk2Iv/btrOvhzGtsE/dQ2oVUxKs2Lni7f4OYWtV0/img.png&quot; data-phocus=&quot;https://blog.kakaocdn.net/dn/cAk2Iv/btrOvhzGtsE/dQ2oVUxKs2Lni7f4OYWtV0/img.png&quot; data-alt=&quot;nGrinder Performance Test Report&quot;&gt;&lt;img src=&quot;https://blog.kakaocdn.net/dn/cAk2Iv/btrOvhzGtsE/dQ2oVUxKs2Lni7f4OYWtV0/img.png&quot; srcset=&quot;https://img1.daumcdn.net/thumb/R1280x0/?scode=mtistory2&amp;fname=https%3A%2F%2Fblog.kakaocdn.net%2Fdn%2FcAk2Iv%2FbtrOvhzGtsE%2FdQ2oVUxKs2Lni7f4OYWtV0%2Fimg.png&quot; onerror=&quot;this.onerror=null; this.src='//t1.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;1212&quot; height=&quot;806&quot; data-origin-width=&quot;1212&quot; data-origin-height=&quot;806&quot;/&gt;&lt;/span&gt;&lt;figcaption&gt;nGrinder Performance Test Report&lt;/figcaption&gt;
&lt;/figure&gt;
&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;테스트가 끝나면 Report 페이지가 뜬다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;h2 data-ke-size=&quot;size26&quot;&gt;마무리.&lt;/h2&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;이렇게 nGrinder 를 사용하여 셋팅부터 호출까지 진행해 보았다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;이제 회원가입, 로그인 script를 구현하여 테스트를 진행할 것이다.&lt;/p&gt;</description>
      <category>엔지니어링/성능테스트</category>
      <category>ngrinder</category>
      <category>성능테스트</category>
      <category>오픈소스</category>
      <category>쿠버네티스</category>
      <author>joon95</author>
      <guid isPermaLink="true">https://flowlog.tistory.com/79</guid>
      <comments>https://flowlog.tistory.com/79#entry79comment</comments>
      <pubDate>Thu, 13 Oct 2022 16:04:41 +0900</pubDate>
    </item>
    <item>
      <title>[OpenJDK] 우분투 20.04.3 LTS 에 openJDK 직접 설치하기</title>
      <link>https://flowlog.tistory.com/78</link>
      <description>&lt;p data-ke-size=&quot;size16&quot;&gt;우분투에 openjdk를 설치하려하니 잘 안되서...&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;ppa 레포를 등록하고 패키지 찾고하면 된다하는데&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;자꾸 안되서 수동 설치방법을 기록한다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;a href=&quot;https://jdk.java.net/archive/&quot; target=&quot;_blank&quot; rel=&quot;noopener&quot;&gt;https://jdk.java.net/archive/&lt;/a&gt;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;해당 페이지에 들어가 다운할 tar.gz의 링크를 복사&lt;/p&gt;
&lt;p&gt;&lt;figure class=&quot;imageblock alignCenter&quot; data-ke-mobileStyle=&quot;widthOrigin&quot; data-origin-width=&quot;731&quot; data-origin-height=&quot;341&quot;&gt;&lt;span data-url=&quot;https://blog.kakaocdn.net/dn/tMmGb/btrOtGlHcBv/Rfx7flYIKtLKYUT8zTksbK/img.png&quot; data-phocus=&quot;https://blog.kakaocdn.net/dn/tMmGb/btrOtGlHcBv/Rfx7flYIKtLKYUT8zTksbK/img.png&quot;&gt;&lt;img src=&quot;https://blog.kakaocdn.net/dn/tMmGb/btrOtGlHcBv/Rfx7flYIKtLKYUT8zTksbK/img.png&quot; srcset=&quot;https://img1.daumcdn.net/thumb/R1280x0/?scode=mtistory2&amp;fname=https%3A%2F%2Fblog.kakaocdn.net%2Fdn%2FtMmGb%2FbtrOtGlHcBv%2FRfx7flYIKtLKYUT8zTksbK%2Fimg.png&quot; onerror=&quot;this.onerror=null; this.src='//t1.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;731&quot; height=&quot;341&quot; data-origin-width=&quot;731&quot; data-origin-height=&quot;341&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;서버에서 wget을 통해 파일을 다운&lt;/p&gt;
&lt;pre id=&quot;code_1665637633112&quot; class=&quot;bash&quot; data-ke-language=&quot;bash&quot; data-ke-type=&quot;codeblock&quot;&gt;&lt;code&gt;$ wget https://download.java.net/java/GA/jdk18.0.2/f6ad4b4450fd4d298113270ec84f30ee/9/GPL/openjdk-18.0.2_linux-x64_bin.tar.gz
--2022-10-13 14:04:39--  https://download.java.net/java/GA/jdk18.0.2/f6ad4b4450fd4d298113270ec84f30ee/9/GPL/openjdk-18.0.2_linux-x64_bin.tar.gz
Resolving download.java.net (download.java.net)... 23.219.68.76
Connecting to download.java.net (download.java.net)|23.219.68.76|:443... connected.
HTTP request sent, awaiting response... 200 OK
Length: 188255633 (180M) [application/x-gzip]
Saving to: &amp;lsquo;openjdk-18.0.2_linux-x64_bin.tar.gz&amp;rsquo;

openjdk-18.0.2_linux-x64_bin.tar.gz                100%[==============================================================================================================&amp;gt;] 179.53M  69.4MB/s    in 2.6s

2022-10-13 14:04:42 (69.4 MB/s) - &amp;lsquo;openjdk-18.0.2_linux-x64_bin.tar.gz&amp;rsquo; saved [188255633/188255633]&lt;/code&gt;&lt;/pre&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;압축을 푼다&lt;/p&gt;
&lt;pre id=&quot;code_1665637644923&quot; class=&quot;bash&quot; data-ke-language=&quot;bash&quot; data-ke-type=&quot;codeblock&quot;&gt;&lt;code&gt;$ tar -xvf openjdk-18.0.2_linux-x64_bin.tar.gz&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;jdk-18.0.2&amp;nbsp;&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;적당한 곳에 파일을 위치하고 환경변수 PATH에 경로를 추가해주면 끝&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;(필자는 귀찮아서 /root/jdk-18.0.2 에 위치하고 있음)&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;home directory 에 있는 .bash_profile 을 열고 PATH 변수에 경로설정을 했다.&lt;/p&gt;
&lt;pre id=&quot;code_1665638247397&quot; class=&quot;bash&quot; data-ke-language=&quot;bash&quot; data-ke-type=&quot;codeblock&quot;&gt;&lt;code&gt;$ vim ~/.bash_profile
export PATH=$PATH:$HOME/jdk-18.0.2/bin&lt;/code&gt;&lt;/pre&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;이제 터미널창을 껏다 키거나 source 명령어를 돌려주면 적용된다.&lt;/p&gt;
&lt;pre id=&quot;code_1665638355434&quot; class=&quot;bash&quot; data-ke-language=&quot;bash&quot; data-ke-type=&quot;codeblock&quot;&gt;&lt;code&gt;$ source ~/.bash_profile&lt;/code&gt;&lt;/pre&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;java 버전보기&lt;/p&gt;
&lt;pre id=&quot;code_1665638375580&quot; class=&quot;bash&quot; data-ke-language=&quot;bash&quot; data-ke-type=&quot;codeblock&quot;&gt;&lt;code&gt;$ java --version
openjdk 18.0.2 2022-07-19
OpenJDK Runtime Environment (build 18.0.2+9-61)
OpenJDK 64-Bit Server VM (build 18.0.2+9-61, mixed mode, sharing)&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;p data-ke-size=&quot;size16&quot;&gt;이제 Ngrinder로 성능테스트를 해 볼 예정이다.&lt;/p&gt;</description>
      <category>엔지니어링/기타</category>
      <category>java설치</category>
      <category>openjdk</category>
      <category>ubuntu</category>
      <author>joon95</author>
      <guid isPermaLink="true">https://flowlog.tistory.com/78</guid>
      <comments>https://flowlog.tistory.com/78#entry78comment</comments>
      <pubDate>Thu, 13 Oct 2022 14:21:25 +0900</pubDate>
    </item>
    <item>
      <title>[PostgreSql] Error: SCRAM authentication requires libpq version 10 or above</title>
      <link>https://flowlog.tistory.com/77</link>
      <description>&lt;p data-ke-size=&quot;size16&quot;&gt;네이버 클라우드에 postgresql 서비스를 올리고&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;ubuntu VM에서 postgres-client 패키지를 다운한뒤 원격 접근하려하니 에러가 발생하였다.&lt;/p&gt;
&lt;h2 data-ke-size=&quot;size26&quot;&gt;&lt;span style=&quot;font-family: AppleSDGothicNeo-Regular, 'Malgun Gothic', '맑은 고딕', dotum, 돋움, sans-serif;&quot;&gt;에러&lt;/span&gt;&lt;/h2&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;span style=&quot;color: #ee2323;&quot;&gt;&lt;b&gt;psql:&amp;nbsp;SCRAM&amp;nbsp;authentication&amp;nbsp;requires&amp;nbsp;libpq&amp;nbsp;version&amp;nbsp;10&amp;nbsp;or&amp;nbsp;above&lt;/b&gt;&lt;/span&gt;&lt;/p&gt;
&lt;pre id=&quot;code_1665633951991&quot; class=&quot;bash&quot; data-ke-language=&quot;bash&quot; data-ke-type=&quot;codeblock&quot;&gt;&lt;code&gt;root@bastion:~# psql -h pg-ceq8g.vpc-cdb-kr.ntruss.com -U mydata -d mydb -W
Password for user mydata:
psql: SCRAM authentication requires libpq version 10 or above&lt;/code&gt;&lt;/pre&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;h2 data-ke-size=&quot;size26&quot;&gt;원인&lt;/h2&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;원인은 간단하다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;접근하려는 클라이언트의 psql 버전이 낮아서 그렇다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;그래서 postgresql에 접근하는 명령어인 psql 이 들어있는 postgres-client 패키지의 버전을 올리면 된다.&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;h2 data-ke-size=&quot;size26&quot;&gt;postgresql-client-11 설치&lt;/h2&gt;
&lt;pre id=&quot;code_1665633839402&quot; class=&quot;bash&quot; data-ke-language=&quot;highlight.js&quot; data-ke-type=&quot;codeblock&quot;&gt;&lt;code&gt;$ apt install -y curl ca-certificates gnupg
$ curl https://www.postgresql.org/media/keys/ACCC4CF8.asc | sudo apt-key add -
$ sh -c 'echo &quot;deb http://apt.postgresql.org/pub/repos/apt $(lsb_release -cs)-pgdg main&quot; &amp;gt; /etc/apt/sources.list.d/postgresql.list' 
$ apt update -y
$ apt install -y postgresql-client-11&lt;/code&gt;&lt;/pre&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;h2 data-ke-size=&quot;size26&quot;&gt;확인&lt;/h2&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;이제 정상적으로 원격 접속이 가능했다.&lt;/p&gt;
&lt;pre id=&quot;code_1665634066037&quot; class=&quot;bash&quot; data-ke-language=&quot;bash&quot; data-ke-type=&quot;codeblock&quot;&gt;&lt;code&gt;root@bastion:~# psql -h pg-ceq8g.vpc-cdb-kr.ntruss.com -U mydata -d mydb -W
Password:
psql (11.17 (Ubuntu 11.17-1.pgdg20.04+1), server 13.3)
WARNING: psql major version 11, server major version 13.
         Some psql features might not work.
Type &quot;help&quot; for help.

mydb=&amp;gt;&lt;/code&gt;&lt;/pre&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;h2 data-ke-size=&quot;size26&quot;&gt;참고자료&lt;/h2&gt;
&lt;figure id=&quot;og_1665633739083&quot; contenteditable=&quot;false&quot; data-ke-type=&quot;opengraph&quot; data-ke-align=&quot;alignCenter&quot; data-og-type=&quot;article&quot; data-og-title=&quot;How to Upgrade PostgreSQL Client from version 10 to version 11 on Ubuntu 18.04&quot; data-og-description=&quot;Upgrade PostgreSQL-Client of Ubuntu 18.04 from default version 10 to version 11.&quot; data-og-host=&quot;medium.com&quot; data-og-source-url=&quot;https://medium.com/@Dylan.Wang/how-to-upgrade-postgresql-client-from-version-10-to-version-11-on-ubuntu-18-04-3a12b5977a3f&quot; data-og-url=&quot;https://medium.com/@Dylan.Wang/how-to-upgrade-postgresql-client-from-version-10-to-version-11-on-ubuntu-18-04-3a12b5977a3f&quot; data-og-image=&quot;&quot;&gt;&lt;a href=&quot;https://medium.com/@Dylan.Wang/how-to-upgrade-postgresql-client-from-version-10-to-version-11-on-ubuntu-18-04-3a12b5977a3f&quot; target=&quot;_blank&quot; rel=&quot;noopener&quot; data-source-url=&quot;https://medium.com/@Dylan.Wang/how-to-upgrade-postgresql-client-from-version-10-to-version-11-on-ubuntu-18-04-3a12b5977a3f&quot;&gt;
&lt;div class=&quot;og-image&quot; style=&quot;background-image: url();&quot;&gt;&amp;nbsp;&lt;/div&gt;
&lt;div class=&quot;og-text&quot;&gt;
&lt;p class=&quot;og-title&quot; data-ke-size=&quot;size16&quot;&gt;How to Upgrade PostgreSQL Client from version 10 to version 11 on Ubuntu 18.04&lt;/p&gt;
&lt;p class=&quot;og-desc&quot; data-ke-size=&quot;size16&quot;&gt;Upgrade PostgreSQL-Client of Ubuntu 18.04 from default version 10 to version 11.&lt;/p&gt;
&lt;p class=&quot;og-host&quot; data-ke-size=&quot;size16&quot;&gt;medium.com&lt;/p&gt;
&lt;/div&gt;
&lt;/a&gt;&lt;/figure&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;</description>
      <category>데이터베이스</category>
      <category>postgresql-client</category>
      <category>psql</category>
      <category>psql11</category>
      <author>joon95</author>
      <guid isPermaLink="true">https://flowlog.tistory.com/77</guid>
      <comments>https://flowlog.tistory.com/77#entry77comment</comments>
      <pubDate>Thu, 13 Oct 2022 13:08:42 +0900</pubDate>
    </item>
    <item>
      <title>[GIT] 불필요한 파일 업로드 제한하기</title>
      <link>https://flowlog.tistory.com/76</link>
      <description>&lt;p data-ke-size=&quot;size16&quot;&gt;지난 spring 멀티 모듈프로젝트 구성 후 이것저것 테스트를 하다가&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;target 폴더 제한하는 방법을 기록해두려한다.&lt;/p&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;.gitignore&lt;/b&gt; 파일을 작성하면 된다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;추가로 이미 업로드 된 상태라면 &lt;b&gt;git rm -r --cache&lt;/b&gt; &lt;b&gt;[폴더]&lt;/b&gt;로 지운 뒤 push 해주면 된다.&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;.gitignore 파일 작성&lt;/h2&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;git clone 후 &lt;b&gt;최상단 경로&lt;/b&gt;에 작성해야 한다.&lt;/p&gt;
&lt;pre id=&quot;code_1665626004872&quot; class=&quot;bash&quot; data-ke-language=&quot;bash&quot; data-ke-type=&quot;codeblock&quot;&gt;&lt;code&gt;# ignore properties file
test/core/target
test/market/target
test/target/

.metadata
test/.settings
test/core/.settings
test/market/.settings&lt;/code&gt;&lt;/pre&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;적당히 target 과 .settings / .metadata 폴더를 제외시켰다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;참고로 필자의 폴더구조는 아래와 같다.(window cmd 명령어)&lt;/p&gt;
&lt;pre id=&quot;code_1665626436771&quot; class=&quot;cmd dos&quot; data-ke-language=&quot;highlight.js&quot; data-ke-type=&quot;codeblock&quot;&gt;&lt;code&gt;&amp;gt; tree | findstr /v /r /c:&quot;^........│&quot; /c:&quot;^│....... &quot;&lt;/code&gt;&lt;/pre&gt;
&lt;pre id=&quot;code_1665626412163&quot; class=&quot;cmd dos&quot; data-ke-language=&quot;highlight.js&quot; data-ke-type=&quot;codeblock&quot;&gt;&lt;code&gt;└─test
    ├─.settings
    ├─core
    │  ├─.settings
    │  ├─src
    │  └─target
    │      ├─classes
    │      │  ├─com
    │      │  │  └─joon95
    │      │  │      └─test
    │      │  │          └─core
    │      │  │              ├─controller
    │      │  │              ├─monster
    │      │  │              ├─repository
    │      │  │              ├─service
    │      │  │              └─vo
    │      │  ├─META-INF
    │      │  │  └─maven
    │      │  │      └─org.springframework.boot
    │      │  │          └─core
    │      │  └─static
    │      │      ├─css
    │      │      └─html
    │      ├─generated-sources
    │      │  └─annotations
    │      ├─generated-test-sources
    │      │  └─test-annotations
    │      └─test-classes
    ├─market
    │  ├─.settings
    │  ├─src
    │  └─target
    │      ├─classes
    │      │  ├─com
    │      │  │  └─joon95
    │      │  │      └─test
    │      │  │          └─market
    │      │  │              └─stage
    │      │  └─META-INF
    │      │      └─maven
    │      │          └─com.joon95
    │      │              └─market
    │      ├─generated-sources
    │      │  └─annotations
    │      ├─generated-test-sources
    │      │  └─test-annotations
    │      └─test-classes
    └─target
        └─classes
            └─META-INF
                └─maven
                    └─com.joon95
                        └─test&lt;/code&gt;&lt;/pre&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;h2 data-ke-size=&quot;size26&quot;&gt;기존 업로드된 폴더 제거&lt;/h2&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;필자는 이미 target 폴더 외 모든 폴더를 commit &amp;amp; push 해놓았다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;이 상태로는 .gitignore 파일이 생겨도 기존 폴더를 삭제하지 않는다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;그래서 git cache를 날려 폴더나 파일을 삭제해야한다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;필자는 1번에 모든 폴더를 날려버리고 진행했다.&lt;/p&gt;
&lt;pre id=&quot;code_1665626965977&quot; class=&quot;git ada&quot; data-ke-language=&quot;highlight.js&quot; data-ke-type=&quot;codeblock&quot;&gt;&lt;code&gt;$ git rm -r --cached .&lt;/code&gt;&lt;/pre&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;size16&quot;&gt;rm&amp;nbsp;'.gitignore'&lt;br /&gt;rm&amp;nbsp;'README.md'&lt;br /&gt;rm&amp;nbsp;'test/.classpath'&lt;br /&gt;rm&amp;nbsp;'test/.gitignore'&lt;br /&gt;rm&amp;nbsp;'test/.project'&lt;br /&gt;rm&amp;nbsp;'test/.settings/org.eclipse.core.resources.prefs'&lt;br /&gt;rm&amp;nbsp;'test/.settings/org.eclipse.jdt.apt.core.prefs'&lt;br /&gt;rm&amp;nbsp;'test/.settings/org.eclipse.jdt.core.prefs'&lt;br /&gt;rm&amp;nbsp;'test/.settings/org.eclipse.m2e.core.prefs'&lt;br /&gt;rm&amp;nbsp;'test/core/.classpath'&lt;br /&gt;rm&amp;nbsp;'test/core/.project'&lt;br /&gt;rm&amp;nbsp;'test/core/.settings/org.eclipse.core.resources.prefs'&lt;br /&gt;rm&amp;nbsp;'test/core/.settings/org.eclipse.jdt.apt.core.prefs'&lt;br /&gt;rm&amp;nbsp;'test/core/.settings/org.eclipse.jdt.core.prefs'&lt;br /&gt;rm&amp;nbsp;'test/core/.settings/org.eclipse.m2e.core.prefs'&lt;br /&gt;rm&amp;nbsp;'test/core/.settings/org.springframework.ide.eclipse.prefs'&lt;br /&gt;rm&amp;nbsp;'test/core/Dockerfile'&lt;br /&gt;rm&amp;nbsp;'test/core/pom.xml'&lt;br /&gt;rm&amp;nbsp;'test/core/src/main/java/com/joon95/test/core/Application.java'&lt;br /&gt;rm&amp;nbsp;'test/core/src/main/java/com/joon95/test/core/AuthInterceptor.java'&lt;br /&gt;rm&amp;nbsp;'test/core/src/main/java/com/joon95/test/core/RedisConfig'&lt;br /&gt;rm&amp;nbsp;'test/core/src/main/java/com/joon95/test/core/SpringSecurity.java'&lt;br /&gt;rm&amp;nbsp;'test/core/src/main/java/com/joon95/test/core/WebMvcConfig.java'&lt;br /&gt;rm&amp;nbsp;'test/core/src/main/java/com/joon95/test/core/controller/UserController.java'&lt;br /&gt;rm&amp;nbsp;'test/core/src/main/java/com/joon95/test/core/monster/MonsterController.java'&lt;br /&gt;rm&amp;nbsp;'test/core/src/main/java/com/joon95/test/core/repository/UserRepository.java'&lt;br /&gt;rm&amp;nbsp;'test/core/src/main/java/com/joon95/test/core/service/UserService.java'&lt;br /&gt;rm&amp;nbsp;'test/core/src/main/java/com/joon95/test/core/vo/Sample.java'&lt;br /&gt;rm&amp;nbsp;'test/core/src/main/java/com/joon95/test/core/vo/User.java'&lt;br /&gt;rm&amp;nbsp;'test/core/src/main/resources/application-local.properties'&lt;br /&gt;rm&amp;nbsp;'test/core/src/main/resources/application-master.properties'&lt;br /&gt;rm&amp;nbsp;'test/core/src/main/resources/application.properties'&lt;br /&gt;rm&amp;nbsp;'test/core/src/main/resources/static/css/bootstrap.min.css'&lt;br /&gt;rm&amp;nbsp;'test/core/src/main/resources/static/css/style.css'&lt;br /&gt;rm&amp;nbsp;'test/core/src/main/resources/static/html/add.html'&lt;br /&gt;rm&amp;nbsp;'test/core/src/main/resources/static/html/item.html'&lt;br /&gt;rm&amp;nbsp;'test/core/src/main/resources/static/html/items.html'&lt;br /&gt;rm&amp;nbsp;'test/core/src/main/resources/static/index.html'&lt;br /&gt;rm&amp;nbsp;'test/core/target/classes/META-INF/MANIFEST.MF'&lt;br /&gt;rm&amp;nbsp;'test/core/target/classes/META-INF/maven/com.joon95/core/pom.properties'&lt;br /&gt;rm&amp;nbsp;'test/core/target/classes/META-INF/maven/com.joon95/core/pom.xml'&lt;br /&gt;rm&amp;nbsp;'test/core/target/classes/application-local.properties'&lt;br /&gt;rm&amp;nbsp;'test/core/target/classes/application.properties'&lt;br /&gt;rm&amp;nbsp;'test/core/target/classes/com/joon95/test/core/Application.class'&lt;br /&gt;rm&amp;nbsp;'test/core/target/classes/com/joon95/test/core/AuthInterceptor.class'&lt;br /&gt;rm&amp;nbsp;'test/core/target/classes/com/joon95/test/core/SpringSecurity.class'&lt;br /&gt;rm&amp;nbsp;'test/core/target/classes/com/joon95/test/core/WebMvcConfig.class'&lt;br /&gt;rm&amp;nbsp;'test/core/target/classes/com/joon95/test/core/controller/UserController.class'&lt;br /&gt;rm&amp;nbsp;'test/core/target/classes/com/joon95/test/core/monster/MonsterController.class'&lt;br /&gt;rm&amp;nbsp;'test/core/target/classes/com/joon95/test/core/repository/UserRepository.class'&lt;br /&gt;rm&amp;nbsp;'test/core/target/classes/com/joon95/test/core/service/UserService.class'&lt;br /&gt;rm&amp;nbsp;'test/core/target/classes/com/joon95/test/core/vo/Sample.class'&lt;br /&gt;rm&amp;nbsp;'test/core/target/classes/com/joon95/test/core/vo/User.class'&lt;br /&gt;rm&amp;nbsp;'test/core/target/classes/static/css/bootstrap.min.css'&lt;br /&gt;rm&amp;nbsp;'test/core/target/classes/static/css/style.css'&lt;br /&gt;rm&amp;nbsp;'test/core/target/classes/static/html/add.html'&lt;br /&gt;rm&amp;nbsp;'test/core/target/classes/static/html/item.html'&lt;br /&gt;rm&amp;nbsp;'test/core/target/classes/static/html/items.html'&lt;br /&gt;rm&amp;nbsp;'test/core/target/classes/static/index.html'&lt;br /&gt;rm&amp;nbsp;'test/core/target/core-0.0.1-SNAPSHOT.jar'&lt;br /&gt;rm&amp;nbsp;'test/core/target/core-0.0.1-SNAPSHOT.jar.original'&lt;br /&gt;rm&amp;nbsp;'test/core/target/maven-archiver/pom.properties'&lt;br /&gt;rm&amp;nbsp;'test/core/target/maven-status/maven-compiler-plugin/compile/default-compile/createdFiles.lst'&lt;br /&gt;rm&amp;nbsp;'test/core/target/maven-status/maven-compiler-plugin/compile/default-compile/inputFiles.lst'&lt;br /&gt;rm&amp;nbsp;'test/core/target/maven-status/maven-compiler-plugin/testCompile/default-testCompile/createdFiles.lst'&lt;br /&gt;rm&amp;nbsp;'test/core/target/maven-status/maven-compiler-plugin/testCompile/default-testCompile/inputFiles.lst'&lt;br /&gt;rm&amp;nbsp;'test/market/.classpath'&lt;br /&gt;rm&amp;nbsp;'test/market/.project'&lt;br /&gt;rm&amp;nbsp;'test/market/.settings/org.eclipse.core.resources.prefs'&lt;br /&gt;rm&amp;nbsp;'test/market/.settings/org.eclipse.jdt.apt.core.prefs'&lt;br /&gt;rm&amp;nbsp;'test/market/.settings/org.eclipse.jdt.core.prefs'&lt;br /&gt;rm&amp;nbsp;'test/market/.settings/org.eclipse.m2e.core.prefs'&lt;br /&gt;rm&amp;nbsp;'test/market/.settings/org.springframework.ide.eclipse.prefs'&lt;br /&gt;rm&amp;nbsp;'test/market/pom.xml'&lt;br /&gt;rm&amp;nbsp;'test/market/src/main/java/com/joon95/test/market/Application.java'&lt;br /&gt;rm&amp;nbsp;'test/market/src/main/java/com/joon95/test/market/stage/StageController.java'&lt;br /&gt;rm&amp;nbsp;'test/market/src/main/resources/application.properties'&lt;br /&gt;rm&amp;nbsp;'test/market/target/classes/META-INF/MANIFEST.MF'&lt;br /&gt;rm&amp;nbsp;'test/market/target/classes/META-INF/maven/com.joon95/market/pom.properties'&lt;br /&gt;rm&amp;nbsp;'test/market/target/classes/META-INF/maven/com.joon95/market/pom.xml'&lt;br /&gt;rm&amp;nbsp;'test/market/target/classes/application.properties'&lt;br /&gt;rm&amp;nbsp;'test/market/target/classes/com/joon95/test/market/Application.class'&lt;br /&gt;rm&amp;nbsp;'test/market/target/classes/com/joon95/test/market/stage/StageController.class'&lt;br /&gt;rm&amp;nbsp;'test/pom.xml'&lt;br /&gt;rm&amp;nbsp;'test/target/classes/META-INF/MANIFEST.MF'&lt;br /&gt;rm&amp;nbsp;'test/target/classes/META-INF/maven/com.joon95/test/pom.properties'&lt;br /&gt;rm&amp;nbsp;'test/target/classes/META-INF/maven/com.joon95/test/pom.xml'&lt;/p&gt;
&lt;/div&gt;
&lt;/div&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;삭제가 되었으면 add &amp;amp; commit &amp;amp; push 하자.&lt;/p&gt;
&lt;pre id=&quot;code_1665627102436&quot; class=&quot;git elixir&quot; data-ke-language=&quot;highlight.js&quot; data-ke-type=&quot;codeblock&quot;&gt;&lt;code&gt;$ git add .
$ git commit -m &quot;ignore 파일제외 적용&quot;
$ git push&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;필자는 네이버클라우드의 git인 SourceCommit 에서 테스트중이다.&lt;/p&gt;
&lt;p&gt;&lt;figure class=&quot;imageblock alignCenter&quot; data-ke-mobileStyle=&quot;widthOrigin&quot; data-origin-width=&quot;371&quot; data-origin-height=&quot;267&quot;&gt;&lt;span data-url=&quot;https://blog.kakaocdn.net/dn/F30vd/btrOs3OaqbN/VjXZwsBoJWb1HFkOz4YFGK/img.png&quot; data-phocus=&quot;https://blog.kakaocdn.net/dn/F30vd/btrOs3OaqbN/VjXZwsBoJWb1HFkOz4YFGK/img.png&quot; data-alt=&quot;ncp SourceCommit (Git)&quot;&gt;&lt;img src=&quot;https://blog.kakaocdn.net/dn/F30vd/btrOs3OaqbN/VjXZwsBoJWb1HFkOz4YFGK/img.png&quot; srcset=&quot;https://img1.daumcdn.net/thumb/R1280x0/?scode=mtistory2&amp;fname=https%3A%2F%2Fblog.kakaocdn.net%2Fdn%2FF30vd%2FbtrOs3OaqbN%2FVjXZwsBoJWb1HFkOz4YFGK%2Fimg.png&quot; onerror=&quot;this.onerror=null; this.src='//t1.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;371&quot; height=&quot;267&quot; data-origin-width=&quot;371&quot; data-origin-height=&quot;267&quot;/&gt;&lt;/span&gt;&lt;figcaption&gt;ncp SourceCommit (Git)&lt;/figcaption&gt;
&lt;/figure&gt;
&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;이제 깔끔하게 지워진 레포지토리를 구경할 수 있다.&lt;/p&gt;</description>
      <category>개발/기타</category>
      <category>Git</category>
      <category>gitignore</category>
      <category>레포지토리</category>
      <author>joon95</author>
      <guid isPermaLink="true">https://flowlog.tistory.com/76</guid>
      <comments>https://flowlog.tistory.com/76#entry76comment</comments>
      <pubDate>Thu, 13 Oct 2022 11:16:09 +0900</pubDate>
    </item>
    <item>
      <title>[Eclipse] Lombok 적용하기</title>
      <link>https://flowlog.tistory.com/75</link>
      <description>&lt;p data-ke-size=&quot;size16&quot;&gt;한번 세팅하면 까먹는 Lombok 설치방법..!&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;먼저 Lombok &lt;b&gt;설치파일을 다운&lt;/b&gt;하자.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;a href=&quot;https://projectlombok.org/download&quot; target=&quot;_blank&quot; rel=&quot;noopener&quot;&gt;https://projectlombok.org/download&lt;/a&gt;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;설치파일은 eclipse나 sts 설치폴더로 이동 시킨 뒤&amp;nbsp;cmd 창에서 실행시킨다.&lt;/p&gt;
&lt;pre id=&quot;code_1665115069618&quot; class=&quot;cmd dos&quot; data-ke-language=&quot;highlistgh.js&quot; data-ke-type=&quot;codeblock&quot;&gt;&lt;code&gt;java -jar lombok.jar&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;&lt;figure class=&quot;imageblock alignCenter&quot; data-ke-mobileStyle=&quot;widthOrigin&quot; data-origin-width=&quot;369&quot; data-origin-height=&quot;42&quot;&gt;&lt;span data-url=&quot;https://blog.kakaocdn.net/dn/2W3mm/btrNY0LSnr3/RX6m56jsZk761LATJNyZgK/img.png&quot; data-phocus=&quot;https://blog.kakaocdn.net/dn/2W3mm/btrNY0LSnr3/RX6m56jsZk761LATJNyZgK/img.png&quot; data-alt=&quot;lombok.jar 실행&quot;&gt;&lt;img src=&quot;https://blog.kakaocdn.net/dn/2W3mm/btrNY0LSnr3/RX6m56jsZk761LATJNyZgK/img.png&quot; srcset=&quot;https://img1.daumcdn.net/thumb/R1280x0/?scode=mtistory2&amp;fname=https%3A%2F%2Fblog.kakaocdn.net%2Fdn%2F2W3mm%2FbtrNY0LSnr3%2FRX6m56jsZk761LATJNyZgK%2Fimg.png&quot; onerror=&quot;this.onerror=null; this.src='//t1.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;369&quot; height=&quot;42&quot; data-origin-width=&quot;369&quot; data-origin-height=&quot;42&quot;/&gt;&lt;/span&gt;&lt;figcaption&gt;lombok.jar 실행&lt;/figcaption&gt;
&lt;/figure&gt;
&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;약간의 로딩시간을 기다리면 자동으로 IDEs가 잡힌다.&lt;/p&gt;
&lt;p&gt;&lt;figure class=&quot;imageblock alignCenter&quot; data-ke-mobileStyle=&quot;widthOrigin&quot; data-origin-width=&quot;838&quot; data-origin-height=&quot;491&quot;&gt;&lt;span data-url=&quot;https://blog.kakaocdn.net/dn/EvoDK/btrN2wbIAqC/bNuPyAnpooF76fCx6bhu3K/img.png&quot; data-phocus=&quot;https://blog.kakaocdn.net/dn/EvoDK/btrN2wbIAqC/bNuPyAnpooF76fCx6bhu3K/img.png&quot; data-alt=&quot;lombok install&quot;&gt;&lt;img src=&quot;https://blog.kakaocdn.net/dn/EvoDK/btrN2wbIAqC/bNuPyAnpooF76fCx6bhu3K/img.png&quot; srcset=&quot;https://img1.daumcdn.net/thumb/R1280x0/?scode=mtistory2&amp;fname=https%3A%2F%2Fblog.kakaocdn.net%2Fdn%2FEvoDK%2FbtrN2wbIAqC%2FbNuPyAnpooF76fCx6bhu3K%2Fimg.png&quot; onerror=&quot;this.onerror=null; this.src='//t1.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;838&quot; height=&quot;491&quot; data-origin-width=&quot;838&quot; data-origin-height=&quot;491&quot;/&gt;&lt;/span&gt;&lt;figcaption&gt;lombok install&lt;/figcaption&gt;
&lt;/figure&gt;
&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;만약 안잡히면 &lt;b&gt;Specify location...&amp;nbsp;&lt;/b&gt;버튼을 클릭해 직접 eclipse 나 sts 실행파일을 지정해주면된다.&lt;/p&gt;
&lt;p&gt;&lt;figure class=&quot;imageblock alignCenter&quot; data-ke-mobileStyle=&quot;widthOrigin&quot; data-origin-width=&quot;703&quot; data-origin-height=&quot;430&quot;&gt;&lt;span data-url=&quot;https://blog.kakaocdn.net/dn/bDTJVa/btrNXVEEKtW/njokNmMd8HeloRrGrdCQF1/img.png&quot; data-phocus=&quot;https://blog.kakaocdn.net/dn/bDTJVa/btrNXVEEKtW/njokNmMd8HeloRrGrdCQF1/img.png&quot; data-alt=&quot;sts(eclipse) exe 파일 선택&quot;&gt;&lt;img src=&quot;https://blog.kakaocdn.net/dn/bDTJVa/btrNXVEEKtW/njokNmMd8HeloRrGrdCQF1/img.png&quot; srcset=&quot;https://img1.daumcdn.net/thumb/R1280x0/?scode=mtistory2&amp;fname=https%3A%2F%2Fblog.kakaocdn.net%2Fdn%2FbDTJVa%2FbtrNXVEEKtW%2FnjokNmMd8HeloRrGrdCQF1%2Fimg.png&quot; onerror=&quot;this.onerror=null; this.src='//t1.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;703&quot; height=&quot;430&quot; data-origin-width=&quot;703&quot; data-origin-height=&quot;430&quot;/&gt;&lt;/span&gt;&lt;figcaption&gt;sts(eclipse) exe 파일 선택&lt;/figcaption&gt;
&lt;/figure&gt;
&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;b&gt;Install / Update&amp;nbsp;&lt;/b&gt;버튼을 클릭해주자.&lt;/p&gt;
&lt;p&gt;&lt;figure class=&quot;imageblock alignCenter&quot; data-ke-mobileStyle=&quot;widthOrigin&quot; data-origin-width=&quot;840&quot; data-origin-height=&quot;490&quot;&gt;&lt;span data-url=&quot;https://blog.kakaocdn.net/dn/ewZ6sA/btrN1y1WwFa/nwAPg0k2585jOSVDRr1vNK/img.png&quot; data-phocus=&quot;https://blog.kakaocdn.net/dn/ewZ6sA/btrN1y1WwFa/nwAPg0k2585jOSVDRr1vNK/img.png&quot; data-alt=&quot;lombok install successful&quot;&gt;&lt;img src=&quot;https://blog.kakaocdn.net/dn/ewZ6sA/btrN1y1WwFa/nwAPg0k2585jOSVDRr1vNK/img.png&quot; srcset=&quot;https://img1.daumcdn.net/thumb/R1280x0/?scode=mtistory2&amp;fname=https%3A%2F%2Fblog.kakaocdn.net%2Fdn%2FewZ6sA%2FbtrN1y1WwFa%2FnwAPg0k2585jOSVDRr1vNK%2Fimg.png&quot; onerror=&quot;this.onerror=null; this.src='//t1.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;840&quot; height=&quot;490&quot; data-origin-width=&quot;840&quot; data-origin-height=&quot;490&quot;/&gt;&lt;/span&gt;&lt;figcaption&gt;lombok install successful&lt;/figcaption&gt;
&lt;/figure&gt;
&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;설치가 완료되었고 quit installer 로 종료하자.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;다음은 pom.xml 에 lombok 디펜던시를 추가하자.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;내용은 아까 다운로드받은 site에서 install 메뉴를 선택한뒤 각자에 맞는 것을 클릭하면 나온다.&lt;/p&gt;
&lt;p&gt;&lt;figure class=&quot;imageblock alignCenter&quot; data-ke-mobileStyle=&quot;widthOrigin&quot; data-origin-width=&quot;790&quot; data-origin-height=&quot;554&quot;&gt;&lt;span data-url=&quot;https://blog.kakaocdn.net/dn/UxOI4/btrN2wbJWmb/Asw679uYMcdK3EMKZmCOEK/img.png&quot; data-phocus=&quot;https://blog.kakaocdn.net/dn/UxOI4/btrN2wbJWmb/Asw679uYMcdK3EMKZmCOEK/img.png&quot; data-alt=&quot;lombok global navigation menu&quot;&gt;&lt;img src=&quot;https://blog.kakaocdn.net/dn/UxOI4/btrN2wbJWmb/Asw679uYMcdK3EMKZmCOEK/img.png&quot; srcset=&quot;https://img1.daumcdn.net/thumb/R1280x0/?scode=mtistory2&amp;fname=https%3A%2F%2Fblog.kakaocdn.net%2Fdn%2FUxOI4%2FbtrN2wbJWmb%2FAsw679uYMcdK3EMKZmCOEK%2Fimg.png&quot; onerror=&quot;this.onerror=null; this.src='//t1.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;790&quot; height=&quot;554&quot; data-origin-width=&quot;790&quot; data-origin-height=&quot;554&quot;/&gt;&lt;/span&gt;&lt;figcaption&gt;lombok global navigation menu&lt;/figcaption&gt;
&lt;/figure&gt;
&lt;/p&gt;
&lt;pre id=&quot;code_1665115376204&quot; class=&quot;xml&quot; data-ke-language=&quot;highlight.js&quot; data-ke-type=&quot;codeblock&quot;&gt;&lt;code&gt;&amp;lt;dependency&amp;gt;
	&amp;lt;groupId&amp;gt;org.projectlombok&amp;lt;/groupId&amp;gt;
	&amp;lt;artifactId&amp;gt;lombok&amp;lt;/artifactId&amp;gt;
	&amp;lt;version&amp;gt;1.18.24&amp;lt;/version&amp;gt;
	&amp;lt;scope&amp;gt;provided&amp;lt;/scope&amp;gt;
&amp;lt;/dependency&amp;gt;&lt;/code&gt;&lt;/pre&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;이제 alt+F5 로 maven update를 진행하면&lt;/p&gt;
&lt;p&gt;&lt;figure class=&quot;imageblock alignCenter&quot; data-ke-mobileStyle=&quot;widthOrigin&quot; data-origin-width=&quot;253&quot; data-origin-height=&quot;155&quot;&gt;&lt;span data-url=&quot;https://blog.kakaocdn.net/dn/cE6u3J/btrNXVkp5HX/KfckLHdhNLEOxfnHneMeZK/img.png&quot; data-phocus=&quot;https://blog.kakaocdn.net/dn/cE6u3J/btrNXVkp5HX/KfckLHdhNLEOxfnHneMeZK/img.png&quot; data-alt=&quot;lombok import&quot;&gt;&lt;img src=&quot;https://blog.kakaocdn.net/dn/cE6u3J/btrNXVkp5HX/KfckLHdhNLEOxfnHneMeZK/img.png&quot; srcset=&quot;https://img1.daumcdn.net/thumb/R1280x0/?scode=mtistory2&amp;fname=https%3A%2F%2Fblog.kakaocdn.net%2Fdn%2FcE6u3J%2FbtrNXVkp5HX%2FKfckLHdhNLEOxfnHneMeZK%2Fimg.png&quot; onerror=&quot;this.onerror=null; this.src='//t1.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;253&quot; height=&quot;155&quot; data-origin-width=&quot;253&quot; data-origin-height=&quot;155&quot;/&gt;&lt;/span&gt;&lt;figcaption&gt;lombok import&lt;/figcaption&gt;
&lt;/figure&gt;
&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;lombok 라이브러리를 사용할 수 있게 되었다.&lt;/p&gt;</description>
      <category>개발/기타</category>
      <category>Eclipse</category>
      <category>Lombok</category>
      <author>joon95</author>
      <guid isPermaLink="true">https://flowlog.tistory.com/75</guid>
      <comments>https://flowlog.tistory.com/75#entry75comment</comments>
      <pubDate>Fri, 7 Oct 2022 13:07:03 +0900</pubDate>
    </item>
    <item>
      <title>[유전자분석] IGV 라이브러리 사용하기</title>
      <link>https://flowlog.tistory.com/74</link>
      <description>&lt;p data-ke-size=&quot;size16&quot;&gt;지난 번에 유전자 분석을 위한 bam 파일에 대해 공부했었다.(자세한건 아래 링크참고)&lt;/p&gt;
&lt;figure id=&quot;og_1665034307190&quot; contenteditable=&quot;false&quot; data-ke-type=&quot;opengraph&quot; data-ke-align=&quot;alignCenter&quot; data-og-type=&quot;article&quot; data-og-title=&quot;유전자 분석을 위한 bam 파일&quot; data-og-description=&quot;프로젝트에서 유전자 분석을 위한 IGV(Integrative Genomics Viewer) 사용을 해봤다. IGV란 유전체 데이터셋을 시각화 해주는 그래픽 기반 프로그램으로 오픈소스이다. igv 사이트에 들어가면 자바스크립&quot; data-og-host=&quot;flowlog.tistory.com&quot; data-og-source-url=&quot;https://flowlog.tistory.com/57&quot; data-og-url=&quot;https://flowlog.tistory.com/57&quot; data-og-image=&quot;https://scrap.kakaocdn.net/dn/eeTfl7/hyP2f7k1O7/8KCKRNOUX4imgXz4tYPpO0/img.jpg?width=318&amp;amp;height=159&amp;amp;face=0_0_318_159,https://scrap.kakaocdn.net/dn/banPie/hyP2gkQlha/g7C7KwXKk5Ah1byZLV34Ok/img.jpg?width=318&amp;amp;height=159&amp;amp;face=0_0_318_159,https://scrap.kakaocdn.net/dn/f01K7/hyP2lsVXu9/AlkoPjiVkSBGxsvoEPzYtk/img.png?width=1807&amp;amp;height=523&amp;amp;face=0_0_1807_523&quot;&gt;&lt;a href=&quot;https://flowlog.tistory.com/57&quot; target=&quot;_blank&quot; rel=&quot;noopener&quot; data-source-url=&quot;https://flowlog.tistory.com/57&quot;&gt;
&lt;div class=&quot;og-image&quot; style=&quot;background-image: url('https://scrap.kakaocdn.net/dn/eeTfl7/hyP2f7k1O7/8KCKRNOUX4imgXz4tYPpO0/img.jpg?width=318&amp;amp;height=159&amp;amp;face=0_0_318_159,https://scrap.kakaocdn.net/dn/banPie/hyP2gkQlha/g7C7KwXKk5Ah1byZLV34Ok/img.jpg?width=318&amp;amp;height=159&amp;amp;face=0_0_318_159,https://scrap.kakaocdn.net/dn/f01K7/hyP2lsVXu9/AlkoPjiVkSBGxsvoEPzYtk/img.png?width=1807&amp;amp;height=523&amp;amp;face=0_0_1807_523');&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;유전자 분석을 위한 bam 파일&lt;/p&gt;
&lt;p class=&quot;og-desc&quot; data-ke-size=&quot;size16&quot;&gt;프로젝트에서 유전자 분석을 위한 IGV(Integrative Genomics Viewer) 사용을 해봤다. IGV란 유전체 데이터셋을 시각화 해주는 그래픽 기반 프로그램으로 오픈소스이다. igv 사이트에 들어가면 자바스크립&lt;/p&gt;
&lt;p class=&quot;og-host&quot; data-ke-size=&quot;size16&quot;&gt;flowlog.tistory.com&lt;/p&gt;
&lt;/div&gt;
&lt;/a&gt;&lt;/figure&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;여기서 유전자를 분석하기 위해 IGV(Integrative Genomics Viewer) 툴을 잠시 보았는데,&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;자바스크립트로 IGV 라이브러리를 활용해보았다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;간단히 부트스트랩 css를 적용하였고, 멀티분석을 위한 track을 추가하는 기능까지 넣어보았다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;테스트를 위한 bam 파일은 url 방식으로 호출한다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;h2 data-ke-size=&quot;size26&quot;&gt;기능구현&lt;/h2&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;먼저 페이지를 열어보면 아래와 같은 화면이 뜨도록 구현하였다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;GENOME 은 분석 기초데이터, locus는 분석 후 그래프의 이동할 위치정보를 넣어주면된다.&lt;/p&gt;
&lt;p&gt;&lt;figure class=&quot;imageblock alignCenter&quot; data-ke-mobileStyle=&quot;widthOrigin&quot; data-origin-width=&quot;1136&quot; data-origin-height=&quot;242&quot;&gt;&lt;span data-url=&quot;https://blog.kakaocdn.net/dn/bYp3Em/btrNUaBySck/2Pt6jJbTjsEKvtLKRFGHDk/img.png&quot; data-phocus=&quot;https://blog.kakaocdn.net/dn/bYp3Em/btrNUaBySck/2Pt6jJbTjsEKvtLKRFGHDk/img.png&quot; data-alt=&quot;페이지 로드&quot;&gt;&lt;img src=&quot;https://blog.kakaocdn.net/dn/bYp3Em/btrNUaBySck/2Pt6jJbTjsEKvtLKRFGHDk/img.png&quot; srcset=&quot;https://img1.daumcdn.net/thumb/R1280x0/?scode=mtistory2&amp;fname=https%3A%2F%2Fblog.kakaocdn.net%2Fdn%2FbYp3Em%2FbtrNUaBySck%2F2Pt6jJbTjsEKvtLKRFGHDk%2Fimg.png&quot; onerror=&quot;this.onerror=null; this.src='//t1.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;1136&quot; height=&quot;242&quot; data-origin-width=&quot;1136&quot; data-origin-height=&quot;242&quot;/&gt;&lt;/span&gt;&lt;figcaption&gt;페이지 로드&lt;/figcaption&gt;
&lt;/figure&gt;
&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;아래 track 추가하기 버튼을 추가하여 track을 추가할 수 있다.&lt;/p&gt;
&lt;p&gt;&lt;figure class=&quot;imageblock alignCenter&quot; data-ke-mobileStyle=&quot;widthOrigin&quot; data-origin-width=&quot;1889&quot; data-origin-height=&quot;611&quot;&gt;&lt;span data-url=&quot;https://blog.kakaocdn.net/dn/sh5zu/btrNTLPB3qq/6YMncYGgC3daCcdYODEKl1/img.png&quot; data-phocus=&quot;https://blog.kakaocdn.net/dn/sh5zu/btrNTLPB3qq/6YMncYGgC3daCcdYODEKl1/img.png&quot; data-alt=&quot;track 추가하기&quot;&gt;&lt;img src=&quot;https://blog.kakaocdn.net/dn/sh5zu/btrNTLPB3qq/6YMncYGgC3daCcdYODEKl1/img.png&quot; srcset=&quot;https://img1.daumcdn.net/thumb/R1280x0/?scode=mtistory2&amp;fname=https%3A%2F%2Fblog.kakaocdn.net%2Fdn%2Fsh5zu%2FbtrNTLPB3qq%2F6YMncYGgC3daCcdYODEKl1%2Fimg.png&quot; onerror=&quot;this.onerror=null; this.src='//t1.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;1889&quot; height=&quot;611&quot; data-origin-width=&quot;1889&quot; data-origin-height=&quot;611&quot;/&gt;&lt;/span&gt;&lt;figcaption&gt;track 추가하기&lt;/figcaption&gt;
&lt;/figure&gt;
&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;분석하기 버튼을 누르면 tracks 에 입력된 데이터를 class 기준으로 불러와 데이터를 가공하여&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;igv-div 라는 div id 안에 그래프를 그려준다.&lt;/p&gt;
&lt;p&gt;&lt;figure class=&quot;imageblock alignCenter&quot; data-ke-mobileStyle=&quot;widthOrigin&quot; data-origin-width=&quot;1872&quot; data-origin-height=&quot;893&quot;&gt;&lt;span data-url=&quot;https://blog.kakaocdn.net/dn/b2Q8sT/btrNWpreQv0/U2BU4N3Emqxreuv6j6XaFK/img.png&quot; data-phocus=&quot;https://blog.kakaocdn.net/dn/b2Q8sT/btrNWpreQv0/U2BU4N3Emqxreuv6j6XaFK/img.png&quot; data-alt=&quot;유전자 분석 그래프 IGV&quot;&gt;&lt;img src=&quot;https://blog.kakaocdn.net/dn/b2Q8sT/btrNWpreQv0/U2BU4N3Emqxreuv6j6XaFK/img.png&quot; srcset=&quot;https://img1.daumcdn.net/thumb/R1280x0/?scode=mtistory2&amp;fname=https%3A%2F%2Fblog.kakaocdn.net%2Fdn%2Fb2Q8sT%2FbtrNWpreQv0%2FU2BU4N3Emqxreuv6j6XaFK%2Fimg.png&quot; onerror=&quot;this.onerror=null; this.src='//t1.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;1872&quot; height=&quot;893&quot; data-origin-width=&quot;1872&quot; data-origin-height=&quot;893&quot;/&gt;&lt;/span&gt;&lt;figcaption&gt;유전자 분석 그래프 IGV&lt;/figcaption&gt;
&lt;/figure&gt;
&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;h2 data-ke-size=&quot;size26&quot;&gt;소스코드&lt;/h2&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;필자가 작성한 소스코드이다.&lt;/p&gt;
&lt;pre id=&quot;code_1665035211002&quot; class=&quot;html xml&quot; data-ke-language=&quot;html&quot; data-ke-type=&quot;codeblock&quot;&gt;&lt;code&gt;&amp;lt;html&amp;gt;
  &amp;lt;head&amp;gt;
    &amp;lt;meta name=&quot;viewport&quot; content=&quot;width=device-width, initial-scale=1&quot; /&amp;gt;
    &amp;lt;!-- 부트스트랩 --&amp;gt;
    &amp;lt;link
      href=&quot;https://cdn.jsdelivr.net/npm/bootstrap@5.2.2/dist/css/bootstrap.min.css&quot;
      rel=&quot;stylesheet&quot;
      integrity=&quot;sha384-Zenh87qX5JnK2Jl0vWa8Ck2rdkQ2Bzep5IDxbcnCeuOxjzrPF/et3URy9Bv1WTRi&quot;
      crossorigin=&quot;anonymous&quot;
    /&amp;gt;
    &amp;lt;script
      src=&quot;https://cdn.jsdelivr.net/npm/bootstrap@5.2.2/dist/js/bootstrap.bundle.min.js&quot;
      integrity=&quot;sha384-OERcA2EqjJCMA+/3y+gxIOqMEjwtxJY7qPCqsdltbNJuaOe923+mo//f6V8Qbsw3&quot;
      crossorigin=&quot;anonymous&quot;
    &amp;gt;&amp;lt;/script&amp;gt;
    &amp;lt;title&amp;gt;IGV 유전자 분석 테스트 - joon95&amp;lt;/title&amp;gt;
    &amp;lt;!-- igv 라이브러리 --&amp;gt;
    &amp;lt;script src=&quot;https://cdn.jsdelivr.net/npm/igv@2.13.3/dist/igv.min.js&quot;&amp;gt;&amp;lt;/script&amp;gt;
    &amp;lt;script&amp;gt;
      function load_tracks() {
        var tracksNames = document.getElementsByName(&quot;in_name&quot;);
        var tracksUrls = document.getElementsByName(&quot;in_url&quot;);
        var tracksIndexUrls = document.getElementsByName(&quot;in_indexURL&quot;);
        var tracksFormats = document.getElementsByName(&quot;in_format&quot;);
        var res = [];
        var obj = {};
        for (var i = 0; i &amp;lt; tracksNames.length; i++) {
          obj = {
            name: tracksNames[i].value,
            url: tracksUrls[i].value,
            indexURL: tracksIndexUrls[i].value,
            format: tracksFormats[i].value,
          };
          res.push(obj);
        }
        return res;
      }
      function add_track() {
        var content =
          &quot;&amp;lt;div class='col-sm-3 mb-3'&amp;gt;&quot; +
          &quot;&amp;lt;div&amp;gt;&amp;lt;span class='input-group-text'&amp;gt;tracks&amp;lt;/span&amp;gt;&quot; +
          &quot;&amp;lt;ul class='list-group'&amp;gt;&quot; +
          &quot;&amp;lt;li class='list-group-item'&amp;gt;Name :&amp;lt;input type='text'id='in_name'name='in_name'value=''class='form-control'/&amp;gt;&amp;lt;/li&amp;gt;&quot; +
          &quot;&amp;lt;li class='list-group-item'&amp;gt;url :&amp;lt;input class='form-control'type='text'id='in_url'name='in_url'value=''/&amp;gt;&amp;lt;/li&amp;gt;&quot; +
          &quot;&amp;lt;li class='list-group-item'&amp;gt;indexURL :&amp;lt;input class='form-control'type='text'id='in_indexURL'name='in_indexURL'value=''/&amp;gt;&amp;lt;/li&amp;gt;&quot; +
          &quot;&amp;lt;li class='list-group-item'&amp;gt;Format :&amp;lt;input type='text'id='in_format'name='in_format'value=''class='form-control'/&amp;gt;&amp;lt;/li&amp;gt;&quot; +
          &quot;&amp;lt;/ul&amp;gt;&amp;lt;/div&amp;gt;&amp;lt;/div&amp;gt;&quot;;
        var trackAria = document.getElementById(&quot;tracks&quot;);
        trackAria.insertAdjacentHTML(&quot;beforeend&quot;, content);
      }
      function igv_start() {
        var igvDiv = document.getElementById(&quot;igv-div&quot;);
        // input value
        var IGVgenome = document.getElementById(&quot;in_genome&quot;).value;
        var IGVlocus = document.getElementById(&quot;in_locus&quot;).value;

        var tracks_content = load_tracks();
        var options = {
          genome: IGVgenome,
          locus: IGVlocus,
          tracks: tracks_content,
        };
        igv.createBrowser(igvDiv, options).then(function (browser) {
          console.log(&quot;Created IGV browser&quot;);
        });
      }
    &amp;lt;/script&amp;gt;
  &amp;lt;/head&amp;gt;
  &amp;lt;body style=&quot;margin: 25px&quot;&amp;gt;
    &amp;lt;figure class=&quot;text-center&quot;&amp;gt;
      &amp;lt;blockquote class=&quot;blockquote&quot;&amp;gt;
        &amp;lt;p&amp;gt;유전자 분석 IGV 라이브러리 테스트&amp;lt;/p&amp;gt;
      &amp;lt;/blockquote&amp;gt;
      &amp;lt;figcaption class=&quot;blockquote-footer&quot;&amp;gt;
        joon95.tistory.com &amp;lt;cite title=&quot;Source Title&quot;&amp;gt;joon95&amp;lt;/cite&amp;gt;
      &amp;lt;/figcaption&amp;gt;
    &amp;lt;/figure&amp;gt;
    &amp;lt;form&amp;gt;
      &amp;lt;div class=&quot;input-group mb-3&quot;&amp;gt;
        &amp;lt;span class=&quot;input-group-text&quot;&amp;gt;GENOME&amp;lt;/span&amp;gt;
        &amp;lt;input
          type=&quot;text&quot;
          id=&quot;in_genome&quot;
          name=&quot;in_genome&quot;
          class=&quot;form-control&quot;
        /&amp;gt;
      &amp;lt;/div&amp;gt;
      &amp;lt;div class=&quot;input-group mb-3&quot;&amp;gt;
        &amp;lt;span class=&quot;input-group-text&quot;&amp;gt;locus&amp;lt;/span&amp;gt;
        &amp;lt;input
          type=&quot;text&quot;
          id=&quot;in_locus&quot;
          name=&quot;in_locus&quot;
          class=&quot;form-control&quot;
        /&amp;gt;
      &amp;lt;/div&amp;gt;
      &amp;lt;div class=&quot;row&quot; id=&quot;tracks&quot;&amp;gt;&amp;lt;/div&amp;gt;
      &amp;lt;div class=&quot;input-group mb-3&quot;&amp;gt;
        &amp;lt;input
          type=&quot;button&quot;
          class=&quot;btn btn-primary&quot;
          value=&quot;분석하기&quot;
          onclick=&quot;igv_start();&quot;
        /&amp;gt;
        &amp;lt;input
          type=&quot;button&quot;
          class=&quot;btn btn-Secondary&quot;
          value=&quot;track 추가하기&quot;
          onclick=&quot;add_track();&quot;
        /&amp;gt;
      &amp;lt;/div&amp;gt;
    &amp;lt;/form&amp;gt;
    &amp;lt;!-- 그래프 영역 --&amp;gt;
    &amp;lt;div id=&quot;igv-div&quot;&amp;gt;&amp;lt;/div&amp;gt;
  &amp;lt;/body&amp;gt;
&amp;lt;/html&amp;gt;&lt;/code&gt;&lt;/pre&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;함수는 igv_start(), add_track(), load_tracks() 총 3개로 구성하여&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;각각 IGV라이브러리 호출, 트랙 추가, 트랙데이터를 가공하는 기능을 구현하였다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;h2 data-ke-size=&quot;size26&quot;&gt;파일 호스팅 서버&lt;/h2&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;테스트 데이터는 &lt;b&gt;amazon s3 버킷&lt;/b&gt;에 업로드된 파일을 불러온다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;필자는 nginx를 올려 내 pc의 파일을 호스팅해 가져오게 하고 싶다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;이 부분에서 자꾸 에러가 떠서 보았는데, &lt;b&gt;CORS 에러&lt;/b&gt;가 뜨고있었다.!!!&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;proxy에 CORS 설정을 해주자.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;간단히 /igv 경로로 들어올 경우 해당 경로의 파일을 보여주도록 함.&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;pre id=&quot;code_1665042045393&quot; class=&quot;ruby&quot; data-ke-language=&quot;ruby&quot; data-ke-type=&quot;codeblock&quot;&gt;&lt;code&gt;server {
    listen       80;
    server_name localhost;
    location /igv {
        alias C:\openresty-1.21.4.1-win64\html\igv;
        autoindex on;
        add_header 'Access-Control-Allow-Origin' '*';
    }
 }&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;nginx 를 기동하고 파일을 경로에 업로드하면 아래와 같이 확인할 수 있다.&lt;/p&gt;
&lt;p&gt;&lt;figure class=&quot;imageblock alignCenter&quot; data-ke-mobileStyle=&quot;widthOrigin&quot; data-origin-width=&quot;634&quot; data-origin-height=&quot;279&quot;&gt;&lt;span data-url=&quot;https://blog.kakaocdn.net/dn/cvEMIN/btrNX8CrkqA/oxmDtp4ULQrRrecpk2Vmp1/img.png&quot; data-phocus=&quot;https://blog.kakaocdn.net/dn/cvEMIN/btrNX8CrkqA/oxmDtp4ULQrRrecpk2Vmp1/img.png&quot; data-alt=&quot;nginx 파일 호스팅&quot;&gt;&lt;img src=&quot;https://blog.kakaocdn.net/dn/cvEMIN/btrNX8CrkqA/oxmDtp4ULQrRrecpk2Vmp1/img.png&quot; srcset=&quot;https://img1.daumcdn.net/thumb/R1280x0/?scode=mtistory2&amp;fname=https%3A%2F%2Fblog.kakaocdn.net%2Fdn%2FcvEMIN%2FbtrNX8CrkqA%2FoxmDtp4ULQrRrecpk2Vmp1%2Fimg.png&quot; onerror=&quot;this.onerror=null; this.src='//t1.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;634&quot; height=&quot;279&quot; data-origin-width=&quot;634&quot; data-origin-height=&quot;279&quot;/&gt;&lt;/span&gt;&lt;figcaption&gt;nginx 파일 호스팅&lt;/figcaption&gt;
&lt;/figure&gt;
&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;이제 위에서만든 소스코드로 파일을 불러와보자.&lt;/p&gt;
&lt;p&gt;&lt;figure class=&quot;imageblock alignCenter&quot; data-ke-mobileStyle=&quot;widthOrigin&quot; data-origin-width=&quot;1879&quot; data-origin-height=&quot;611&quot;&gt;&lt;span data-url=&quot;https://blog.kakaocdn.net/dn/2eU4v/btrNYVihU0T/w0NHNhAcUC7k6sW7tsvx7K/img.png&quot; data-phocus=&quot;https://blog.kakaocdn.net/dn/2eU4v/btrNYVihU0T/w0NHNhAcUC7k6sW7tsvx7K/img.png&quot; data-alt=&quot;igv 데이터 입력&quot;&gt;&lt;img src=&quot;https://blog.kakaocdn.net/dn/2eU4v/btrNYVihU0T/w0NHNhAcUC7k6sW7tsvx7K/img.png&quot; srcset=&quot;https://img1.daumcdn.net/thumb/R1280x0/?scode=mtistory2&amp;fname=https%3A%2F%2Fblog.kakaocdn.net%2Fdn%2F2eU4v%2FbtrNYVihU0T%2Fw0NHNhAcUC7k6sW7tsvx7K%2Fimg.png&quot; onerror=&quot;this.onerror=null; this.src='//t1.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;1879&quot; height=&quot;611&quot; data-origin-width=&quot;1879&quot; data-origin-height=&quot;611&quot;/&gt;&lt;/span&gt;&lt;figcaption&gt;igv 데이터 입력&lt;/figcaption&gt;
&lt;/figure&gt;
&lt;figure class=&quot;imageblock alignCenter&quot; data-ke-mobileStyle=&quot;widthOrigin&quot; data-origin-width=&quot;1876&quot; data-origin-height=&quot;945&quot;&gt;&lt;span data-url=&quot;https://blog.kakaocdn.net/dn/bsDNvB/btrNXs9rNOb/yUFyHkggrzNV7L2FYf5mJK/img.png&quot; data-phocus=&quot;https://blog.kakaocdn.net/dn/bsDNvB/btrNXs9rNOb/yUFyHkggrzNV7L2FYf5mJK/img.png&quot; data-alt=&quot;igv 데이터 분석&quot;&gt;&lt;img src=&quot;https://blog.kakaocdn.net/dn/bsDNvB/btrNXs9rNOb/yUFyHkggrzNV7L2FYf5mJK/img.png&quot; srcset=&quot;https://img1.daumcdn.net/thumb/R1280x0/?scode=mtistory2&amp;fname=https%3A%2F%2Fblog.kakaocdn.net%2Fdn%2FbsDNvB%2FbtrNXs9rNOb%2FyUFyHkggrzNV7L2FYf5mJK%2Fimg.png&quot; onerror=&quot;this.onerror=null; this.src='//t1.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;1876&quot; height=&quot;945&quot; data-origin-width=&quot;1876&quot; data-origin-height=&quot;945&quot;/&gt;&lt;/span&gt;&lt;figcaption&gt;igv 데이터 분석&lt;/figcaption&gt;
&lt;/figure&gt;
&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;이렇게 파일을 잘 불러올 수 있었다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;추가로 클라우드에서 Cors 설정을 어떻게 하는지 확인해보자&lt;/p&gt;
&lt;h3 data-ke-size=&quot;size23&quot;&gt;&lt;b&gt;Azure&lt;/b&gt; storageaccount&lt;/h3&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;Azure 도 stroage account explorer 프로그램에서 지정가능하고,&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;REST API, AzureCli 등 많은 방식이 존재한다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;필자는 Azure CLI를 통해 진행하였다.&lt;/p&gt;
&lt;pre id=&quot;code_1665047701716&quot; class=&quot;shell&quot; data-ke-language=&quot;shell&quot; data-ke-type=&quot;codeblock&quot;&gt;&lt;code&gt;$ az login
$ accountName=서비스어카운트네임
$ accountKey=서비스어카운트키

# cors 추가
$ az storage cors add '
	--methods GET '
	--origins * '
	--services f '
	--allowed-headers * '
	--account-name accountName '
	--account-key accountKey

# cors 목록
$ az storage cors list `
	--services f `
	--account-name accountName '
	--account-key accountKey

# cors 전체삭제
$ az storage cors clear `
	--services f `
	--account-name accountName '
	--account-key accountKey&lt;/code&gt;&lt;/pre&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;참고로 --services 는 4개이다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;b : Blob&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;f : File&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;q : Queue&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;t : Table&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;AWS&lt;/b&gt; s3&lt;/h3&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;아마존에서는 S3 버킷에 들어간다.&lt;/p&gt;
&lt;p&gt;&lt;figure class=&quot;imageblock alignCenter&quot; data-ke-mobileStyle=&quot;widthOrigin&quot; data-origin-width=&quot;278&quot; data-origin-height=&quot;212&quot;&gt;&lt;span data-url=&quot;https://blog.kakaocdn.net/dn/JgSIY/btrNXYGQ45S/wIfsA3lKmckH75V1GrkoM1/img.png&quot; data-phocus=&quot;https://blog.kakaocdn.net/dn/JgSIY/btrNXYGQ45S/wIfsA3lKmckH75V1GrkoM1/img.png&quot;&gt;&lt;img src=&quot;https://blog.kakaocdn.net/dn/JgSIY/btrNXYGQ45S/wIfsA3lKmckH75V1GrkoM1/img.png&quot; srcset=&quot;https://img1.daumcdn.net/thumb/R1280x0/?scode=mtistory2&amp;fname=https%3A%2F%2Fblog.kakaocdn.net%2Fdn%2FJgSIY%2FbtrNXYGQ45S%2FwIfsA3lKmckH75V1GrkoM1%2Fimg.png&quot; onerror=&quot;this.onerror=null; this.src='//t1.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;278&quot; height=&quot;212&quot; data-origin-width=&quot;278&quot; data-origin-height=&quot;212&quot;/&gt;&lt;/span&gt;&lt;/figure&gt;
&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;상단 탭바 중 '&lt;b&gt;권한&lt;/b&gt;' 에 들어간다.&lt;/p&gt;
&lt;p&gt;&lt;figure class=&quot;imageblock alignCenter&quot; data-ke-mobileStyle=&quot;widthOrigin&quot; data-origin-width=&quot;476&quot; data-origin-height=&quot;183&quot;&gt;&lt;span data-url=&quot;https://blog.kakaocdn.net/dn/c4ckgC/btrNX83DiCo/lej6nLWhLqUjjeK5Fo1QJ1/img.png&quot; data-phocus=&quot;https://blog.kakaocdn.net/dn/c4ckgC/btrNX83DiCo/lej6nLWhLqUjjeK5Fo1QJ1/img.png&quot;&gt;&lt;img src=&quot;https://blog.kakaocdn.net/dn/c4ckgC/btrNX83DiCo/lej6nLWhLqUjjeK5Fo1QJ1/img.png&quot; srcset=&quot;https://img1.daumcdn.net/thumb/R1280x0/?scode=mtistory2&amp;fname=https%3A%2F%2Fblog.kakaocdn.net%2Fdn%2Fc4ckgC%2FbtrNX83DiCo%2Flej6nLWhLqUjjeK5Fo1QJ1%2Fimg.png&quot; onerror=&quot;this.onerror=null; this.src='//t1.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;476&quot; height=&quot;183&quot; data-origin-width=&quot;476&quot; data-origin-height=&quot;183&quot;/&gt;&lt;/span&gt;&lt;/figure&gt;
&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;맨 밑으로 스크롤 한뒤 CORS 항목에서 &lt;b&gt;[편집]&lt;/b&gt; 버튼을 클릭한다.&lt;/p&gt;
&lt;p&gt;&lt;figure class=&quot;imageblock alignCenter&quot; data-ke-mobileStyle=&quot;widthOrigin&quot; data-origin-width=&quot;1522&quot; data-origin-height=&quot;83&quot;&gt;&lt;span data-url=&quot;https://blog.kakaocdn.net/dn/bKQ58f/btrNYo6dIMX/m9fqKCKYXB3x8cy7Q63fnk/img.png&quot; data-phocus=&quot;https://blog.kakaocdn.net/dn/bKQ58f/btrNYo6dIMX/m9fqKCKYXB3x8cy7Q63fnk/img.png&quot;&gt;&lt;img src=&quot;https://blog.kakaocdn.net/dn/bKQ58f/btrNYo6dIMX/m9fqKCKYXB3x8cy7Q63fnk/img.png&quot; srcset=&quot;https://img1.daumcdn.net/thumb/R1280x0/?scode=mtistory2&amp;fname=https%3A%2F%2Fblog.kakaocdn.net%2Fdn%2FbKQ58f%2FbtrNYo6dIMX%2Fm9fqKCKYXB3x8cy7Q63fnk%2Fimg.png&quot; onerror=&quot;this.onerror=null; this.src='//t1.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;1522&quot; height=&quot;83&quot; data-origin-width=&quot;1522&quot; data-origin-height=&quot;83&quot;/&gt;&lt;/span&gt;&lt;/figure&gt;
&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;json 형식으로 설정을 기입한다.(소스 아래 첨부)&lt;/p&gt;
&lt;p&gt;&lt;figure class=&quot;imageblock alignCenter&quot; data-ke-mobileStyle=&quot;widthOrigin&quot; data-origin-width=&quot;838&quot; data-origin-height=&quot;701&quot;&gt;&lt;span data-url=&quot;https://blog.kakaocdn.net/dn/cB2xoZ/btrNXM00yyD/WkBUu8V5YQOUbg0ACnZas0/img.png&quot; data-phocus=&quot;https://blog.kakaocdn.net/dn/cB2xoZ/btrNXM00yyD/WkBUu8V5YQOUbg0ACnZas0/img.png&quot;&gt;&lt;img src=&quot;https://blog.kakaocdn.net/dn/cB2xoZ/btrNXM00yyD/WkBUu8V5YQOUbg0ACnZas0/img.png&quot; srcset=&quot;https://img1.daumcdn.net/thumb/R1280x0/?scode=mtistory2&amp;fname=https%3A%2F%2Fblog.kakaocdn.net%2Fdn%2FcB2xoZ%2FbtrNXM00yyD%2FWkBUu8V5YQOUbg0ACnZas0%2Fimg.png&quot; onerror=&quot;this.onerror=null; this.src='//t1.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;838&quot; height=&quot;701&quot; data-origin-width=&quot;838&quot; data-origin-height=&quot;701&quot;/&gt;&lt;/span&gt;&lt;/figure&gt;
&lt;/p&gt;
&lt;pre id=&quot;code_1665043772378&quot; class=&quot;json&quot; data-ke-language=&quot;highlight.js&quot; data-ke-type=&quot;codeblock&quot;&gt;&lt;code&gt;[
    {
        &quot;AllowedHeaders&quot;: [
            &quot;*&quot;
        ],
        &quot;AllowedMethods&quot;: [
            &quot;HEAD&quot;,
            &quot;GET&quot;,
            &quot;PUT&quot;,
            &quot;POST&quot;,
            &quot;DELETE&quot;
        ],
        &quot;AllowedOrigins&quot;: [
            &quot;*&quot;
        ],
        &quot;ExposeHeaders&quot;: [
            &quot;ETag&quot;,
            &quot;x-amz-meta-custom-header&quot;
        ]
    }
]&lt;/code&gt;&lt;/pre&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;이것으로 해당 버킷의 CORS 설정이 끝났다.&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;NCP&lt;/b&gt; object storage&lt;/h3&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;ncp 의 오브젝트스토리지는 aws의 s3와 동일하다고 한다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;그래서 awscli 를 설치해서 설정하는 것도 가능하다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;오늘은 S3 Browser 라는 프로그램을 이용하여 등록해보자.(다운로드링크)&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;a href=&quot;https://s3browser.com/download.aspx&quot; target=&quot;_blank&quot; rel=&quot;noopener&quot;&gt;https://s3browser.com/download.aspx&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;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;Account type은 &lt;b&gt;S3 Compatible Storage&lt;/b&gt; 로 하고&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;네이버 클라우드의 API key를 입력해야한다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;그리고 Rest Endpoint 는 &lt;b&gt;kr.object.ncloudstorage.com &lt;/b&gt;를 입력.&lt;/p&gt;
&lt;p&gt;&lt;figure class=&quot;imageblock alignCenter&quot; data-ke-mobileStyle=&quot;widthOrigin&quot; data-origin-width=&quot;718&quot; data-origin-height=&quot;778&quot;&gt;&lt;span data-url=&quot;https://blog.kakaocdn.net/dn/ob3MK/btrNWpFop75/absizEsn4mpEm9idqbdE8K/img.png&quot; data-phocus=&quot;https://blog.kakaocdn.net/dn/ob3MK/btrNWpFop75/absizEsn4mpEm9idqbdE8K/img.png&quot;&gt;&lt;img src=&quot;https://blog.kakaocdn.net/dn/ob3MK/btrNWpFop75/absizEsn4mpEm9idqbdE8K/img.png&quot; srcset=&quot;https://img1.daumcdn.net/thumb/R1280x0/?scode=mtistory2&amp;fname=https%3A%2F%2Fblog.kakaocdn.net%2Fdn%2Fob3MK%2FbtrNWpFop75%2FabsizEsn4mpEm9idqbdE8K%2Fimg.png&quot; onerror=&quot;this.onerror=null; this.src='//t1.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;718&quot; height=&quot;778&quot; data-origin-width=&quot;718&quot; data-origin-height=&quot;778&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;이제 cors 설정을 등록해보자&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;상단메뉴 &lt;b&gt;Buckets&amp;gt;CORS Configuration...&lt;/b&gt; 클릭&lt;/p&gt;
&lt;p&gt;&lt;figure class=&quot;imageblock alignCenter&quot; data-ke-mobileStyle=&quot;widthOrigin&quot; data-origin-width=&quot;485&quot; data-origin-height=&quot;689&quot;&gt;&lt;span data-url=&quot;https://blog.kakaocdn.net/dn/skDR2/btrNXLgT3ND/i1NxpiK2k4mUmKykGV2pw0/img.png&quot; data-phocus=&quot;https://blog.kakaocdn.net/dn/skDR2/btrNXLgT3ND/i1NxpiK2k4mUmKykGV2pw0/img.png&quot; data-alt=&quot;S3 Browser 에서 CORS 설정 메뉴&quot;&gt;&lt;img src=&quot;https://blog.kakaocdn.net/dn/skDR2/btrNXLgT3ND/i1NxpiK2k4mUmKykGV2pw0/img.png&quot; srcset=&quot;https://img1.daumcdn.net/thumb/R1280x0/?scode=mtistory2&amp;fname=https%3A%2F%2Fblog.kakaocdn.net%2Fdn%2FskDR2%2FbtrNXLgT3ND%2Fi1NxpiK2k4mUmKykGV2pw0%2Fimg.png&quot; onerror=&quot;this.onerror=null; this.src='//t1.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;485&quot; height=&quot;689&quot; data-origin-width=&quot;485&quot; data-origin-height=&quot;689&quot;/&gt;&lt;/span&gt;&lt;figcaption&gt;S3 Browser 에서 CORS 설정 메뉴&lt;/figcaption&gt;
&lt;/figure&gt;
&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;AWS CORS 적용시 사용했던 json 을 그대로 넣었더니 json은 안된다고 떴다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;그래서 XML 포맷을 작성하여 넣었다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p&gt;&lt;figure class=&quot;imageblock alignCenter&quot; data-ke-mobileStyle=&quot;widthOrigin&quot; data-origin-width=&quot;596&quot; data-origin-height=&quot;611&quot;&gt;&lt;span data-url=&quot;https://blog.kakaocdn.net/dn/cxibWe/btrNXYGXBRc/Ym8G5h4jR4ln85k3MGgqBk/img.png&quot; data-phocus=&quot;https://blog.kakaocdn.net/dn/cxibWe/btrNXYGXBRc/Ym8G5h4jR4ln85k3MGgqBk/img.png&quot; data-alt=&quot;CORS XML 설정&quot;&gt;&lt;img src=&quot;https://blog.kakaocdn.net/dn/cxibWe/btrNXYGXBRc/Ym8G5h4jR4ln85k3MGgqBk/img.png&quot; srcset=&quot;https://img1.daumcdn.net/thumb/R1280x0/?scode=mtistory2&amp;fname=https%3A%2F%2Fblog.kakaocdn.net%2Fdn%2FcxibWe%2FbtrNXYGXBRc%2FYm8G5h4jR4ln85k3MGgqBk%2Fimg.png&quot; onerror=&quot;this.onerror=null; this.src='//t1.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;596&quot; height=&quot;611&quot; data-origin-width=&quot;596&quot; data-origin-height=&quot;611&quot;/&gt;&lt;/span&gt;&lt;figcaption&gt;CORS XML 설정&lt;/figcaption&gt;
&lt;/figure&gt;
&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;이렇게 해서 NCP 쪽 오브젝트스토리지도 cors 설정이 되었다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;h2 data-ke-size=&quot;size26&quot;&gt;마치며.&lt;/h2&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;오랜만에 자바스크립트를 썼는데, 간단히 놀기는 편했다.ㅋㅋ&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;하다가 잘 안되던건 &lt;b&gt;DOM&lt;/b&gt;에 태그를 삽입할 경우 &lt;b&gt;.insertAdjacentHTML&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;/p&gt;
&lt;figure contenteditable=&quot;false&quot; data-ke-type=&quot;emoticon&quot; data-ke-align=&quot;alignCenter&quot; data-emoticon-type=&quot;friends1&quot; data-emoticon-name=&quot;013&quot; data-emoticon-isanimation=&quot;false&quot; data-emoticon-src=&quot;https://t1.daumcdn.net/keditor/emoticon/friends1/large/013.gif&quot;&gt;&lt;img src=&quot;https://t1.daumcdn.net/keditor/emoticon/friends1/large/013.gif&quot; width=&quot;150&quot; /&gt;&lt;/figure&gt;</description>
      <category>개발/기타</category>
      <category>html</category>
      <category>IGV</category>
      <category>JavaScript</category>
      <category>개발</category>
      <category>유전자분석</category>
      <author>joon95</author>
      <guid isPermaLink="true">https://flowlog.tistory.com/74</guid>
      <comments>https://flowlog.tistory.com/74#entry74comment</comments>
      <pubDate>Thu, 6 Oct 2022 15:10:23 +0900</pubDate>
    </item>
    <item>
      <title>[Springboot] Maven 멀티 모듈 프로젝트 만들기(Eclipse)</title>
      <link>https://flowlog.tistory.com/73</link>
      <description>&lt;p data-ke-size=&quot;size16&quot;&gt;이제는 쿠버네티스 서비스가 주로 이루면서, 마이크로서비스아키텍처 서비스개발이 많이 진행되어가고 있다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;spring 에서도 gateway, discovery, config 등 cloud 서비스를 구축하는데에 필요한 데모프로젝트들이 많이 있는데,&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;오늘은 Maven 멀티 모듈 프로젝트를 구성을 포스팅한다.&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;Maven Project 생성&lt;/h2&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;가장 먼저 Eclipse 를 키고 New&amp;gt;Maven Project 를 선택한다.&lt;/p&gt;
&lt;p&gt;&lt;figure class=&quot;imageblock alignCenter&quot; data-ke-mobileStyle=&quot;widthOrigin&quot; data-origin-width=&quot;510&quot; data-origin-height=&quot;492&quot;&gt;&lt;span data-url=&quot;https://blog.kakaocdn.net/dn/k3dRB/btrNNn7bOZb/owaYUuufaHcq5tpAS0QRg1/img.png&quot; data-phocus=&quot;https://blog.kakaocdn.net/dn/k3dRB/btrNNn7bOZb/owaYUuufaHcq5tpAS0QRg1/img.png&quot; data-alt=&quot;Eclipse Project 생성 창&quot;&gt;&lt;img src=&quot;https://blog.kakaocdn.net/dn/k3dRB/btrNNn7bOZb/owaYUuufaHcq5tpAS0QRg1/img.png&quot; srcset=&quot;https://img1.daumcdn.net/thumb/R1280x0/?scode=mtistory2&amp;fname=https%3A%2F%2Fblog.kakaocdn.net%2Fdn%2Fk3dRB%2FbtrNNn7bOZb%2FowaYUuufaHcq5tpAS0QRg1%2Fimg.png&quot; onerror=&quot;this.onerror=null; this.src='//t1.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;510&quot; height=&quot;492&quot; data-origin-width=&quot;510&quot; data-origin-height=&quot;492&quot;/&gt;&lt;/span&gt;&lt;figcaption&gt;Eclipse Project 생성 창&lt;/figcaption&gt;
&lt;/figure&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 style=&quot;width: 100%;&quot; colspan=&quot;2&quot;&gt;상위 프로젝트&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td style=&quot;width: 50%;&quot;&gt;프로젝트 타입&lt;/td&gt;
&lt;td style=&quot;width: 50%;&quot;&gt;Maven Project&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td style=&quot;width: 50%;&quot;&gt;프로젝트 이름&lt;/td&gt;
&lt;td style=&quot;width: 50%;&quot;&gt;test&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td style=&quot;width: 50%;&quot;&gt;패키지 이름&lt;/td&gt;
&lt;td style=&quot;width: 50%;&quot;&gt;com.joon95.test&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;h2 data-ke-size=&quot;size26&quot;&gt;Maven Module 생성&lt;/h2&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;이제 1에서 생성한 project를 우클릭&amp;gt;New&amp;gt;Other &lt;b&gt;Maven Module&lt;/b&gt;을 생성한다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;테스트로 core, market 프로젝트를 만들었다.&lt;/p&gt;
&lt;p&gt;&lt;figure class=&quot;imageblock alignCenter&quot; data-ke-mobileStyle=&quot;widthOrigin&quot; data-origin-width=&quot;510&quot; data-origin-height=&quot;491&quot;&gt;&lt;span data-url=&quot;https://blog.kakaocdn.net/dn/dDHj8S/btrNKfPG8rc/QEEai9dNuGWhMtr2hNXdd1/img.png&quot; data-phocus=&quot;https://blog.kakaocdn.net/dn/dDHj8S/btrNKfPG8rc/QEEai9dNuGWhMtr2hNXdd1/img.png&quot;&gt;&lt;img src=&quot;https://blog.kakaocdn.net/dn/dDHj8S/btrNKfPG8rc/QEEai9dNuGWhMtr2hNXdd1/img.png&quot; srcset=&quot;https://img1.daumcdn.net/thumb/R1280x0/?scode=mtistory2&amp;fname=https%3A%2F%2Fblog.kakaocdn.net%2Fdn%2FdDHj8S%2FbtrNKfPG8rc%2FQEEai9dNuGWhMtr2hNXdd1%2Fimg.png&quot; onerror=&quot;this.onerror=null; this.src='//t1.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;510&quot; height=&quot;491&quot; data-origin-width=&quot;510&quot; data-origin-height=&quot;491&quot;/&gt;&lt;/span&gt;&lt;/figure&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 style=&quot;width: 175%;&quot; colspan=&quot;4&quot;&gt;하위 프로젝트&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td style=&quot;width: 25%;&quot;&gt;프로젝트 타입&lt;/td&gt;
&lt;td style=&quot;width: 25%;&quot;&gt;Maven Module&lt;/td&gt;
&lt;td style=&quot;width: 25%;&quot;&gt;프로젝트 타입&lt;/td&gt;
&lt;td style=&quot;width: 100%;&quot;&gt;Maven Module&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td style=&quot;width: 25%;&quot;&gt;프로젝트 이름&lt;/td&gt;
&lt;td style=&quot;width: 25%;&quot;&gt;core&lt;/td&gt;
&lt;td style=&quot;width: 25%;&quot;&gt;프로젝트 이름&lt;/td&gt;
&lt;td style=&quot;width: 100%;&quot;&gt;market&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td style=&quot;width: 25%;&quot;&gt;패키지 이름&lt;/td&gt;
&lt;td style=&quot;width: 25%;&quot;&gt;com.joon95.test.core&lt;/td&gt;
&lt;td style=&quot;width: 25%;&quot;&gt;패키지 이름&lt;/td&gt;
&lt;td style=&quot;width: 100%;&quot;&gt;com.joon95.test.market&lt;/td&gt;
&lt;/tr&gt;
&lt;/tbody&gt;
&lt;/table&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;생성된 Maven 폴더 구조를 보자.&lt;/p&gt;
&lt;p&gt;&lt;figure class=&quot;imageblock alignCenter&quot; data-ke-mobileStyle=&quot;widthOrigin&quot; data-origin-width=&quot;134&quot; data-origin-height=&quot;160&quot;&gt;&lt;span data-url=&quot;https://blog.kakaocdn.net/dn/btWa61/btrNNzzFxtn/V73pS5PvQZzj76iJ57BFNK/img.png&quot; data-phocus=&quot;https://blog.kakaocdn.net/dn/btWa61/btrNNzzFxtn/V73pS5PvQZzj76iJ57BFNK/img.png&quot; data-alt=&quot;Project Explorer View&quot;&gt;&lt;img src=&quot;https://blog.kakaocdn.net/dn/btWa61/btrNNzzFxtn/V73pS5PvQZzj76iJ57BFNK/img.png&quot; srcset=&quot;https://img1.daumcdn.net/thumb/R1280x0/?scode=mtistory2&amp;fname=https%3A%2F%2Fblog.kakaocdn.net%2Fdn%2FbtWa61%2FbtrNNzzFxtn%2FV73pS5PvQZzj76iJ57BFNK%2Fimg.png&quot; onerror=&quot;this.onerror=null; this.src='//t1.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;134&quot; height=&quot;160&quot; data-origin-width=&quot;134&quot; data-origin-height=&quot;160&quot;/&gt;&lt;/span&gt;&lt;figcaption&gt;Project Explorer View&lt;/figcaption&gt;
&lt;/figure&gt;
&lt;figure class=&quot;imageblock alignCenter&quot; data-ke-mobileStyle=&quot;widthOrigin&quot; data-origin-width=&quot;142&quot; data-origin-height=&quot;194&quot;&gt;&lt;span data-url=&quot;https://blog.kakaocdn.net/dn/lNuVq/btrNMGMQmJB/XrMa5HqHKEWTcZ5brotH0k/img.png&quot; data-phocus=&quot;https://blog.kakaocdn.net/dn/lNuVq/btrNMGMQmJB/XrMa5HqHKEWTcZ5brotH0k/img.png&quot; data-alt=&quot;Package Explorer View&quot;&gt;&lt;img src=&quot;https://blog.kakaocdn.net/dn/lNuVq/btrNMGMQmJB/XrMa5HqHKEWTcZ5brotH0k/img.png&quot; srcset=&quot;https://img1.daumcdn.net/thumb/R1280x0/?scode=mtistory2&amp;fname=https%3A%2F%2Fblog.kakaocdn.net%2Fdn%2FlNuVq%2FbtrNMGMQmJB%2FXrMa5HqHKEWTcZ5brotH0k%2Fimg.png&quot; onerror=&quot;this.onerror=null; this.src='//t1.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;142&quot; height=&quot;194&quot; data-origin-width=&quot;142&quot; data-origin-height=&quot;194&quot;/&gt;&lt;/span&gt;&lt;figcaption&gt;Package Explorer View&lt;/figcaption&gt;
&lt;/figure&gt;
&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;그동안 멀티모듈 프로젝트에 익숙치 않아서 Package Explorer View만 보았는데,&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;Project Explorer View로 보니 확실히 모듈단위가 나뉘어져서 보기 좋았다.&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;Spring boot 세팅&lt;/h2&gt;
&lt;h3 data-ke-size=&quot;size23&quot;&gt;Maven Project (test)&lt;/h3&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;이제 spring starter 모듈을 어디에서 가져올까 했는데,&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;Maven Module 프로젝트는 pom.xml에 parent 태그가 자동으로 지정되어 있어서&lt;/p&gt;
&lt;p&gt;&lt;figure class=&quot;imageblock alignCenter&quot; data-ke-mobileStyle=&quot;widthOrigin&quot; data-origin-width=&quot;306&quot; data-origin-height=&quot;130&quot;&gt;&lt;span data-url=&quot;https://blog.kakaocdn.net/dn/b0Srq2/btrNGxcmNiG/vz5YTBxp0fPT8ExCem4M51/img.png&quot; data-phocus=&quot;https://blog.kakaocdn.net/dn/b0Srq2/btrNGxcmNiG/vz5YTBxp0fPT8ExCem4M51/img.png&quot; data-alt=&quot;pom.xml parent tag&quot;&gt;&lt;img src=&quot;https://blog.kakaocdn.net/dn/b0Srq2/btrNGxcmNiG/vz5YTBxp0fPT8ExCem4M51/img.png&quot; srcset=&quot;https://img1.daumcdn.net/thumb/R1280x0/?scode=mtistory2&amp;fname=https%3A%2F%2Fblog.kakaocdn.net%2Fdn%2Fb0Srq2%2FbtrNGxcmNiG%2Fvz5YTBxp0fPT8ExCem4M51%2Fimg.png&quot; onerror=&quot;this.onerror=null; this.src='//t1.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;306&quot; height=&quot;130&quot; data-origin-width=&quot;306&quot; data-origin-height=&quot;130&quot;/&gt;&lt;/span&gt;&lt;figcaption&gt;pom.xml parent tag&lt;/figcaption&gt;
&lt;/figure&gt;
&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;상위 프로젝트인 'test'에 현날짜기준 가장 최신의 &lt;b&gt;spring-boot-starter-parent&amp;nbsp;&lt;/b&gt;라이브러리를 가져오게했다.&lt;/p&gt;
&lt;p&gt;&lt;figure class=&quot;imageblock alignCenter&quot; data-ke-mobileStyle=&quot;widthOrigin&quot; data-origin-width=&quot;1508&quot; data-origin-height=&quot;557&quot;&gt;&lt;span data-url=&quot;https://blog.kakaocdn.net/dn/bWJabl/btrNDVLqez3/80SihzrNTFC21DXnu4JO51/img.png&quot; data-phocus=&quot;https://blog.kakaocdn.net/dn/bWJabl/btrNDVLqez3/80SihzrNTFC21DXnu4JO51/img.png&quot; data-alt=&quot;Maven Repository&quot;&gt;&lt;img src=&quot;https://blog.kakaocdn.net/dn/bWJabl/btrNDVLqez3/80SihzrNTFC21DXnu4JO51/img.png&quot; srcset=&quot;https://img1.daumcdn.net/thumb/R1280x0/?scode=mtistory2&amp;fname=https%3A%2F%2Fblog.kakaocdn.net%2Fdn%2FbWJabl%2FbtrNDVLqez3%2F80SihzrNTFC21DXnu4JO51%2Fimg.png&quot; onerror=&quot;this.onerror=null; this.src='//t1.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;1508&quot; height=&quot;557&quot; data-origin-width=&quot;1508&quot; data-origin-height=&quot;557&quot;/&gt;&lt;/span&gt;&lt;figcaption&gt;Maven Repository&lt;/figcaption&gt;
&lt;/figure&gt;
&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;test 프로젝트의 pom.xml&lt;/p&gt;
&lt;pre id=&quot;code_1664860451705&quot; class=&quot;xml&quot; data-ke-language=&quot;highlight.js&quot; data-ke-type=&quot;codeblock&quot;&gt;&lt;code&gt;&amp;lt;project xmlns=&quot;http://maven.apache.org/POM/4.0.0&quot; xmlns:xsi=&quot;http://www.w3.org/2001/XMLSchema-instance&quot; xsi:schemaLocation=&quot;http://maven.apache.org/POM/4.0.0 https://maven.apache.org/xsd/maven-4.0.0.xsd&quot;&amp;gt;
  &amp;lt;modelVersion&amp;gt;4.0.0&amp;lt;/modelVersion&amp;gt;
  &amp;lt;groupId&amp;gt;com.joon95&amp;lt;/groupId&amp;gt;
  &amp;lt;artifactId&amp;gt;test&amp;lt;/artifactId&amp;gt;
  &amp;lt;version&amp;gt;0.0.1-SNAPSHOT&amp;lt;/version&amp;gt;
  &amp;lt;name&amp;gt;multi-proj&amp;lt;/name&amp;gt;
  &amp;lt;packaging&amp;gt;pom&amp;lt;/packaging&amp;gt;
  &amp;lt;modules&amp;gt;
    &amp;lt;module&amp;gt;core&amp;lt;/module&amp;gt;
    &amp;lt;module&amp;gt;market&amp;lt;/module&amp;gt;
  &amp;lt;/modules&amp;gt;
  &amp;lt;parent&amp;gt;
    &amp;lt;groupId&amp;gt;org.springframework.boot&amp;lt;/groupId&amp;gt;
    &amp;lt;artifactId&amp;gt;spring-boot-starter-parent&amp;lt;/artifactId&amp;gt;
    &amp;lt;version&amp;gt;2.7.4&amp;lt;/version&amp;gt;
  &amp;lt;/parent&amp;gt;
&amp;lt;/project&amp;gt;&lt;/code&gt;&lt;/pre&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;h3 data-ke-size=&quot;size23&quot;&gt;Maven Module (core)&lt;/h3&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;먼저 공통 모듈로 사용할 core 프로젝트먼저 세팅해보자.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;pom.xml 에 8080 port 를 listen 할 &lt;b&gt;spring-boot-starter-web&amp;nbsp;&lt;/b&gt;을 넣어주고,&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;빌드 플러그인을 maven 으로 세팅해줬다.&lt;/p&gt;
&lt;pre id=&quot;code_1664860785194&quot; class=&quot;xml&quot; data-ke-language=&quot;highlight.js&quot; data-ke-type=&quot;codeblock&quot;&gt;&lt;code&gt;&amp;lt;project xmlns=&quot;http://maven.apache.org/POM/4.0.0&quot; xmlns:xsi=&quot;http://www.w3.org/2001/XMLSchema-instance&quot; xsi:schemaLocation=&quot;http://maven.apache.org/POM/4.0.0 https://maven.apache.org/xsd/maven-4.0.0.xsd&quot;&amp;gt;
  &amp;lt;modelVersion&amp;gt;4.0.0&amp;lt;/modelVersion&amp;gt;
  &amp;lt;parent&amp;gt;
    &amp;lt;groupId&amp;gt;com.joon95&amp;lt;/groupId&amp;gt;
    &amp;lt;artifactId&amp;gt;test&amp;lt;/artifactId&amp;gt;
    &amp;lt;version&amp;gt;0.0.1-SNAPSHOT&amp;lt;/version&amp;gt;
  &amp;lt;/parent&amp;gt;
  &amp;lt;artifactId&amp;gt;core&amp;lt;/artifactId&amp;gt;
  &amp;lt;name&amp;gt;core-proj&amp;lt;/name&amp;gt;
  &amp;lt;properties&amp;gt;
    &amp;lt;java.version&amp;gt;11&amp;lt;/java.version&amp;gt;
  &amp;lt;/properties&amp;gt;

  &amp;lt;dependencies&amp;gt;
    &amp;lt;dependency&amp;gt;
      &amp;lt;groupId&amp;gt;org.springframework.boot&amp;lt;/groupId&amp;gt;
      &amp;lt;artifactId&amp;gt;spring-boot-starter-web&amp;lt;/artifactId&amp;gt;
    &amp;lt;/dependency&amp;gt;
  &amp;lt;/dependencies&amp;gt;
  &amp;lt;build&amp;gt;
    &amp;lt;plugins&amp;gt;
      &amp;lt;plugin&amp;gt;
        &amp;lt;groupId&amp;gt;org.springframework.boot&amp;lt;/groupId&amp;gt;
        &amp;lt;artifactId&amp;gt;spring-boot-maven-plugin&amp;lt;/artifactId&amp;gt;
        &amp;lt;configuration&amp;gt;
          &amp;lt;excludes&amp;gt;
            &amp;lt;exclude&amp;gt;
              &amp;lt;groupId&amp;gt;org.projectlombok&amp;lt;/groupId&amp;gt;
              &amp;lt;artifactId&amp;gt;lombok&amp;lt;/artifactId&amp;gt;
            &amp;lt;/exclude&amp;gt;
          &amp;lt;/excludes&amp;gt;
        &amp;lt;/configuration&amp;gt;
      &amp;lt;/plugin&amp;gt;
    &amp;lt;/plugins&amp;gt;
  &amp;lt;/build&amp;gt;
&amp;lt;/project&amp;gt;&lt;/code&gt;&lt;/pre&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;(Alt+F5 로 Maven Build)&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;그리고, SpringBoot를 실행시킬 Main Java를 생성해야한다.&lt;/p&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;src/main/java 마우스 우클릭&amp;gt;New&amp;gt;Package&lt;/p&gt;
&lt;p&gt;&lt;figure class=&quot;imageblock alignCenter&quot; data-ke-mobileStyle=&quot;widthOrigin&quot; data-origin-width=&quot;717&quot; data-origin-height=&quot;135&quot;&gt;&lt;span data-url=&quot;https://blog.kakaocdn.net/dn/bJ9q7U/btrNKWo8LCD/DeCqTB3RjQRvQKJmAOoQ7K/img.png&quot; data-phocus=&quot;https://blog.kakaocdn.net/dn/bJ9q7U/btrNKWo8LCD/DeCqTB3RjQRvQKJmAOoQ7K/img.png&quot; data-alt=&quot;Package 생성&quot;&gt;&lt;img src=&quot;https://blog.kakaocdn.net/dn/bJ9q7U/btrNKWo8LCD/DeCqTB3RjQRvQKJmAOoQ7K/img.png&quot; srcset=&quot;https://img1.daumcdn.net/thumb/R1280x0/?scode=mtistory2&amp;fname=https%3A%2F%2Fblog.kakaocdn.net%2Fdn%2FbJ9q7U%2FbtrNKWo8LCD%2FDeCqTB3RjQRvQKJmAOoQ7K%2Fimg.png&quot; onerror=&quot;this.onerror=null; this.src='//t1.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;717&quot; height=&quot;135&quot; data-origin-width=&quot;717&quot; data-origin-height=&quot;135&quot;/&gt;&lt;/span&gt;&lt;figcaption&gt;Package 생성&lt;/figcaption&gt;
&lt;/figure&gt;
&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;패키지이름은 &lt;b&gt;com.joon95.test.core&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;이제 Class를 만들어주자.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;생성한 패키지 마우스 우클릭&amp;gt;New&amp;gt;Class&lt;/p&gt;
&lt;p&gt;&lt;figure class=&quot;imageblock alignCenter&quot; data-ke-mobileStyle=&quot;widthOrigin&quot; data-origin-width=&quot;729&quot; data-origin-height=&quot;150&quot;&gt;&lt;span data-url=&quot;https://blog.kakaocdn.net/dn/yrCpx/btrNLCjGbfP/x7nHuX9xnzBk3lUqV6cxM0/img.png&quot; data-phocus=&quot;https://blog.kakaocdn.net/dn/yrCpx/btrNLCjGbfP/x7nHuX9xnzBk3lUqV6cxM0/img.png&quot; data-alt=&quot;Class 생성&quot;&gt;&lt;img src=&quot;https://blog.kakaocdn.net/dn/yrCpx/btrNLCjGbfP/x7nHuX9xnzBk3lUqV6cxM0/img.png&quot; srcset=&quot;https://img1.daumcdn.net/thumb/R1280x0/?scode=mtistory2&amp;fname=https%3A%2F%2Fblog.kakaocdn.net%2Fdn%2FyrCpx%2FbtrNLCjGbfP%2Fx7nHuX9xnzBk3lUqV6cxM0%2Fimg.png&quot; onerror=&quot;this.onerror=null; this.src='//t1.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;729&quot; height=&quot;150&quot; data-origin-width=&quot;729&quot; data-origin-height=&quot;150&quot;/&gt;&lt;/span&gt;&lt;figcaption&gt;Class 생성&lt;/figcaption&gt;
&lt;/figure&gt;
&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;통상 자동으로 생성되는 이름인 &lt;b&gt;Application&amp;nbsp;&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;내용은 그냥 SpringBootApplication 어노테이션을 붙여주어 스프링이 기동되게 한다.&lt;/p&gt;
&lt;pre id=&quot;code_1664861212962&quot; class=&quot;java&quot; data-ke-language=&quot;java&quot; data-ke-type=&quot;codeblock&quot;&gt;&lt;code&gt;package com.joon95.test.core;

import org.springframework.boot.SpringApplication;
import org.springframework.boot.autoconfigure.SpringBootApplication;

@SpringBootApplication
public class Application {
    public static void main(String[] args) {
        SpringApplication.run(Application.class, args);
    }
}&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;여기까지 잘 따라 왔다면 springboot를 실행시켜보자.&lt;/p&gt;
&lt;p&gt;&lt;figure class=&quot;imageblock alignCenter&quot; data-ke-mobileStyle=&quot;widthOrigin&quot; data-origin-width=&quot;642&quot; data-origin-height=&quot;226&quot;&gt;&lt;span data-url=&quot;https://blog.kakaocdn.net/dn/dzaPCe/btrNM5y53jL/KFhLLbb4thnM20KMQicS41/img.png&quot; data-phocus=&quot;https://blog.kakaocdn.net/dn/dzaPCe/btrNM5y53jL/KFhLLbb4thnM20KMQicS41/img.png&quot; data-alt=&quot;springboot run&quot;&gt;&lt;img src=&quot;https://blog.kakaocdn.net/dn/dzaPCe/btrNM5y53jL/KFhLLbb4thnM20KMQicS41/img.png&quot; srcset=&quot;https://img1.daumcdn.net/thumb/R1280x0/?scode=mtistory2&amp;fname=https%3A%2F%2Fblog.kakaocdn.net%2Fdn%2FdzaPCe%2FbtrNM5y53jL%2FKFhLLbb4thnM20KMQicS41%2Fimg.png&quot; onerror=&quot;this.onerror=null; this.src='//t1.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;642&quot; height=&quot;226&quot; data-origin-width=&quot;642&quot; data-origin-height=&quot;226&quot;/&gt;&lt;/span&gt;&lt;figcaption&gt;springboot run&lt;/figcaption&gt;
&lt;/figure&gt;
&lt;figure class=&quot;imageblock alignCenter&quot; data-ke-mobileStyle=&quot;widthOrigin&quot; data-origin-width=&quot;1238&quot; data-origin-height=&quot;370&quot;&gt;&lt;span data-url=&quot;https://blog.kakaocdn.net/dn/zeS9Y/btrNCfXGd3p/MKDqcnUhFBKhOtc727pRlK/img.png&quot; data-phocus=&quot;https://blog.kakaocdn.net/dn/zeS9Y/btrNCfXGd3p/MKDqcnUhFBKhOtc727pRlK/img.png&quot; data-alt=&quot;springboot 로그&quot;&gt;&lt;img src=&quot;https://blog.kakaocdn.net/dn/zeS9Y/btrNCfXGd3p/MKDqcnUhFBKhOtc727pRlK/img.png&quot; srcset=&quot;https://img1.daumcdn.net/thumb/R1280x0/?scode=mtistory2&amp;fname=https%3A%2F%2Fblog.kakaocdn.net%2Fdn%2FzeS9Y%2FbtrNCfXGd3p%2FMKDqcnUhFBKhOtc727pRlK%2Fimg.png&quot; onerror=&quot;this.onerror=null; this.src='//t1.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;370&quot; data-origin-width=&quot;1238&quot; data-origin-height=&quot;370&quot;/&gt;&lt;/span&gt;&lt;figcaption&gt;springboot 로그&lt;/figcaption&gt;
&lt;/figure&gt;
&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;정상적으로 기동되었다면 8080 포트가 열리고, 접근할 수 있다.&lt;/p&gt;
&lt;p&gt;&lt;figure class=&quot;imageblock alignCenter&quot; data-ke-mobileStyle=&quot;widthOrigin&quot; data-origin-width=&quot;668&quot; data-origin-height=&quot;262&quot;&gt;&lt;span data-url=&quot;https://blog.kakaocdn.net/dn/caa1Ii/btrNKWQa2B2/BvbyUr35ikvUBHhr9Fvjc0/img.png&quot; data-phocus=&quot;https://blog.kakaocdn.net/dn/caa1Ii/btrNKWQa2B2/BvbyUr35ikvUBHhr9Fvjc0/img.png&quot;&gt;&lt;img src=&quot;https://blog.kakaocdn.net/dn/caa1Ii/btrNKWQa2B2/BvbyUr35ikvUBHhr9Fvjc0/img.png&quot; srcset=&quot;https://img1.daumcdn.net/thumb/R1280x0/?scode=mtistory2&amp;fname=https%3A%2F%2Fblog.kakaocdn.net%2Fdn%2Fcaa1Ii%2FbtrNKWQa2B2%2FBvbyUr35ikvUBHhr9Fvjc0%2Fimg.png&quot; onerror=&quot;this.onerror=null; this.src='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png'; this.srcset='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png';&quot; loading=&quot;lazy&quot; width=&quot;668&quot; height=&quot;262&quot; data-origin-width=&quot;668&quot; data-origin-height=&quot;262&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;src/main/resources/&lt;b&gt;application.properties&lt;/b&gt;&lt;/p&gt;
&lt;pre id=&quot;code_1664861612266&quot; class=&quot;scala&quot; data-ke-language=&quot;scala&quot; data-ke-type=&quot;codeblock&quot;&gt;&lt;code&gt;logging.level.root = DEBUG
server.port = 8081&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;p data-ke-size=&quot;size16&quot;&gt;패키지명 : &lt;b&gt;com.joon95.test.core.monster&lt;/b&gt;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;클래스명 : &lt;b&gt;MonsterController&lt;/b&gt;&lt;/p&gt;
&lt;p&gt;&lt;figure class=&quot;imageblock alignCenter&quot; data-ke-mobileStyle=&quot;widthOrigin&quot; data-origin-width=&quot;221&quot; data-origin-height=&quot;91&quot;&gt;&lt;span data-url=&quot;https://blog.kakaocdn.net/dn/c2YvTS/btrNMagmQOz/niN3kHPiDJCvWPAku3l0T0/img.png&quot; data-phocus=&quot;https://blog.kakaocdn.net/dn/c2YvTS/btrNMagmQOz/niN3kHPiDJCvWPAku3l0T0/img.png&quot;&gt;&lt;img src=&quot;https://blog.kakaocdn.net/dn/c2YvTS/btrNMagmQOz/niN3kHPiDJCvWPAku3l0T0/img.png&quot; srcset=&quot;https://img1.daumcdn.net/thumb/R1280x0/?scode=mtistory2&amp;fname=https%3A%2F%2Fblog.kakaocdn.net%2Fdn%2Fc2YvTS%2FbtrNMagmQOz%2FniN3kHPiDJCvWPAku3l0T0%2Fimg.png&quot; onerror=&quot;this.onerror=null; this.src='//t1.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;221&quot; height=&quot;91&quot; data-origin-width=&quot;221&quot; data-origin-height=&quot;91&quot;/&gt;&lt;/span&gt;&lt;/figure&gt;
&lt;/p&gt;
&lt;pre id=&quot;code_1664861406786&quot; class=&quot;java&quot; data-ke-language=&quot;java&quot; data-ke-type=&quot;codeblock&quot;&gt;&lt;code&gt;package com.joon95.test.core.monster;

import org.springframework.web.bind.annotation.GetMapping;
import org.springframework.web.bind.annotation.RequestParam;
import org.springframework.web.bind.annotation.RestController;

@RestController
public class MonsterController {
	
	@GetMapping(&quot;/test&quot;)
	public String newLog(@RequestParam String param) {
		System.out.println(&quot;===========core프로젝트의 method 호출========&quot;);
		System.out.println(&quot;newLog : &quot; + param);
		System.out.println(&quot;=========================================&quot;);
		return &quot;파라메타는 &quot; + param;
	}
}&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-origin-width=&quot;405&quot; data-origin-height=&quot;126&quot;&gt;&lt;span data-url=&quot;https://blog.kakaocdn.net/dn/JxnGs/btrNM50blxG/3N4mhelTDMWQ975UJxWlJ0/img.png&quot; data-phocus=&quot;https://blog.kakaocdn.net/dn/JxnGs/btrNM50blxG/3N4mhelTDMWQ975UJxWlJ0/img.png&quot;&gt;&lt;img src=&quot;https://blog.kakaocdn.net/dn/JxnGs/btrNM50blxG/3N4mhelTDMWQ975UJxWlJ0/img.png&quot; srcset=&quot;https://img1.daumcdn.net/thumb/R1280x0/?scode=mtistory2&amp;fname=https%3A%2F%2Fblog.kakaocdn.net%2Fdn%2FJxnGs%2FbtrNM50blxG%2F3N4mhelTDMWQ975UJxWlJ0%2Fimg.png&quot; onerror=&quot;this.onerror=null; this.src='//t1.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;405&quot; height=&quot;126&quot; data-origin-width=&quot;405&quot; data-origin-height=&quot;126&quot;/&gt;&lt;/span&gt;&lt;/figure&gt;
&lt;figure class=&quot;imageblock alignCenter&quot; data-ke-mobileStyle=&quot;widthOrigin&quot; data-origin-width=&quot;341&quot; data-origin-height=&quot;95&quot;&gt;&lt;span data-url=&quot;https://blog.kakaocdn.net/dn/kxzsU/btrNMGM8Y60/gTpKekRXcGzSeLkqr8rEH0/img.png&quot; data-phocus=&quot;https://blog.kakaocdn.net/dn/kxzsU/btrNMGM8Y60/gTpKekRXcGzSeLkqr8rEH0/img.png&quot;&gt;&lt;img src=&quot;https://blog.kakaocdn.net/dn/kxzsU/btrNMGM8Y60/gTpKekRXcGzSeLkqr8rEH0/img.png&quot; srcset=&quot;https://img1.daumcdn.net/thumb/R1280x0/?scode=mtistory2&amp;fname=https%3A%2F%2Fblog.kakaocdn.net%2Fdn%2FkxzsU%2FbtrNMGM8Y60%2FgTpKekRXcGzSeLkqr8rEH0%2Fimg.png&quot; onerror=&quot;this.onerror=null; this.src='//t1.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;341&quot; height=&quot;95&quot; data-origin-width=&quot;341&quot; data-origin-height=&quot;95&quot;/&gt;&lt;/span&gt;&lt;/figure&gt;
&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;생성한 MonsterController를 타 프로젝트(market)에서 사용할 것이다.&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;Maven Module (market)&lt;/h3&gt;
&lt;h4 data-ke-size=&quot;size20&quot;&gt;core 모듈 .jar 추출&lt;/h4&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;제일 먼저 core 모듈 사용을 위해 빌드를 돌려 .jar 파일로 만들어 주자.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;core 프로젝트 우클릭&amp;gt;Run As&amp;gt;Maven build&lt;/p&gt;
&lt;p&gt;&lt;figure class=&quot;imageblock alignCenter&quot; data-ke-mobileStyle=&quot;widthOrigin&quot; data-origin-width=&quot;645&quot; data-origin-height=&quot;253&quot;&gt;&lt;span data-url=&quot;https://blog.kakaocdn.net/dn/c03maK/btrNM3VBxqk/7sZsgPg72MJWqnmvwY2lC1/img.png&quot; data-phocus=&quot;https://blog.kakaocdn.net/dn/c03maK/btrNM3VBxqk/7sZsgPg72MJWqnmvwY2lC1/img.png&quot;&gt;&lt;img src=&quot;https://blog.kakaocdn.net/dn/c03maK/btrNM3VBxqk/7sZsgPg72MJWqnmvwY2lC1/img.png&quot; srcset=&quot;https://img1.daumcdn.net/thumb/R1280x0/?scode=mtistory2&amp;fname=https%3A%2F%2Fblog.kakaocdn.net%2Fdn%2Fc03maK%2FbtrNM3VBxqk%2F7sZsgPg72MJWqnmvwY2lC1%2Fimg.png&quot; onerror=&quot;this.onerror=null; this.src='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png'; this.srcset='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png';&quot; loading=&quot;lazy&quot; width=&quot;645&quot; height=&quot;253&quot; data-origin-width=&quot;645&quot; data-origin-height=&quot;253&quot;/&gt;&lt;/span&gt;&lt;/figure&gt;
&lt;figure class=&quot;imageblock alignCenter&quot; data-ke-mobileStyle=&quot;widthOrigin&quot; data-origin-width=&quot;724&quot; data-origin-height=&quot;692&quot;&gt;&lt;span data-url=&quot;https://blog.kakaocdn.net/dn/Nf3r4/btrNGv6NUni/vhj2h9VKzlWEivSMvQKZX0/img.png&quot; data-phocus=&quot;https://blog.kakaocdn.net/dn/Nf3r4/btrNGv6NUni/vhj2h9VKzlWEivSMvQKZX0/img.png&quot;&gt;&lt;img src=&quot;https://blog.kakaocdn.net/dn/Nf3r4/btrNGv6NUni/vhj2h9VKzlWEivSMvQKZX0/img.png&quot; srcset=&quot;https://img1.daumcdn.net/thumb/R1280x0/?scode=mtistory2&amp;fname=https%3A%2F%2Fblog.kakaocdn.net%2Fdn%2FNf3r4%2FbtrNGv6NUni%2Fvhj2h9VKzlWEivSMvQKZX0%2Fimg.png&quot; onerror=&quot;this.onerror=null; this.src='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png'; this.srcset='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png';&quot; loading=&quot;lazy&quot; width=&quot;724&quot; height=&quot;692&quot; data-origin-width=&quot;724&quot; data-origin-height=&quot;692&quot;/&gt;&lt;/span&gt;&lt;/figure&gt;
&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;Goals : clean package&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;만약 아래처럼 build error 가 난다면&lt;/p&gt;
&lt;p&gt;&lt;figure class=&quot;imageblock alignCenter&quot; data-ke-mobileStyle=&quot;widthOrigin&quot; data-origin-width=&quot;749&quot; data-origin-height=&quot;326&quot;&gt;&lt;span data-url=&quot;https://blog.kakaocdn.net/dn/1jQ35/btrNNZrviNZ/QVntACgwkMKktAo0vR9vjk/img.png&quot; data-phocus=&quot;https://blog.kakaocdn.net/dn/1jQ35/btrNNZrviNZ/QVntACgwkMKktAo0vR9vjk/img.png&quot;&gt;&lt;img src=&quot;https://blog.kakaocdn.net/dn/1jQ35/btrNNZrviNZ/QVntACgwkMKktAo0vR9vjk/img.png&quot; srcset=&quot;https://img1.daumcdn.net/thumb/R1280x0/?scode=mtistory2&amp;fname=https%3A%2F%2Fblog.kakaocdn.net%2Fdn%2F1jQ35%2FbtrNNZrviNZ%2FQVntACgwkMKktAo0vR9vjk%2Fimg.png&quot; onerror=&quot;this.onerror=null; this.src='//t1.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;749&quot; height=&quot;326&quot; data-origin-width=&quot;749&quot; data-origin-height=&quot;326&quot;/&gt;&lt;/span&gt;&lt;/figure&gt;
&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;필자의 경우엔 OpenJDK Platform binary 라는 process를 꺼주니 되었다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;(이전 build 로 만들어진 .jar를 삭제하려니 사용중이라고 해서 찾아냄)&lt;/p&gt;
&lt;p&gt;&lt;figure class=&quot;imageblock alignCenter&quot; data-ke-mobileStyle=&quot;widthOrigin&quot; data-origin-width=&quot;550&quot; data-origin-height=&quot;97&quot;&gt;&lt;span data-url=&quot;https://blog.kakaocdn.net/dn/uMsaB/btrNEwSo9gh/z5adg0hQ3f1V0InbAw3cl1/img.png&quot; data-phocus=&quot;https://blog.kakaocdn.net/dn/uMsaB/btrNEwSo9gh/z5adg0hQ3f1V0InbAw3cl1/img.png&quot;&gt;&lt;img src=&quot;https://blog.kakaocdn.net/dn/uMsaB/btrNEwSo9gh/z5adg0hQ3f1V0InbAw3cl1/img.png&quot; srcset=&quot;https://img1.daumcdn.net/thumb/R1280x0/?scode=mtistory2&amp;fname=https%3A%2F%2Fblog.kakaocdn.net%2Fdn%2FuMsaB%2FbtrNEwSo9gh%2Fz5adg0hQ3f1V0InbAw3cl1%2Fimg.png&quot; onerror=&quot;this.onerror=null; this.src='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png'; this.srcset='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png';&quot; loading=&quot;lazy&quot; width=&quot;550&quot; height=&quot;97&quot; data-origin-width=&quot;550&quot; data-origin-height=&quot;97&quot;/&gt;&lt;/span&gt;&lt;/figure&gt;
&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;빌드가 되었으면, core 프로젝트의 target 폴더 안에 빌드가 완료된 .jar 파일이 생성된다.&lt;/p&gt;
&lt;p&gt;&lt;figure class=&quot;imageblock alignCenter&quot; data-ke-mobileStyle=&quot;widthOrigin&quot; data-origin-width=&quot;688&quot; data-origin-height=&quot;188&quot;&gt;&lt;span data-url=&quot;https://blog.kakaocdn.net/dn/EgzL9/btrNMHkP9K3/DbuWpxlQsncrMX9PS4hkh0/img.png&quot; data-phocus=&quot;https://blog.kakaocdn.net/dn/EgzL9/btrNMHkP9K3/DbuWpxlQsncrMX9PS4hkh0/img.png&quot;&gt;&lt;img src=&quot;https://blog.kakaocdn.net/dn/EgzL9/btrNMHkP9K3/DbuWpxlQsncrMX9PS4hkh0/img.png&quot; srcset=&quot;https://img1.daumcdn.net/thumb/R1280x0/?scode=mtistory2&amp;fname=https%3A%2F%2Fblog.kakaocdn.net%2Fdn%2FEgzL9%2FbtrNMHkP9K3%2FDbuWpxlQsncrMX9PS4hkh0%2Fimg.png&quot; onerror=&quot;this.onerror=null; this.src='//t1.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;688&quot; height=&quot;188&quot; data-origin-width=&quot;688&quot; data-origin-height=&quot;188&quot;/&gt;&lt;/span&gt;&lt;/figure&gt;
&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;이제 이 파일을 가지고 다른 maven 프로젝트에서 사용할 수 있다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;h4 data-ke-size=&quot;size20&quot;&gt;market 프로젝트 설정&lt;/h4&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;core 프로젝트 설정처럼 pom.xml을 작성한 뒤,&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;추출한 .jar 를 가져올 수 있도록 설정해줘야 한다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;market 프로젝트 우클릭&amp;gt;Build Path&amp;gt;Configure Build Path&lt;/p&gt;
&lt;p&gt;&lt;figure class=&quot;imageblock alignCenter&quot; data-ke-mobileStyle=&quot;widthOrigin&quot; data-origin-width=&quot;505&quot; data-origin-height=&quot;390&quot;&gt;&lt;span data-url=&quot;https://blog.kakaocdn.net/dn/dd79ZN/btrNNyOzJEv/mbWrgtIwLrqK7QwY3vP4M1/img.png&quot; data-phocus=&quot;https://blog.kakaocdn.net/dn/dd79ZN/btrNNyOzJEv/mbWrgtIwLrqK7QwY3vP4M1/img.png&quot;&gt;&lt;img src=&quot;https://blog.kakaocdn.net/dn/dd79ZN/btrNNyOzJEv/mbWrgtIwLrqK7QwY3vP4M1/img.png&quot; srcset=&quot;https://img1.daumcdn.net/thumb/R1280x0/?scode=mtistory2&amp;fname=https%3A%2F%2Fblog.kakaocdn.net%2Fdn%2Fdd79ZN%2FbtrNNyOzJEv%2FmbWrgtIwLrqK7QwY3vP4M1%2Fimg.png&quot; onerror=&quot;this.onerror=null; this.src='//t1.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;505&quot; height=&quot;390&quot; data-origin-width=&quot;505&quot; data-origin-height=&quot;390&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;classpath 클릭&amp;gt;Add External JARs...&lt;/p&gt;
&lt;p&gt;&lt;figure class=&quot;imageblock alignCenter&quot; data-ke-mobileStyle=&quot;widthOrigin&quot; data-origin-width=&quot;934&quot; data-origin-height=&quot;516&quot;&gt;&lt;span data-url=&quot;https://blog.kakaocdn.net/dn/bdlNaQ/btrNKguAZiA/ZvYwygPVtPRTeARfpNQUE0/img.png&quot; data-phocus=&quot;https://blog.kakaocdn.net/dn/bdlNaQ/btrNKguAZiA/ZvYwygPVtPRTeARfpNQUE0/img.png&quot;&gt;&lt;img src=&quot;https://blog.kakaocdn.net/dn/bdlNaQ/btrNKguAZiA/ZvYwygPVtPRTeARfpNQUE0/img.png&quot; srcset=&quot;https://img1.daumcdn.net/thumb/R1280x0/?scode=mtistory2&amp;fname=https%3A%2F%2Fblog.kakaocdn.net%2Fdn%2FbdlNaQ%2FbtrNKguAZiA%2FZvYwygPVtPRTeARfpNQUE0%2Fimg.png&quot; onerror=&quot;this.onerror=null; this.src='//t1.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;934&quot; height=&quot;516&quot; data-origin-width=&quot;934&quot; data-origin-height=&quot;516&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;이제 pom.xml 에서 해당 라이브러리를 디펜던시 추가를 해주면 이용할 수 있다.&lt;/p&gt;
&lt;pre id=&quot;code_1664862504066&quot; class=&quot;xml&quot; data-ke-language=&quot;highlight.js&quot; data-ke-type=&quot;codeblock&quot;&gt;&lt;code&gt;&amp;lt;project xmlns=&quot;http://maven.apache.org/POM/4.0.0&quot; xmlns:xsi=&quot;http://www.w3.org/2001/XMLSchema-instance&quot; xsi:schemaLocation=&quot;http://maven.apache.org/POM/4.0.0 https://maven.apache.org/xsd/maven-4.0.0.xsd&quot;&amp;gt;
  &amp;lt;modelVersion&amp;gt;4.0.0&amp;lt;/modelVersion&amp;gt;
  &amp;lt;parent&amp;gt;
    &amp;lt;groupId&amp;gt;com.joon95&amp;lt;/groupId&amp;gt;
    &amp;lt;artifactId&amp;gt;test&amp;lt;/artifactId&amp;gt;
    &amp;lt;version&amp;gt;0.0.1-SNAPSHOT&amp;lt;/version&amp;gt;
  &amp;lt;/parent&amp;gt;
  &amp;lt;artifactId&amp;gt;market&amp;lt;/artifactId&amp;gt;
  &amp;lt;properties&amp;gt;
    &amp;lt;java.version&amp;gt;11&amp;lt;/java.version&amp;gt;
  &amp;lt;/properties&amp;gt;
  &amp;lt;!--
  &amp;lt;repositories&amp;gt;
    &amp;lt;repository&amp;gt;
      &amp;lt;id&amp;gt;local-joon95&amp;lt;/id&amp;gt;
      &amp;lt;name&amp;gt;local my repos&amp;lt;/name&amp;gt;
      &amp;lt;url&amp;gt;file://C:\sts-workspace\test\core\target&amp;lt;/url&amp;gt;
    &amp;lt;/repository&amp;gt;
  &amp;lt;/repositories&amp;gt;
  --&amp;gt;
  &amp;lt;dependencies&amp;gt;
    &amp;lt;dependency&amp;gt;
      &amp;lt;groupId&amp;gt;com.joon95.test&amp;lt;/groupId&amp;gt;
      &amp;lt;artifactId&amp;gt;core&amp;lt;/artifactId&amp;gt;
      &amp;lt;version&amp;gt;0.0.1-SNAPSHOT&amp;lt;/version&amp;gt;
    &amp;lt;/dependency&amp;gt;
    &amp;lt;dependency&amp;gt;
      &amp;lt;groupId&amp;gt;org.springframework.boot&amp;lt;/groupId&amp;gt;
      &amp;lt;artifactId&amp;gt;spring-boot-starter-web&amp;lt;/artifactId&amp;gt;
    &amp;lt;/dependency&amp;gt;
  &amp;lt;/dependencies&amp;gt;  
  &amp;lt;build&amp;gt;
    &amp;lt;plugins&amp;gt;
      &amp;lt;plugin&amp;gt;
        &amp;lt;groupId&amp;gt;org.springframework.boot&amp;lt;/groupId&amp;gt;
        &amp;lt;artifactId&amp;gt;spring-boot-maven-plugin&amp;lt;/artifactId&amp;gt;
        &amp;lt;configuration&amp;gt;
          &amp;lt;excludes&amp;gt;
            &amp;lt;exclude&amp;gt;
              &amp;lt;groupId&amp;gt;org.projectlombok&amp;lt;/groupId&amp;gt;
              &amp;lt;artifactId&amp;gt;lombok&amp;lt;/artifactId&amp;gt;
            &amp;lt;/exclude&amp;gt;
          &amp;lt;/excludes&amp;gt;
        &amp;lt;/configuration&amp;gt;
      &amp;lt;/plugin&amp;gt;
    &amp;lt;/plugins&amp;gt;
  &amp;lt;/build&amp;gt;
&amp;lt;/project&amp;gt;&lt;/code&gt;&lt;/pre&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;pom 설정이 되었다면 core 프로젝트처럼 Application.java 클래스를 생성해주자.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;이제 컨트롤러를 하나 만들어서 core에서 만들어둔 메소드를 실행시켜보자.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;패키지명 : &lt;b&gt;com.joon95.test.market.stage&lt;/b&gt;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;클래스명: &lt;b&gt;StageController&lt;/b&gt;&lt;/p&gt;
&lt;p&gt;&lt;figure class=&quot;imageblock alignCenter&quot; data-ke-mobileStyle=&quot;widthOrigin&quot; data-origin-width=&quot;217&quot; data-origin-height=&quot;91&quot;&gt;&lt;span data-url=&quot;https://blog.kakaocdn.net/dn/cp6oes/btrNNoSTJpi/28D9IeuR6GYY3YlSy21Mj1/img.png&quot; data-phocus=&quot;https://blog.kakaocdn.net/dn/cp6oes/btrNNoSTJpi/28D9IeuR6GYY3YlSy21Mj1/img.png&quot;&gt;&lt;img src=&quot;https://blog.kakaocdn.net/dn/cp6oes/btrNNoSTJpi/28D9IeuR6GYY3YlSy21Mj1/img.png&quot; srcset=&quot;https://img1.daumcdn.net/thumb/R1280x0/?scode=mtistory2&amp;fname=https%3A%2F%2Fblog.kakaocdn.net%2Fdn%2Fcp6oes%2FbtrNNoSTJpi%2F28D9IeuR6GYY3YlSy21Mj1%2Fimg.png&quot; onerror=&quot;this.onerror=null; this.src='//t1.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;217&quot; height=&quot;91&quot; data-origin-width=&quot;217&quot; data-origin-height=&quot;91&quot;/&gt;&lt;/span&gt;&lt;/figure&gt;
&lt;/p&gt;
&lt;pre id=&quot;code_1664866102982&quot; class=&quot;scala&quot; data-ke-language=&quot;scala&quot; data-ke-type=&quot;codeblock&quot;&gt;&lt;code&gt;package com.joon95.test.market.stage;

import org.springframework.web.bind.annotation.GetMapping;
import org.springframework.web.bind.annotation.RequestParam;
import org.springframework.web.bind.annotation.RestController;
import com.joon95.test.core.monster.MonsterController;

@RestController
public class StageController {
	
	@GetMapping(&quot;/stage1&quot;)
	public String stage1(@RequestParam String param) {
		MonsterController monsterController = new MonsterController();
		monsterController.newLog(param);
		return &quot;market프로젝트 &quot; + param;
	}
}&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;이제 market 프로젝트를 run 시키면, core에서 작성한 MonsterController가 작동하는 것을 볼 수 있다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;오버라이딩 등등 수많은 액션을 취할 수 있으니 역시 개발 최고.&lt;/p&gt;
&lt;p&gt;&lt;figure class=&quot;imageblock alignCenter&quot; data-ke-mobileStyle=&quot;widthOrigin&quot; data-origin-width=&quot;433&quot; data-origin-height=&quot;128&quot;&gt;&lt;span data-url=&quot;https://blog.kakaocdn.net/dn/TCpkS/btrNM4G9Zxc/syp0WCrqeJeCCKjDqsbKKK/img.png&quot; data-phocus=&quot;https://blog.kakaocdn.net/dn/TCpkS/btrNM4G9Zxc/syp0WCrqeJeCCKjDqsbKKK/img.png&quot;&gt;&lt;img src=&quot;https://blog.kakaocdn.net/dn/TCpkS/btrNM4G9Zxc/syp0WCrqeJeCCKjDqsbKKK/img.png&quot; srcset=&quot;https://img1.daumcdn.net/thumb/R1280x0/?scode=mtistory2&amp;fname=https%3A%2F%2Fblog.kakaocdn.net%2Fdn%2FTCpkS%2FbtrNM4G9Zxc%2Fsyp0WCrqeJeCCKjDqsbKKK%2Fimg.png&quot; onerror=&quot;this.onerror=null; this.src='//t1.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;433&quot; height=&quot;128&quot; data-origin-width=&quot;433&quot; data-origin-height=&quot;128&quot;/&gt;&lt;/span&gt;&lt;/figure&gt;
&lt;figure class=&quot;imageblock alignCenter&quot; data-ke-mobileStyle=&quot;widthOrigin&quot; data-origin-width=&quot;342&quot; data-origin-height=&quot;95&quot;&gt;&lt;span data-url=&quot;https://blog.kakaocdn.net/dn/cWNXBt/btrNM5TASVb/uT5Kj5TRze06dKTLpxewZk/img.png&quot; data-phocus=&quot;https://blog.kakaocdn.net/dn/cWNXBt/btrNM5TASVb/uT5Kj5TRze06dKTLpxewZk/img.png&quot;&gt;&lt;img src=&quot;https://blog.kakaocdn.net/dn/cWNXBt/btrNM5TASVb/uT5Kj5TRze06dKTLpxewZk/img.png&quot; srcset=&quot;https://img1.daumcdn.net/thumb/R1280x0/?scode=mtistory2&amp;fname=https%3A%2F%2Fblog.kakaocdn.net%2Fdn%2FcWNXBt%2FbtrNM5TASVb%2FuT5Kj5TRze06dKTLpxewZk%2Fimg.png&quot; onerror=&quot;this.onerror=null; this.src='//t1.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;342&quot; height=&quot;95&quot; data-origin-width=&quot;342&quot; data-origin-height=&quot;95&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;/h2&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;멀티모듈 프로젝트를 이해해보려 쭉 진행해보았다....&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;뭐... 공통이 되는 모듈에 더 많은 사항을 테스트 해보며 더 알아가려한다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;예를 들면 mysql, postgresql 등 커넥터를 core에서 만들어두고&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;core를 가져가 사용할 때 불필요한 패키지는 exclude 하는 것이다.&lt;/p&gt;</description>
      <category>개발/Spring</category>
      <category>maven</category>
      <category>SpringBoot</category>
      <category>멀티모듈</category>
      <category>메이븐</category>
      <author>joon95</author>
      <guid isPermaLink="true">https://flowlog.tistory.com/73</guid>
      <comments>https://flowlog.tistory.com/73#entry73comment</comments>
      <pubDate>Tue, 4 Oct 2022 16:02:15 +0900</pubDate>
    </item>
    <item>
      <title>[Openresty] x-forwarded-for 설정하기</title>
      <link>https://flowlog.tistory.com/72</link>
      <description>&lt;p data-ke-size=&quot;size16&quot;&gt;x-forwarded-for 는 백엔드서버가 접근하는데 &lt;b&gt;클라이언트의 ip를 체크&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;다양한 네트워크 구간이 존재하기 때문에 proxy 서버에서 &lt;b&gt;x-forwarded-for 설정&lt;/b&gt;이 없다면&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;백엔드서버의 바로 앞단에 있던 &lt;b&gt;proxy 서버의 ip&lt;/b&gt;를 가지고 있는다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;h2 data-ke-size=&quot;size26&quot;&gt;테스트 환경&lt;/h2&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;openresty 서버를 2개를 두어 proxy pass 시 x-forwarded-for ip를 로그로 찍으며 테스트한 걸 기록하려한다.&lt;/p&gt;
&lt;table style=&quot;border-collapse: collapse; width: 100%;&quot; border=&quot;1&quot; data-ke-align=&quot;alignLeft&quot;&gt;
&lt;tbody&gt;
&lt;tr&gt;
&lt;td style=&quot;width: 33.3333%;&quot;&gt;테스트 VM&lt;/td&gt;
&lt;td style=&quot;width: 33.3333%;&quot;&gt;Public IP&lt;/td&gt;
&lt;td style=&quot;width: 33.3333%;&quot;&gt;Private IP&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td style=&quot;width: 33.3333%;&quot;&gt;호출서버&lt;/td&gt;
&lt;td style=&quot;width: 33.3333%;&quot;&gt;223.130.162.121&lt;/td&gt;
&lt;td style=&quot;width: 33.3333%;&quot;&gt;&amp;nbsp;&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td style=&quot;width: 33.3333%;&quot;&gt;proxy 1번&lt;/td&gt;
&lt;td style=&quot;width: 33.3333%;&quot;&gt;175.45.193.85&lt;/td&gt;
&lt;td style=&quot;width: 33.3333%;&quot;&gt;10.160.227.8&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td style=&quot;width: 33.3333%;&quot;&gt;proxy 2번&lt;/td&gt;
&lt;td style=&quot;width: 33.3333%;&quot;&gt;175.106.97.177&lt;/td&gt;
&lt;td style=&quot;width: 33.3333%;&quot;&gt;10.160.227.9&lt;/td&gt;
&lt;/tr&gt;
&lt;/tbody&gt;
&lt;/table&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;시나리오는 아래와 같다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;1. 호출서버에서 proxy 1번 호출(http)&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;2. proxy 1번서버는 proxy2번서버로 proxy pass&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;3. proxy 2번서버는 proxy1번서버 81포트로 proxy_pass, 헤더값을 none 으로 치환&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;4. proxy 1번서버의 81포트는 index.html 페이지 return&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;proxy pass로 흘려주고, 중간에 헤더값을 강제로 치환시키며 log를 분석한다.&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;nginx.conf 수정&lt;/h2&gt;
&lt;h3 data-ke-size=&quot;size23&quot;&gt;proxy 1번서버&lt;/h3&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;간단히 80, 81 포트를 열고&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;proxy pass 는 Proxy 2번의 Private IP를 사용해보았다.&lt;/p&gt;
&lt;pre id=&quot;code_1664501037668&quot; class=&quot;java&quot; data-ke-language=&quot;java&quot; data-ke-type=&quot;codeblock&quot;&gt;&lt;code&gt;server {
    listen 80;
    location / {
        proxy_pass http://10.160.227.9;
        proxy_set_header X-Forwarded-For $proxy_add_x_forwarded_for;
    }
}

server {
    listen       81;
    location / {
         root   html;
         index  index.html index.htm;
    }
}&lt;/code&gt;&lt;/pre&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;h3 data-ke-size=&quot;size23&quot;&gt;proxy 2번서버&lt;/h3&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;80 포트를 열고 proxy 1번 public ip의 81 포트로 접근하며,&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;커스텀헤더인 sa-head의 값을 none 으로 설정하였다.&lt;/p&gt;
&lt;pre id=&quot;code_1664501154160&quot; class=&quot;java&quot; data-ke-language=&quot;java&quot; data-ke-type=&quot;codeblock&quot;&gt;&lt;code&gt;server {
    listen       80;
    location / {
        proxy_pass http://175.45.193.85:81;
        proxy_set_header sa-head &quot;none&quot;;
        proxy_set_header X-Forwarded-For $proxy_add_x_forwarded_for;
    }
}&lt;/code&gt;&lt;/pre&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;h3 data-ke-size=&quot;size23&quot;&gt;access.log 포맷&lt;/h3&gt;
&lt;pre id=&quot;code_1664501457080&quot; class=&quot;java&quot; data-ke-language=&quot;java&quot; data-ke-type=&quot;codeblock&quot;&gt;&lt;code&gt;log_format  main  'remote_addr : $remote_addr '
                  'x-forwarded-for : &quot;$http_x_forwarded_for&quot; '
                  'proxy_add_x_forwarded_for : &quot;$proxy_add_x_forwarded_for&quot; '
                  'sa_head : &quot;$http_sa_head&quot;';&lt;/code&gt;&lt;/pre&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;간단하게 이전 ip, x-forwararded-for, 커스텀헤더를 출력하게 하였다.&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;(커스텀헤더는 &lt;b&gt;http_&lt;/b&gt;헤더명 방식으로 사용할 수 있음)&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;로그포맷을 사용하려면 access_log 쪽에 포맷이름을 기입해주어야한다.&lt;/p&gt;
&lt;pre id=&quot;code_1664501470566&quot; class=&quot;java&quot; data-ke-language=&quot;java&quot; data-ke-type=&quot;codeblock&quot;&gt;&lt;code&gt;access_log  logs/access.log  main;&lt;/code&gt;&lt;/pre&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;h2 data-ke-size=&quot;size26&quot;&gt;호출테스트&lt;/h2&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;이제 호출을 해보자&lt;/p&gt;
&lt;pre id=&quot;code_1664501621445&quot; class=&quot;java&quot; data-ke-language=&quot;java&quot; data-ke-type=&quot;codeblock&quot;&gt;&lt;code&gt;$ curl 175.45.193.85 -H 'sa-head:joon95'&lt;/code&gt;&lt;/pre&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;1번째(proxy1:80 listen)&lt;/p&gt;
&lt;pre id=&quot;code_1664501743868&quot; class=&quot;java&quot; data-ke-language=&quot;java&quot; data-ke-type=&quot;codeblock&quot;&gt;&lt;code&gt;remote_addr : 223.130.162.121 
 x-forwarded-for : &quot;-&quot; 
 proxy_add_x_forwarded_for : &quot;223.130.162.121&quot;
 sa_head : &quot;joon95&quot;&lt;/code&gt;&lt;/pre&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;2번째(proxy2:80 listen)&lt;/p&gt;
&lt;pre id=&quot;code_1664501903464&quot; class=&quot;java&quot; data-ke-language=&quot;java&quot; data-ke-type=&quot;codeblock&quot;&gt;&lt;code&gt;remote_addr : 10.160.227.8
 x-forwarded-for : &quot;223.130.162.121&quot;
 proxy_add_x_forwarded_for : &quot;223.130.162.121, 10.160.227.8&quot; 
 sa_head : &quot;joon95&quot;&lt;/code&gt;&lt;/pre&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;3번째(proxy1:81 listen)&lt;/p&gt;
&lt;pre id=&quot;code_1664501839675&quot; class=&quot;java&quot; data-ke-language=&quot;java&quot; data-ke-type=&quot;codeblock&quot;&gt;&lt;code&gt;remote_addr : 175.106.97.177 
 x-forwarded-for : &quot;223.130.162.121, 10.160.227.8&quot; 
 proxy_add_x_forwarded_for : &quot;223.130.162.121, 10.160.227.8, 175.106.97.177&quot;
 sa_head : &quot;none&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;로그를 보면 x-forwarded-for 헤더에 추가적으로 ip가 넘어가는 걸 볼 수 있다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;근데 proxy_add_x_forwarded_for 라는 변수는 넘어온 x-forwarded-for ip 목록에 remote_addr를 추가해서 가지고 있다는 사실을 확인할 수 있었다. 그래서 proxy_pass 를 사용할 때 x-forwarded-for 헤더에 이 변수를 기입하여 모든 ip를 연속적으로 넘길 수 있다(nginx 사이트의 해당 변수 처리를 보았음).&lt;/p&gt;
&lt;p&gt;&lt;figure class=&quot;imageblock alignCenter&quot; data-ke-mobileStyle=&quot;widthOrigin&quot; data-origin-width=&quot;871&quot; data-origin-height=&quot;230&quot;&gt;&lt;span data-url=&quot;https://blog.kakaocdn.net/dn/brJUXU/btrNqsPnL1M/tHvJIU40R2KONZIjPuYZG1/img.png&quot; data-phocus=&quot;https://blog.kakaocdn.net/dn/brJUXU/btrNqsPnL1M/tHvJIU40R2KONZIjPuYZG1/img.png&quot;&gt;&lt;img src=&quot;https://blog.kakaocdn.net/dn/brJUXU/btrNqsPnL1M/tHvJIU40R2KONZIjPuYZG1/img.png&quot; srcset=&quot;https://img1.daumcdn.net/thumb/R1280x0/?scode=mtistory2&amp;fname=https%3A%2F%2Fblog.kakaocdn.net%2Fdn%2FbrJUXU%2FbtrNqsPnL1M%2FtHvJIU40R2KONZIjPuYZG1%2Fimg.png&quot; onerror=&quot;this.onerror=null; this.src='//t1.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;871&quot; height=&quot;230&quot; data-origin-width=&quot;871&quot; data-origin-height=&quot;230&quot;/&gt;&lt;/span&gt;&lt;/figure&gt;
&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;h2 data-ke-size=&quot;size26&quot;&gt;마치며&lt;/h2&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;간만에 로그를 분석해가며 테스트해보았는데, 많은 도움이 된 것 같다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;긴 글 읽어주셔서 감사합니다.^^&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;h2 data-ke-size=&quot;size26&quot;&gt;참고사이트&lt;/h2&gt;
&lt;figure id=&quot;og_1664502329657&quot; contenteditable=&quot;false&quot; data-ke-type=&quot;opengraph&quot; data-ke-align=&quot;alignCenter&quot; data-og-type=&quot;website&quot; data-og-title=&quot;Using the Forwarded header | NGINX&quot; data-og-description=&quot;&quot; data-og-host=&quot;www.nginx.com&quot; data-og-source-url=&quot;https://www.nginx.com/resources/wiki/start/topics/examples/forwarded/&quot; data-og-url=&quot;https://www.nginx.com/resources/wiki/start/topics/examples/forwarded/&quot; data-og-image=&quot;&quot;&gt;&lt;a href=&quot;https://www.nginx.com/resources/wiki/start/topics/examples/forwarded/&quot; target=&quot;_blank&quot; rel=&quot;noopener&quot; data-source-url=&quot;https://www.nginx.com/resources/wiki/start/topics/examples/forwarded/&quot;&gt;
&lt;div class=&quot;og-image&quot; style=&quot;background-image: url();&quot;&gt;&amp;nbsp;&lt;/div&gt;
&lt;div class=&quot;og-text&quot;&gt;
&lt;p class=&quot;og-title&quot; data-ke-size=&quot;size16&quot;&gt;Using the Forwarded header | NGINX&lt;/p&gt;
&lt;p class=&quot;og-desc&quot; data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p class=&quot;og-host&quot; data-ke-size=&quot;size16&quot;&gt;www.nginx.com&lt;/p&gt;
&lt;/div&gt;
&lt;/a&gt;&lt;/figure&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;</description>
      <category>엔지니어링/프록시</category>
      <category>nginx</category>
      <category>openresry</category>
      <category>proxy pass</category>
      <category>x-forwarded-for</category>
      <category>망분리</category>
      <author>joon95</author>
      <guid isPermaLink="true">https://flowlog.tistory.com/72</guid>
      <comments>https://flowlog.tistory.com/72#entry72comment</comments>
      <pubDate>Fri, 30 Sep 2022 10:50:42 +0900</pubDate>
    </item>
  </channel>
</rss>