<?xml version="1.0" encoding="UTF-8"?>
<rss version="2.0">
  <channel>
    <title>공부하는 블로그</title>
    <link>https://rusharp.tistory.com/</link>
    <description>QA에 대한것을 공부하기 위한 블로그입니다.</description>
    <language>ko</language>
    <pubDate>Sat, 27 Jun 2026 16:46:20 +0900</pubDate>
    <generator>TISTORY</generator>
    <ttl>100</ttl>
    <managingEditor>몽자비루</managingEditor>
    <image>
      <title>공부하는 블로그</title>
      <url>https://tistory1.daumcdn.net/tistory/6196023/attach/5a07fc74368e4b4399efe6a75fde7d9e</url>
      <link>https://rusharp.tistory.com</link>
    </image>
    <item>
      <title>CLAUDE Code 기본 사용법 및 에이전트에게 컨텍스트 제공하기.</title>
      <link>https://rusharp.tistory.com/212</link>
      <description>&lt;p data-ke-size=&quot;size16&quot;&gt;이번에는 주요 명령어 및 단축키를 소개하고, Prompt Engineering 과 Context Engineering 차이를 알아보려 한다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;특히 Context Window 개념을 바탕으로 에이전트의 성능을 향상시키는 3가지 원칙을 제공하고,&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;AI 에게 프로젝트 배경지식을 제공하는 md 파일의 효과적인 작성 및 관리 기준을 다룬다.&lt;/p&gt;
&lt;hr data-path-to-node=&quot;3&quot; data-ke-style=&quot;style1&quot; /&gt;
&lt;h3 data-path-to-node=&quot;4&quot; data-ke-size=&quot;size23&quot;&gt;&lt;b&gt;1. 기본 인터페이스 익히기&lt;/b&gt;&lt;/h3&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;먼저 나는 Claude 를 사용하고 있는데, 그중에서 자주 사용되는 몇가지만 언급하려고 한다.&lt;/p&gt;
&lt;table style=&quot;border-collapse: collapse; width: 100%; height: 172px;&quot; border=&quot;1&quot; data-ke-align=&quot;alignLeft&quot; data-ke-style=&quot;style12&quot;&gt;
&lt;tbody&gt;
&lt;tr style=&quot;height: 22px;&quot;&gt;
&lt;td style=&quot;width: 20.5814%; height: 22px;&quot;&gt;조작&lt;/td&gt;
&lt;td style=&quot;width: 79.4186%; height: 22px;&quot;&gt;기능&lt;/td&gt;
&lt;/tr&gt;
&lt;tr style=&quot;height: 22px;&quot;&gt;
&lt;td style=&quot;width: 20.5814%; height: 22px;&quot;&gt;텍스트 입력 후 Enter&lt;/td&gt;
&lt;td style=&quot;width: 79.4186%; height: 22px;&quot;&gt;사용자가 작성하는 프롬프트를 제출한다.&lt;/td&gt;
&lt;/tr&gt;
&lt;tr style=&quot;height: 16px;&quot;&gt;
&lt;td style=&quot;width: 20.5814%; height: 16px;&quot;&gt;Shift + Enter&lt;/td&gt;
&lt;td style=&quot;width: 79.4186%; height: 16px;&quot;&gt;제출하지 않고 줄바꿈을 삽입한다.&lt;/td&gt;
&lt;/tr&gt;
&lt;tr style=&quot;height: 16px;&quot;&gt;
&lt;td style=&quot;width: 20.5814%; height: 16px;&quot;&gt;Esc&lt;/td&gt;
&lt;td style=&quot;width: 79.4186%; height: 16px;&quot;&gt;대답중인 패널을 중지시킨다.&lt;/td&gt;
&lt;/tr&gt;
&lt;tr style=&quot;height: 16px;&quot;&gt;
&lt;td style=&quot;width: 20.5814%; height: 16px;&quot;&gt;Esc + Esc&lt;/td&gt;
&lt;td style=&quot;width: 79.4186%; height: 16px;&quot;&gt;입력된 모든 텍스트를 삭제한다.&lt;/td&gt;
&lt;/tr&gt;
&lt;tr style=&quot;height: 16px;&quot;&gt;
&lt;td style=&quot;width: 20.5814%; height: 16px;&quot;&gt;/model&lt;/td&gt;
&lt;td style=&quot;width: 79.4186%; height: 16px;&quot;&gt;모델을 변경한다.&lt;/td&gt;
&lt;/tr&gt;
&lt;tr style=&quot;height: 16px;&quot;&gt;
&lt;td style=&quot;width: 20.5814%; height: 16px;&quot;&gt;Shift+Tab&lt;/td&gt;
&lt;td style=&quot;width: 79.4186%; height: 16px;&quot;&gt;플래닝 모드로 변경한다.&amp;nbsp;&lt;/td&gt;
&lt;/tr&gt;
&lt;tr style=&quot;height: 16px;&quot;&gt;
&lt;td style=&quot;width: 20.5814%; height: 16px;&quot;&gt;/context&lt;/td&gt;
&lt;td style=&quot;width: 79.4186%; height: 16px;&quot;&gt;현재 세션 토큰 사용량 확인한다.&lt;/td&gt;
&lt;/tr&gt;
&lt;tr style=&quot;height: 16px;&quot;&gt;
&lt;td style=&quot;width: 20.5814%; height: 16px;&quot;&gt;/compact&lt;/td&gt;
&lt;td style=&quot;width: 79.4186%; height: 16px;&quot;&gt;대화를 요역한다.&lt;/td&gt;
&lt;/tr&gt;
&lt;tr style=&quot;height: 16px;&quot;&gt;
&lt;td style=&quot;width: 20.5814%; height: 16px;&quot;&gt;/init&lt;/td&gt;
&lt;td style=&quot;width: 79.4186%; height: 16px;&quot;&gt;CLAUDE.md 를 자동으로 생성한다.&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;혹시 추가적인 command 및 단축키에 대해서 알고 싶다면 아래 링크를 참고하면 좋을것 같다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;a href=&quot;https://code.claude.com/docs/en/slash-commands&quot; target=&quot;_blank&quot; rel=&quot;noopener&amp;nbsp;noreferrer&quot;&gt;https://code.claude.com/docs/en/slash-commands&lt;/a&gt;&lt;/p&gt;
&lt;h3 data-path-to-node=&quot;4&quot; data-ke-size=&quot;size23&quot;&gt;&lt;b&gt;2. 프롬프트 엔지니어링과 Context Engin&lt;/b&gt;&lt;b&gt;eering의 차이&lt;/b&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;프롬프트 기반으로 개발하는 것은 &quot;프롬프트 엔지니어링&quot;이라고 볼 수 있다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;이러한 프롬프트 엔지니어링은 Gemini, Chatgpt 등 웹 기반의 챗봇에서는 유의미하지만&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;Antigravity CLI, Claude code, Codex 와 같은 코딩 에이전트에서는 다르다.&lt;/p&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;b&gt;Context Engineering&lt;/b&gt; 이라고 하는데,&amp;nbsp;해당 프로젝트에 대한 배경지식이나 제약사항 등을&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;md 파일을 통해 제공하고, AI 가 이를 참고하여 프로젝트 문맥을 이해할 수 있다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;여기서 또하나 중요한 개념이 들어가는데, &lt;b&gt;Context Window&lt;/b&gt; 이다.&lt;/p&gt;
&lt;h3 data-path-to-node=&quot;6&quot; data-ke-size=&quot;size23&quot;&gt;&lt;b&gt;3. 컨텍스트 윈도우: AI의 RAM&lt;/b&gt;&lt;/h3&gt;
&lt;p data-path-to-node=&quot;7&quot; data-ke-size=&quot;size16&quot;&gt;AI 모델의 연산 능력을 CPU에 비유한다면, &lt;b data-index-in-node=&quot;26&quot; data-path-to-node=&quot;7&quot;&gt;컨텍스트 윈도우는 한 번에 처리할 수 있는 정보량을 의미하는 RAM&lt;/b&gt;에 해당한다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p style=&quot;text-align: start;&quot; data-ke-size=&quot;size16&quot;&gt;만약 내가 Claude 를 사용한다는 가정 하에, 무언가를 요청한다면 Claude 는&lt;/p&gt;
&lt;p style=&quot;text-align: start;&quot; data-ke-size=&quot;size16&quot;&gt;&lt;b&gt;프롬프트, Claude.md 의 마크다운 파일, 직접 읽은 소스 코드, 이전 명령 실행 결과 및 대화내용&lt;/b&gt;을&lt;/p&gt;
&lt;p style=&quot;text-align: start;&quot; data-ke-size=&quot;size16&quot;&gt;전부 읽고 동작하는데, 이때 Claude 가 읽는 모든 내용을 Context Windows 라고 한다.&lt;/p&gt;
&lt;p style=&quot;text-align: start;&quot; data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p style=&quot;text-align: start;&quot; data-ke-size=&quot;size16&quot;&gt;다만 Context Windows 는 한계가 있어서 대화가 길어지면 성능 저하, 혹은 실수가 늘어난다.&lt;/p&gt;
&lt;p style=&quot;text-align: start;&quot; data-ke-size=&quot;size16&quot;&gt;그래서 꼭 필요한 정보만 넣고, 대화가 길어지면 대화가 길어지는 경우 /compact 기능을 활용해야 한다.&lt;/p&gt;
&lt;p&gt;&lt;figure class=&quot;imageblock alignCenter&quot; data-ke-mobileStyle=&quot;widthOrigin&quot; data-filename=&quot;2221.jfif&quot; data-origin-width=&quot;1024&quot; data-origin-height=&quot;559&quot;&gt;&lt;span data-url=&quot;https://blog.kakaocdn.net/dn/bojp7n/dJMcacXDcIh/rohCRleMioKsidkqyS1pC0/img.jpg&quot; data-phocus=&quot;https://blog.kakaocdn.net/dn/bojp7n/dJMcacXDcIh/rohCRleMioKsidkqyS1pC0/img.jpg&quot;&gt;&lt;img src=&quot;https://blog.kakaocdn.net/dn/bojp7n/dJMcacXDcIh/rohCRleMioKsidkqyS1pC0/img.jpg&quot; srcset=&quot;https://img1.daumcdn.net/thumb/R1280x0/?scode=mtistory2&amp;fname=https%3A%2F%2Fblog.kakaocdn.net%2Fdn%2Fbojp7n%2FdJMcacXDcIh%2FrohCRleMioKsidkqyS1pC0%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;1024&quot; height=&quot;559&quot; data-filename=&quot;2221.jfif&quot; data-origin-width=&quot;1024&quot; data-origin-height=&quot;559&quot;/&gt;&lt;/span&gt;&lt;/figure&gt;
&lt;/p&gt;
&lt;h3 data-path-to-node=&quot;9&quot; data-ke-size=&quot;size23&quot;&gt;&lt;b&gt;4. AI 에이전트 성능을 높이는 3가지 원칙&lt;/b&gt;&lt;/h3&gt;
&lt;p data-path-to-node=&quot;10&quot; data-ke-size=&quot;size16&quot;&gt;Context Engineering 에서 AI agent 성능을 높이기 위한 원칙은 총 세가지가 있다.&lt;/p&gt;
&lt;ol style=&quot;list-style-type: decimal;&quot; data-path-to-node=&quot;11&quot; data-ke-list-type=&quot;decimal&quot;&gt;
&lt;li&gt;&lt;b data-index-in-node=&quot;0&quot; data-path-to-node=&quot;11,0,0&quot;&gt;검증 수단 제공&lt;/b&gt;: 테스트 코드, 스크린샷, 기대 결과물 등을 함께 제공하여 AI가 자신의 작업을 스스로 검증하게 한다.&lt;/li&gt;
&lt;li&gt;&lt;b data-index-in-node=&quot;0&quot; data-path-to-node=&quot;11,1,0&quot;&gt;단계적 접근 (탐색 &amp;gt; 설계 &amp;gt; 구현 &amp;gt; 커밋)&lt;/b&gt;: 즉시 코드를 작성하게 하지 않고, 계획 모드로 탐색과 설계를 마친 뒤 구현한다.&lt;/li&gt;
&lt;li&gt;&lt;b data-index-in-node=&quot;0&quot; data-path-to-node=&quot;11,2,0&quot;&gt;구체적인 컨텍스트 제공&lt;/b&gt;: 프로젝트의 목적과 기대하는 사용자 경험, 구체적인 제약조건을 명확히 제시한다.&lt;/li&gt;
&lt;/ol&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;예를 들자면 &quot; 농수산물 웹페이지를 만들어줘&quot; 라고 말하기보다는,&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&quot;coupang 스타일의 농수산물 웹페이지를 만들거야. 그런데 카테고리 별로 상품이 분리되어야 하고, 상품을 선택하면 상품 상세 페이지로 이동하도록 구현해줘&quot;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;라고 구체적인 컨텍스트를 제공했을 때, 조금 더 기대하는 것과 동일한 결과가 나올 수 있다.&lt;/p&gt;
&lt;p&gt;&lt;figure class=&quot;imageblock alignCenter&quot; data-ke-mobileStyle=&quot;widthOrigin&quot; data-filename=&quot;2222.png&quot; data-origin-width=&quot;2816&quot; data-origin-height=&quot;1536&quot;&gt;&lt;span data-url=&quot;https://blog.kakaocdn.net/dn/bkrUqS/dJMcah5KPGc/OAxuqHm84kYpanHnGfv4k0/img.png&quot; data-phocus=&quot;https://blog.kakaocdn.net/dn/bkrUqS/dJMcah5KPGc/OAxuqHm84kYpanHnGfv4k0/img.png&quot;&gt;&lt;img src=&quot;https://blog.kakaocdn.net/dn/bkrUqS/dJMcah5KPGc/OAxuqHm84kYpanHnGfv4k0/img.png&quot; srcset=&quot;https://img1.daumcdn.net/thumb/R1280x0/?scode=mtistory2&amp;fname=https%3A%2F%2Fblog.kakaocdn.net%2Fdn%2FbkrUqS%2FdJMcah5KPGc%2FOAxuqHm84kYpanHnGfv4k0%2Fimg.png&quot; onerror=&quot;this.onerror=null; this.src='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png'; this.srcset='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png';&quot; loading=&quot;lazy&quot; width=&quot;2816&quot; height=&quot;1536&quot; data-filename=&quot;2222.png&quot; data-origin-width=&quot;2816&quot; data-origin-height=&quot;1536&quot;/&gt;&lt;/span&gt;&lt;/figure&gt;
&lt;/p&gt;
&lt;h3 data-path-to-node=&quot;12&quot; data-ke-size=&quot;size23&quot;&gt;&lt;b&gt;5. CLAUDE.md 작성 및 관리 가이드&lt;/b&gt;&lt;/h3&gt;
&lt;p data-path-to-node=&quot;13&quot; data-ke-size=&quot;size16&quot;&gt;그렇다면 이제 Context Engineering 의 꽃, md 파일에 대해서 이야기를 해보려고 한다.&lt;/p&gt;
&lt;p data-path-to-node=&quot;13&quot; data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-path-to-node=&quot;13&quot; data-ke-size=&quot;size16&quot;&gt;md 파일이란 예전에는 &quot;매번 프롬프트에 추가하기 귀찮으니까 미리 만들어둔 파일&quot; 이라고 생각했는데&lt;/p&gt;
&lt;p data-path-to-node=&quot;13&quot; data-ke-size=&quot;size16&quot;&gt;어느정도 맞지만, 굳이 이야기하자면 AI 에게 제공하는 배경지식, 지침가이드라고 볼 수 있다.&lt;/p&gt;
&lt;p data-path-to-node=&quot;13&quot; data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-path-to-node=&quot;13&quot; data-ke-size=&quot;size16&quot;&gt;이런 md 파일에는 포함해야 할 부분과 불필요한 부분이 있는데, 위에서 언급했다시피&lt;/p&gt;
&lt;p data-path-to-node=&quot;13&quot; data-ke-size=&quot;size16&quot;&gt;컨텍스트 윈도우에는 한계가 있기 때문에 최소한의 데이터로 최고의 효율을 내도록 작성해야 한다.&lt;/p&gt;
&lt;p data-path-to-node=&quot;13&quot; data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-path-to-node=&quot;13&quot; data-ke-size=&quot;size16&quot;&gt;예를 들어, 프로젝트의 목적이나 AI 가 예측하지 못하는 한계사항 등에 대해서는 작성하되,&lt;/p&gt;
&lt;p data-path-to-node=&quot;13&quot; data-ke-size=&quot;size16&quot;&gt;AI 가 이미 문서를 통해 유추할 수 있는 부분이나 추상적인 부분, 일회성의 요청 등은 지양하는 것이 좋다.&lt;/p&gt;
&lt;p data-path-to-node=&quot;13&quot; data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-path-to-node=&quot;13&quot; data-ke-size=&quot;size16&quot;&gt;아래는&lt;a href=&quot;https://code.claude.com/docs/ko/best-practices#환경-구성하기&quot; target=&quot;_blank&quot; rel=&quot;noopener&quot;&gt; Claude 개발사, Anthropic 에서 제안한 &quot;효과적인 CLAUDE.md&quot; 작성하기&lt;/a&gt; 기준이다.&lt;/p&gt;
&lt;table style=&quot;color: #9e9e9e; text-align: start; 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;/td&gt;
&lt;td&gt;&lt;span style=&quot;text-align: start;&quot;&gt;❌ 제외&lt;/span&gt;&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;Claude가 추측할 수 없는 Bash 명령&lt;/td&gt;
&lt;td&gt;Claude가 코드를 읽어서 파악할 수 있는 것&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;기본값과 다른 코드 스타일 규칙&lt;/td&gt;
&lt;td&gt;Claude가 이미 알고 있는 표준 언어 규칙&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;테스트 지시사항 및 선호하는 테스트 러너&lt;/td&gt;
&lt;td&gt;상세한 API 문서(대신 문서 링크)&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;저장소 에티켓(분기 이름 지정, PR 규칙)&lt;/td&gt;
&lt;td&gt;자주 변경되는 정보&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;프로젝트에 특정한 아키텍처 결정&lt;/td&gt;
&lt;td&gt;긴 설명 또는 튜토리얼&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;개발자 환경 특이성(필수 환경 변수)&lt;/td&gt;
&lt;td&gt;자명한 관행(예: &amp;ldquo;깨끗한 코드 작성&amp;rdquo;)&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;일반적인 함정 또는 명백하지 않은 동작&lt;/td&gt;
&lt;td&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;여기에 추가로 정말 중요한것은 모순되지 않도록 해야한다는 점이다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;예를 들어, md 파일 상단에 &quot;농수산물 판매 페이지를 만들 것입니다. 카테고리 별로 분류하겠습니다.&quot; 라고 적고&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;하단에는 &quot;카테고리 구분 없이 농수산물을 확인할 수 있습니다.&quot; 라고 작성한다면 혼란이 올 수 있다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;동시에 md 파일중 정말 중요하다고 생각하는 부분에 대해서는 강조 표현을 사용하여 우선순위를 높일 수 있다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;이렇듯 AI 코딩 에이전트의 성능은 프롬프트의 길이나 단순 지시가 아닌, 컨텍스트의 품질에 의해 결정된다.&lt;/p&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;명확하게 전달하는 Context Engineering을 통해 에이전트의 정확도와 프로젝트 구현 효율을 극대화할 수 있다.&lt;/p&gt;</description>
      <category>AI</category>
      <author>몽자비루</author>
      <guid isPermaLink="true">https://rusharp.tistory.com/212</guid>
      <comments>https://rusharp.tistory.com/212#entry212comment</comments>
      <pubDate>Wed, 3 Jun 2026 20:43:40 +0900</pubDate>
    </item>
    <item>
      <title>Antigravity CLI 시작하기</title>
      <link>https://rusharp.tistory.com/211</link>
      <description>&lt;h3 id=&quot;roadmap-checklist&quot; data-ke-size=&quot;size23&quot;&gt;1. Antigravity CLI 설치하기&lt;/h3&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;먼저 Antigravity CLI 를 설치하기 위해 아래와 같은 명령어를 입력하여 설치하면 된다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;b&gt;macOS / Linux&lt;/b&gt;&lt;span&gt;&amp;nbsp;&lt;/span&gt;:&lt;/p&gt;
&lt;div style=&quot;background-color: #f8f9fc;&quot;&gt;
&lt;div&gt;
&lt;pre class=&quot;awk&quot;&gt;&lt;code&gt;   curl -fsSL https://antigravity.google/cli/install.sh | bash&lt;/code&gt;&lt;/pre&gt;
&lt;/div&gt;
&lt;/div&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;b&gt;윈도우(Powershell)&lt;/b&gt;&lt;span&gt;&amp;nbsp;&lt;/span&gt;:&lt;span&gt;&lt;/span&gt;&lt;/p&gt;
&lt;div style=&quot;background-color: #f8f9fc;&quot;&gt;
&lt;div&gt;
&lt;pre class=&quot;awk&quot;&gt;&lt;code&gt;   irm https://antigravity.google/cli/install.ps1 | iex&lt;/code&gt;&lt;/pre&gt;
&lt;/div&gt;
&lt;/div&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;b&gt;윈도우(CMD)&lt;/b&gt;&lt;span&gt;&amp;nbsp;&lt;/span&gt;:&lt;/p&gt;
&lt;div style=&quot;background-color: #f8f9fc;&quot;&gt;
&lt;div&gt;
&lt;pre class=&quot;dos&quot;&gt;&lt;code&gt;   curl -fsSL https://antigravity.google/cli/install.cmd -o install.cmd &amp;amp;&amp;amp; install.cmd &amp;amp;&amp;amp; del install.cmd&lt;/code&gt;&lt;/pre&gt;
&lt;/div&gt;
&lt;/div&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;이렇게 설치된 프로그램은 아래 명시된 디렉토리에 저장된다.&lt;/p&gt;
&lt;ul style=&quot;list-style-type: disc;&quot; data-ke-list-type=&quot;disc&quot;&gt;
&lt;li&gt;&lt;span&gt;&lt;b&gt;macOS / Linux&lt;/b&gt;&lt;span&gt;&amp;nbsp;&lt;/span&gt;:&lt;/span&gt;&lt;span&gt;~/.local/bin/agy&lt;/span&gt;&lt;/li&gt;
&lt;li&gt;&lt;span&gt;&lt;b&gt;Windows&lt;/b&gt;&lt;span&gt;&amp;nbsp;&lt;/span&gt;:&lt;span&gt;&amp;nbsp;&lt;/span&gt;&lt;/span&gt;&lt;span&gt;C:\Users\&amp;lt;Username&amp;gt;\AppData\Local\agy\bin&lt;/span&gt;&lt;/li&gt;
&lt;/ul&gt;
&lt;p&gt;&lt;figure class=&quot;imageblock alignCenter&quot; data-ke-mobileStyle=&quot;widthOrigin&quot; data-origin-width=&quot;1096&quot; data-origin-height=&quot;264&quot;&gt;&lt;span data-url=&quot;https://blog.kakaocdn.net/dn/kCg4P/dJMcai4xsoV/nylkJVLBMiDacjN3HB7dbk/img.png&quot; data-phocus=&quot;https://blog.kakaocdn.net/dn/kCg4P/dJMcai4xsoV/nylkJVLBMiDacjN3HB7dbk/img.png&quot;&gt;&lt;img src=&quot;https://blog.kakaocdn.net/dn/kCg4P/dJMcai4xsoV/nylkJVLBMiDacjN3HB7dbk/img.png&quot; srcset=&quot;https://img1.daumcdn.net/thumb/R1280x0/?scode=mtistory2&amp;fname=https%3A%2F%2Fblog.kakaocdn.net%2Fdn%2FkCg4P%2FdJMcai4xsoV%2FnylkJVLBMiDacjN3HB7dbk%2Fimg.png&quot; onerror=&quot;this.onerror=null; this.src='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png'; this.srcset='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png';&quot; loading=&quot;lazy&quot; width=&quot;1096&quot; height=&quot;264&quot; data-origin-width=&quot;1096&quot; data-origin-height=&quot;264&quot;/&gt;&lt;/span&gt;&lt;/figure&gt;
&lt;/p&gt;
&lt;h3 data-ke-size=&quot;size23&quot;&gt;2. Antigravity CLI 실행하기&lt;span&gt;&lt;/span&gt;&lt;/h3&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;Antigravity CLI 를 실행하기 위해 아래와 같은 명령어를 실행하면 된다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;단, 실행하기 전에 프로젝트의 코드베이스 디렉토리로 이동한 다음 명령어를 실행해야 한다.&lt;/p&gt;
&lt;pre class=&quot;ebnf&quot; style=&quot;background-color: #f8f8f8; color: #383a42; text-align: start;&quot;&gt;&lt;code&gt;   agy&lt;/code&gt;&lt;/pre&gt;
&lt;p data-ke-size=&quot;size16&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&gt;&lt;b&gt;색상 구성표&lt;/b&gt;&lt;span&gt;&amp;nbsp;&lt;/span&gt;:&lt;span&gt;&amp;nbsp;&lt;/span&gt;&lt;/span&gt;&lt;span&gt;원하는 테마(솔라라이즈드, 다크, 솔라라이즈드 라이트 또는 표준 터미널 색상) 선택&lt;/span&gt;&lt;/li&gt;
&lt;li&gt;&lt;span&gt;&lt;b&gt;렌더링 모드&lt;/b&gt;&lt;span&gt;&amp;nbsp;&lt;/span&gt;: &lt;span style=&quot;text-align: start;&quot;&gt;Alt-Screen mode&lt;/span&gt;&lt;/span&gt; 또는 &lt;span style=&quot;text-align: start;&quot;&gt;Inline mode &lt;/span&gt;중에서 선택&lt;/li&gt;
&lt;li&gt;&lt;span&gt;&lt;b&gt;워크스페이스 신뢰도&lt;/b&gt;&lt;span&gt;&amp;nbsp;&lt;/span&gt;:&lt;span&gt;&amp;nbsp;&lt;/span&gt;&lt;/span&gt;&lt;span&gt;저장소 디렉터리를 신뢰하는지 확인&lt;/span&gt;&lt;/li&gt;
&lt;/ul&gt;
&lt;p&gt;&lt;figure class=&quot;imageblock alignCenter&quot; data-ke-mobileStyle=&quot;widthOrigin&quot; data-origin-width=&quot;1103&quot; data-origin-height=&quot;441&quot;&gt;&lt;span data-url=&quot;https://blog.kakaocdn.net/dn/bgeXBe/dJMcaaS3cEt/4A5n7dZHjpKeaY7h9D5KKK/img.png&quot; data-phocus=&quot;https://blog.kakaocdn.net/dn/bgeXBe/dJMcaaS3cEt/4A5n7dZHjpKeaY7h9D5KKK/img.png&quot;&gt;&lt;img src=&quot;https://blog.kakaocdn.net/dn/bgeXBe/dJMcaaS3cEt/4A5n7dZHjpKeaY7h9D5KKK/img.png&quot; srcset=&quot;https://img1.daumcdn.net/thumb/R1280x0/?scode=mtistory2&amp;fname=https%3A%2F%2Fblog.kakaocdn.net%2Fdn%2FbgeXBe%2FdJMcaaS3cEt%2F4A5n7dZHjpKeaY7h9D5KKK%2Fimg.png&quot; onerror=&quot;this.onerror=null; this.src='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png'; this.srcset='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png';&quot; loading=&quot;lazy&quot; width=&quot;1103&quot; height=&quot;441&quot; data-origin-width=&quot;1103&quot; data-origin-height=&quot;441&quot;/&gt;&lt;/span&gt;&lt;/figure&gt;
&lt;/p&gt;
&lt;h3 id=&quot;roadmap-checklist&quot; style=&quot;color: #000000; text-align: start;&quot; data-ke-size=&quot;size23&quot;&gt;3. Antigravity CLI 사용하기&lt;/h3&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;예를 들어 &quot;농수산물 판매 웹사이트를 만들어줘&quot; 라고 요청하면 오른쪽처럼&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;1916&quot; data-origin-height=&quot;1029&quot;&gt;&lt;span data-url=&quot;https://blog.kakaocdn.net/dn/IUVaw/dJMcabdh8zk/1WOv7palhchZ70DBEACeik/img.png&quot; data-phocus=&quot;https://blog.kakaocdn.net/dn/IUVaw/dJMcabdh8zk/1WOv7palhchZ70DBEACeik/img.png&quot;&gt;&lt;img src=&quot;https://blog.kakaocdn.net/dn/IUVaw/dJMcabdh8zk/1WOv7palhchZ70DBEACeik/img.png&quot; srcset=&quot;https://img1.daumcdn.net/thumb/R1280x0/?scode=mtistory2&amp;fname=https%3A%2F%2Fblog.kakaocdn.net%2Fdn%2FIUVaw%2FdJMcabdh8zk%2F1WOv7palhchZ70DBEACeik%2Fimg.png&quot; onerror=&quot;this.onerror=null; this.src='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png'; this.srcset='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png';&quot; loading=&quot;lazy&quot; width=&quot;1916&quot; height=&quot;1029&quot; data-origin-width=&quot;1916&quot; data-origin-height=&quot;1029&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;이제 여기에 조금더 DB 혹은 서버와 같은 부분을 요청하면 실제로 사용할 수도 있다.&lt;/p&gt;
&lt;p&gt;&lt;figure class=&quot;imageblock alignCenter&quot; data-ke-mobileStyle=&quot;widthOrigin&quot; data-origin-width=&quot;1920&quot; data-origin-height=&quot;1031&quot;&gt;&lt;span data-url=&quot;https://blog.kakaocdn.net/dn/vjisA/dJMcaiKfO88/Su46RWsSyCta4sgQuKBaV1/img.png&quot; data-phocus=&quot;https://blog.kakaocdn.net/dn/vjisA/dJMcaiKfO88/Su46RWsSyCta4sgQuKBaV1/img.png&quot;&gt;&lt;img src=&quot;https://blog.kakaocdn.net/dn/vjisA/dJMcaiKfO88/Su46RWsSyCta4sgQuKBaV1/img.png&quot; srcset=&quot;https://img1.daumcdn.net/thumb/R1280x0/?scode=mtistory2&amp;fname=https%3A%2F%2Fblog.kakaocdn.net%2Fdn%2FvjisA%2FdJMcaiKfO88%2FSu46RWsSyCta4sgQuKBaV1%2Fimg.png&quot; onerror=&quot;this.onerror=null; this.src='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png'; this.srcset='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png';&quot; loading=&quot;lazy&quot; width=&quot;1920&quot; height=&quot;1031&quot; data-origin-width=&quot;1920&quot; data-origin-height=&quot;1031&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;해당 부분은 꼭 Antigravity 로만 해야하는건 아니고, Cluade, Codex 등으로도 가능하다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;심지어 위 세가지 모두 md 파일이나, mcp 설정 등이 거의 유사하게 구성되고 있기 때문에&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;한 가지라도 깊게 공부해서 구조를 이해하면, 다른 ai 는 비교적 쉽게 사용할 수 있도록 변경되는 추이다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;다음번에는 MCP 와 슬래시 명령어 등을 활용하여 어떻게 AI 를 조금 더 커스터마이징 할 수 있는지,&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;조금 더 다양하게 활용할 수 있는 방안이 있는지에 대해서 포스팅을 진행해 보려고 한다.&lt;/p&gt;</description>
      <category>AI</category>
      <author>몽자비루</author>
      <guid isPermaLink="true">https://rusharp.tistory.com/211</guid>
      <comments>https://rusharp.tistory.com/211#entry211comment</comments>
      <pubDate>Tue, 2 Jun 2026 10:12:40 +0900</pubDate>
    </item>
    <item>
      <title>코드 한줄도 안보고  trello 자동화 만들기</title>
      <link>https://rusharp.tistory.com/210</link>
      <description>&lt;p data-ke-size=&quot;size16&quot;&gt;최근 AI 가 실무에서 적극적으로 도입되며 더이상 바이브코딩과 AI 를 사용한 효율성을 높이는 것이 불가능하지 않게 되었다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;심지어 Cluade code, Antigravity, Codex 가 치열하게 경쟁하면서 세가지 모두 코드 작성에 엄청난 효율을 보이고있다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;나는 세가지 모두 직/간접적으로 사용해보긴 했지만, 현재는 Google 의 Antigravity 를 메인으로 사용하고 있었다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;Antigravity 를 사용해 코드 한줄도 안보고 Google Calendar 자동화 만들기를 실제로 구현했는데,&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;2026.05.22 Antigravity 2.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;간단하게 이야기하자면 기존에는 VS Code 에 copilot 등을 추가했던 형식으로 Antigravity 가 구현되어 있었는데,&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;이번에는 Codex 와 비슷한 구조로, 채팅 인터페이스를 통해 개발을 완료할 수 있는 환경으로 변경되었다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;다만 기존대비 token 사용량이 어마어마하게 소모되면서 이 부분에 대한 해결을 어떻게 진행할지 지켜봐야 할것같다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;여하튼 이번에는 Antigarivy 를 사용하여 코드 한줄도 보지 않고 Trello 자동화를 만들어 보기로 했다.&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. MD 파일 생성하기&lt;/h3&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;먼저 해당 코드를 만들기 전에 md 파일을 만들었다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;md 파일을 생성한 이유는 단순히 방대한 API 문서를 전달하는 것을 넘어서,&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;Antigravity 에이전트가 코드를 작성할 때 지켜야 할 시스템 가이드라인을 명확히 정의하여&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://developer.atlassian.com/cloud/trello/rest/&quot; target=&quot;_blank&quot; rel=&quot;noopener&amp;nbsp;noreferrer&quot;&gt;https://developer.atlassian.com/cloud/trello/rest/&lt;/a&gt;&lt;br /&gt;위 사이트에서 Download OpenAPI definition 을 통해 Trello API 문서를 다운로드 하여 해당 폴더에 넣고&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;채팅창에 아래 두개의 프롬프트를 입력하여 자동으로 md 파일을 생성하게 만들었다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;두번째 프롬프트는 Gemini 에게 md파일 및 해당 경로 내의 모든 파일을 입력한 뒤 보완할 부분을 제공받았다.&lt;/p&gt;
&lt;pre id=&quot;code_1779707705656&quot; class=&quot;erlang&quot; style=&quot;background-color: #f8f8f8; color: #383a42; text-align: start;&quot; data-ke-type=&quot;codeblock&quot; data-ke-language=&quot;python&quot;&gt;&lt;code&gt;해당 workspace 에서는 trello 의 자동화 도구를 만들 예정입니다.

https://developer.atlassian.com/cloud/trello/rest/api-group-actions/

의 내용을 참고하고 이를 기반으로 제가 원하는 내용을 입력하면 자동으로 자동화 도구를 생성하는 역할을 합니다.

1. 데이터를 기반으로 자동화 도구를 생성합니다.
2. 해당 자동화 도구를 실행하고 동작상의 오류가 없는지 파악합니다.
3. 오류가 있다면 해당 자동화 도구를 수정하고 2번으로 다시 이동합니다.

위 내용을 완성할 때까지 반복합니다. 

C:\Users\owner\Documents\antigravity\trello_automation\trello_api 

에 OpenAPI Definition 을 저장해두었으며, 이를 참고하도록 &quot;md 파일&quot; 을 만들고자 합니다.&lt;/code&gt;&lt;/pre&gt;
&lt;pre id=&quot;code_1779707711914&quot; class=&quot;python&quot; style=&quot;background-color: #f8f8f8; color: #383a42; text-align: start;&quot; data-ke-type=&quot;codeblock&quot; data-ke-language=&quot;python&quot;&gt;&lt;code&gt;md 파일에 아래 부분을 추가하여 작성해주세요.

1. 컨텍스트 크기 관리 (229KB 파일 처리)
trello_api_reference.md 파일의 크기가 229KB입니다. LLM 모델이 이를 한 번에 읽고 처리할 수는 있지만, 매번 전체 문서를 읽으면 응답 속도가 느려지고 할루시네이션(환각)이 발생할 확률이 높아집니다.

보완책: 에이전트에게 &quot;전체 문서를 한 번에 맹목적으로 읽지 말고, 사용자가 요구한 작업(예: 카드 생성)과 관련된 '목차(Table of Contents)'와 '해당 API 엔드포인트' 부분만 선택적으로 검색해서 참조하라&quot;는 지침을 추가해야 합니다.

2. 보안 및 인증 정보 하드코딩 방지
자동화 스크립트를 생성할 때 API Key와 Token이 코드 내부에 평문으로 노출되는 것을 막아야 합니다.

보완책: 스크립트 생성 시 반드시 .env 파일을 활용한 환경 변수 로드 방식(예: Node.js의 dotenv, Python의 os.environ)을 사용하도록 강제해야 합니다.

3. 디버깅을 위한 로깅(Logging) 표준화
'오류가 있다면 수정하고 다시 실행'하는 자동 루프가 원활하게 작동하려면, 에이전트가 실패 원인을 정확히 파악할 수 있어야 합니다.

보완책: 에이전트가 코드를 작성할 때, API 요청 실패 시 HTTP 상태 코드와 응답 Body의 에러 메시지를 상세하게 콘솔에 출력(console.error 또는 logging.error)하도록 코딩 규칙을 명시해야 합니다.

4. 기술 스택 확정
Python 을 사용합니다.&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;2. 수동이 필수 환경 세팅하기&lt;/h3&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;그 다음에 가볍게 Board CRUD 를 테스트 하려고 하니까 기능이 정상적으로 동작하지 않았다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;그래서 &quot;어디에서 막히는지&quot; 에 대해 물어봤을 때, API Key 와 Token 이 없어 테스트가 불가능한 상태였다.&lt;/p&gt;
&lt;p&gt;&lt;figure class=&quot;imageblock alignCenter&quot; data-ke-mobileStyle=&quot;widthOrigin&quot; data-origin-width=&quot;719&quot; data-origin-height=&quot;556&quot;&gt;&lt;span data-url=&quot;https://blog.kakaocdn.net/dn/b1n2iA/dJMcaak5Scp/ahyQ64fOSRx3931yCl6KTk/img.png&quot; data-phocus=&quot;https://blog.kakaocdn.net/dn/b1n2iA/dJMcaak5Scp/ahyQ64fOSRx3931yCl6KTk/img.png&quot;&gt;&lt;img src=&quot;https://blog.kakaocdn.net/dn/b1n2iA/dJMcaak5Scp/ahyQ64fOSRx3931yCl6KTk/img.png&quot; srcset=&quot;https://img1.daumcdn.net/thumb/R1280x0/?scode=mtistory2&amp;fname=https%3A%2F%2Fblog.kakaocdn.net%2Fdn%2Fb1n2iA%2FdJMcaak5Scp%2FahyQ64fOSRx3931yCl6KTk%2Fimg.png&quot; onerror=&quot;this.onerror=null; this.src='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png'; this.srcset='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png';&quot; loading=&quot;lazy&quot; width=&quot;719&quot; height=&quot;556&quot; data-origin-width=&quot;719&quot; data-origin-height=&quot;556&quot;/&gt;&lt;/span&gt;&lt;/figure&gt;
&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;그래서 아래와 같은 스텝을 통해 API-Key 와 Token 을 발급하여 제공했다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;a href=&quot;https://trello.com/power-ups/admin&quot; target=&quot;_blank&quot; rel=&quot;noopener&amp;nbsp;noreferrer&quot;&gt;https://trello.com/power-ups/admin&lt;/a&gt;&lt;/p&gt;
&lt;p&gt;&lt;figure class=&quot;imageblock alignCenter&quot; data-ke-mobileStyle=&quot;widthOrigin&quot; data-origin-width=&quot;951&quot; data-origin-height=&quot;592&quot;&gt;&lt;span data-url=&quot;https://blog.kakaocdn.net/dn/cCErNB/dJMcafzYrWk/Uiq6YxsDkWc8hCNnnPWMbK/img.png&quot; data-phocus=&quot;https://blog.kakaocdn.net/dn/cCErNB/dJMcafzYrWk/Uiq6YxsDkWc8hCNnnPWMbK/img.png&quot;&gt;&lt;img src=&quot;https://blog.kakaocdn.net/dn/cCErNB/dJMcafzYrWk/Uiq6YxsDkWc8hCNnnPWMbK/img.png&quot; srcset=&quot;https://img1.daumcdn.net/thumb/R1280x0/?scode=mtistory2&amp;fname=https%3A%2F%2Fblog.kakaocdn.net%2Fdn%2FcCErNB%2FdJMcafzYrWk%2FUiq6YxsDkWc8hCNnnPWMbK%2Fimg.png&quot; onerror=&quot;this.onerror=null; this.src='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png'; this.srcset='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png';&quot; loading=&quot;lazy&quot; width=&quot;951&quot; height=&quot;592&quot; data-origin-width=&quot;951&quot; data-origin-height=&quot;592&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;841&quot; data-origin-height=&quot;825&quot;&gt;&lt;span data-url=&quot;https://blog.kakaocdn.net/dn/btzSjb/dJMcab5oiGl/BB5I9M6tLmIrY7aKvBZyxK/img.png&quot; data-phocus=&quot;https://blog.kakaocdn.net/dn/btzSjb/dJMcab5oiGl/BB5I9M6tLmIrY7aKvBZyxK/img.png&quot;&gt;&lt;img src=&quot;https://blog.kakaocdn.net/dn/btzSjb/dJMcab5oiGl/BB5I9M6tLmIrY7aKvBZyxK/img.png&quot; srcset=&quot;https://img1.daumcdn.net/thumb/R1280x0/?scode=mtistory2&amp;fname=https%3A%2F%2Fblog.kakaocdn.net%2Fdn%2FbtzSjb%2FdJMcab5oiGl%2FBB5I9M6tLmIrY7aKvBZyxK%2Fimg.png&quot; onerror=&quot;this.onerror=null; this.src='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png'; this.srcset='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png';&quot; loading=&quot;lazy&quot; width=&quot;841&quot; height=&quot;825&quot; data-origin-width=&quot;841&quot; data-origin-height=&quot;825&quot;/&gt;&lt;/span&gt;&lt;/figure&gt;
&lt;/p&gt;
&lt;table style=&quot;text-align: start; 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;text-align: left; width: 19.8838%;&quot;&gt;Name&lt;/td&gt;
&lt;td style=&quot;text-align: left; width: 79.8837%;&quot;&gt;앱 이름.&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td style=&quot;text-align: left; width: 19.8838%;&quot;&gt;Workspace&lt;/td&gt;
&lt;td style=&quot;text-align: left; width: 79.8837%;&quot;&gt;해당 앱이 속한 Trello 워크스페이스&lt;br /&gt;파워업의 경우, 선택한 워크스페이스의 보드에서 파워업을 사용할 수 있다.&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td style=&quot;text-align: left; width: 19.8838%;&quot;&gt;Email&lt;/td&gt;
&lt;td style=&quot;text-align: left; width: 79.8837%;&quot;&gt;이 앱과 관련하여 연락받을 수 있는 이메일 주소.&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td style=&quot;text-align: left; width: 19.8838%;&quot;&gt;Support Email&lt;/td&gt;
&lt;td style=&quot;text-align: left; width: 79.8837%;&quot;&gt;사용자로부터 앱 관련 문의 사항이 발생하거나 플랫폼 변경 사항과 관련하여 연락이 필요한 경우 사용할 이메일 주소&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td style=&quot;text-align: left; width: 19.8838%;&quot;&gt;Author&lt;/td&gt;
&lt;td style=&quot;text-align: left; width: 79.8837%;&quot;&gt;디렉토리 및 권한 부여 화면에서 작성자로 표시될 이름.&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td style=&quot;text-align: left; width: 19.8838%;&quot;&gt;iframe Connector URL&lt;/td&gt;
&lt;td style=&quot;text-align: left; width: 79.8837%;&quot;&gt;앱이 파워업 기능을 사용하는 경우, 해당 URL은 파워업이 활성화될 때 로드될 iframe 커넥터에 사용된다.&lt;br /&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;&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;902&quot; data-origin-height=&quot;427&quot;&gt;&lt;span data-url=&quot;https://blog.kakaocdn.net/dn/cxNn5B/dJMcabK9rGQ/RLEFjoAB0n6t4gxD04cPR0/img.png&quot; data-phocus=&quot;https://blog.kakaocdn.net/dn/cxNn5B/dJMcabK9rGQ/RLEFjoAB0n6t4gxD04cPR0/img.png&quot;&gt;&lt;img src=&quot;https://blog.kakaocdn.net/dn/cxNn5B/dJMcabK9rGQ/RLEFjoAB0n6t4gxD04cPR0/img.png&quot; srcset=&quot;https://img1.daumcdn.net/thumb/R1280x0/?scode=mtistory2&amp;fname=https%3A%2F%2Fblog.kakaocdn.net%2Fdn%2FcxNn5B%2FdJMcabK9rGQ%2FRLEFjoAB0n6t4gxD04cPR0%2Fimg.png&quot; onerror=&quot;this.onerror=null; this.src='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png'; this.srcset='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png';&quot; loading=&quot;lazy&quot; width=&quot;902&quot; height=&quot;427&quot; data-origin-width=&quot;902&quot; data-origin-height=&quot;427&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;1190&quot; data-origin-height=&quot;553&quot;&gt;&lt;span data-url=&quot;https://blog.kakaocdn.net/dn/cpeBX0/dJMcahLjMhq/pp2nkclbBckM3ikNPEDGB1/img.png&quot; data-phocus=&quot;https://blog.kakaocdn.net/dn/cpeBX0/dJMcahLjMhq/pp2nkclbBckM3ikNPEDGB1/img.png&quot;&gt;&lt;img src=&quot;https://blog.kakaocdn.net/dn/cpeBX0/dJMcahLjMhq/pp2nkclbBckM3ikNPEDGB1/img.png&quot; srcset=&quot;https://img1.daumcdn.net/thumb/R1280x0/?scode=mtistory2&amp;fname=https%3A%2F%2Fblog.kakaocdn.net%2Fdn%2FcpeBX0%2FdJMcahLjMhq%2Fpp2nkclbBckM3ikNPEDGB1%2Fimg.png&quot; onerror=&quot;this.onerror=null; this.src='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png'; this.srcset='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png';&quot; loading=&quot;lazy&quot; width=&quot;1190&quot; height=&quot;553&quot; data-origin-width=&quot;1190&quot; data-origin-height=&quot;553&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; API Key 와 Secret key 를 받은 뒤 이를 프롬프트로 다시 던져주는데,&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://trello.com/1/authorize?expiration=1day&amp;amp;scope=read&amp;amp;response_type=token&amp;amp;key=&quot;&gt;https://trello.com/1/authorize?expiration=1day&amp;amp;scope=read&amp;amp;response_type=token&amp;amp;key=&lt;/a&gt;&lt;span style=&quot;color: #ee2323;&quot;&gt;&lt;a style=&quot;color: #ee2323; text-align: start;&quot; href=&quot;https://trello.com/1/authorize?expiration=1day&amp;amp;scope=read&amp;amp;response_type=token&amp;amp;key=%7BYourAPIKey%7D&quot;&gt;{API_KEY}&lt;/a&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;495&quot; data-origin-height=&quot;321&quot;&gt;&lt;span data-url=&quot;https://blog.kakaocdn.net/dn/HRbe1/dJMcageztvl/5ZUpsqWGkRXpI0JthaY0a1/img.png&quot; data-phocus=&quot;https://blog.kakaocdn.net/dn/HRbe1/dJMcageztvl/5ZUpsqWGkRXpI0JthaY0a1/img.png&quot;&gt;&lt;img src=&quot;https://blog.kakaocdn.net/dn/HRbe1/dJMcageztvl/5ZUpsqWGkRXpI0JthaY0a1/img.png&quot; srcset=&quot;https://img1.daumcdn.net/thumb/R1280x0/?scode=mtistory2&amp;fname=https%3A%2F%2Fblog.kakaocdn.net%2Fdn%2FHRbe1%2FdJMcageztvl%2F5ZUpsqWGkRXpI0JthaY0a1%2Fimg.png&quot; onerror=&quot;this.onerror=null; this.src='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png'; this.srcset='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png';&quot; loading=&quot;lazy&quot; width=&quot;495&quot; height=&quot;321&quot; data-origin-width=&quot;495&quot; data-origin-height=&quot;321&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;그다음에 CRUD 를 진행하기 위한 read/write 권한을 허용해준다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;a href=&quot;https://trello.com/1/authorize?expiration=never&amp;amp;scope=read,write,account&amp;amp;response_type=token&amp;amp;key=&quot; target=&quot;_blank&quot; rel=&quot;noopener&quot;&gt;https://trello.com/1/authorize?expiration=never&amp;amp;scope=read,write,account&amp;amp;response_type=token&amp;amp;key=&lt;/a&gt;&lt;span style=&quot;color: #ee2323;&quot;&gt;{API_KEY}&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;770&quot; data-origin-height=&quot;289&quot;&gt;&lt;span data-url=&quot;https://blog.kakaocdn.net/dn/bgnIj3/dJMcaipUXBn/Ju7RQFlBc7xnvy91KBOCDk/img.png&quot; data-phocus=&quot;https://blog.kakaocdn.net/dn/bgnIj3/dJMcaipUXBn/Ju7RQFlBc7xnvy91KBOCDk/img.png&quot;&gt;&lt;img src=&quot;https://blog.kakaocdn.net/dn/bgnIj3/dJMcaipUXBn/Ju7RQFlBc7xnvy91KBOCDk/img.png&quot; srcset=&quot;https://img1.daumcdn.net/thumb/R1280x0/?scode=mtistory2&amp;fname=https%3A%2F%2Fblog.kakaocdn.net%2Fdn%2FbgnIj3%2FdJMcaipUXBn%2FJu7RQFlBc7xnvy91KBOCDk%2Fimg.png&quot; onerror=&quot;this.onerror=null; this.src='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png'; this.srcset='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png';&quot; loading=&quot;lazy&quot; width=&quot;770&quot; height=&quot;289&quot; data-origin-width=&quot;770&quot; data-origin-height=&quot;289&quot;/&gt;&lt;/span&gt;&lt;/figure&gt;
&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;이후에 나오는 Key 를 antigravity 에 제공하면 자동으로 .env 파일에 해당 key를 사용하게 된다.&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;3. 자연어를 사용하여 자동화 도구 생성하기.&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&gt;&lt;figure class=&quot;imageblock alignCenter&quot; data-ke-mobileStyle=&quot;widthOrigin&quot; data-origin-width=&quot;1920&quot; data-origin-height=&quot;1040&quot;&gt;&lt;span data-url=&quot;https://blog.kakaocdn.net/dn/SrDKK/dJMcaf7LkM0/a2Jca5eJ4vulZK3ZNNhC7k/img.png&quot; data-phocus=&quot;https://blog.kakaocdn.net/dn/SrDKK/dJMcaf7LkM0/a2Jca5eJ4vulZK3ZNNhC7k/img.png&quot;&gt;&lt;img src=&quot;https://blog.kakaocdn.net/dn/SrDKK/dJMcaf7LkM0/a2Jca5eJ4vulZK3ZNNhC7k/img.png&quot; srcset=&quot;https://img1.daumcdn.net/thumb/R1280x0/?scode=mtistory2&amp;fname=https%3A%2F%2Fblog.kakaocdn.net%2Fdn%2FSrDKK%2FdJMcaf7LkM0%2Fa2Jca5eJ4vulZK3ZNNhC7k%2Fimg.png&quot; onerror=&quot;this.onerror=null; this.src='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png'; this.srcset='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png';&quot; loading=&quot;lazy&quot; width=&quot;1920&quot; height=&quot;1040&quot; data-origin-width=&quot;1920&quot; data-origin-height=&quot;1040&quot;/&gt;&lt;/span&gt;&lt;/figure&gt;
&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;그렇다면 본격적으로 이러한 기능을 통해 자동화를 자동으로 만들 수 있지 않을까?&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;보통 API 테스트에서 기본적으로 CRUD 를 테스트하곤 하는데,&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;새나티 테스트를 진행하기 위해 기본적인 API CRUD 테스트 도구를 생성한다고 가정했을 때,&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;1638&quot; data-origin-height=&quot;900&quot;&gt;&lt;span data-url=&quot;https://blog.kakaocdn.net/dn/u04B2/dJMcadhRaXL/CfXcz0ndeHkY5sZTiY7FK0/img.png&quot; data-phocus=&quot;https://blog.kakaocdn.net/dn/u04B2/dJMcadhRaXL/CfXcz0ndeHkY5sZTiY7FK0/img.png&quot;&gt;&lt;img src=&quot;https://blog.kakaocdn.net/dn/u04B2/dJMcadhRaXL/CfXcz0ndeHkY5sZTiY7FK0/img.png&quot; srcset=&quot;https://img1.daumcdn.net/thumb/R1280x0/?scode=mtistory2&amp;fname=https%3A%2F%2Fblog.kakaocdn.net%2Fdn%2Fu04B2%2FdJMcadhRaXL%2FCfXcz0ndeHkY5sZTiY7FK0%2Fimg.png&quot; onerror=&quot;this.onerror=null; this.src='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png'; this.srcset='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png';&quot; loading=&quot;lazy&quot; width=&quot;1638&quot; height=&quot;900&quot; data-origin-width=&quot;1638&quot; data-origin-height=&quot;900&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;이걸 테스트하는 방법은 아래와 같이 pass/fail 여부를 상세하게 요청하면 어느정도의 내용이 출력된다.&lt;/p&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;email 로 초대 메일이 매번 수신되기 때문에 정상 동작하는 것을 눈으로 확인할 수 있었다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;이번 Trello 자동화의 핵심은 코드를 단 한 줄도 확인하거나 수정하지 않고,&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;오직 자연어 명령과 초기 환경 세팅만으로 실제 구동되는 자동화 도구를 완성했다는 점에 있다. &lt;br /&gt;&lt;br /&gt;기존에는 AI가 코드를 짜주더라도 결국 사람이 편집기를 열고 코드를 복사&amp;middot;붙여넣기 하거나&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;에러를 직접 확인하며 디버깅해야 했는데 최근의 에이전트 환경에서는 AI가 API 문서를 직접 읽고,&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;스스로 실행 루프를 돌며, 발생한 에러를 판단해 코드를 고치는 전 과정을 알아서 처리한다. &lt;br /&gt;&lt;br /&gt;물론 자율 루프가 반복되는 만큼 토큰 소모량이 심해 비용적인 모니터링은 필요하겠지만,&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;'코드 편집창을 아예 보지 않는 개발 방식'이 현실화되었다는 의의는 크다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;이제 생산성의 기준은 코딩 실력이 아니라, AI 에이전트가 헤매지 않도록&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;명확한 규칙(MD 파일)을 제시하고 통제하는 기획 및 검증 능력으로 이동하고 있다.&lt;/p&gt;</description>
      <category>AI</category>
      <author>몽자비루</author>
      <guid isPermaLink="true">https://rusharp.tistory.com/210</guid>
      <comments>https://rusharp.tistory.com/210#entry210comment</comments>
      <pubDate>Mon, 25 May 2026 23:35:44 +0900</pubDate>
    </item>
    <item>
      <title>Playwright 핵심 사용법 정리</title>
      <link>https://rusharp.tistory.com/209</link>
      <description>&lt;p data-ke-size=&quot;size16&quot;&gt;Selenium 은 브라우저를 제어하여 자동으로 테스트를 진행하는 도구이다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;하지만 최근에 Playwright 를 사용하는 환경도 많은데 두 도구의 차이와 기본 사용방법을 알아보려 한다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;먼저 Selenium 은 W3C WebDriver 프로토콜을 사용하기 때문에 코드가 실행될 때마다&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;HTTP 요청을 보내고 응답을 받는 방식으로, 각 명령마다 네트워크 오버헤드가 발생한다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;반면 Playwright 는 WebSocket 을 활용하여 브라우저와 단일 연결을 맺고 양방향 통신을 수행한다.&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;Playwright 의 딱 하나 단점이 있다면, 최신 모던 브라우저 엔진만 지원하기 때문에&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;레거시 브라우저에서 테스트를 해야만 한다면 Selenium 을 활용하여&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;브라우저 버전에 맞는 WebDriver&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;그럼 이러한 특징을 가지고 있는 playwright의 기본 사용법을 알아보려고 한다.&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. 브라우저 &amp;amp; 페이지 기본 설정&lt;/h3&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;여기에서 Chromium, WebKit, Firefox 엔진을 선택하여 실행할 수 있고, 브라우저 크기도 설정할 수 있다.&amp;nbsp;&lt;/p&gt;
&lt;pre id=&quot;code_1774268988397&quot; class=&quot;python&quot; data-ke-language=&quot;python&quot; data-ke-type=&quot;codeblock&quot;&gt;&lt;code&gt;def demo_browser_setup() -&amp;gt; None:
    &quot;&quot;&quot;브라우저 실행부터 종료까지의 기본 흐름.&quot;&quot;&quot;
    with sync_playwright() as pw:
        # Chromium / Firefox / Webkit 선택 가능
        browser = pw.chromium.launch(
            headless=False,   # True: 화면 없이 실행 (CI 환경), False: 화면 표시
            slow_mo=50,        # 각 동작 사이 50ms 딜레이 (디버깅용)
        )

        # 새 탭(페이지) 생성
        page = browser.new_page(
            viewport={&quot;width&quot;: 1920, &quot;height&quot;: 1080},   # 해상도 설정
        )

        # 이후 동작들 ...

        page.close()
        browser.close()&lt;/code&gt;&lt;/pre&gt;
&lt;h3 data-ke-size=&quot;size23&quot;&gt;2. 페이지 접속 (Navigation)&lt;/h3&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;두번째로 페이지 이동 및 로딩 대기방법이다. page.goto()를 사용하여 페이지로 이동할 수 있고,&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;타임아웃 시간을 명시적으로 작성하거나 네트워크 상태에 따라 로딩 대기 방법을 선택할 수 있다.&amp;nbsp;&lt;/p&gt;
&lt;pre id=&quot;code_1774269803462&quot; class=&quot;python&quot; data-ke-language=&quot;python&quot; data-ke-type=&quot;codeblock&quot;&gt;&lt;code&gt;def demo_navigation(page: Page) -&amp;gt; None:
    &quot;&quot;&quot;URL 이동 및 페이지 로딩 대기.&quot;&quot;&quot;

    # 기본 접속
    page.goto(&quot;https://blaze.qa.oncloud.hanwhavision.cloud/&quot;)

    # 타임아웃 지정 (ms 단위, 기본값 30_000ms = 30초)
    page.goto(&quot;https://blaze.qa.oncloud.hanwhavision.cloud/&quot;, timeout=60_000)

    # ── 페이지 로딩 완료 기준 설정 ──────────────────────────────────
    # &quot;load&quot;        : window.onload 이벤트 발생 시 (기본값)
    # &quot;domcontentloaded&quot; : HTML 파싱 완료 시 (JS 실행 전)
    # &quot;networkidle&quot; : 500ms 동안 네트워크 요청이 없을 때 (SPA에서 유용)
    page.goto(&quot;https://blaze.qa.oncloud.hanwhavision.cloud/&quot;, wait_until=&quot;networkidle&quot;)

    # 뒤로 / 앞으로 / 새로고침
    page.go_back()
    page.go_forward()
    page.reload()

    # 현재 URL 확인
    print(page.url)&lt;/code&gt;&lt;/pre&gt;
&lt;h3 data-ke-size=&quot;size23&quot;&gt;3. 요소 찾기 (Locator)&amp;nbsp;&lt;/h3&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;다음으로는 요소를 탐색하는 방법이다. Playwright 는 사용자 중심의 로케이터 사용을 권장한다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;CSS 선택자나 XPath 는 유지보수에 취약하기 때문에 최후의 수단으로 사용하는 것이 좋다.&lt;/p&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 selector 을 자주 사용하는 편이다.&lt;/p&gt;
&lt;pre id=&quot;code_1774271408275&quot; class=&quot;python&quot; data-ke-language=&quot;python&quot; data-ke-type=&quot;codeblock&quot;&gt;&lt;code&gt;def demo_locators(page: Page) -&amp;gt; None:
    &quot;&quot;&quot;User-facing 로케이터 우선 사용 (XPath / CSS는 최후 수단).&quot;&quot;&quot;

    # ── 권장 로케이터 ────────────────────────────────────────────────

    # role: 버튼, 링크, 체크박스 등 ARIA 역할로 찾기
    btn: Locator = page.get_by_role(&quot;button&quot;, name=&quot;로그인&quot;)
    link: Locator = page.get_by_role(&quot;link&quot;, name=&quot;대시보드&quot;)
    checkbox: Locator = page.get_by_role(&quot;checkbox&quot;, name=&quot;기억하기&quot;)

    # label: &amp;lt;label&amp;gt;과 연결된 입력 필드 찾기
    email_input: Locator = page.get_by_label(&quot;이메일&quot;)
    password_input: Locator = page.get_by_label(&quot;비밀번호&quot;)

    # placeholder: 입력창의 placeholder 텍스트로 찾기
    search_input: Locator = page.get_by_placeholder(&quot;검색어를 입력하세요&quot;)

    # text: 화면에 보이는 텍스트로 찾기
    title: Locator = page.get_by_text(&quot;환영합니다&quot;)
    exact_title: Locator = page.get_by_text(&quot;환영합니다&quot;, exact=True)  # 정확히 일치

    # test-id: data-testid 속성으로 찾기 (개발자와 협의 필요)
    menu: Locator = page.get_by_test_id(&quot;main-menu&quot;)

    # ── 차선 로케이터 (꼭 필요한 경우만) ────────────────────────────

    # CSS selector
    header: Locator = page.locator(&quot;h1.page-title&quot;)

    # XPath (가장 최후 수단)
    item: Locator = page.locator(&quot;//ul[@id='list']/li[1]&quot;)

    # ── 로케이터 조합 (필터링) ────────────────────────────────────────

    # 특정 텍스트를 포함하는 버튼만 필터링
    delete_btn: Locator = page.get_by_role(&quot;button&quot;).filter(has_text=&quot;삭제&quot;)

    # 부모 요소 안에서 자식 요소 찾기
    card = page.locator(&quot;.card&quot;).filter(has_text=&quot;카메라 01&quot;)
    card_btn: Locator = card.get_by_role(&quot;button&quot;, name=&quot;편집&quot;)

    # 여러 개일 때 인덱스로 지정 (0-based)
    first_row: Locator = page.locator(&quot;table tr&quot;).nth(1)   # 첫 번째 데이터 행
    last_row: Locator = page.locator(&quot;table tr&quot;).last&lt;/code&gt;&lt;/pre&gt;
&lt;h3 data-ke-size=&quot;size23&quot;&gt;4. 상호작용&amp;nbsp;&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;&amp;nbsp;또한 텍스트 입력도 단축키나 특수키도 입력할 수 있고 위의 요소찾기와 대부분 짝으로 사용된다.&lt;/p&gt;
&lt;pre id=&quot;code_1774270904886&quot; class=&quot;python&quot; data-ke-language=&quot;python&quot; data-ke-type=&quot;codeblock&quot;&gt;&lt;code&gt;def demo_click(page: Page) -&amp;gt; None:
    &quot;&quot;&quot;다양한 클릭 동작.&quot;&quot;&quot;

    btn = page.get_by_role(&quot;button&quot;, name=&quot;저장&quot;)

    # 기본 클릭
    btn.click()

    # 더블 클릭
    btn.dblclick()

    # 우클릭 (컨텍스트 메뉴)
    btn.click(button=&quot;right&quot;)

    # 특정 좌표 클릭 (요소 내 상대 위치)
    btn.click(position={&quot;x&quot;: 10, &quot;y&quot;: 5})

    # 클릭 전 강제 표시 여부 무시 (숨겨진 요소 강제 클릭, 사용 자제)
    btn.click(force=True)

    # 키를 누른 상태로 클릭
    btn.click(modifiers=[&quot;Shift&quot;])  # Shift+클릭
    btn.click(modifiers=[&quot;Meta&quot;])   # Cmd+클릭 (macOS)
    
    # 드래그앤 드롭
    page.drag_and_drop(&quot;#source&quot;, &quot;#target&quot;)

def demo_input(page: Page) -&amp;gt; None:
    &quot;&quot;&quot;텍스트 필드 입력 방법.&quot;&quot;&quot;

    email = page.get_by_label(&quot;이메일&quot;)
    password = page.get_by_label(&quot;비밀번호&quot;)

    # fill: 기존 값을 지우고 새 값 입력 &amp;larr; 일반적으로 이걸 사용
    email.fill(&quot;test@example.com&quot;)
    password.fill(&quot;password123&quot;)

    # clear: 입력값 지우기
    email.clear()

    # type: 키보드 타이핑처럼 한 글자씩 입력 (특수 입력 로직 트리거 필요 시)
    email.type(&quot;test@example.com&quot;, delay=50)   # 50ms 간격으로 입력

    # 키보드 단축키 / 특수 키 입력
    email.press(&quot;Tab&quot;)              # Tab 키
    email.press(&quot;Enter&quot;)            # Enter 키
    email.press(&quot;Control+a&quot;)        # Ctrl+A (전체선택)
    email.press(&quot;Backspace&quot;)        # Backspace

    # 드롭다운(select) 선택
    dropdown = page.get_by_label(&quot;역할&quot;)
    dropdown.select_option(&quot;admin&quot;)                      # value로 선택
    dropdown.select_option(label=&quot;관리자&quot;)               # 표시 텍스트로 선택
    dropdown.select_option(index=1)                      # 인덱스로 선택

    # 체크박스 토글
    checkbox = page.get_by_label(&quot;이용약관 동의&quot;)
    checkbox.check()    # 체크
    checkbox.uncheck()  # 체크 해제&lt;/code&gt;&lt;/pre&gt;
&lt;h3 data-ke-size=&quot;size23&quot;&gt;5. 대기 (Wait)&lt;/h3&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;Playwright는 기본적으로 요소가 상호작용 가능해질 때까지 자동 대기(Auto-waiting)를 수행한다.&lt;/p&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;pre id=&quot;code_1774271137444&quot; class=&quot;python&quot; data-ke-language=&quot;python&quot; data-ke-type=&quot;codeblock&quot;&gt;&lt;code&gt;def demo_wait(page: Page) -&amp;gt; None:
    &quot;&quot;&quot;요소 및 상태 대기 방법 총정리.&quot;&quot;&quot;

    # ── A. expect() &amp;mdash; 권장 방식 ──────────────────────────────────────
    # Playwright 내장 Auto-retry 포함. 조건이 충족될 때까지 자동 재시도.
    # 기본 타임아웃: pytest-playwright 기본값 5_000ms

    locator = page.get_by_role(&quot;button&quot;, name=&quot;저장&quot;)

    expect(locator).to_be_visible()           # 화면에 표시될 때까지
    expect(locator).to_be_enabled()           # 클릭 가능한 상태가 될 때까지
    expect(locator).to_be_hidden()            # 숨겨질 때까지
    expect(locator).to_be_checked()           # 체크박스가 체크될 때까지
    expect(locator).to_have_text(&quot;완료&quot;)      # 텍스트가 일치할 때까지
    expect(locator).to_have_value(&quot;입력값&quot;)   # input value가 일치할 때까지
    expect(locator).to_have_count(5)          # 요소 개수가 일치할 때까지
    expect(page).to_have_url(&quot;https://example.com/dashboard&quot;)  # URL 확인
    expect(page).to_have_title(&quot;대시보드&quot;)    # 페이지 타이틀 확인

    # 타임아웃 직접 지정
    expect(locator).to_be_visible(timeout=10_000)   # 10초 대기

    # ── B. wait_for() &amp;mdash; Locator 레벨 대기 ──────────────────────────
    # expect()가 어려운 상황에서 사용

    locator.wait_for(state=&quot;visible&quot;)    # 표시될 때까지
    locator.wait_for(state=&quot;hidden&quot;)     # 숨겨질 때까지
    locator.wait_for(state=&quot;attached&quot;)   # DOM에 추가될 때까지
    locator.wait_for(state=&quot;detached&quot;)   # DOM에서 제거될 때까지

    # ── C. page.wait_for_*() &amp;mdash; 페이지 레벨 대기 ─────────────────────

    # 페이지 로딩 상태 대기
    page.wait_for_load_state(&quot;load&quot;)             # window.onload (기본값)
    page.wait_for_load_state(&quot;domcontentloaded&quot;) # DOM 파싱 완료
    page.wait_for_load_state(&quot;networkidle&quot;)      # 네트워크 유휴 상태 (SPA에 적합)

    # 특정 URL 로 이동할 때까지 대기
    page.wait_for_url(&quot;**/dashboard&quot;)            # glob 패턴 지원
    page.wait_for_url(&quot;https://example.com/dashboard&quot;, timeout=10_000)

    # 특정 요소가 DOM에 나타날 때까지 대기 (CSS selector)
    page.wait_for_selector(&quot;.loading-spinner&quot;, state=&quot;hidden&quot;)   # 로딩 스피너 사라질 때까지
    page.wait_for_selector(&quot;#result-table&quot;, state=&quot;visible&quot;)     # 결과 테이블 나타날 때까지

    # 특정 시간 강제 대기 (가능하면 사용 자제 &amp;mdash; 테스트 불안정 원인)
    page.wait_for_timeout(2_000)   # 2초 고정 대기&lt;/code&gt;&lt;/pre&gt;
&lt;h3 data-ke-size=&quot;size23&quot;&gt;&lt;br /&gt;6. 팝업 / 다이얼로그 / 새 탭 처리&lt;/h3&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;이를 가로채서 처리할 이벤트 리스너를 미리 등록(page.on)하는 방식으로 제어한다.&lt;/p&gt;
&lt;pre id=&quot;code_1774271450930&quot; class=&quot;python&quot; data-ke-language=&quot;python&quot; data-ke-type=&quot;codeblock&quot;&gt;&lt;code&gt;def demo_dialog_and_popup(page: Page) -&amp;gt; None:
    &quot;&quot;&quot;브라우저 기본 다이얼로그 및 새 탭 처리.&quot;&quot;&quot;

    # ── alert / confirm / prompt 처리 ────────────────────────────────
    # 이벤트 리스너를 클릭 '이전'에 등록해야 함
    page.on(&quot;dialog&quot;, lambda dialog: dialog.accept())          # 확인 클릭
    # page.on(&quot;dialog&quot;, lambda dialog: dialog.dismiss())       # 취소 클릭
    # page.on(&quot;dialog&quot;, lambda dialog: dialog.accept(&quot;입력값&quot;))# prompt 입력 후 확인

    page.get_by_role(&quot;button&quot;, name=&quot;삭제&quot;).click()

    # ── 새 탭(popup) 처리 ────────────────────────────────────────────
    with page.expect_popup() as popup_info:
        page.get_by_role(&quot;link&quot;, name=&quot;새 창으로 열기&quot;).click()
    new_tab = popup_info.value
    new_tab.wait_for_load_state(&quot;networkidle&quot;)
    print(new_tab.url)
    new_tab.close()&lt;/code&gt;&lt;/pre&gt;
&lt;h3 data-ke-size=&quot;size23&quot;&gt;&lt;br /&gt;7. 검증 (Assertion)&lt;/h3&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;다음으로 검증에 대해 이야기할 수 있는데, 테스트 과정 중 UI가 의도한 상태로 변경되었는지 확인하고,&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;해당 시점의 상태를 확실히 보장하여 테스트의 신뢰성을 높이기 위해 사용한다.&lt;/p&gt;
&lt;pre id=&quot;code_1774271583345&quot; class=&quot;python&quot; data-ke-language=&quot;python&quot; data-ke-type=&quot;codeblock&quot;&gt;&lt;code&gt;def demo_assertions(page: Page) -&amp;gt; None:
    &quot;&quot;&quot;expect() 기반 검증 예시.&quot;&quot;&quot;

    # 텍스트 포함 여부
    expect(page.get_by_role(&quot;heading&quot;)).to_contain_text(&quot;대시보드&quot;)

    # CSS 클래스 보유 여부
    expect(page.locator(&quot;.status-badge&quot;)).to_have_class(&quot;active&quot;)

    # 속성 값 확인
    expect(page.get_by_role(&quot;img&quot;, name=&quot;로고&quot;)).to_have_attribute(&quot;alt&quot;, &quot;Company Logo&quot;)

    # 요소가 존재하지 않음을 검증
    expect(page.get_by_text(&quot;오류 메시지&quot;)).not_to_be_visible()&lt;/code&gt;&lt;/pre&gt;
&lt;h3 data-ke-size=&quot;size23&quot;&gt;&lt;br /&gt;8. 스크린샷 &amp;amp; 디버깅 &lt;/h3&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;마지막으로 스크린샷 캡처 기능이다. 만약 headless 상태로 동작하다가 에러가 발생했지만&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;pre id=&quot;code_1774271688872&quot; class=&quot;python&quot; data-ke-language=&quot;python&quot; data-ke-type=&quot;codeblock&quot;&gt;&lt;code&gt;def demo_debug(page: Page) -&amp;gt; None:
    &quot;&quot;&quot;디버깅 도구.&quot;&quot;&quot;

    # 전체 페이지 스크린샷
    page.screenshot(path=&quot;screenshot.png&quot;, full_page=True)

    # 특정 요소만 스크린샷
    page.locator(&quot;.main-content&quot;).screenshot(path=&quot;element.png&quot;)

    # 콘솔 로그 출력 (이벤트 등록은 page 생성 직후에 할 것)
    page.on(&quot;console&quot;, lambda msg: print(f&quot;[CONSOLE] {msg.type}: {msg.text}&quot;))

    # Playwright Inspector 실행 (로케이터 탐색용, headless=False 환경에서만 동작)
    # page.pause()   # &amp;larr; 이 줄 주석 해제하면 Inspector 창이 열림&lt;/code&gt;&lt;/pre&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;br /&gt;지금까지 Selenium과 비교한 Playwright의 핵심 특징과 기본 사용법을 정리해보았다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;Playwright는 내장된 자동 대기 기능과 빠른 양방향 통신 구조 덕분에,&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;IE와 같은 레거시 브라우저 테스트가 강제되는 환경이 아니라면,&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;새로운 자동화 환경 구축 시 Playwright는 가장 효율적인 선택지가 될 것이라고 생각한다.. &lt;/p&gt;</description>
      <category>python/python_selenium</category>
      <author>몽자비루</author>
      <guid isPermaLink="true">https://rusharp.tistory.com/209</guid>
      <comments>https://rusharp.tistory.com/209#entry209comment</comments>
      <pubDate>Mon, 23 Mar 2026 22:26:47 +0900</pubDate>
    </item>
    <item>
      <title>컨테이너 기반의 프로젝트에서 많이 사용되는 CI/CD 구축하기</title>
      <link>https://rusharp.tistory.com/208</link>
      <description>&lt;p data-ke-size=&quot;size16&quot;&gt;이번에는 저번에 이어서 docker 을 활용해서 컨테이너 기반 프로젝트에서 주로 사용되는 CI/CD 를 구축해보려고 한다.&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. EC2에 Docker 설치&lt;/h3&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;먼저 EC2에 Docker 설치하고 ECR 을 세팅해야 하는데 아래 명령어를 통해 Docker을 설치한다.&lt;/p&gt;
&lt;pre id=&quot;code_1768304807622&quot; class=&quot;bash&quot; data-ke-language=&quot;bash&quot; data-ke-type=&quot;codeblock&quot;&gt;&lt;code&gt;sudo apt-get update &amp;amp;&amp;amp; \
sudo apt-get install -y apt-transport-https ca-certificates curl software-properties-common &amp;amp;&amp;amp; \
curl -fsSL https://download.docker.com/linux/ubuntu/gpg | sudo apt-key add - &amp;amp;&amp;amp; \
sudo apt-key fingerprint 0EBFCD88 &amp;amp;&amp;amp; \
sudo add-apt-repository &quot;deb [arch=amd64] https://download.docker.com/linux/ubuntu $(lsb_release -cs) stable&quot; &amp;amp;&amp;amp; \
sudo apt-get update &amp;amp;&amp;amp; \
sudo apt-get install -y docker-ce &amp;amp;&amp;amp; \
sudo usermod -aG docker ubuntu &amp;amp;&amp;amp; \
sudo curl -L &quot;https://github.com/docker/compose/releases/download/1.23.2/docker-compose-$(uname -s)-$(uname -m)&quot; -o /usr/local/bin/docker-compose &amp;amp;&amp;amp; \
sudo chmod +x /usr/local/bin/docker-compose &amp;amp;&amp;amp; \
sudo ln -s /usr/local/bin/docker-compose /usr/bin/docker-compose&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;완료된 이후 docker 와 docker compose 설치가 잘 되었는지 확인을 위해&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&quot;&lt;b&gt;&lt;i&gt;docker -v; docker compose version&lt;/i&gt;&lt;/b&gt;&quot; 를 입력하면 아래와 같이 버전을 확인할 수 있다.&lt;/p&gt;
&lt;p&gt;&lt;figure class=&quot;imageblock alignCenter&quot; data-ke-mobileStyle=&quot;widthOrigin&quot; data-origin-width=&quot;616&quot; data-origin-height=&quot;65&quot;&gt;&lt;span data-url=&quot;https://blog.kakaocdn.net/dn/ctpQ9E/dJMcacPm3sq/Qlc90bowZLyw3P9AvmXk3k/img.png&quot; data-phocus=&quot;https://blog.kakaocdn.net/dn/ctpQ9E/dJMcacPm3sq/Qlc90bowZLyw3P9AvmXk3k/img.png&quot;&gt;&lt;img src=&quot;https://blog.kakaocdn.net/dn/ctpQ9E/dJMcacPm3sq/Qlc90bowZLyw3P9AvmXk3k/img.png&quot; srcset=&quot;https://img1.daumcdn.net/thumb/R1280x0/?scode=mtistory2&amp;fname=https%3A%2F%2Fblog.kakaocdn.net%2Fdn%2FctpQ9E%2FdJMcacPm3sq%2FQlc90bowZLyw3P9AvmXk3k%2Fimg.png&quot; onerror=&quot;this.onerror=null; this.src='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png'; this.srcset='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png';&quot; loading=&quot;lazy&quot; width=&quot;616&quot; height=&quot;65&quot; data-origin-width=&quot;616&quot; data-origin-height=&quot;65&quot;/&gt;&lt;/span&gt;&lt;/figure&gt;
&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;h3 data-ke-size=&quot;size23&quot;&gt;2. EC2에 ECR 권한 세팅&lt;/h3&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;두번째로 ECR에 이미지를 전달하기 위해서는 ECR에 접근할 수 있는 권한이 있어야 한다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;Github Actions 에 IAM 사용자를 지정해줘야 하는데, 여기서 IAM 사용자는&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;AWS 외부의 리소스가 AWS 리소스를 사용할 때 주로 사용되고 반대로 IAM 역할은&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;특정 AWS 리소스가 다른 AWS 리소스를 사용할 때 생성하는 역할을 한다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p  data-ke-size=&quot;size16&quot;&gt;&lt;b&gt;1. ECR 에서 리포지토리를 생성&lt;/b&gt;&lt;/p&gt;
&lt;p  data-ke-size=&quot;size16&quot;&gt;ECR &amp;gt; 프라이빗 레지스트리 &amp;gt; 리포지토리에서 [리포지토리 생성] 버튼을 눌러 생성한다.&lt;/p&gt;
&lt;p&gt;&lt;figure class=&quot;imageblock alignCenter&quot; data-ke-mobileStyle=&quot;widthOrigin&quot; data-origin-width=&quot;956&quot; data-origin-height=&quot;395&quot;&gt;&lt;span data-url=&quot;https://blog.kakaocdn.net/dn/b9JgsD/dJMcagxtAXD/gJKceMrnLEAo9LXArKGU21/img.png&quot; data-phocus=&quot;https://blog.kakaocdn.net/dn/b9JgsD/dJMcagxtAXD/gJKceMrnLEAo9LXArKGU21/img.png&quot;&gt;&lt;img src=&quot;https://blog.kakaocdn.net/dn/b9JgsD/dJMcagxtAXD/gJKceMrnLEAo9LXArKGU21/img.png&quot; srcset=&quot;https://img1.daumcdn.net/thumb/R1280x0/?scode=mtistory2&amp;fname=https%3A%2F%2Fblog.kakaocdn.net%2Fdn%2Fb9JgsD%2FdJMcagxtAXD%2FgJKceMrnLEAo9LXArKGU21%2Fimg.png&quot; onerror=&quot;this.onerror=null; this.src='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png'; this.srcset='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png';&quot; loading=&quot;lazy&quot; width=&quot;956&quot; height=&quot;395&quot; data-origin-width=&quot;956&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;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;b&gt;2. EC2용 IAM 역할을 생성&lt;/b&gt;&lt;br /&gt;IAM &amp;gt; 역할 &amp;gt; 역할 생성에서 사용 사례에 EC2를 입력한 뒤 권한 정책에서&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;AmazonEC2ContainerRegistryFullAccess 정책을 추가하고, 역할 이름은 ec2-ecr-role 으로 지정한다.&lt;/p&gt;
&lt;p&gt;&lt;figure class=&quot;imageblock alignCenter&quot; data-ke-mobileStyle=&quot;widthOrigin&quot; data-origin-width=&quot;949&quot; data-origin-height=&quot;281&quot;&gt;&lt;span data-url=&quot;https://blog.kakaocdn.net/dn/dAglCf/dJMcafL6BSX/D0sO5Z7LgTo7UFySkrgd3K/img.png&quot; data-phocus=&quot;https://blog.kakaocdn.net/dn/dAglCf/dJMcafL6BSX/D0sO5Z7LgTo7UFySkrgd3K/img.png&quot;&gt;&lt;img src=&quot;https://blog.kakaocdn.net/dn/dAglCf/dJMcafL6BSX/D0sO5Z7LgTo7UFySkrgd3K/img.png&quot; srcset=&quot;https://img1.daumcdn.net/thumb/R1280x0/?scode=mtistory2&amp;fname=https%3A%2F%2Fblog.kakaocdn.net%2Fdn%2FdAglCf%2FdJMcafL6BSX%2FD0sO5Z7LgTo7UFySkrgd3K%2Fimg.png&quot; onerror=&quot;this.onerror=null; this.src='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png'; this.srcset='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png';&quot; loading=&quot;lazy&quot; width=&quot;949&quot; height=&quot;281&quot; data-origin-width=&quot;949&quot; data-origin-height=&quot;281&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;1233&quot; data-origin-height=&quot;401&quot;&gt;&lt;span data-url=&quot;https://blog.kakaocdn.net/dn/dzIZbj/dJMcahwpqGR/drrWiriAswU5CVkV1bsyM1/img.png&quot; data-phocus=&quot;https://blog.kakaocdn.net/dn/dzIZbj/dJMcahwpqGR/drrWiriAswU5CVkV1bsyM1/img.png&quot;&gt;&lt;img src=&quot;https://blog.kakaocdn.net/dn/dzIZbj/dJMcahwpqGR/drrWiriAswU5CVkV1bsyM1/img.png&quot; srcset=&quot;https://img1.daumcdn.net/thumb/R1280x0/?scode=mtistory2&amp;fname=https%3A%2F%2Fblog.kakaocdn.net%2Fdn%2FdzIZbj%2FdJMcahwpqGR%2FdrrWiriAswU5CVkV1bsyM1%2Fimg.png&quot; onerror=&quot;this.onerror=null; this.src='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png'; this.srcset='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png';&quot; loading=&quot;lazy&quot; width=&quot;1233&quot; height=&quot;401&quot; data-origin-width=&quot;1233&quot; data-origin-height=&quot;401&quot;/&gt;&lt;/span&gt;&lt;/figure&gt;
&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;b&gt;3. EC2인스턴스에 역할 연결&lt;/b&gt;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;EC2에서 인스턴스 체크박스 선택하고 우측 상단 작업 &amp;gt; 보안 &amp;gt; IAM 역할 수정 버튼을 클릭한다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;이후 IAM 역할 드롭다운에서 ec2-ecr-role 을 선택한 뒤 IAM 역할 업데이트 버튼을 클릭한다.&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;1300&quot; data-origin-height=&quot;378&quot;&gt;&lt;span data-url=&quot;https://blog.kakaocdn.net/dn/ce9rRH/dJMcacV8yoJ/qtj7ttK9RVwUdFiK7rKRkk/img.png&quot; data-phocus=&quot;https://blog.kakaocdn.net/dn/ce9rRH/dJMcacV8yoJ/qtj7ttK9RVwUdFiK7rKRkk/img.png&quot;&gt;&lt;img src=&quot;https://blog.kakaocdn.net/dn/ce9rRH/dJMcacV8yoJ/qtj7ttK9RVwUdFiK7rKRkk/img.png&quot; srcset=&quot;https://img1.daumcdn.net/thumb/R1280x0/?scode=mtistory2&amp;fname=https%3A%2F%2Fblog.kakaocdn.net%2Fdn%2Fce9rRH%2FdJMcacV8yoJ%2Fqtj7ttK9RVwUdFiK7rKRkk%2Fimg.png&quot; onerror=&quot;this.onerror=null; this.src='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png'; this.srcset='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png';&quot; loading=&quot;lazy&quot; width=&quot;1300&quot; height=&quot;378&quot; data-origin-width=&quot;1300&quot; data-origin-height=&quot;378&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;1146&quot; data-origin-height=&quot;385&quot;&gt;&lt;span data-url=&quot;https://blog.kakaocdn.net/dn/dM90WS/dJMcacokqkp/7UfHhcmiXZXGrKv9ZISgj0/img.png&quot; data-phocus=&quot;https://blog.kakaocdn.net/dn/dM90WS/dJMcacokqkp/7UfHhcmiXZXGrKv9ZISgj0/img.png&quot;&gt;&lt;img src=&quot;https://blog.kakaocdn.net/dn/dM90WS/dJMcacokqkp/7UfHhcmiXZXGrKv9ZISgj0/img.png&quot; srcset=&quot;https://img1.daumcdn.net/thumb/R1280x0/?scode=mtistory2&amp;fname=https%3A%2F%2Fblog.kakaocdn.net%2Fdn%2FdM90WS%2FdJMcacokqkp%2F7UfHhcmiXZXGrKv9ZISgj0%2Fimg.png&quot; onerror=&quot;this.onerror=null; this.src='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png'; this.srcset='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png';&quot; loading=&quot;lazy&quot; width=&quot;1146&quot; height=&quot;385&quot; data-origin-width=&quot;1146&quot; data-origin-height=&quot;385&quot;/&gt;&lt;/span&gt;&lt;/figure&gt;
&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;b&gt;4. Github Actions 용 IAM 사용자 생성&lt;/b&gt;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;다음으로 IAM 콘솔에서 엑세스 관리/사용자에서 사용자 생성 버튼을 선택하고 이름 입력 후 다음 클릭한다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;이후 권한 설정에서 직접 정책 연결을 선택하고 &lt;b&gt;AmazonEC2ContainerRegistryFullAccess&lt;/b&gt;&lt;span style=&quot;color: #333333; text-align: start;&quot;&gt;&lt;span&gt;&amp;nbsp;&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;1283&quot; data-origin-height=&quot;687&quot;&gt;&lt;span data-url=&quot;https://blog.kakaocdn.net/dn/XhcCY/dJMcajt9EGg/Yi2vsuhRQtG97h1IXlk7n1/img.png&quot; data-phocus=&quot;https://blog.kakaocdn.net/dn/XhcCY/dJMcajt9EGg/Yi2vsuhRQtG97h1IXlk7n1/img.png&quot;&gt;&lt;img src=&quot;https://blog.kakaocdn.net/dn/XhcCY/dJMcajt9EGg/Yi2vsuhRQtG97h1IXlk7n1/img.png&quot; srcset=&quot;https://img1.daumcdn.net/thumb/R1280x0/?scode=mtistory2&amp;fname=https%3A%2F%2Fblog.kakaocdn.net%2Fdn%2FXhcCY%2FdJMcajt9EGg%2FYi2vsuhRQtG97h1IXlk7n1%2Fimg.png&quot; onerror=&quot;this.onerror=null; this.src='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png'; this.srcset='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png';&quot; loading=&quot;lazy&quot; width=&quot;1283&quot; height=&quot;687&quot; data-origin-width=&quot;1283&quot; data-origin-height=&quot;687&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;엑세스 키 만들기 버튼을 누르고 엑세스 키 모범 사례 및 대안에서 CLI 선택 후 설명 태그는 생략한다.&lt;/p&gt;
&lt;p&gt;&lt;figure class=&quot;imageblock alignCenter&quot; data-ke-mobileStyle=&quot;widthOrigin&quot; data-origin-width=&quot;1355&quot; data-origin-height=&quot;779&quot;&gt;&lt;span data-url=&quot;https://blog.kakaocdn.net/dn/ssrLc/dJMcadHunuj/w6uwETPlabENlH1z3BMGI0/img.png&quot; data-phocus=&quot;https://blog.kakaocdn.net/dn/ssrLc/dJMcadHunuj/w6uwETPlabENlH1z3BMGI0/img.png&quot;&gt;&lt;img src=&quot;https://blog.kakaocdn.net/dn/ssrLc/dJMcadHunuj/w6uwETPlabENlH1z3BMGI0/img.png&quot; srcset=&quot;https://img1.daumcdn.net/thumb/R1280x0/?scode=mtistory2&amp;fname=https%3A%2F%2Fblog.kakaocdn.net%2Fdn%2FssrLc%2FdJMcadHunuj%2Fw6uwETPlabENlH1z3BMGI0%2Fimg.png&quot; onerror=&quot;this.onerror=null; this.src='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png'; this.srcset='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png';&quot; loading=&quot;lazy&quot; width=&quot;1355&quot; height=&quot;779&quot; data-origin-width=&quot;1355&quot; data-origin-height=&quot;779&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;986&quot; data-origin-height=&quot;415&quot;&gt;&lt;span data-url=&quot;https://blog.kakaocdn.net/dn/3qzeA/dJMb996ddNy/ysXLUnAF7BurmLDOgqHkXk/img.png&quot; data-phocus=&quot;https://blog.kakaocdn.net/dn/3qzeA/dJMb996ddNy/ysXLUnAF7BurmLDOgqHkXk/img.png&quot;&gt;&lt;img src=&quot;https://blog.kakaocdn.net/dn/3qzeA/dJMb996ddNy/ysXLUnAF7BurmLDOgqHkXk/img.png&quot; srcset=&quot;https://img1.daumcdn.net/thumb/R1280x0/?scode=mtistory2&amp;fname=https%3A%2F%2Fblog.kakaocdn.net%2Fdn%2F3qzeA%2FdJMb996ddNy%2FysXLUnAF7BurmLDOgqHkXk%2Fimg.png&quot; onerror=&quot;this.onerror=null; this.src='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png'; this.srcset='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png';&quot; loading=&quot;lazy&quot; width=&quot;986&quot; height=&quot;415&quot; data-origin-width=&quot;986&quot; data-origin-height=&quot;415&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;922&quot; data-origin-height=&quot;554&quot;&gt;&lt;span data-url=&quot;https://blog.kakaocdn.net/dn/bkkjnb/dJMcaiINh3o/kF0jfEjCM7NZma6eFadKok/img.png&quot; data-phocus=&quot;https://blog.kakaocdn.net/dn/bkkjnb/dJMcaiINh3o/kF0jfEjCM7NZma6eFadKok/img.png&quot;&gt;&lt;img src=&quot;https://blog.kakaocdn.net/dn/bkkjnb/dJMcaiINh3o/kF0jfEjCM7NZma6eFadKok/img.png&quot; srcset=&quot;https://img1.daumcdn.net/thumb/R1280x0/?scode=mtistory2&amp;fname=https%3A%2F%2Fblog.kakaocdn.net%2Fdn%2Fbkkjnb%2FdJMcaiINh3o%2FkF0jfEjCM7NZma6eFadKok%2Fimg.png&quot; onerror=&quot;this.onerror=null; this.src='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png'; this.srcset='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png';&quot; loading=&quot;lazy&quot; width=&quot;922&quot; height=&quot;554&quot; data-origin-width=&quot;922&quot; data-origin-height=&quot;554&quot;/&gt;&lt;/span&gt;&lt;/figure&gt;
&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;b&gt;5. Github 저장소에 키 등록&lt;/b&gt;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;다음으로 이렇게 저장된 값을 Github 의 Settings 에서 New repository secret 을 선택하고&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;아까 저장한 값을 각각 AWS_ACCESS_KEY_ID 와 AWS_SECRET_ACCESS_KEY 에 설정한다.&lt;/p&gt;
&lt;p&gt;&lt;figure class=&quot;imageblock alignCenter&quot; data-ke-mobileStyle=&quot;widthOrigin&quot; data-origin-width=&quot;1541&quot; data-origin-height=&quot;911&quot;&gt;&lt;span data-url=&quot;https://blog.kakaocdn.net/dn/bGCIzp/dJMcaacXX2u/bhsBDqQCrEHoWb6yvxZ8lK/img.png&quot; data-phocus=&quot;https://blog.kakaocdn.net/dn/bGCIzp/dJMcaacXX2u/bhsBDqQCrEHoWb6yvxZ8lK/img.png&quot;&gt;&lt;img src=&quot;https://blog.kakaocdn.net/dn/bGCIzp/dJMcaacXX2u/bhsBDqQCrEHoWb6yvxZ8lK/img.png&quot; srcset=&quot;https://img1.daumcdn.net/thumb/R1280x0/?scode=mtistory2&amp;fname=https%3A%2F%2Fblog.kakaocdn.net%2Fdn%2FbGCIzp%2FdJMcaacXX2u%2FbhsBDqQCrEHoWb6yvxZ8lK%2Fimg.png&quot; onerror=&quot;this.onerror=null; this.src='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png'; this.srcset='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png';&quot; loading=&quot;lazy&quot; width=&quot;1541&quot; height=&quot;911&quot; data-origin-width=&quot;1541&quot; data-origin-height=&quot;911&quot;/&gt;&lt;/span&gt;&lt;/figure&gt;
&lt;/p&gt;
&lt;h3 data-ke-size=&quot;size23&quot;&gt;3. EC2에 CI/CD 구축&lt;/h3&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;나는 &lt;a href=&quot;https://github.com/awslabs/amazon-ecr-credential-helper&quot; target=&quot;_blank&quot; rel=&quot;noopener&amp;nbsp;noreferrer&quot;&gt;amazon-ecr-credential-helper &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;i&gt;&lt;b&gt;sudo apt install amazon-ecr-credential-helper&lt;/b&gt;&lt;/i&gt; 명령어를 입력하여 설치하고&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;home 디렉토리 안에 docker 라는 폴더를 만든 뒤 &lt;a href=&quot;https://github.com/awslabs/amazon-ecr-credential-helper?tab=readme-ov-file#configuration&quot; target=&quot;_blank&quot; rel=&quot;noopener&quot;&gt;config.json&lt;/a&gt; 파일을 생성한다.&lt;/p&gt;
&lt;p&gt;&lt;figure class=&quot;imageblock alignCenter&quot; data-ke-mobileStyle=&quot;widthOrigin&quot; data-origin-width=&quot;448&quot; data-origin-height=&quot;72&quot;&gt;&lt;span data-url=&quot;https://blog.kakaocdn.net/dn/bg4LVY/dJMcahb5P1u/73tiBnLg8DNvlgXLQLSWk0/img.png&quot; data-phocus=&quot;https://blog.kakaocdn.net/dn/bg4LVY/dJMcahb5P1u/73tiBnLg8DNvlgXLQLSWk0/img.png&quot;&gt;&lt;img src=&quot;https://blog.kakaocdn.net/dn/bg4LVY/dJMcahb5P1u/73tiBnLg8DNvlgXLQLSWk0/img.png&quot; srcset=&quot;https://img1.daumcdn.net/thumb/R1280x0/?scode=mtistory2&amp;fname=https%3A%2F%2Fblog.kakaocdn.net%2Fdn%2Fbg4LVY%2FdJMcahb5P1u%2F73tiBnLg8DNvlgXLQLSWk0%2Fimg.png&quot; onerror=&quot;this.onerror=null; this.src='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png'; this.srcset='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png';&quot; loading=&quot;lazy&quot; width=&quot;448&quot; height=&quot;72&quot; data-origin-width=&quot;448&quot; data-origin-height=&quot;72&quot;/&gt;&lt;/span&gt;&lt;/figure&gt;
&lt;/p&gt;
&lt;pre id=&quot;code_1768720801622&quot; class=&quot;bash&quot; data-ke-language=&quot;bash&quot; data-ke-type=&quot;codeblock&quot;&gt;&lt;code&gt;{
	&quot;credsStore&quot;: &quot;ecr-login&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;그리고 yml 파일을 아래와 같이 고치는데, 여기에서&amp;nbsp; &quot;steps.login-ecr.outputs.registry&quot; 는&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;ECR 의 프라이빗 리포지토리에서 URL 에 포함되는 내용이다.&lt;/p&gt;
&lt;pre id=&quot;code_1768736161624&quot; class=&quot;bash&quot; data-ke-language=&quot;bash&quot; data-ke-type=&quot;codeblock&quot;&gt;&lt;code&gt;name : Deploy To EC2

on:
  push:
    branches:
      - main

jobs:
    Deploy-Job:
      runs-on: ubuntu-latest
      steps:
        - name: Github Repository 파일 불러오기 with dockerfile
          uses: actions/checkout@v4

        - name: JDK 17 설치
          uses: actions/setup-java@v4
          with:
            java-version: '17'
            distribution: 'temurin'
        
        - name: application.properties 파일 생성 및 EC2 배포
          run: |
            echo &quot;${{ secrets.APPLICATION_PROPERTIES }}&quot; &amp;gt; src/main/resources/application.properties

        - name: Test and Build with Gradle
          run: ./gradlew clean build

        - name: AWS Recource 에 접근하도록 AWS credentials 설정
          uses: aws-actions/configure-aws-credentials@v2
          with:
            aws-access-key-id: ${{ secrets.AWS_ACCESS_KEY_ID }}
            aws-secret-access-key: ${{ secrets.AWS_SECRET_ACCESS_KEY }}
            aws-region: ap-northeast-2
        
        - name: ECR 로그인
          id: login-ecr
          uses: aws-actions/amazon-ecr-login@v2

        # 현재 파일에 있는 Dockerfile 기반으로 rusharp-server이라는 이름의 도커 이미지 생성
        - name: Docker Image 생성
          run: docker build -t rusharp-server .
        
        # docker tag 명령어를 사용해서 도커 이미지에 태그를 설정 (레포지토리 URI/rusharp-server:latest)
        - name: Docker Image 태그 설정
          run: docker tag rusharp-server ${{ steps.login-ecr.outputs.registry }}/rusharp-server:latest

        # AWS ECR에 도커 이미지 푸시
        - name: Docker Image ECR에 푸시
          run: docker push ${{ steps.login-ecr.outputs.registry }}/rusharp-server:latest

        # EC2에 SSH로 접속하여 기존 컨테이너 종료 및 삭제, 새 이미지로 컨테이너 실행
        - name: SSH로 EC2에 접속하기
          uses: appleboy/ssh-action@v1.0.3
          with:
            host: ${{ secrets.EC2_HOST }} # EC2의 주소
            username: ${{ secrets.EC2_USERNAME }} # EC2 접속 username
            key: ${{ secrets.EC2_PRIVATE_KEY }} # EC2의 Key 파일의 내부 텍스트
            script_stop: true # 아래 script 중 실패하는 명령이 하나라도 있으면 실패로 처리
            script: |
              # 기존에 실행중인 컨테이너가 있으면 종료 및 삭제
              sudo docker rm -f rusharp-server || true
              # 만약 도커 이미지가 없으면 pull 받아오기
              docker pull ${{ steps.login-ecr.outputs.registry }}/rusharp-server:latest
              # 도커 컨테이너 실행
              docker run -d --name rusharp-server -p 8080:8080 ${{ steps.login-ecr.outputs.registry }}/rusharp-server:latest&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;그리고 최상위 파일에 Dockerfile 을 생성하고 아래와 같은 내용을 입력한다.&lt;/p&gt;
&lt;pre id=&quot;code_1768741027176&quot; class=&quot;bash&quot; data-ke-language=&quot;bash&quot; data-ke-type=&quot;codeblock&quot;&gt;&lt;code&gt;# Eclipse Temurin의 OpenJDK 17 Alpine 이미지를 베이스로 사용
FROM eclipse-temurin:17-jdk-alpine
# build/libs/ 경로에 있는 JAR 파일을 컨테이너의 project.jar로 복사
COPY ./build/libs/*SNAPSHOT.jar /project.jar
# 컨테이너가 시작될 때 JAR 파일을 실행  
ENTRYPOINT [&quot;java&quot;, &quot;-jar&quot;, &quot;/project.jar&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;이후 git push 를 진행하면 이렇게 성공하면 해당 링크로 접근하여 웹페이지도 확인 가능하다.&lt;/p&gt;
&lt;p&gt;&lt;figure class=&quot;imageblock alignCenter&quot; data-ke-mobileStyle=&quot;widthOrigin&quot; data-origin-width=&quot;1745&quot; data-origin-height=&quot;895&quot;&gt;&lt;span data-url=&quot;https://blog.kakaocdn.net/dn/b6Yqzo/dJMcahpEiMR/0j1RAAqmYDplS320k81wQk/img.png&quot; data-phocus=&quot;https://blog.kakaocdn.net/dn/b6Yqzo/dJMcahpEiMR/0j1RAAqmYDplS320k81wQk/img.png&quot;&gt;&lt;img src=&quot;https://blog.kakaocdn.net/dn/b6Yqzo/dJMcahpEiMR/0j1RAAqmYDplS320k81wQk/img.png&quot; srcset=&quot;https://img1.daumcdn.net/thumb/R1280x0/?scode=mtistory2&amp;fname=https%3A%2F%2Fblog.kakaocdn.net%2Fdn%2Fb6Yqzo%2FdJMcahpEiMR%2F0j1RAAqmYDplS320k81wQk%2Fimg.png&quot; onerror=&quot;this.onerror=null; this.src='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png'; this.srcset='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png';&quot; loading=&quot;lazy&quot; width=&quot;1745&quot; height=&quot;895&quot; data-origin-width=&quot;1745&quot; data-origin-height=&quot;895&quot;/&gt;&lt;/span&gt;&lt;/figure&gt;
&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;이렇게 Docker와 GitHub Actions, AWS ECR을 연동하여 컨테이너 기반의 CI/CD 파이프라인 구축을 진행해봤다.&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;무엇보다 Docker 을 사용하기 전에는 기존 로컬 개발 환경과 EC2서버 환경 등이 달라서&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;애플리케이션 실행에 필요한 모든 환경을 격리시켜, 환경 일관성을 높이고 에러 위험을 줄일 수 있었다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;또한, EC2 서버에 각종 라이브러리를 일일이 설치하고 관리할 필요 없이 Docker 만 설치되어 있다면&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;언제 어디서든지 동일한 애플리케이션을 즉시 수정할 수 있어 인프라 관리가 훨씬 간결하다.&lt;/p&gt;</description>
      <category>Server&amp;amp;load/CI CD</category>
      <author>몽자비루</author>
      <guid isPermaLink="true">https://rusharp.tistory.com/208</guid>
      <comments>https://rusharp.tistory.com/208#entry208comment</comments>
      <pubDate>Sun, 18 Jan 2026 22:05:41 +0900</pubDate>
    </item>
    <item>
      <title>container 기반의 프로젝트 CI/CD 구축 방법 (Docker)</title>
      <link>https://rusharp.tistory.com/207</link>
      <description>&lt;p data-ke-size=&quot;size16&quot;&gt;이번에는 컨테이너 기반 프로젝트에서 사용되는 프로젝트 CI/CD 구축 방법에 대해서 알아보려고 한다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;컨테이너란 애플리케이션 실행에 필요한 코드, 라이브러리, 시스템 도구 등을 모두 포함하여&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;어디서든지 동일하게 실행되도록 패키딩한 가볍고 독립적인 소프트웨어 단위이다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;즉, 애플리케이션에 필요한 설치 프로그램을 다 상자에 담아서 옮겨주는 것이라고 생각하면 될 것이다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;h3 data-ke-size=&quot;size23&quot;&gt;1. container 기반의 프로젝트에서 많이 사용되는 CI/CD 구축 방법&amp;nbsp;&lt;/h3&gt;
&lt;p&gt;&lt;figure class=&quot;imageblock alignCenter&quot; data-ke-mobileStyle=&quot;widthOrigin&quot; data-origin-width=&quot;1280&quot; data-origin-height=&quot;693&quot;&gt;&lt;span data-url=&quot;https://blog.kakaocdn.net/dn/ckH5Ij/dJMcabpmJtR/Uo5rXgY0OV7IYe5lBlaVP1/img.png&quot; data-phocus=&quot;https://blog.kakaocdn.net/dn/ckH5Ij/dJMcabpmJtR/Uo5rXgY0OV7IYe5lBlaVP1/img.png&quot;&gt;&lt;img src=&quot;https://blog.kakaocdn.net/dn/ckH5Ij/dJMcabpmJtR/Uo5rXgY0OV7IYe5lBlaVP1/img.png&quot; srcset=&quot;https://img1.daumcdn.net/thumb/R1280x0/?scode=mtistory2&amp;fname=https%3A%2F%2Fblog.kakaocdn.net%2Fdn%2FckH5Ij%2FdJMcabpmJtR%2FUo5rXgY0OV7IYe5lBlaVP1%2Fimg.png&quot; onerror=&quot;this.onerror=null; this.src='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png'; this.srcset='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png';&quot; loading=&quot;lazy&quot; width=&quot;1280&quot; height=&quot;693&quot; data-origin-width=&quot;1280&quot; data-origin-height=&quot;693&quot;/&gt;&lt;/span&gt;&lt;/figure&gt;
&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;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;먼저 Github 에 push를 진행하면 먼저 Github Actions 에서 Docker Image를 생성하고'&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;Docker Image를 &quot;도커 허브와 같이 도커 이미지를 저장하는 장소&quot; ECR 로 전달한다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;이후 ECR로부터 Docke image를 다운로드 받아서 배포를 진행할 수 있다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;단점이 있다면 무중단 배포를 구현하거나 여러 EC2 인스턴스에서 배포를 해야 하는 상황이라면,&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;직접 Github Actions 에 스크립트를 작성해야 구현해야 하는데, 이 과정이 생각보다 복잡하다는 것이다.&lt;/p&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;소규모 프로젝트일 때 주로 활용되며, 그 외에는 Code deploy를 활용하게 된다.&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;2. container 기반이면서 확장성을 고려한 프로젝트에서 사용되는 CI/CD 구축 방법&amp;nbsp;&lt;/h3&gt;
&lt;p&gt;&lt;figure class=&quot;imageblock alignCenter&quot; data-ke-mobileStyle=&quot;widthOrigin&quot; data-origin-width=&quot;1280&quot; data-origin-height=&quot;730&quot;&gt;&lt;span data-url=&quot;https://blog.kakaocdn.net/dn/cfWnOH/dJMcachvUcm/0adCWCKRvnFvCLMcRhJx2k/img.png&quot; data-phocus=&quot;https://blog.kakaocdn.net/dn/cfWnOH/dJMcachvUcm/0adCWCKRvnFvCLMcRhJx2k/img.png&quot;&gt;&lt;img src=&quot;https://blog.kakaocdn.net/dn/cfWnOH/dJMcachvUcm/0adCWCKRvnFvCLMcRhJx2k/img.png&quot; srcset=&quot;https://img1.daumcdn.net/thumb/R1280x0/?scode=mtistory2&amp;fname=https%3A%2F%2Fblog.kakaocdn.net%2Fdn%2FcfWnOH%2FdJMcachvUcm%2F0adCWCKRvnFvCLMcRhJx2k%2Fimg.png&quot; onerror=&quot;this.onerror=null; this.src='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png'; this.srcset='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png';&quot; loading=&quot;lazy&quot; width=&quot;1280&quot; height=&quot;730&quot; data-origin-width=&quot;1280&quot; data-origin-height=&quot;730&quot;/&gt;&lt;/span&gt;&lt;/figure&gt;
&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;Code deploy를 활용하면 위와 같은 플로우로 서버가 동작하게 된다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;먼저 Git push를 진행하면 Github Actions 가 인식하고 Build, Test 를 진행한다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;이후 위와같이 도커 이미지를 생성한 뒤에 ECR로 전달하고 EC2 SSH로 접속하는 것이 아닌,&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;deploy관련된 파일을 S3에 업로드 시킨 뒤 AWS code deploy 에 명령한다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;이후 Code Deploy가 EC2에게 'S3으로부터 Code deploy 관련 파일을 다운로드 후 배포'를 명령하면&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;그제서야 EC2가 S3으로부터 파일을 다운로드 받고 컨테이너를 실행하는 과정을 거치게 된다.&lt;/p&gt;
&lt;p data-ke-size=&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;Code deploy를 사용함으로써 인프라 구조가 복잡해져 관리, 유지보수가 힘들다는 단점이 있다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;이 방법은 컨테이너 서버가 여러 대 이상 있거나 무중단 배포가 중요할 때 종종 사용하게 된다.&lt;/p&gt;</description>
      <category>Server&amp;amp;load/CI CD</category>
      <author>몽자비루</author>
      <guid isPermaLink="true">https://rusharp.tistory.com/207</guid>
      <comments>https://rusharp.tistory.com/207#entry207comment</comments>
      <pubDate>Sun, 11 Jan 2026 23:54:11 +0900</pubDate>
    </item>
    <item>
      <title>일반 프로젝트에서 쓰이는 CI/CD 구축 방식</title>
      <link>https://rusharp.tistory.com/206</link>
      <description>&lt;p data-ke-size=&quot;size16&quot;&gt;저번에는 개인 프로젝트에서 쓰이는 CI/CD 구축방식에 대해서 작성해 보았다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;복습하자면, github 에 코드를 넣은 뒤, AWS 의 EC2에서 git pull을 진행한 뒤에 자동으로 빌드되도록 했는데&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;이번에는 일반 프로젝트처럼 github actions 에서 빌드 완성물을 만든 뒤 EC2에 전달해 볼 예정이다.&lt;/p&gt;
&lt;h3 data-ke-size=&quot;size23&quot;&gt;1. 일반 프로젝트에서 쓰이는 CI/CD 구축 방식&lt;/h3&gt;
&lt;pre id=&quot;code_1768039644763&quot; class=&quot;bash&quot; data-ke-language=&quot;bash&quot; data-ke-type=&quot;codeblock&quot;&gt;&lt;code&gt;name : Deploy To EC2

on:
  push:
    branches:
      - main

jobs:
  My-Deploy-Job:
    runs-on: ubuntu-latest
    steps:
      - name: Github Repository 에 올린 파일들을 불러오기
        uses: actions/checkout@v4
      
      - name: Github Actions 확인
        run: |
          ls
          pwd
        
        
      - name: JDK 17 설치
        uses: actions/setup-java@v4
        with:
          java-version: '17'
          distribution: 'temurin'

      - name: application.properties 파일 생성 및 EC2 배포
        run: |
          echo &quot;${{ secrets.APPLICATION_PROPERTIES }}&quot; &amp;gt; src/main/resources/application.properties

      - name: Test and Build with Gradle
        run: ./gradlew clean build

      - name: 빌드 결과물 확인
        run: |
          ls build/libs

      - name: 빌드된 파일 이름 변경
        run: |
          mv build/libs/*SNAPSHOT.jar ./rusharp-server-SNAPSHOT.jar

      - name: SCP로 빌드된 파일을 EC2로 복사
        uses: appleboy/scp-action@v0.1.7
        with:
          host: ${{ secrets.EC2_HOST }} # EC2의 주소
          username: ${{ secrets.EC2_USERNAME }} # EC2 접속 username
          key: ${{ secrets.EC2_PRIVATE_KEY }} # EC2의 Key 파일의 내부 텍스트
          source: &quot;rusharp-server-SNAPSHOT.jar&quot; # 복사할 파일 경로
          target: &quot;/home/ubuntu/rusharp-server/tobe&quot; # 나중에 변경될 프로젝트 파일 경로
      
      - name: SSH 로 EC2에 접속
        uses: appleboy/ssh-action@v1.0.3
        with:
          host: ${{ secrets.EC2_HOST }} # EC2의 주소
          username: ${{ secrets.EC2_USERNAME }} # EC2 접속 username
          key: ${{ secrets.EC2_PRIVATE_KEY }} # EC2의 Key 파일의 내부 텍스트
          script_stop: true # 아래 script 중 실패하는 명령이 하나라도 있으면 실패로 처리
          script: |
            rm -rf /home/ubuntu/rusharp-server/current # 기존 환경변수 파일 삭제
            mkdir /home/ubuntu/rusharp-server/current

            # tobe 폴더에 복사된 jar 파일을 current 폴더로 이동
            mv /home/ubuntu/rusharp-server/tobe/rusharp-server-SNAPSHOT.jar /home/ubuntu/rusharp-server/current/rusharp-server-SNAPSHOT.jar
            cd /home/ubuntu/rusharp-server/current
            sudo fuser -k -n tcp 8080 || true
            nohup java -jar rusharp-server-SNAPSHOT.jar &amp;gt; ./output.log 2&amp;gt;&amp;amp;1 &amp;amp;
            rm -rf /home/ubuntu/rusharp-server/tobe/*&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;하나하나 차례대로 살펴보자면&lt;/p&gt;
&lt;p&gt;&lt;figure class=&quot;imageblock alignCenter&quot; data-ke-mobileStyle=&quot;widthOrigin&quot; data-origin-width=&quot;489&quot; data-origin-height=&quot;189&quot;&gt;&lt;span data-url=&quot;https://blog.kakaocdn.net/dn/bVzEVc/dJMcai9OTIe/by2ATQcTP0ePKcYHsxFCpk/img.png&quot; data-phocus=&quot;https://blog.kakaocdn.net/dn/bVzEVc/dJMcai9OTIe/by2ATQcTP0ePKcYHsxFCpk/img.png&quot;&gt;&lt;img src=&quot;https://blog.kakaocdn.net/dn/bVzEVc/dJMcai9OTIe/by2ATQcTP0ePKcYHsxFCpk/img.png&quot; srcset=&quot;https://img1.daumcdn.net/thumb/R1280x0/?scode=mtistory2&amp;fname=https%3A%2F%2Fblog.kakaocdn.net%2Fdn%2FbVzEVc%2FdJMcai9OTIe%2Fby2ATQcTP0ePKcYHsxFCpk%2Fimg.png&quot; onerror=&quot;this.onerror=null; this.src='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png'; this.srcset='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png';&quot; loading=&quot;lazy&quot; width=&quot;489&quot; height=&quot;189&quot; data-origin-width=&quot;489&quot; data-origin-height=&quot;189&quot;/&gt;&lt;/span&gt;&lt;/figure&gt;
&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;github repository에 있는 소스 코드를 Github Actions 컴퓨터로 불러온 뒤&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;ls 를 통해 현재 경로 하위의 파일과 폴더를 확인하고 pwd 로 현재 경로를 확인한다.&lt;/p&gt;
&lt;p&gt;&lt;figure class=&quot;imageblock alignCenter&quot; data-ke-mobileStyle=&quot;widthOrigin&quot; data-origin-width=&quot;316&quot; data-origin-height=&quot;130&quot;&gt;&lt;span data-url=&quot;https://blog.kakaocdn.net/dn/cojbTN/dJMcai9OTH5/A8oqwkHdfqrvopvNTQUCYk/img.png&quot; data-phocus=&quot;https://blog.kakaocdn.net/dn/cojbTN/dJMcai9OTH5/A8oqwkHdfqrvopvNTQUCYk/img.png&quot;&gt;&lt;img src=&quot;https://blog.kakaocdn.net/dn/cojbTN/dJMcai9OTH5/A8oqwkHdfqrvopvNTQUCYk/img.png&quot; srcset=&quot;https://img1.daumcdn.net/thumb/R1280x0/?scode=mtistory2&amp;fname=https%3A%2F%2Fblog.kakaocdn.net%2Fdn%2FcojbTN%2FdJMcai9OTH5%2FA8oqwkHdfqrvopvNTQUCYk%2Fimg.png&quot; onerror=&quot;this.onerror=null; this.src='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png'; this.srcset='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png';&quot; loading=&quot;lazy&quot; width=&quot;316&quot; height=&quot;130&quot; data-origin-width=&quot;316&quot; data-origin-height=&quot;130&quot;/&gt;&lt;/span&gt;&lt;/figure&gt;
&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;프로젝트 빌드에 필요한 Java개발 환경을 설치한다.&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;92&quot;&gt;&lt;span data-url=&quot;https://blog.kakaocdn.net/dn/dKuAtR/dJMcahC62LB/GNEOGOeVFkzsd4kLJXIk60/img.png&quot; data-phocus=&quot;https://blog.kakaocdn.net/dn/dKuAtR/dJMcahC62LB/GNEOGOeVFkzsd4kLJXIk60/img.png&quot;&gt;&lt;img src=&quot;https://blog.kakaocdn.net/dn/dKuAtR/dJMcahC62LB/GNEOGOeVFkzsd4kLJXIk60/img.png&quot; srcset=&quot;https://img1.daumcdn.net/thumb/R1280x0/?scode=mtistory2&amp;fname=https%3A%2F%2Fblog.kakaocdn.net%2Fdn%2FdKuAtR%2FdJMcahC62LB%2FGNEOGOeVFkzsd4kLJXIk60%2Fimg.png&quot; onerror=&quot;this.onerror=null; this.src='//t1.daumcdn.net/tistory_admin/static/images/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;92&quot; data-origin-width=&quot;915&quot; data-origin-height=&quot;92&quot;/&gt;&lt;/span&gt;&lt;/figure&gt;
&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;gitignore 에 포함된 application.properties 내용을 Github Secrets 에서 가져와서 빌드하기 전에 직접 파일로 만든다.&lt;/p&gt;
&lt;p&gt;&lt;figure class=&quot;imageblock alignCenter&quot; data-ke-mobileStyle=&quot;widthOrigin&quot; data-origin-width=&quot;368&quot; data-origin-height=&quot;156&quot;&gt;&lt;span data-url=&quot;https://blog.kakaocdn.net/dn/EXwlm/dJMcac9BDaX/QQWFKzrSQajsWm0AgOkZH1/img.png&quot; data-phocus=&quot;https://blog.kakaocdn.net/dn/EXwlm/dJMcac9BDaX/QQWFKzrSQajsWm0AgOkZH1/img.png&quot;&gt;&lt;img src=&quot;https://blog.kakaocdn.net/dn/EXwlm/dJMcac9BDaX/QQWFKzrSQajsWm0AgOkZH1/img.png&quot; srcset=&quot;https://img1.daumcdn.net/thumb/R1280x0/?scode=mtistory2&amp;fname=https%3A%2F%2Fblog.kakaocdn.net%2Fdn%2FEXwlm%2FdJMcac9BDaX%2FQQWFKzrSQajsWm0AgOkZH1%2Fimg.png&quot; onerror=&quot;this.onerror=null; this.src='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png'; this.srcset='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png';&quot; loading=&quot;lazy&quot; width=&quot;368&quot; height=&quot;156&quot; data-origin-width=&quot;368&quot; data-origin-height=&quot;156&quot;/&gt;&lt;/span&gt;&lt;/figure&gt;
&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;실제 프로젝트를 빌드하는 핵심 단계로 기존의 빌드 흔적을 지운 뒤 코드를 컴파일 한 뒤 실행 가능한 JAR파일을 만든다.&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;101&quot;&gt;&lt;span data-url=&quot;https://blog.kakaocdn.net/dn/dpR5lV/dJMcadgnWov/BmZ9oqcszkowYZRZgUtcR0/img.png&quot; data-phocus=&quot;https://blog.kakaocdn.net/dn/dpR5lV/dJMcadgnWov/BmZ9oqcszkowYZRZgUtcR0/img.png&quot;&gt;&lt;img src=&quot;https://blog.kakaocdn.net/dn/dpR5lV/dJMcadgnWov/BmZ9oqcszkowYZRZgUtcR0/img.png&quot; srcset=&quot;https://img1.daumcdn.net/thumb/R1280x0/?scode=mtistory2&amp;fname=https%3A%2F%2Fblog.kakaocdn.net%2Fdn%2FdpR5lV%2FdJMcadgnWov%2FBmZ9oqcszkowYZRZgUtcR0%2Fimg.png&quot; onerror=&quot;this.onerror=null; this.src='//t1.daumcdn.net/tistory_admin/static/images/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;101&quot; data-origin-width=&quot;615&quot; data-origin-height=&quot;101&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;768&quot; data-origin-height=&quot;211&quot;&gt;&lt;span data-url=&quot;https://blog.kakaocdn.net/dn/GmEIG/dJMcahwl2KE/SPzomCo9JRsSztIsRJGrUK/img.png&quot; data-phocus=&quot;https://blog.kakaocdn.net/dn/GmEIG/dJMcahwl2KE/SPzomCo9JRsSztIsRJGrUK/img.png&quot;&gt;&lt;img src=&quot;https://blog.kakaocdn.net/dn/GmEIG/dJMcahwl2KE/SPzomCo9JRsSztIsRJGrUK/img.png&quot; srcset=&quot;https://img1.daumcdn.net/thumb/R1280x0/?scode=mtistory2&amp;fname=https%3A%2F%2Fblog.kakaocdn.net%2Fdn%2FGmEIG%2FdJMcahwl2KE%2FSPzomCo9JRsSztIsRJGrUK%2Fimg.png&quot; onerror=&quot;this.onerror=null; this.src='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png'; this.srcset='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png';&quot; loading=&quot;lazy&quot; width=&quot;768&quot; height=&quot;211&quot; data-origin-width=&quot;768&quot; data-origin-height=&quot;211&quot;/&gt;&lt;/span&gt;&lt;/figure&gt;
&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;이후 EC2에 해당 jar 파일을 실제 운영 서버인 EC2로 전송한다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;참고로 /home/ubuntu/rusharp-server/current 폴더에 있는 서버는 실행중인 상태로 해당 폴더에&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;동일한 파일을 옮길 수 없어서 tobe라는 임시 폴더에 jar파일을 옮긴 뒤 추후 삭제한다.&amp;nbsp;&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;1309&quot; data-origin-height=&quot;410&quot;&gt;&lt;span data-url=&quot;https://blog.kakaocdn.net/dn/bSqUGY/dJMb99ZpcUg/wFOahx7n15o5LAj2eMsOC1/img.png&quot; data-phocus=&quot;https://blog.kakaocdn.net/dn/bSqUGY/dJMb99ZpcUg/wFOahx7n15o5LAj2eMsOC1/img.png&quot;&gt;&lt;img src=&quot;https://blog.kakaocdn.net/dn/bSqUGY/dJMb99ZpcUg/wFOahx7n15o5LAj2eMsOC1/img.png&quot; srcset=&quot;https://img1.daumcdn.net/thumb/R1280x0/?scode=mtistory2&amp;fname=https%3A%2F%2Fblog.kakaocdn.net%2Fdn%2FbSqUGY%2FdJMb99ZpcUg%2FwFOahx7n15o5LAj2eMsOC1%2Fimg.png&quot; onerror=&quot;this.onerror=null; this.src='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png'; this.srcset='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png';&quot; loading=&quot;lazy&quot; width=&quot;1309&quot; height=&quot;410&quot; data-origin-width=&quot;1309&quot; data-origin-height=&quot;410&quot;/&gt;&lt;/span&gt;&lt;/figure&gt;
&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;마지막으로 SSH로 EC2에 접속한 뒤에 기존 배포 폴더를 정리하고 새로 만든다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;일단 그전에 jar 파일이 있는 current 폴더를 삭제한 뒤 동일한 경로의 폴더를 새로 생성한다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;이후 tobe 폴더의 jar파일을 current 폴더로 이동하고 현재 진행중인 서버를 강제 종료시킨 다음에&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;새 서버를 실행시킨 뒤 마지막으로 임시로 썼던 tobe 폴더를 삭제함으로써 배포를 완료한다.&lt;/p&gt;
&lt;pre id=&quot;code_1768039313422&quot; class=&quot;bash&quot; data-ke-language=&quot;bash&quot; data-ke-type=&quot;codeblock&quot;&gt;&lt;code&gt;git update-index --chmod=+x gradlew
git add gradlew
git commit -m &quot;Add execute permission to gradlew&quot;
git push origin main&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;참고로 간혹 &quot;./gradlew: Permission denied&quot; 에러가 나오는 경우가 있는데,&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;그럴 땐 git bash에서 &amp;gt;아래 명령어를 입력하여 git index 의 gradlew 파일을 실행 가능 상태로 변경해야 한다.&lt;/p&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;1280&quot; data-origin-height=&quot;331&quot;&gt;&lt;span data-url=&quot;https://blog.kakaocdn.net/dn/74ycA/dJMcagRKBEe/PQ7vTC7CTPCu8eaPYmow2K/img.png&quot; data-phocus=&quot;https://blog.kakaocdn.net/dn/74ycA/dJMcagRKBEe/PQ7vTC7CTPCu8eaPYmow2K/img.png&quot;&gt;&lt;img src=&quot;https://blog.kakaocdn.net/dn/74ycA/dJMcagRKBEe/PQ7vTC7CTPCu8eaPYmow2K/img.png&quot; srcset=&quot;https://img1.daumcdn.net/thumb/R1280x0/?scode=mtistory2&amp;fname=https%3A%2F%2Fblog.kakaocdn.net%2Fdn%2F74ycA%2FdJMcagRKBEe%2FPQ7vTC7CTPCu8eaPYmow2K%2Fimg.png&quot; onerror=&quot;this.onerror=null; this.src='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png'; this.srcset='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png';&quot; loading=&quot;lazy&quot; width=&quot;1280&quot; height=&quot;331&quot; data-origin-width=&quot;1280&quot; data-origin-height=&quot;331&quot;/&gt;&lt;/span&gt;&lt;/figure&gt;
&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;jobs의 name 기준으로 &quot;Github Repository 에 올린 파일들을 불러오기&quot; 부터&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&quot;Test and Build with Gradle&quot;까지 Github Actions 안에서 실제 빌드 파일을 생성한다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;두번째로 &quot;SCP로 빌드된 파일을 EC2로 복사&quot;에서 빌드 파일을 EC2로 복사하고,&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&quot;SSH 로 EC2에 접속&quot; 에서 ssh 로 EC2에 접속하여 배포 스크립트를 진행까지 해보았다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;다음에는 Docker 까지 활용해서 CI/CD 프로세스를 구축해 볼 예정이다.&lt;/p&gt;</description>
      <category>Server&amp;amp;load/CI CD</category>
      <author>몽자비루</author>
      <guid isPermaLink="true">https://rusharp.tistory.com/206</guid>
      <comments>https://rusharp.tistory.com/206#entry206comment</comments>
      <pubDate>Sat, 10 Jan 2026 20:51:19 +0900</pubDate>
    </item>
    <item>
      <title>개인 프로젝트에서 쓰이는 CI/CD 구축 방법 실습하기</title>
      <link>https://rusharp.tistory.com/205</link>
      <description>&lt;p data-ke-size=&quot;size16&quot;&gt;이번에는 개인 프로젝트에서 쓰이는 CI/CD 프로세스를 직접 구축해보려고 한다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;h3 style=&quot;color: #000000; text-align: start;&quot; data-ke-size=&quot;size23&quot;&gt;1. spring boot 환경 구축하기&lt;/h3&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;먼저 해당 &lt;a href=&quot;https://start.spring.io/&quot; target=&quot;_blank&quot; rel=&quot;noopener&quot;&gt;링크&lt;/a&gt;에서 아래와 같이 설정한 뒤에 다운로드한 스트링 부트 파일을 사용할 예정이고,&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;해당 폴더를 VS Code 에서 열어 자동으로 build 를 실행하므로 끝날 때까지 기다려준다.&lt;/p&gt;
&lt;p&gt;&lt;figure class=&quot;imageblock alignCenter&quot; data-ke-mobileStyle=&quot;widthOrigin&quot; data-origin-width=&quot;1527&quot; data-origin-height=&quot;722&quot;&gt;&lt;span data-url=&quot;https://blog.kakaocdn.net/dn/c5P6Zc/dJMcabbImkj/ZCEzT5E59Y3fZ5PC2O6auk/img.png&quot; data-phocus=&quot;https://blog.kakaocdn.net/dn/c5P6Zc/dJMcabbImkj/ZCEzT5E59Y3fZ5PC2O6auk/img.png&quot;&gt;&lt;img src=&quot;https://blog.kakaocdn.net/dn/c5P6Zc/dJMcabbImkj/ZCEzT5E59Y3fZ5PC2O6auk/img.png&quot; srcset=&quot;https://img1.daumcdn.net/thumb/R1280x0/?scode=mtistory2&amp;fname=https%3A%2F%2Fblog.kakaocdn.net%2Fdn%2Fc5P6Zc%2FdJMcabbImkj%2FZCEzT5E59Y3fZ5PC2O6auk%2Fimg.png&quot; onerror=&quot;this.onerror=null; this.src='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png'; this.srcset='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png';&quot; loading=&quot;lazy&quot; width=&quot;1527&quot; height=&quot;722&quot; data-origin-width=&quot;1527&quot; data-origin-height=&quot;722&quot;/&gt;&lt;/span&gt;&lt;/figure&gt;
&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;이후에 src/main/java/com/example/{file_name} 에 &quot;AppController&quot; 파일을 추가하고 아래 내용을 입력한다.&lt;/p&gt;
&lt;pre id=&quot;code_1766321642735&quot; class=&quot;python&quot; data-ke-language=&quot;python&quot; data-ke-type=&quot;codeblock&quot;&gt;&lt;code&gt;package com.example.rusharp_server;

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

@RestController
public class AppController {
    // Add your endpoint mappings and methods here
    @GetMapping(&quot;/&quot;)
    public String home() {
        return &quot;Welcome to Rusharp Server!&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;이후에 RusharpServerApplication.java 을 실행시키면 localhost:8080 에서 &quot;Welcome to Rusharp Server!&quot;가 나온다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;이렇게 Springboot 가 실행되는 것을 확인한 뒤에 이제 Github repository에 올리고 CI/CD를 진행해보려고 한다.&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;2. EC2에 spring boot 서버 배포하고 접속하기&lt;/h3&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;먼저 Github 에 아래와 같이 push를 진행하여 넣고 AWS 를 사용하여 EC2 에 서버를 배포한다.&lt;/p&gt;
&lt;p&gt;&lt;figure class=&quot;imageblock alignCenter&quot; data-ke-mobileStyle=&quot;widthOrigin&quot; data-origin-width=&quot;903&quot; data-origin-height=&quot;639&quot;&gt;&lt;span data-url=&quot;https://blog.kakaocdn.net/dn/nmn6H/dJMcadUQXr4/iWvE2qytDVpjYfzn9s0KJK/img.png&quot; data-phocus=&quot;https://blog.kakaocdn.net/dn/nmn6H/dJMcadUQXr4/iWvE2qytDVpjYfzn9s0KJK/img.png&quot;&gt;&lt;img src=&quot;https://blog.kakaocdn.net/dn/nmn6H/dJMcadUQXr4/iWvE2qytDVpjYfzn9s0KJK/img.png&quot; srcset=&quot;https://img1.daumcdn.net/thumb/R1280x0/?scode=mtistory2&amp;fname=https%3A%2F%2Fblog.kakaocdn.net%2Fdn%2Fnmn6H%2FdJMcadUQXr4%2FiWvE2qytDVpjYfzn9s0KJK%2Fimg.png&quot; onerror=&quot;this.onerror=null; this.src='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png'; this.srcset='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png';&quot; loading=&quot;lazy&quot; width=&quot;903&quot; height=&quot;639&quot; data-origin-width=&quot;903&quot; data-origin-height=&quot;639&quot;/&gt;&lt;/span&gt;&lt;/figure&gt;
&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;그 다음으로 AWS 에서 EC2의 인스턴스를 시작하고, 아래와 같이 설정했다.&lt;/p&gt;
&lt;p&gt;&lt;figure class=&quot;imageblock alignCenter&quot; data-ke-mobileStyle=&quot;widthOrigin&quot; data-origin-width=&quot;1024&quot; data-origin-height=&quot;803&quot;&gt;&lt;span data-url=&quot;https://blog.kakaocdn.net/dn/mo9Wq/dJMcahpwwY8/VprS3k4iQ5beUbh5wST6KK/img.png&quot; data-phocus=&quot;https://blog.kakaocdn.net/dn/mo9Wq/dJMcahpwwY8/VprS3k4iQ5beUbh5wST6KK/img.png&quot; data-alt=&quot;Machine Image&quot;&gt;&lt;img src=&quot;https://blog.kakaocdn.net/dn/mo9Wq/dJMcahpwwY8/VprS3k4iQ5beUbh5wST6KK/img.png&quot; srcset=&quot;https://img1.daumcdn.net/thumb/R1280x0/?scode=mtistory2&amp;fname=https%3A%2F%2Fblog.kakaocdn.net%2Fdn%2Fmo9Wq%2FdJMcahpwwY8%2FVprS3k4iQ5beUbh5wST6KK%2Fimg.png&quot; onerror=&quot;this.onerror=null; this.src='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png'; this.srcset='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png';&quot; loading=&quot;lazy&quot; width=&quot;1024&quot; height=&quot;803&quot; data-origin-width=&quot;1024&quot; data-origin-height=&quot;803&quot;/&gt;&lt;/span&gt;&lt;figcaption&gt;Machine Image&lt;/figcaption&gt;
&lt;/figure&gt;
&lt;figure class=&quot;imageblock alignCenter&quot; data-ke-mobileStyle=&quot;widthOrigin&quot; data-origin-width=&quot;1023&quot; data-origin-height=&quot;378&quot;&gt;&lt;span data-url=&quot;https://blog.kakaocdn.net/dn/tKPcQ/dJMcahpww1b/PjGkNbDK3hw6WAm2alRz2K/img.png&quot; data-phocus=&quot;https://blog.kakaocdn.net/dn/tKPcQ/dJMcahpww1b/PjGkNbDK3hw6WAm2alRz2K/img.png&quot; data-alt=&quot;instance 유형 및 key pair&quot;&gt;&lt;img src=&quot;https://blog.kakaocdn.net/dn/tKPcQ/dJMcahpww1b/PjGkNbDK3hw6WAm2alRz2K/img.png&quot; srcset=&quot;https://img1.daumcdn.net/thumb/R1280x0/?scode=mtistory2&amp;fname=https%3A%2F%2Fblog.kakaocdn.net%2Fdn%2FtKPcQ%2FdJMcahpww1b%2FPjGkNbDK3hw6WAm2alRz2K%2Fimg.png&quot; onerror=&quot;this.onerror=null; this.src='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png'; this.srcset='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png';&quot; loading=&quot;lazy&quot; width=&quot;1023&quot; height=&quot;378&quot; data-origin-width=&quot;1023&quot; data-origin-height=&quot;378&quot;/&gt;&lt;/span&gt;&lt;figcaption&gt;instance 유형 및 key pair&lt;/figcaption&gt;
&lt;/figure&gt;
&lt;figure class=&quot;imageblock alignCenter&quot; data-ke-mobileStyle=&quot;widthOrigin&quot; data-origin-width=&quot;1009&quot; data-origin-height=&quot;806&quot;&gt;&lt;span data-url=&quot;https://blog.kakaocdn.net/dn/byzNl9/dJMcagqAXBq/pQlaEqsCEUlrH0ELcfrySk/img.png&quot; data-phocus=&quot;https://blog.kakaocdn.net/dn/byzNl9/dJMcagqAXBq/pQlaEqsCEUlrH0ELcfrySk/img.png&quot; data-alt=&quot;네트워크 설정에 HTTP, HTTPS 추가 및 편집으로 8080 port추가&quot;&gt;&lt;img src=&quot;https://blog.kakaocdn.net/dn/byzNl9/dJMcagqAXBq/pQlaEqsCEUlrH0ELcfrySk/img.png&quot; srcset=&quot;https://img1.daumcdn.net/thumb/R1280x0/?scode=mtistory2&amp;fname=https%3A%2F%2Fblog.kakaocdn.net%2Fdn%2FbyzNl9%2FdJMcagqAXBq%2FpQlaEqsCEUlrH0ELcfrySk%2Fimg.png&quot; onerror=&quot;this.onerror=null; this.src='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png'; this.srcset='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png';&quot; loading=&quot;lazy&quot; width=&quot;1009&quot; height=&quot;806&quot; data-origin-width=&quot;1009&quot; data-origin-height=&quot;806&quot;/&gt;&lt;/span&gt;&lt;figcaption&gt;네트워크 설정에 HTTP, HTTPS 추가 및 편집으로 8080 port추가&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;해당 인스턴스에 연결하고 t2.micro의 memory 는 1GB 인데 이를 높이기 위해서 t2.micro 램 스왑을 설정해야 한다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;만약 해당 과정이 번거롭다면 인스턴스 유형을 t3a.small 이상으로 진행하면 된다.&lt;/p&gt;
&lt;pre id=&quot;code_1766898706129&quot; class=&quot;bash&quot; data-ke-language=&quot;bash&quot; data-ke-type=&quot;codeblock&quot;&gt;&lt;code&gt;#1) Swap 파일 생성 (2GB)
#dd 명령어를 사용하여 128MB 블록을 16개 생성하여 총 2GB를 만든다. (약간의 시간이 소요될 수 있습니다.)

sudo dd if=/dev/zero of=/swapfile bs=128M count=16

#2) 권한 설정
#보안을 위해 root 계정만 읽고 쓸 수 있도록 권한을 수정힌다.

sudo chmod 600 /swapfile

#3) Swap 영역 설정
#생성한 파일을 Swap 공간으로 포맷팅한다.

sudo mkswap /swapfile

#4) Swap 활성화
#Swap 메모리를 즉시 활성화한다.

sudo swapon /swapfile

#5) 설정 확인
#메모리가 정상적으로 늘어났는지 확인합니다. Swap 항목에 약 2.0G가 잡혀있으면 성공.

free -h

#6) 재부팅 후에도 유지하기 (fstab 등록)
#위의 swapon 명령어는 리부팅 하면 초기화된다. 서버가 재시작되어도 자동으로 Swap이 잡히도록 설정 파일(/etc/fstab)을 수정한다..
#아래 명령어를 복사해서 실행하면 파일 맨 끝에 설정이 자동으로 추가된다.

echo '/swapfile swap swap defaults 0 0' | sudo tee -a /etc/fstab

#7) [옵션] JVM 옵션 설정하기
#Swap을 설정했다고 해서 무한정 메모리를 쓸 수 있는 것은 아니다. Swap(디스크)은 RAM보다 속도가 훨씬 느리기 때문에, 애플리케이션이 Swap 영역을 너무 적극적으로 사용하면 서버가 매우 느려진다.
#따라서 Spring Boot 실행 시 최대 힙 메모리(-Xmx)를 물리 메모리의 70~80% 수준으로 제한하는 것이 좋다다.

#-Xmx768m: 힙 메모리를 최대 768MB로 제한하여, 남은 물리 메모리와 Swap 영역을 OS 및 기타 프로세스가 사용할 수 있도록 여유를 줍니다.

java -Xmx768m -jar your-application.jar&lt;/code&gt;&lt;/pre&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;그 다음으로 스프링 부트 프로젝트를 EC2 서벗에서 배포하고 실행시키기 위한 환경을 설정한다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;아리 코드를 순서대로 입력하고 나면 java 17버전으로 설치되는 것을 확인할 수 있다.&lt;/p&gt;
&lt;pre id=&quot;code_1766904381530&quot; class=&quot;bash&quot; data-ke-language=&quot;bash&quot; data-ke-type=&quot;codeblock&quot;&gt;&lt;code&gt;sudo apt update
sudo apt install openjdk-17-jdk -y
java -version&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;889&quot; data-origin-height=&quot;117&quot;&gt;&lt;span data-url=&quot;https://blog.kakaocdn.net/dn/bmaxGG/dJMcahiJ4MK/MGLjdpiOfEziTeYaSHVofK/img.png&quot; data-phocus=&quot;https://blog.kakaocdn.net/dn/bmaxGG/dJMcahiJ4MK/MGLjdpiOfEziTeYaSHVofK/img.png&quot;&gt;&lt;img src=&quot;https://blog.kakaocdn.net/dn/bmaxGG/dJMcahiJ4MK/MGLjdpiOfEziTeYaSHVofK/img.png&quot; srcset=&quot;https://img1.daumcdn.net/thumb/R1280x0/?scode=mtistory2&amp;fname=https%3A%2F%2Fblog.kakaocdn.net%2Fdn%2FbmaxGG%2FdJMcahiJ4MK%2FMGLjdpiOfEziTeYaSHVofK%2Fimg.png&quot; onerror=&quot;this.onerror=null; this.src='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png'; this.srcset='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png';&quot; loading=&quot;lazy&quot; width=&quot;889&quot; height=&quot;117&quot; data-origin-width=&quot;889&quot; data-origin-height=&quot;117&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;이후 git clone 을 사용해서 EC2에 spring boot 폴더를 불러온 뒤 &lt;i&gt;&lt;b&gt;./gradlew clean build&amp;nbsp;&lt;/b&gt;&lt;/i&gt;로 실행한다.&lt;/p&gt;
&lt;p&gt;&lt;figure class=&quot;imageblock alignCenter&quot; data-ke-mobileStyle=&quot;widthOrigin&quot; data-origin-width=&quot;1495&quot; data-origin-height=&quot;561&quot;&gt;&lt;span data-url=&quot;https://blog.kakaocdn.net/dn/rJUsm/dJMcab3TD8k/Ni6iPzXBf79PXI7N8LzhKk/img.png&quot; data-phocus=&quot;https://blog.kakaocdn.net/dn/rJUsm/dJMcab3TD8k/Ni6iPzXBf79PXI7N8LzhKk/img.png&quot;&gt;&lt;img src=&quot;https://blog.kakaocdn.net/dn/rJUsm/dJMcab3TD8k/Ni6iPzXBf79PXI7N8LzhKk/img.png&quot; srcset=&quot;https://img1.daumcdn.net/thumb/R1280x0/?scode=mtistory2&amp;fname=https%3A%2F%2Fblog.kakaocdn.net%2Fdn%2FrJUsm%2FdJMcab3TD8k%2FNi6iPzXBf79PXI7N8LzhKk%2Fimg.png&quot; onerror=&quot;this.onerror=null; this.src='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png'; this.srcset='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png';&quot; loading=&quot;lazy&quot; width=&quot;1495&quot; height=&quot;561&quot; data-origin-width=&quot;1495&quot; data-origin-height=&quot;561&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;이후 /build/libs 하위에 rusharp-server-0.0.1 SNAPSHOT.jar 파일을 &lt;i&gt;&lt;b&gt;nohup java -jar [filename] &amp;amp;&lt;/b&gt;&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;다음이 lsof -i:8080 으로 8080 port에서 실행되는 프로세스가 java 인지 확인하고&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;publiciPs:8080 으로 접속하면&amp;nbsp;&amp;nbsp;&quot;Welcome&amp;nbsp;to&amp;nbsp;Rusharp&amp;nbsp;Server!&quot;&amp;nbsp;텍스트가&amp;nbsp;나오는&amp;nbsp;것을&amp;nbsp;확인할&amp;nbsp;수&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;1241&quot; data-origin-height=&quot;220&quot;&gt;&lt;span data-url=&quot;https://blog.kakaocdn.net/dn/bwFPzo/dJMcafrHloH/SstIpzbptcq2JuFCWUAa91/img.png&quot; data-phocus=&quot;https://blog.kakaocdn.net/dn/bwFPzo/dJMcafrHloH/SstIpzbptcq2JuFCWUAa91/img.png&quot;&gt;&lt;img src=&quot;https://blog.kakaocdn.net/dn/bwFPzo/dJMcafrHloH/SstIpzbptcq2JuFCWUAa91/img.png&quot; srcset=&quot;https://img1.daumcdn.net/thumb/R1280x0/?scode=mtistory2&amp;fname=https%3A%2F%2Fblog.kakaocdn.net%2Fdn%2FbwFPzo%2FdJMcafrHloH%2FSstIpzbptcq2JuFCWUAa91%2Fimg.png&quot; onerror=&quot;this.onerror=null; this.src='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png'; this.srcset='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png';&quot; loading=&quot;lazy&quot; width=&quot;1241&quot; height=&quot;220&quot; data-origin-width=&quot;1241&quot; data-origin-height=&quot;220&quot;/&gt;&lt;/span&gt;&lt;/figure&gt;
&lt;/p&gt;
&lt;h3 style=&quot;color: #000000; text-align: start;&quot; data-ke-size=&quot;size23&quot;&gt;3. 코드 업데이트 후 배포하기&lt;/h3&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;먼저 local&amp;nbsp; 환경에서 코드를 수정한 뒤에 push를 통해 github 환경에 코드를 등록한다.&lt;/p&gt;
&lt;p&gt;&lt;figure class=&quot;imageblock alignCenter&quot; data-ke-mobileStyle=&quot;widthOrigin&quot; data-origin-width=&quot;1022&quot; data-origin-height=&quot;341&quot;&gt;&lt;span data-url=&quot;https://blog.kakaocdn.net/dn/kAvna/dJMcafLZOZi/l9CGcVM824yxS63SAtfZd1/img.png&quot; data-phocus=&quot;https://blog.kakaocdn.net/dn/kAvna/dJMcafLZOZi/l9CGcVM824yxS63SAtfZd1/img.png&quot;&gt;&lt;img src=&quot;https://blog.kakaocdn.net/dn/kAvna/dJMcafLZOZi/l9CGcVM824yxS63SAtfZd1/img.png&quot; srcset=&quot;https://img1.daumcdn.net/thumb/R1280x0/?scode=mtistory2&amp;fname=https%3A%2F%2Fblog.kakaocdn.net%2Fdn%2FkAvna%2FdJMcafLZOZi%2Fl9CGcVM824yxS63SAtfZd1%2Fimg.png&quot; onerror=&quot;this.onerror=null; this.src='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png'; this.srcset='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png';&quot; loading=&quot;lazy&quot; width=&quot;1022&quot; height=&quot;341&quot; data-origin-width=&quot;1022&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;그다음으로 EC2로 들어가서 git pull을 진행한 뒤에 기존에 있던 서버 (8080 포트)를 종료시킨다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;다음으로 새로 받은 코드를 기준으로 ./gradlew clean build 를 통해 빌드를 진행한 뒤에&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;SNAPSHOT.jar 파일을 실행시키고 8080포트에서 실행중인 프로세스를 확인하면 java인 것을 볼 수 있다.&lt;/p&gt;
&lt;p&gt;&lt;figure class=&quot;imageblock alignCenter&quot; data-ke-mobileStyle=&quot;widthOrigin&quot; data-origin-width=&quot;1320&quot; data-origin-height=&quot;773&quot;&gt;&lt;span data-url=&quot;https://blog.kakaocdn.net/dn/baQNNi/dJMcadAA3vK/gVhFkjNimUuKDK4iQB5ELK/img.png&quot; data-phocus=&quot;https://blog.kakaocdn.net/dn/baQNNi/dJMcadAA3vK/gVhFkjNimUuKDK4iQB5ELK/img.png&quot;&gt;&lt;img src=&quot;https://blog.kakaocdn.net/dn/baQNNi/dJMcadAA3vK/gVhFkjNimUuKDK4iQB5ELK/img.png&quot; srcset=&quot;https://img1.daumcdn.net/thumb/R1280x0/?scode=mtistory2&amp;fname=https%3A%2F%2Fblog.kakaocdn.net%2Fdn%2FbaQNNi%2FdJMcadAA3vK%2FgVhFkjNimUuKDK4iQB5ELK%2Fimg.png&quot; onerror=&quot;this.onerror=null; this.src='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png'; this.srcset='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png';&quot; loading=&quot;lazy&quot; width=&quot;1320&quot; height=&quot;773&quot; data-origin-width=&quot;1320&quot; data-origin-height=&quot;773&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;github actions 의 CI/CD 프로세스를 사용하면 위와 같은 모늗 일련의 과정을 한번에 처리할 수 있다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;그 전에 현재는 pull할 때마다 매번 닉네임과 비밀번호를 입력해야 하는데 자동 로그인이 되도록 하기 위해서&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;i&gt;&lt;b&gt;git config --global credential.helper store&lt;/b&gt; &lt;/i&gt;를 입력하고 git pull 을 진행했을 때,&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;닉네임과 token 비밀번호를 한번 입력하면 그 다음부터는 닉네임과 비밀번호를 요청하지 않는다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;다만 이렇게 되면 EC2에 접근할 수 있는 모든 사용자가 개인 token비밀번호를 볼 수 있는 보안적 한계가 생긴다.&lt;/p&gt;
&lt;p&gt;&lt;figure class=&quot;imageblock alignCenter&quot; data-ke-mobileStyle=&quot;widthOrigin&quot; data-origin-width=&quot;750&quot; data-origin-height=&quot;382&quot;&gt;&lt;span data-url=&quot;https://blog.kakaocdn.net/dn/bJncSp/dJMcaacQKMh/jZI66VtjJjkrnHwkDZlVVk/img.png&quot; data-phocus=&quot;https://blog.kakaocdn.net/dn/bJncSp/dJMcaacQKMh/jZI66VtjJjkrnHwkDZlVVk/img.png&quot;&gt;&lt;img src=&quot;https://blog.kakaocdn.net/dn/bJncSp/dJMcaacQKMh/jZI66VtjJjkrnHwkDZlVVk/img.png&quot; srcset=&quot;https://img1.daumcdn.net/thumb/R1280x0/?scode=mtistory2&amp;fname=https%3A%2F%2Fblog.kakaocdn.net%2Fdn%2FbJncSp%2FdJMcaacQKMh%2FjZI66VtjJjkrnHwkDZlVVk%2Fimg.png&quot; onerror=&quot;this.onerror=null; this.src='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png'; this.srcset='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png';&quot; loading=&quot;lazy&quot; width=&quot;750&quot; height=&quot;382&quot; data-origin-width=&quot;750&quot; data-origin-height=&quot;382&quot;/&gt;&lt;/span&gt;&lt;/figure&gt;
&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;h3 style=&quot;color: #000000; text-align: start;&quot; data-ke-size=&quot;size23&quot;&gt;3. CI/CD 프로세스로 배포/빌드하기&lt;/h3&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;그렇다면 위 과정을 CI/CD 프로세스로 배포하기 위해서는 먼저 .github/workflows 에서 deploy.yml파일을 생성한다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;앞서 Github Actions 는 일종의 로직을 실행시킬 수 있는 하나의 컴퓨터라고 정의했었는데,&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;EC2도 spring boot 를 실행할 수 있는 일종의 컴퓨터로서, Github actions 에서 ssh 로 원격접속 후 실행되어야 한다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;ssh 로 원격접속하는 방법은 여러개가 있는데, appleboy/ssh-action 을 사용했고 형식은 &lt;a href=&quot;https://github.com/marketplace/actions/ssh-remote-commands#%EF%B8%8F-usage-scenarios--advanced-examples&quot; target=&quot;_blank&quot; rel=&quot;noopener&quot;&gt;링크&lt;/a&gt;를 참고했다.&lt;/p&gt;
&lt;pre id=&quot;code_1766917194937&quot; class=&quot;bash&quot; data-ke-language=&quot;bash&quot; data-ke-type=&quot;codeblock&quot;&gt;&lt;code&gt;name : Deploy To EC2

on:
  push:
    branches:
      - main

jobs:
  My-Deploy-Job:
    runs-on: ubuntu-latest

    steps:
      - name: SSH 로 EC2에 접속
        uses: appleboy/ssh-action@v1.0.3
        with:
          host: ${{ secrets.EC2_HOST }} # EC2의 주소
          username: ${{ secrets.EC2_USERNAME }} # EC2 접속 username
          key: ${{ secrets.EC2_PRIVATE_KEY }} # EC2의 Key 파일의 내부 텍스트
          script_stop: true # 아래 script 중 실패하는 명령이 하나라도 있으면 실패로 처리
          script: |
            cd /home/ubuntu/rusharp-server/
            git pull origin main
            ./gradlew clean build
            sudo fuser -k -n tcp 8080 || true # || true를 붙인 이유는 8080에 종료시킬 프로세스가 없더라도 실패로 처리하지 않음
            # jar 파일을 실행시키는 명령어. 그리고 발생하는 로그를 ./output.log 파일에 남김
            nohup java -jar build/libs/*SNAPSHOT.jar &amp;gt; ./output.log 2&amp;gt;&amp;amp;1 &amp;amp;&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;여기에서 script 하위에 있는 명령어들은 위 EC2에서 동작했던 내용들을 기반으로 작성했다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;그리고 보안 변수는 github repository settings 의 Secrets and variables/Actions&amp;nbsp;Repository secrets 에 추가한다.&lt;/p&gt;
&lt;p&gt;&lt;figure class=&quot;imageblock alignCenter&quot; data-ke-mobileStyle=&quot;widthOrigin&quot; data-origin-width=&quot;890&quot; data-origin-height=&quot;279&quot;&gt;&lt;span data-url=&quot;https://blog.kakaocdn.net/dn/qvkFN/dJMcafSLPT0/1II8lCpt1HXdesvohRgF51/img.png&quot; data-phocus=&quot;https://blog.kakaocdn.net/dn/qvkFN/dJMcafSLPT0/1II8lCpt1HXdesvohRgF51/img.png&quot;&gt;&lt;img src=&quot;https://blog.kakaocdn.net/dn/qvkFN/dJMcafSLPT0/1II8lCpt1HXdesvohRgF51/img.png&quot; srcset=&quot;https://img1.daumcdn.net/thumb/R1280x0/?scode=mtistory2&amp;fname=https%3A%2F%2Fblog.kakaocdn.net%2Fdn%2FqvkFN%2FdJMcafSLPT0%2F1II8lCpt1HXdesvohRgF51%2Fimg.png&quot; onerror=&quot;this.onerror=null; this.src='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png'; this.srcset='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png';&quot; loading=&quot;lazy&quot; width=&quot;890&quot; height=&quot;279&quot; data-origin-width=&quot;890&quot; data-origin-height=&quot;279&quot;/&gt;&lt;/span&gt;&lt;/figure&gt;
&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;여기에서 EC2_USERNAME 는 EC2에서 whoami 를 입력했을 때 나오는 사용자 이름이고,&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;EC2_HOST 는 EC2의 PublicIPs, PRIVATE_KEY 는 EC2 key_par의 값으로, 터미널에서 확인할 수 있다.&lt;/p&gt;
&lt;p&gt;&lt;figure class=&quot;imageblock alignCenter&quot; data-ke-mobileStyle=&quot;widthOrigin&quot; data-origin-width=&quot;660&quot; data-origin-height=&quot;559&quot;&gt;&lt;span data-url=&quot;https://blog.kakaocdn.net/dn/bFhxiJ/dJMcafLZRGV/JzHvYKcPgwGWfyNs9Pbdu0/img.png&quot; data-phocus=&quot;https://blog.kakaocdn.net/dn/bFhxiJ/dJMcafLZRGV/JzHvYKcPgwGWfyNs9Pbdu0/img.png&quot;&gt;&lt;img src=&quot;https://blog.kakaocdn.net/dn/bFhxiJ/dJMcafLZRGV/JzHvYKcPgwGWfyNs9Pbdu0/img.png&quot; srcset=&quot;https://img1.daumcdn.net/thumb/R1280x0/?scode=mtistory2&amp;fname=https%3A%2F%2Fblog.kakaocdn.net%2Fdn%2FbFhxiJ%2FdJMcafLZRGV%2FJzHvYKcPgwGWfyNs9Pbdu0%2Fimg.png&quot; onerror=&quot;this.onerror=null; this.src='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png'; this.srcset='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png';&quot; loading=&quot;lazy&quot; width=&quot;660&quot; height=&quot;559&quot; data-origin-width=&quot;660&quot; data-origin-height=&quot;559&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;이제 git push 를 진행한 뒤에 Actions 에서 확인하면 아래와 같이 성공한 것을 볼 수 있다.&lt;/p&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번에서 코드 업데이트 후 배포하는 모든 과정을 yml을 통해서 자동으로 돌아가도록 만들어봤다.&lt;/p&gt;
&lt;p&gt;&lt;figure class=&quot;imageblock alignCenter&quot; data-ke-mobileStyle=&quot;widthOrigin&quot; data-origin-width=&quot;1011&quot; data-origin-height=&quot;420&quot;&gt;&lt;span data-url=&quot;https://blog.kakaocdn.net/dn/V0VsQ/dJMcacV2dRS/ylA3Lpame3qjkkkDulHZv0/img.png&quot; data-phocus=&quot;https://blog.kakaocdn.net/dn/V0VsQ/dJMcacV2dRS/ylA3Lpame3qjkkkDulHZv0/img.png&quot;&gt;&lt;img src=&quot;https://blog.kakaocdn.net/dn/V0VsQ/dJMcacV2dRS/ylA3Lpame3qjkkkDulHZv0/img.png&quot; srcset=&quot;https://img1.daumcdn.net/thumb/R1280x0/?scode=mtistory2&amp;fname=https%3A%2F%2Fblog.kakaocdn.net%2Fdn%2FV0VsQ%2FdJMcacV2dRS%2FylA3Lpame3qjkkkDulHZv0%2Fimg.png&quot; onerror=&quot;this.onerror=null; this.src='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png'; this.srcset='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png';&quot; loading=&quot;lazy&quot; width=&quot;1011&quot; height=&quot;420&quot; data-origin-width=&quot;1011&quot; data-origin-height=&quot;420&quot;/&gt;&lt;/span&gt;&lt;/figure&gt;
&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;h3 style=&quot;color: #000000; text-align: start;&quot; data-ke-size=&quot;size23&quot;&gt;4. gitignore 에 포함된 파일을 함께 관리하기&lt;/h3&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;지금까지는 CI/CD 를 사용해서 배포를 진행해봤는데, 간혹 보안적인 이슈로 인해&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;예를 들어 src/main/resources/templates의 application.properties 가 .gitignore에 포함되어있다면&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;이를 배포할 때마다 직접 EC2에 접속해서 해당 파일을 생성해 줘야하기 때문에 번거롭다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;해당 내용을 secret 변수로 등록하고 script 안에서 secret 에 대한 변수를 사용하게끔 만들 수 있다.&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;903&quot; data-origin-height=&quot;493&quot;&gt;&lt;span data-url=&quot;https://blog.kakaocdn.net/dn/ql4EY/dJMcaa4ZeJ0/ezCL9DnRR6EoAMKQTyEHKK/img.png&quot; data-phocus=&quot;https://blog.kakaocdn.net/dn/ql4EY/dJMcaa4ZeJ0/ezCL9DnRR6EoAMKQTyEHKK/img.png&quot;&gt;&lt;img src=&quot;https://blog.kakaocdn.net/dn/ql4EY/dJMcaa4ZeJ0/ezCL9DnRR6EoAMKQTyEHKK/img.png&quot; srcset=&quot;https://img1.daumcdn.net/thumb/R1280x0/?scode=mtistory2&amp;fname=https%3A%2F%2Fblog.kakaocdn.net%2Fdn%2Fql4EY%2FdJMcaa4ZeJ0%2FezCL9DnRR6EoAMKQTyEHKK%2Fimg.png&quot; onerror=&quot;this.onerror=null; this.src='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png'; this.srcset='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png';&quot; loading=&quot;lazy&quot; width=&quot;903&quot; height=&quot;493&quot; data-origin-width=&quot;903&quot; data-origin-height=&quot;493&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;1154&quot; data-origin-height=&quot;570&quot;&gt;&lt;span data-url=&quot;https://blog.kakaocdn.net/dn/d5r6Ob/dJMcaioppuq/9VRwphdah3AvnxsylrehRK/img.png&quot; data-phocus=&quot;https://blog.kakaocdn.net/dn/d5r6Ob/dJMcaioppuq/9VRwphdah3AvnxsylrehRK/img.png&quot;&gt;&lt;img src=&quot;https://blog.kakaocdn.net/dn/d5r6Ob/dJMcaioppuq/9VRwphdah3AvnxsylrehRK/img.png&quot; srcset=&quot;https://img1.daumcdn.net/thumb/R1280x0/?scode=mtistory2&amp;fname=https%3A%2F%2Fblog.kakaocdn.net%2Fdn%2Fd5r6Ob%2FdJMcaioppuq%2F9VRwphdah3AvnxsylrehRK%2Fimg.png&quot; onerror=&quot;this.onerror=null; this.src='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png'; this.srcset='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png';&quot; loading=&quot;lazy&quot; width=&quot;1154&quot; height=&quot;570&quot; data-origin-width=&quot;1154&quot; data-origin-height=&quot;570&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;내용에 대해서 설명하자면 env 안에서 secret 변수 값을 APPLICATION_PROPERTIES 에 선언하고&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;script 안에서 기존에 있던 application.properties 파일을 삭제한 뒤에 다시 생성한다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;그러면 아래와 같이 github 에서는 application.properties 파일이 없지만&amp;nbsp;EC2에서는 해당 파일이 존재한다.&lt;/p&gt;
&lt;p&gt;&lt;figure class=&quot;imageblock alignCenter&quot; data-ke-mobileStyle=&quot;widthOrigin&quot; data-origin-width=&quot;957&quot; data-origin-height=&quot;624&quot;&gt;&lt;span data-url=&quot;https://blog.kakaocdn.net/dn/c4VVMv/dJMcaaYep0F/vIBS5RXs5CGdw2S6rC5Zv1/img.png&quot; data-phocus=&quot;https://blog.kakaocdn.net/dn/c4VVMv/dJMcaaYep0F/vIBS5RXs5CGdw2S6rC5Zv1/img.png&quot;&gt;&lt;img src=&quot;https://blog.kakaocdn.net/dn/c4VVMv/dJMcaaYep0F/vIBS5RXs5CGdw2S6rC5Zv1/img.png&quot; srcset=&quot;https://img1.daumcdn.net/thumb/R1280x0/?scode=mtistory2&amp;fname=https%3A%2F%2Fblog.kakaocdn.net%2Fdn%2Fc4VVMv%2FdJMcaaYep0F%2FvIBS5RXs5CGdw2S6rC5Zv1%2Fimg.png&quot; onerror=&quot;this.onerror=null; this.src='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png'; this.srcset='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png';&quot; loading=&quot;lazy&quot; width=&quot;957&quot; height=&quot;624&quot; data-origin-width=&quot;957&quot; data-origin-height=&quot;624&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;이렇게 개인 프로젝트에서 일반적으로 사용되는 CI/CD 프로세스에 대해서 공부해보았는데,&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;git pull을 사용하기 때문에 심플하긴 하지만 보안적으로 취약하다는 단점이 있다는 것을 알 수 있었다.&lt;/p&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>Server&amp;amp;load/CI CD</category>
      <category>CI/CD</category>
      <category>Github Actions</category>
      <author>몽자비루</author>
      <guid isPermaLink="true">https://rusharp.tistory.com/205</guid>
      <comments>https://rusharp.tistory.com/205#entry205comment</comments>
      <pubDate>Sun, 28 Dec 2025 22:07:36 +0900</pubDate>
    </item>
    <item>
      <title>Thread, Sync, Async 의 동작 방식</title>
      <link>https://rusharp.tistory.com/204</link>
      <description>&lt;p data-ke-size=&quot;size16&quot;&gt;지금까지 다양한 자동화 도구를 만들어 왔는데, 이때에 거의 Sync 방식으로 작성해두었다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;그러다가 동작이 완료되는 데까지 시간이 너무 오래걸리게 되어 병렬적으로 Thread 방식으로 개선도 진행해보고&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;리소스 사용량이 너무 많아 크래시가 발생하길래 Async방식도 시도해봤는데, 이 과정에서&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;먼저 제목에도 있다시피 Sync, Async, Thread 세 가지는 함수를 어떻게 기다리고, 어떻게 이어서 실행하느냐&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;1. Sync (동기)&lt;/h3&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;Sync방식은 하나의 작업이 끝나야 다음 작업을 진행하는 방식으로 기다리는 동안 아무것도 하지 않는다.&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;pre id=&quot;code_1765946306056&quot; class=&quot;python&quot; data-ke-language=&quot;python&quot; data-ke-type=&quot;codeblock&quot;&gt;&lt;code&gt;import time

def work():
    print(&quot;시작&quot;)
    time.sleep(2)
    print(&quot;끝&quot;)

print(&quot;메인 시작&quot;)
work()
print(&quot;메인 끝&quot;)


# 실행 결과
&quot;&quot;&quot;
메인 시작
시작
끝
메인 끝
&quot;&quot;&quot;&lt;/code&gt;&lt;/pre&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;위 내용을 보면 순차적인 진행을 100% 보장하며, 구도가 간단해 코드가 직관적인 동시에&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;흐름 추적 및 디버깅이 쉬워서 반드시 순서대로 진행되어야 하지만 대기가 적은 경우에 자주 사용된다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;다만, 대기가 많은 경우 기다리는 동안 리소스 낭비가 발생할 수 있고, 확장성이 낮아서 처리량이 늘어날수록&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;작업 속도가 느려지고 리소스 샤용량이 늘어나서 안정적으로 프로그램을 돌리기에 한계가 있다.&lt;/p&gt;
&lt;h3 style=&quot;color: #000000; text-align: start;&quot; data-ke-size=&quot;size23&quot;&gt;2. Async/Await (비동기)&lt;/h3&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;이러한 한계를 해결하기 위한 방법으로 Async 방식, 즉 비동기 방식을 선택할 수 있다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;비동기 방식은 기다릴 때 기다리고 준비되면 다시 실행할 수 있는 방식으로 리소스를 잡고 있지 않는다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;Async 방식을 가장 쉽게 알 수 있는 코드는 아래와 같다.&lt;/p&gt;
&lt;pre id=&quot;code_1765947073219&quot; class=&quot;python&quot; data-ke-language=&quot;python&quot; data-ke-type=&quot;codeblock&quot;&gt;&lt;code&gt;import asyncio

async def func1():
    print(&quot;실행1&quot;)
    await asyncio.sleep(3)
    print(&quot;실행2&quot;)

async def func2():
    print(&quot;실행3&quot;)
    await asyncio.sleep(0)
    print(&quot;실행4&quot;)

async def main():

    print(&quot;asyncio 시작&quot;)
    
    task1 = asyncio.create_task(func1())
    task2 = asyncio.create_task(func2())
    await task1
    await task2

    print()
    print(&quot;await 시작&quot;)
    await func1()
    await func2()

asyncio.run(main())


#실행 결과:
&quot;&quot;&quot;
asyncio 시작
실행1
실행3
실행4
실행2

await 시작
실행1
실행2
실행3
&quot;&quot;&quot;&lt;/code&gt;&lt;/pre&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;여기서 async def 는 비동기 함수, await은 멈춤 지점, asyncio 실행 엔진이라고 볼 수 있다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;먼저 asyncio에 대해서 먼저 언급하자면, task1 = asyncio.creat_task(func1()) 은 해당 함수를 실행한다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;이후 await task1 은 해당 함수가 끝날때까지 기다리는 역할을 하는 것으로 위 내용의 경우에는&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;func1 에서 await asyncio.sleep(3) 지점에 도착하면 제어권을 넘기고 func2를 실행한다\&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;그렇다면 task1 = asyncio.creat_task(func1()) , task2 = asyncio.creat_task(func2()) 여기에서&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;func1의 실행1이 먼저 진행되고 3초동안 기다리면서 func2가 실행 3, 4 가 출력된다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;그리고 &quot;await 시작&quot; 이 바로 출력되지 않는 이유는 await task1, await task2 가 코드에 있기 때문이다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;이 두개로 인해 func1 과 func2 가&amp;nbsp;&amp;nbsp;끝나기 전까지는 이 뒤의 실행이 동작하지 않는데 정확히 말하자면&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;await 을 만나면 코루틴은 대기 상태로 전환되고 이벤트 루프는 실행 가능한 다른 task를 처리한다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;추가로 실무에서는 asyncio.gather()를 자주 사용한다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;이전에는 task1 = asyncio.creat_task(func1()) 처럼 작업을 각각 예약하고 각각 await을 진행했지만,&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;코드가 복잡해지는 것을 방지하기 위해 await asyncio.gather(func1(), func2()) 를 사용하기도 한다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;그렇게 되면 내부적으로 create_task 를 실행하는 것과 동일하게 여러 비동기 작업을 동시에 실행시키고&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;그렇다면 await func1() 은 무엇일까?&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;task1 = asyncio.creat_task(func1()) 그리고 await task1 을 합친 것이라고 볼 수 있다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;즉, func1을 실행시키고 func1 이 끝날 때까지 기다리는 형태로 동작하게 된다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;따라서 await 으로 함수를 실행하게 되면 func1 이 끝날때까지 다른 함수가 실행되지 못하므로&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;3초의 대기 시간동안에도 func2 를 실행시키지 못하지만, sync 와 달리 대기 시간동안 리소스를 잡지 않는다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;즉, await 기준으로 순서가 보장되며 CPU 사용을 최소화할 수 있어서 대기가 많은 작업에 최적화 되어있다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;대규모 I/O 처리가 가능하고 특히 Playwright 에서는 async /await 을 기본적으로 사용하게 된다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;h3 style=&quot;color: #000000; text-align: start;&quot; data-ke-size=&quot;size23&quot;&gt;3. Thread&lt;/h3&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;그렇다면 여기에서 나아가서, 여러 동작을 병렬적으로 실행하고 싶을 땐 아래와 같이 thread 함수를 사용한다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;thread 동작 결과를 가장 쉽게 보여줄 수 있는 코드는 아래와 같다.&lt;/p&gt;
&lt;pre id=&quot;code_1765959437390&quot; class=&quot;python&quot; data-ke-language=&quot;python&quot; data-ke-type=&quot;codeblock&quot;&gt;&lt;code&gt;import threading
import time

threads = []
num = 0
lock = threading.Lock()

def add_number():
    global num
    # with lock:
    temp = num          
    time.sleep(0.001)   
    temp += 1           
    num = temp          

for _ in range(100):
    t = threading.Thread(target=add_number)
    threads.append(t)
    t.start()

for t in threads:
    t.join()

print(&quot;Lock 없음 결과:&quot;, num)

#실행 결과:
&quot;&quot;&quot;
Lock 없음 결과: 5
&quot;&quot;&quot;&lt;/code&gt;&lt;/pre&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;약간 복잡할 수 있는데, 먼저 lock 이라는 개념을 보지 말고 위 동작을 확인해보자.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;t 에 threading.Tread(target=add_number) 으로 add_number 함수를 넣은 뒤에&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;t.start 로 add_number 함수를 Thread 방식으로 동작시키면, 병렬적으로 add_number 이 실행된다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;근데 왜 num 은 100이 이 아닌 5가 되었을까?&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;num = temp 를 하기 전에 다른 add_number 함수가 num 변수를 가져갔기 때문이다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;Thread 는 쉽게 말하자면 여러명의 사람이 해당 동작을 실행하는 방식이다.&lt;/p&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_number은 global number 을 가져가는데 이때 20개의 add_number 이&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;0이라는 숫자를 가져간 다음에 1을 더하고 다음에 num 에 1을 덮어씌워 버리게 된다.&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;그다음에 또다시 20개의 함수가 1을 가져가고 거기에 1을 더해 2를 덮어씌워 버리는 것을 반복한다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;num 은 결과적으로 100이 되지 않고 중복되는 Thread 동작으로 인해 임의의 숫자가 된다.&lt;/p&gt;
&lt;pre id=&quot;code_1765975464964&quot; class=&quot;python&quot; data-ke-language=&quot;python&quot; data-ke-type=&quot;codeblock&quot;&gt;&lt;code&gt;import threading
import time

threads = []
num = 0
lock = threading.Lock()

def add_number_with_lock():
    global num
    with lock:
        temp = num          
        time.sleep(0.001)   
        temp += 1           
        num = temp

for _ in range(100):
    t = threading.Thread(target=add_number_with_lock)
    threads.append(t)
    t.start()

for t in threads:
    t.join()

print(&quot;Lock 있음 결과:&quot;, num)

#실행 결과:
&quot;&quot;&quot;
Lock 있음 결과: 100
&quot;&quot;&quot;&lt;/code&gt;&lt;/pre&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;Thread 를 사용하는 환경에서 num 을 100으로 만들기 위해서는 with lock 이 있다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;with lock 이란 해당 구간을 Thread 가 한번만 들어올 수 있게 만들어 temp 가 반드시&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;이전에 +1된 값을 받아오므로, 위와 같이 동시에 num에 접근할 수 없다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;이 Thread 함수는 return을 받을 수 없기 때문에, 만약 함수 안에서 어떠한 값을 받고 싶다면&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;위와 같이 공유 변수를 사용하거나 list/dict 같은 컨테이너에 담는 방법이 가장 흔하고,&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;실무에서는 안전성을 위해서 queue.Queue 에 담아서 사용하기도 한다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;마지막으로 join() 에 대해서 이야기하자면, join() 은 메인 스레드가 해당 스레드의 종료를&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;Thread는 여러 thread 가 동시에 진행되므로, 동작이 끝나지 않았는데도 프로그램이 끝날 수 있다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;이때 이 Thread 가 끝나기 전까지는 실행된 프로그램을 종료를 강제로 대기시키는 것이다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;이렇게 작업을 병렬 처리하므로 API 로 여러 서버를 동시에 전송하거나 병렬적인 작업엔 유리하다.&lt;/p&gt;
&lt;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;특히 구조가 커지거나 lock 실수를 하는 경우, 버그 혹은 데드락까지 생길 수 있을 정도로 위험성이 높다.&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p&gt;&lt;figure class=&quot;imageblock alignCenter&quot; data-ke-mobileStyle=&quot;widthOrigin&quot; data-origin-width=&quot;638&quot; data-origin-height=&quot;359&quot;&gt;&lt;span data-url=&quot;https://blog.kakaocdn.net/dn/LNu5h/dJMcabQidL8/G3cGxJfjRLfqbVWAwSqrwK/img.jpg&quot; data-phocus=&quot;https://blog.kakaocdn.net/dn/LNu5h/dJMcabQidL8/G3cGxJfjRLfqbVWAwSqrwK/img.jpg&quot;&gt;&lt;img src=&quot;https://blog.kakaocdn.net/dn/LNu5h/dJMcabQidL8/G3cGxJfjRLfqbVWAwSqrwK/img.jpg&quot; srcset=&quot;https://img1.daumcdn.net/thumb/R1280x0/?scode=mtistory2&amp;fname=https%3A%2F%2Fblog.kakaocdn.net%2Fdn%2FLNu5h%2FdJMcabQidL8%2FG3cGxJfjRLfqbVWAwSqrwK%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;638&quot; height=&quot;359&quot; data-origin-width=&quot;638&quot; data-origin-height=&quot;359&quot;/&gt;&lt;/span&gt;&lt;/figure&gt;
&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;이렇게 세가지 동작 방식에 대해 알아봤는데, 어떤 것이 더 좋다나쁘다를 따지는 것이 아니라&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;본인이 만들고자 하는 프로그램의 성격과 리소스 흐름을 정확하게 파악하는 것이 무엇보다 중요하다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;-&amp;nbsp;Sync&amp;nbsp;&amp;nbsp;:&amp;nbsp;순서가&amp;nbsp;중요하고&amp;nbsp;대기가&amp;nbsp;적을&amp;nbsp;때 &lt;br /&gt;-&amp;nbsp;Async&amp;nbsp;:&amp;nbsp;I/O&amp;nbsp;대기가&amp;nbsp;많고&amp;nbsp;확장성이&amp;nbsp;필요할&amp;nbsp;때 &lt;br /&gt;-&amp;nbsp;Thread:&amp;nbsp;병렬&amp;nbsp;처리가&amp;nbsp;필요하지만&amp;nbsp;async&amp;nbsp;사용이&amp;nbsp;어려울&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;다만, Python의&amp;nbsp; Thread 는 GIL 로 인해 CPU 연산 병렬 처리에는 한계가 있으므로 Thread는 한계가 있다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;따라서 계산량이 아주 많은 작업을 할 때에는 Multiprocessing 을 고려해야 할 수도 있다.&lt;/p&gt;</description>
      <category>python</category>
      <category>Async</category>
      <category>python</category>
      <category>Sync</category>
      <category>thread</category>
      <author>몽자비루</author>
      <guid isPermaLink="true">https://rusharp.tistory.com/204</guid>
      <comments>https://rusharp.tistory.com/204#entry204comment</comments>
      <pubDate>Wed, 17 Dec 2025 22:56:02 +0900</pubDate>
    </item>
    <item>
      <title>커밋 관리하기</title>
      <link>https://rusharp.tistory.com/203</link>
      <description>&lt;p data-ke-size=&quot;size16&quot;&gt;협업을 하다보면 github에 commit 을 하는 경우가 많은데, 이때 불필요하게 자주 commit 을 하거나&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;알아보기 어려운 내용의 commit message를 작성하면 추후 문제가 생겼을 때 관리하기 어렵다.&lt;/p&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;팀원끼리 합의된 방식을 잘 준수하여 일관된 형태의 commit 을 작성하는 것이 가장 중요하다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;특히 커밋 메시지를 작성하는 널리 사용되는 방식인 컨벤션이 있는데 형태는 아래와 같다.&lt;/p&gt;
&lt;blockquote data-ke-style=&quot;style3&quot;&gt;type : subject&lt;br /&gt;&lt;br /&gt;body(optional)&lt;br /&gt;&lt;br /&gt;footer(optional)&lt;/blockquote&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;여기서 type은 해당 작업이 어느 부류에 속하는 지 바로 알 수 있도록 붙이는 태그를 의미한다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;예를 들어 feat 는 새로운 기능 추가, fix 는 버그 수정, docs 는 문서 수정, type 은 스타일 수정,&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;refactor 은 코드 리팩토링, perf 는 성능 개선, test 는 테스트 추가, chore는 보조 기능 수정 등이 있다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;Subject 는 커밋의 작읍 내용을 간단히 설명하고, body 는 길게 설명할 필요가 있는 경우 작성한다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;마지막으로&amp;nbsp;footer는 중요한 변경사항이나 특정 이슈 수정사항인 경우, 해당 이슈번호를 적기도 하며,&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;여기서 중요한 점은 type과 body, footer 사이에 각각 한줄씩 띄워서 입력해야 한다는 것이다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;예를 들어 로그인에 비밀번호 검사 기능의 이슈를 수정했다는 가정 하에 아래와 같이 작성할 수 있다.&lt;/p&gt;
&lt;blockquote data-ke-style=&quot;style3&quot;&gt;fix : 로그인 비밀번호 검사 이슈 수정&lt;br /&gt;&lt;br /&gt;로그인 비밀번호 검사 시, 모든 조건을 만족했음에도 fail처리되는 이슈를 수정함.&lt;br /&gt;- 특수문자 인식 에러 수정&lt;br /&gt;- 공백 포함하는 경우, 공백을 제거하도록 처리&lt;br /&gt;&lt;br /&gt;Close #123&lt;/blockquote&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;요즘에는 gitmogji 라고 해서 type에 이모지를 활용하기도 하는데 관련사항은 &lt;a href=&quot;http://gitmoji.dev&quot; target=&quot;_blank&quot; rel=&quot;noopener&quot;&gt;링크를&lt;/a&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. add 와 commit을 분할하여 진행하기&lt;/h3&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;git add . 은 현재 local의 모든 수정사항을 stage area 에 넣는데,&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;git add 를 하기 전에 하나하나 확인하거나 헝크별로 진행하고 싶은 경우가 있다.&lt;/p&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;i&gt;&lt;b&gt;git add -p&lt;/b&gt;&lt;/i&gt; 를 입력하면 아래와 같은 페이지로 이동하게 되고 여기에서 보이는 부분 중&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;변화된 내용 하나하나를 hunk라고 부르며 이 hunk를 받아들일지 여부를 결정할 수 있다.&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;317&quot;&gt;&lt;span data-url=&quot;https://blog.kakaocdn.net/dn/SEAvO/dJMb9LYh8EI/JOxKfXGLGKpXdZHOgkfVaK/img.png&quot; data-phocus=&quot;https://blog.kakaocdn.net/dn/SEAvO/dJMb9LYh8EI/JOxKfXGLGKpXdZHOgkfVaK/img.png&quot;&gt;&lt;img src=&quot;https://blog.kakaocdn.net/dn/SEAvO/dJMb9LYh8EI/JOxKfXGLGKpXdZHOgkfVaK/img.png&quot; srcset=&quot;https://img1.daumcdn.net/thumb/R1280x0/?scode=mtistory2&amp;fname=https%3A%2F%2Fblog.kakaocdn.net%2Fdn%2FSEAvO%2FdJMb9LYh8EI%2FJOxKfXGLGKpXdZHOgkfVaK%2Fimg.png&quot; onerror=&quot;this.onerror=null; this.src='//t1.daumcdn.net/tistory_admin/static/images/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;317&quot; data-origin-width=&quot;676&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;p data-ke-size=&quot;size16&quot;&gt;여기서&amp;nbsp;y,&amp;nbsp;n,&amp;nbsp;q,&amp;nbsp;a,&amp;nbsp;d,&amp;nbsp;s,&amp;nbsp;e,&amp;nbsp;p,&amp;nbsp;?&amp;nbsp;중&amp;nbsp;하나를&amp;nbsp;입력하여&amp;nbsp;추가&amp;nbsp;액션을&amp;nbsp;취할&amp;nbsp;수&amp;nbsp;있는데 &lt;br /&gt;각각에&amp;nbsp;대한&amp;nbsp;설명은&amp;nbsp;아래&amp;nbsp;이미지를&amp;nbsp;참고하면&amp;nbsp;되고,&amp;nbsp;주로&amp;nbsp;사용되는&amp;nbsp;것은 &lt;br /&gt;y를&amp;nbsp;입력하여&amp;nbsp;hank를&amp;nbsp;받아들이거나&amp;nbsp;n를&amp;nbsp;입력하여&amp;nbsp;hank를&amp;nbsp;받아들이지&amp;nbsp;않을&amp;nbsp;수&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;594&quot; data-origin-height=&quot;204&quot;&gt;&lt;span data-url=&quot;https://blog.kakaocdn.net/dn/bFXeTE/dJMb9d8gAEQ/hDTqy47yXkZvc5BJbpNzqk/img.png&quot; data-phocus=&quot;https://blog.kakaocdn.net/dn/bFXeTE/dJMb9d8gAEQ/hDTqy47yXkZvc5BJbpNzqk/img.png&quot;&gt;&lt;img src=&quot;https://blog.kakaocdn.net/dn/bFXeTE/dJMb9d8gAEQ/hDTqy47yXkZvc5BJbpNzqk/img.png&quot; srcset=&quot;https://img1.daumcdn.net/thumb/R1280x0/?scode=mtistory2&amp;fname=https%3A%2F%2Fblog.kakaocdn.net%2Fdn%2FbFXeTE%2FdJMb9d8gAEQ%2FhDTqy47yXkZvc5BJbpNzqk%2Fimg.png&quot; onerror=&quot;this.onerror=null; this.src='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png'; this.srcset='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png';&quot; loading=&quot;lazy&quot; width=&quot;594&quot; height=&quot;204&quot; data-origin-width=&quot;594&quot; data-origin-height=&quot;204&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;git status를 진행했을 때, add가 된 부분과 되지 않은 부분 모두 해당 파일이 노출된다.&lt;/p&gt;
&lt;p&gt;&lt;figure class=&quot;imageblock alignCenter&quot; data-ke-mobileStyle=&quot;widthOrigin&quot; data-origin-width=&quot;704&quot; data-origin-height=&quot;373&quot;&gt;&lt;span data-url=&quot;https://blog.kakaocdn.net/dn/cJi66c/dJMb9LYh8EM/m5o8WzvVaJrluP3r4xWPkk/img.png&quot; data-phocus=&quot;https://blog.kakaocdn.net/dn/cJi66c/dJMb9LYh8EM/m5o8WzvVaJrluP3r4xWPkk/img.png&quot;&gt;&lt;img src=&quot;https://blog.kakaocdn.net/dn/cJi66c/dJMb9LYh8EM/m5o8WzvVaJrluP3r4xWPkk/img.png&quot; srcset=&quot;https://img1.daumcdn.net/thumb/R1280x0/?scode=mtistory2&amp;fname=https%3A%2F%2Fblog.kakaocdn.net%2Fdn%2FcJi66c%2FdJMb9LYh8EM%2Fm5o8WzvVaJrluP3r4xWPkk%2Fimg.png&quot; onerror=&quot;this.onerror=null; this.src='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png'; this.srcset='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png';&quot; loading=&quot;lazy&quot; width=&quot;704&quot; height=&quot;373&quot; data-origin-width=&quot;704&quot; data-origin-height=&quot;373&quot;/&gt;&lt;/span&gt;&lt;/figure&gt;
&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;br /&gt;이후 &lt;i&gt;&lt;b&gt;git commit -v&lt;/b&gt; &lt;/i&gt;를 진행하면, 아래에 어떤 부분이 변경되었는지 자세하게 확인할 수 있다.&lt;br /&gt;이는 &lt;i&gt;&lt;b&gt;git commit&lt;/b&gt;&lt;/i&gt; 과 &lt;i&gt;&lt;b&gt;git diff --staged&lt;/b&gt; &lt;/i&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;466&quot;&gt;&lt;span data-url=&quot;https://blog.kakaocdn.net/dn/d9Y4vK/dJMb9OAH4Du/s3R3KlXwKtCkHNJpFxKITk/img.png&quot; data-phocus=&quot;https://blog.kakaocdn.net/dn/d9Y4vK/dJMb9OAH4Du/s3R3KlXwKtCkHNJpFxKITk/img.png&quot;&gt;&lt;img src=&quot;https://blog.kakaocdn.net/dn/d9Y4vK/dJMb9OAH4Du/s3R3KlXwKtCkHNJpFxKITk/img.png&quot; srcset=&quot;https://img1.daumcdn.net/thumb/R1280x0/?scode=mtistory2&amp;fname=https%3A%2F%2Fblog.kakaocdn.net%2Fdn%2Fd9Y4vK%2FdJMb9OAH4Du%2Fs3R3KlXwKtCkHNJpFxKITk%2Fimg.png&quot; onerror=&quot;this.onerror=null; this.src='//t1.daumcdn.net/tistory_admin/static/images/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;466&quot; data-origin-width=&quot;453&quot; data-origin-height=&quot;466&quot;/&gt;&lt;/span&gt;&lt;/figure&gt;
&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;br /&gt;이후에 git status를 입력하면 수정되지 않은 상태로만 노출되고 기존에 수정된 상태는 사라졌다.&lt;/p&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;303&quot;&gt;&lt;span data-url=&quot;https://blog.kakaocdn.net/dn/4B8E2/dJMb84DucGC/sPawTMdkKnEWJWxdF6n1rk/img.png&quot; data-phocus=&quot;https://blog.kakaocdn.net/dn/4B8E2/dJMb84DucGC/sPawTMdkKnEWJWxdF6n1rk/img.png&quot;&gt;&lt;img src=&quot;https://blog.kakaocdn.net/dn/4B8E2/dJMb84DucGC/sPawTMdkKnEWJWxdF6n1rk/img.png&quot; srcset=&quot;https://img1.daumcdn.net/thumb/R1280x0/?scode=mtistory2&amp;fname=https%3A%2F%2Fblog.kakaocdn.net%2Fdn%2F4B8E2%2FdJMb84DucGC%2FsPawTMdkKnEWJWxdF6n1rk%2Fimg.png&quot; onerror=&quot;this.onerror=null; this.src='//t1.daumcdn.net/tistory_admin/static/images/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;303&quot; data-origin-width=&quot;707&quot; data-origin-height=&quot;303&quot;/&gt;&lt;/span&gt;&lt;/figure&gt;
&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;br /&gt;마지막으로 git add . 과 git commit 을 입력하면 나머지 변경사항이 전체 수정되는 것을 볼 수 있다.&lt;br /&gt;이렇게 하나의 파일로도 원하는 부분만 수정함으로써 여러번 commit을 할 수 있다.&lt;br /&gt;&lt;br /&gt;따라서 큰 파일에 작성된 변경사항도 분할되어 담길 수 있어 세심하게 add와 commit을 진행할 수 있다.&lt;/p&gt;
&lt;p&gt;&lt;figure class=&quot;imageblock alignCenter&quot; data-ke-mobileStyle=&quot;widthOrigin&quot; data-origin-width=&quot;1342&quot; data-origin-height=&quot;473&quot;&gt;&lt;span data-url=&quot;https://blog.kakaocdn.net/dn/cJ1Lj2/dJMb9dAqBIL/Tv1PGxSUriik8XMDM1Ug5K/img.png&quot; data-phocus=&quot;https://blog.kakaocdn.net/dn/cJ1Lj2/dJMb9dAqBIL/Tv1PGxSUriik8XMDM1Ug5K/img.png&quot;&gt;&lt;img src=&quot;https://blog.kakaocdn.net/dn/cJ1Lj2/dJMb9dAqBIL/Tv1PGxSUriik8XMDM1Ug5K/img.png&quot; srcset=&quot;https://img1.daumcdn.net/thumb/R1280x0/?scode=mtistory2&amp;fname=https%3A%2F%2Fblog.kakaocdn.net%2Fdn%2FcJ1Lj2%2FdJMb9dAqBIL%2FTv1PGxSUriik8XMDM1Ug5K%2Fimg.png&quot; onerror=&quot;this.onerror=null; this.src='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png'; this.srcset='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png';&quot; loading=&quot;lazy&quot; width=&quot;1342&quot; height=&quot;473&quot; data-origin-width=&quot;1342&quot; data-origin-height=&quot;473&quot;/&gt;&lt;/span&gt;&lt;/figure&gt;
&lt;/p&gt;
&lt;h3 data-ke-size=&quot;size23&quot;&gt;2. git stash&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;i&gt;&lt;b&gt;git stash&lt;/b&gt;&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;다만 tracking이 되는 파일에 한해서 한정적으로 사용할 수 있다는 주의점이 있다.&lt;/p&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&amp;nbsp;stash&amp;nbsp;를&amp;nbsp;진행하면&amp;nbsp;이전에&amp;nbsp;수정하던&amp;nbsp;항목들이&amp;nbsp;전체&amp;nbsp;비활성화되서&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;1290&quot; data-origin-height=&quot;680&quot;&gt;&lt;span data-url=&quot;https://blog.kakaocdn.net/dn/zZjs5/dJMb9Mv8riS/SPA6ek0zK2ikRKFq5jwpd0/img.png&quot; data-phocus=&quot;https://blog.kakaocdn.net/dn/zZjs5/dJMb9Mv8riS/SPA6ek0zK2ikRKFq5jwpd0/img.png&quot;&gt;&lt;img src=&quot;https://blog.kakaocdn.net/dn/zZjs5/dJMb9Mv8riS/SPA6ek0zK2ikRKFq5jwpd0/img.png&quot; srcset=&quot;https://img1.daumcdn.net/thumb/R1280x0/?scode=mtistory2&amp;fname=https%3A%2F%2Fblog.kakaocdn.net%2Fdn%2FzZjs5%2FdJMb9Mv8riS%2FSPA6ek0zK2ikRKFq5jwpd0%2Fimg.png&quot; onerror=&quot;this.onerror=null; this.src='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png'; this.srcset='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png';&quot; loading=&quot;lazy&quot; width=&quot;1290&quot; height=&quot;680&quot; data-origin-width=&quot;1290&quot; data-origin-height=&quot;680&quot;/&gt;&lt;/span&gt;&lt;/figure&gt;
&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;br /&gt;해당내용은&amp;nbsp;sourcetree에서&amp;nbsp;확인해보면&amp;nbsp;스태시라는&amp;nbsp;공간에&amp;nbsp;노출되는&amp;nbsp;것을&amp;nbsp;볼&amp;nbsp;수&amp;nbsp;있다. &lt;br /&gt;이후 다른작업을 한 뒤 stash의 값은 같은 브랜치 또는 다른 브랜치 위에 언제든 적용할 수 있다.&lt;/p&gt;
&lt;p&gt;&lt;figure class=&quot;imageblock alignCenter&quot; data-ke-mobileStyle=&quot;widthOrigin&quot; data-origin-width=&quot;1226&quot; data-origin-height=&quot;691&quot;&gt;&lt;span data-url=&quot;https://blog.kakaocdn.net/dn/5hwQU/dJMb9V0Pyca/NKsBWEuNZmjgkMPVYI3k31/img.png&quot; data-phocus=&quot;https://blog.kakaocdn.net/dn/5hwQU/dJMb9V0Pyca/NKsBWEuNZmjgkMPVYI3k31/img.png&quot;&gt;&lt;img src=&quot;https://blog.kakaocdn.net/dn/5hwQU/dJMb9V0Pyca/NKsBWEuNZmjgkMPVYI3k31/img.png&quot; srcset=&quot;https://img1.daumcdn.net/thumb/R1280x0/?scode=mtistory2&amp;fname=https%3A%2F%2Fblog.kakaocdn.net%2Fdn%2F5hwQU%2FdJMb9V0Pyca%2FNKsBWEuNZmjgkMPVYI3k31%2Fimg.png&quot; onerror=&quot;this.onerror=null; this.src='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png'; this.srcset='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png';&quot; loading=&quot;lazy&quot; width=&quot;1226&quot; height=&quot;691&quot; data-origin-width=&quot;1226&quot; data-origin-height=&quot;691&quot;/&gt;&lt;/span&gt;&lt;/figure&gt;
&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;br /&gt;&lt;i&gt;&lt;b&gt;git&amp;nbsp;stash&amp;nbsp;pop&lt;/b&gt;&lt;/i&gt;을&amp;nbsp;진행하면&amp;nbsp;기존에&amp;nbsp;작업한&amp;nbsp;것들이&amp;nbsp;돌아오고&amp;nbsp;언제든&amp;nbsp;다시&amp;nbsp;적용할&amp;nbsp;수&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;1287&quot; data-origin-height=&quot;713&quot;&gt;&lt;span data-url=&quot;https://blog.kakaocdn.net/dn/wxxG5/dJMb9kzyt0n/uB6rOOgPkuuBipCBikOMG1/img.png&quot; data-phocus=&quot;https://blog.kakaocdn.net/dn/wxxG5/dJMb9kzyt0n/uB6rOOgPkuuBipCBikOMG1/img.png&quot;&gt;&lt;img src=&quot;https://blog.kakaocdn.net/dn/wxxG5/dJMb9kzyt0n/uB6rOOgPkuuBipCBikOMG1/img.png&quot; srcset=&quot;https://img1.daumcdn.net/thumb/R1280x0/?scode=mtistory2&amp;fname=https%3A%2F%2Fblog.kakaocdn.net%2Fdn%2FwxxG5%2FdJMb9kzyt0n%2FuB6rOOgPkuuBipCBikOMG1%2Fimg.png&quot; onerror=&quot;this.onerror=null; this.src='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png'; this.srcset='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png';&quot; loading=&quot;lazy&quot; width=&quot;1287&quot; height=&quot;713&quot; data-origin-width=&quot;1287&quot; data-origin-height=&quot;713&quot;/&gt;&lt;/span&gt;&lt;/figure&gt;
&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;br /&gt;또한&amp;nbsp;원하는것만&amp;nbsp;stash를&amp;nbsp;하기&amp;nbsp;위해서는&amp;nbsp;&lt;i&gt;&lt;b&gt;git&amp;nbsp;stash&amp;nbsp;-p&lt;/b&gt;&lt;/i&gt;를&amp;nbsp;진행하면&amp;nbsp;되는데&amp;nbsp;아래와&amp;nbsp;같이 &lt;br /&gt;수정사항 중 stash 에 넣고싶은 부분이 있냐고 물어보고 y, n를 통해 선택한다. &lt;br /&gt;&lt;br /&gt;여기에서&amp;nbsp;y를&amp;nbsp;하는&amp;nbsp;항목만&amp;nbsp;stash로&amp;nbsp;이동되고&amp;nbsp;나머지는&amp;nbsp;local에&amp;nbsp;유지되는&amp;nbsp;것을&amp;nbsp;볼수있다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;아래처럼 test1.py만 적용하면 test2.py의 수정내용은 유지되고 test1.py의 수정내용은 사라진다.&lt;/p&gt;
&lt;p&gt;&lt;figure class=&quot;imageblock alignCenter&quot; data-ke-mobileStyle=&quot;widthOrigin&quot; data-origin-width=&quot;1286&quot; data-origin-height=&quot;960&quot;&gt;&lt;span data-url=&quot;https://blog.kakaocdn.net/dn/yN4nh/dJMb9MQqGg0/YkUwc5Mx63xqVuTf8xH7t0/img.png&quot; data-phocus=&quot;https://blog.kakaocdn.net/dn/yN4nh/dJMb9MQqGg0/YkUwc5Mx63xqVuTf8xH7t0/img.png&quot;&gt;&lt;img src=&quot;https://blog.kakaocdn.net/dn/yN4nh/dJMb9MQqGg0/YkUwc5Mx63xqVuTf8xH7t0/img.png&quot; srcset=&quot;https://img1.daumcdn.net/thumb/R1280x0/?scode=mtistory2&amp;fname=https%3A%2F%2Fblog.kakaocdn.net%2Fdn%2FyN4nh%2FdJMb9MQqGg0%2FYkUwc5Mx63xqVuTf8xH7t0%2Fimg.png&quot; onerror=&quot;this.onerror=null; this.src='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png'; this.srcset='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png';&quot; loading=&quot;lazy&quot; width=&quot;1286&quot; height=&quot;960&quot; data-origin-width=&quot;1286&quot; data-origin-height=&quot;960&quot;/&gt;&lt;/span&gt;&lt;/figure&gt;
&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;br /&gt;또한&amp;nbsp;stash를&amp;nbsp;할&amp;nbsp;때에&amp;nbsp;메시지를&amp;nbsp;작성해서&amp;nbsp;어떤것을&amp;nbsp;stash로&amp;nbsp;넣었는지&amp;nbsp;명시해둘&amp;nbsp;수&amp;nbsp;있는데, &lt;br /&gt;&lt;i&gt;&lt;b&gt;git stash -m &quot;stash message&quot;&amp;nbsp;&lt;/b&gt;&lt;/i&gt;를&amp;nbsp;입력함으로써&amp;nbsp;stash&amp;nbsp;message를&amp;nbsp;입력할&amp;nbsp;수&amp;nbsp;있다. &lt;br /&gt;&lt;br /&gt;이후&amp;nbsp;stash&amp;nbsp;로&amp;nbsp;이동시킨&amp;nbsp;목록을&amp;nbsp;stash&amp;nbsp;list로&amp;nbsp;살펴볼&amp;nbsp;수&amp;nbsp;있고&amp;nbsp;stash&amp;nbsp;우측의&amp;nbsp;번호로&amp;nbsp;불러올&amp;nbsp;수&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;675&quot; data-origin-height=&quot;214&quot;&gt;&lt;span data-url=&quot;https://blog.kakaocdn.net/dn/5raRV/dJMb9PTVxpe/tXkCP4X7qghwMTKK7y9EC0/img.png&quot; data-phocus=&quot;https://blog.kakaocdn.net/dn/5raRV/dJMb9PTVxpe/tXkCP4X7qghwMTKK7y9EC0/img.png&quot;&gt;&lt;img src=&quot;https://blog.kakaocdn.net/dn/5raRV/dJMb9PTVxpe/tXkCP4X7qghwMTKK7y9EC0/img.png&quot; srcset=&quot;https://img1.daumcdn.net/thumb/R1280x0/?scode=mtistory2&amp;fname=https%3A%2F%2Fblog.kakaocdn.net%2Fdn%2F5raRV%2FdJMb9PTVxpe%2FtXkCP4X7qghwMTKK7y9EC0%2Fimg.png&quot; onerror=&quot;this.onerror=null; this.src='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png'; this.srcset='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png';&quot; loading=&quot;lazy&quot; width=&quot;675&quot; height=&quot;214&quot; data-origin-width=&quot;675&quot; data-origin-height=&quot;214&quot;/&gt;&lt;/span&gt;&lt;/figure&gt;
&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;br /&gt;불러오는&amp;nbsp;방법은&amp;nbsp;&lt;i&gt;&lt;b&gt;git&amp;nbsp;stash&amp;nbsp;apply&amp;nbsp;stash@{n}&amp;nbsp;&lt;/b&gt;&lt;/i&gt;형식이며,&amp;nbsp;만약&amp;nbsp;이렇게&amp;nbsp;일부만&amp;nbsp;불러오는&amp;nbsp;경우에 &lt;br /&gt;해당&amp;nbsp;항목이&amp;nbsp;자동으로&amp;nbsp;list에서&amp;nbsp;사라지지&amp;nbsp;않기&amp;nbsp;때문에&amp;nbsp;명시적으로&amp;nbsp;삭제해줘야&amp;nbsp;한다. &lt;br /&gt;&lt;br /&gt;이때&amp;nbsp;사용되는&amp;nbsp;명령어는&amp;nbsp;&lt;i&gt;&lt;b&gt;git stash drop stash@{n} &lt;/b&gt;&lt;/i&gt;이며,&amp;nbsp;이후&amp;nbsp;list에서&amp;nbsp;해당&amp;nbsp;stash가&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;706&quot; data-origin-height=&quot;641&quot;&gt;&lt;span data-url=&quot;https://blog.kakaocdn.net/dn/ctyK3G/dJMb9OHtwHC/rxMlRj0GhLqgMyr9QTzC6k/img.png&quot; data-phocus=&quot;https://blog.kakaocdn.net/dn/ctyK3G/dJMb9OHtwHC/rxMlRj0GhLqgMyr9QTzC6k/img.png&quot;&gt;&lt;img src=&quot;https://blog.kakaocdn.net/dn/ctyK3G/dJMb9OHtwHC/rxMlRj0GhLqgMyr9QTzC6k/img.png&quot; srcset=&quot;https://img1.daumcdn.net/thumb/R1280x0/?scode=mtistory2&amp;fname=https%3A%2F%2Fblog.kakaocdn.net%2Fdn%2FctyK3G%2FdJMb9OHtwHC%2FrxMlRj0GhLqgMyr9QTzC6k%2Fimg.png&quot; onerror=&quot;this.onerror=null; this.src='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png'; this.srcset='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png';&quot; loading=&quot;lazy&quot; width=&quot;706&quot; height=&quot;641&quot; data-origin-width=&quot;706&quot; data-origin-height=&quot;641&quot;/&gt;&lt;/span&gt;&lt;/figure&gt;
&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;br /&gt;그렇다면&amp;nbsp;여기에서&amp;nbsp;git&amp;nbsp;stash&amp;nbsp;apply&amp;nbsp;와&amp;nbsp;drop을&amp;nbsp;동시에&amp;nbsp;할&amp;nbsp;수&amp;nbsp;있는&amp;nbsp;방법은&amp;nbsp;없을까? &lt;br /&gt;&lt;i&gt;&lt;b&gt;git&amp;nbsp;stash&amp;nbsp;pop&amp;nbsp;stash@{n}&lt;/b&gt;&lt;/i&gt;을&amp;nbsp;입력하면&amp;nbsp;특정&amp;nbsp;stash에&amp;nbsp;대해&amp;nbsp;두가지&amp;nbsp;액션을&amp;nbsp;한번에&amp;nbsp;할&amp;nbsp;수&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;680&quot; data-origin-height=&quot;345&quot;&gt;&lt;span data-url=&quot;https://blog.kakaocdn.net/dn/nWZCL/dJMb9QeeD4d/CyKzBEL8a6BWKAp4NcBvH1/img.png&quot; data-phocus=&quot;https://blog.kakaocdn.net/dn/nWZCL/dJMb9QeeD4d/CyKzBEL8a6BWKAp4NcBvH1/img.png&quot;&gt;&lt;img src=&quot;https://blog.kakaocdn.net/dn/nWZCL/dJMb9QeeD4d/CyKzBEL8a6BWKAp4NcBvH1/img.png&quot; srcset=&quot;https://img1.daumcdn.net/thumb/R1280x0/?scode=mtistory2&amp;fname=https%3A%2F%2Fblog.kakaocdn.net%2Fdn%2FnWZCL%2FdJMb9QeeD4d%2FCyKzBEL8a6BWKAp4NcBvH1%2Fimg.png&quot; onerror=&quot;this.onerror=null; this.src='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png'; this.srcset='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png';&quot; loading=&quot;lazy&quot; width=&quot;680&quot; height=&quot;345&quot; data-origin-width=&quot;680&quot; data-origin-height=&quot;345&quot;/&gt;&lt;/span&gt;&lt;/figure&gt;
&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;br /&gt;추가적으로&amp;nbsp;&lt;i&gt;&lt;b&gt;git&amp;nbsp;stash&amp;nbsp;branch&amp;nbsp;&quot;branch&amp;nbsp;name&quot;&lt;/b&gt;&lt;/i&gt;을&amp;nbsp;입력하면&amp;nbsp;새로운&amp;nbsp;브랜치를&amp;nbsp;생성하여&amp;nbsp;pop하는데 &lt;br /&gt;일반적으로&amp;nbsp;수정된&amp;nbsp;파일과&amp;nbsp;stash의&amp;nbsp;내용이&amp;nbsp;충돌이&amp;nbsp;있는&amp;nbsp;경우&amp;nbsp;유용하게&amp;nbsp;쓰일&amp;nbsp;수&amp;nbsp;있다. &lt;br /&gt;&lt;br /&gt;또한&amp;nbsp;stash의&amp;nbsp;모든&amp;nbsp;항목들을&amp;nbsp;비우기&amp;nbsp;위해서는&amp;nbsp;&lt;i&gt;&lt;b&gt;git&amp;nbsp;stash&amp;nbsp;clear&amp;nbsp;&lt;/b&gt;&lt;/i&gt;명령어를&amp;nbsp;입력하여&amp;nbsp;동작할&amp;nbsp;수&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;637&quot; data-origin-height=&quot;220&quot;&gt;&lt;span data-url=&quot;https://blog.kakaocdn.net/dn/blfL6j/dJMb9XYEjSV/zbF2MxLQvjzukXYbCLKUq0/img.png&quot; data-phocus=&quot;https://blog.kakaocdn.net/dn/blfL6j/dJMb9XYEjSV/zbF2MxLQvjzukXYbCLKUq0/img.png&quot;&gt;&lt;img src=&quot;https://blog.kakaocdn.net/dn/blfL6j/dJMb9XYEjSV/zbF2MxLQvjzukXYbCLKUq0/img.png&quot; srcset=&quot;https://img1.daumcdn.net/thumb/R1280x0/?scode=mtistory2&amp;fname=https%3A%2F%2Fblog.kakaocdn.net%2Fdn%2FblfL6j%2FdJMb9XYEjSV%2FzbF2MxLQvjzukXYbCLKUq0%2Fimg.png&quot; onerror=&quot;this.onerror=null; this.src='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png'; this.srcset='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png';&quot; loading=&quot;lazy&quot; width=&quot;637&quot; height=&quot;220&quot; data-origin-width=&quot;637&quot; data-origin-height=&quot;220&quot;/&gt;&lt;/span&gt;&lt;/figure&gt;
&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;br /&gt;만약&amp;nbsp;소스트리에서&amp;nbsp;stash를&amp;nbsp;하고싶은&amp;nbsp;경우,&amp;nbsp;상단의&amp;nbsp;&quot;스태시&quot;&amp;nbsp;버튼을&amp;nbsp;눌러서&amp;nbsp;원하는&amp;nbsp;내용을&amp;nbsp;입력하고 &lt;br /&gt;확인을&amp;nbsp;누르면&amp;nbsp;수정중인&amp;nbsp;내용이&amp;nbsp;LNB의&amp;nbsp;&quot;스태시&quot;&amp;nbsp;하위로&amp;nbsp;이동되고&amp;nbsp;나중에&amp;nbsp;적용하고&amp;nbsp;싶다면 &lt;br /&gt;스태시&amp;nbsp;목록을&amp;nbsp;우클릭한&amp;nbsp;뒤&amp;nbsp;스태시&amp;nbsp;적용&amp;nbsp;버튼을&amp;nbsp;누르면&amp;nbsp;다시&amp;nbsp;파일에&amp;nbsp;적용시킬&amp;nbsp;수&amp;nbsp;있다.&lt;/p&gt;
&lt;p&gt;&lt;figure class=&quot;imageblock alignCenter&quot; data-ke-mobileStyle=&quot;widthOrigin&quot; data-origin-width=&quot;1226&quot; data-origin-height=&quot;728&quot;&gt;&lt;span data-url=&quot;https://blog.kakaocdn.net/dn/2nEsY/dJMb8WZK26s/ngN9HzvXjxkNTLKM1s0rZK/img.png&quot; data-phocus=&quot;https://blog.kakaocdn.net/dn/2nEsY/dJMb8WZK26s/ngN9HzvXjxkNTLKM1s0rZK/img.png&quot;&gt;&lt;img src=&quot;https://blog.kakaocdn.net/dn/2nEsY/dJMb8WZK26s/ngN9HzvXjxkNTLKM1s0rZK/img.png&quot; srcset=&quot;https://img1.daumcdn.net/thumb/R1280x0/?scode=mtistory2&amp;fname=https%3A%2F%2Fblog.kakaocdn.net%2Fdn%2F2nEsY%2FdJMb8WZK26s%2FngN9HzvXjxkNTLKM1s0rZK%2Fimg.png&quot; onerror=&quot;this.onerror=null; this.src='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png'; this.srcset='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png';&quot; loading=&quot;lazy&quot; width=&quot;1226&quot; height=&quot;728&quot; data-origin-width=&quot;1226&quot; data-origin-height=&quot;728&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;1226&quot; data-origin-height=&quot;728&quot;&gt;&lt;span data-url=&quot;https://blog.kakaocdn.net/dn/8N9p5/dJMb9Qyxh59/mV1X0XESjOSsSY0GpyaK7K/img.png&quot; data-phocus=&quot;https://blog.kakaocdn.net/dn/8N9p5/dJMb9Qyxh59/mV1X0XESjOSsSY0GpyaK7K/img.png&quot;&gt;&lt;img src=&quot;https://blog.kakaocdn.net/dn/8N9p5/dJMb9Qyxh59/mV1X0XESjOSsSY0GpyaK7K/img.png&quot; srcset=&quot;https://img1.daumcdn.net/thumb/R1280x0/?scode=mtistory2&amp;fname=https%3A%2F%2Fblog.kakaocdn.net%2Fdn%2F8N9p5%2FdJMb9Qyxh59%2FmV1X0XESjOSsSY0GpyaK7K%2Fimg.png&quot; onerror=&quot;this.onerror=null; this.src='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png'; this.srcset='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png';&quot; loading=&quot;lazy&quot; width=&quot;1226&quot; height=&quot;728&quot; data-origin-width=&quot;1226&quot; data-origin-height=&quot;728&quot;/&gt;&lt;/span&gt;&lt;/figure&gt;
&lt;/p&gt;
&lt;h3 data-ke-size=&quot;size23&quot;&gt;3. 커밋 수정하기&lt;/h3&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;이렇게 커밋하기 전에 미리 확인하기와 잠시 로컬의 내용을 옮기는 작업을 했는데,&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;만약 이미 커밋한 상태라면, 그리고 커밋의 내용을 수정하고 싶다면 어떻게 해야할까?&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;i&gt;&lt;b&gt;git commit --amend&lt;/b&gt;&lt;/i&gt; 를 사용해보면 commit message를 수정할 수 있는 에디터가 열린다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;이후 commit message를 변경한 뒤 :wq 로 저장하면 git commend가 수정된다.&lt;/p&gt;
&lt;p&gt;&lt;figure class=&quot;imageblock alignCenter&quot; data-ke-mobileStyle=&quot;widthOrigin&quot; data-origin-width=&quot;1014&quot; data-origin-height=&quot;435&quot;&gt;&lt;span data-url=&quot;https://blog.kakaocdn.net/dn/sy2CD/dJMb9WZJYT8/tJkF1y2cjLN3YqTlqQ1ikk/img.png&quot; data-phocus=&quot;https://blog.kakaocdn.net/dn/sy2CD/dJMb9WZJYT8/tJkF1y2cjLN3YqTlqQ1ikk/img.png&quot;&gt;&lt;img src=&quot;https://blog.kakaocdn.net/dn/sy2CD/dJMb9WZJYT8/tJkF1y2cjLN3YqTlqQ1ikk/img.png&quot; srcset=&quot;https://img1.daumcdn.net/thumb/R1280x0/?scode=mtistory2&amp;fname=https%3A%2F%2Fblog.kakaocdn.net%2Fdn%2Fsy2CD%2FdJMb9WZJYT8%2FtJkF1y2cjLN3YqTlqQ1ikk%2Fimg.png&quot; onerror=&quot;this.onerror=null; this.src='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png'; this.srcset='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png';&quot; loading=&quot;lazy&quot; width=&quot;1014&quot; height=&quot;435&quot; data-origin-width=&quot;1014&quot; data-origin-height=&quot;435&quot;/&gt;&lt;/span&gt;&lt;/figure&gt;
&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;두번째로 커밋에 포함해야 하는 내용이 누락된 경우가 있는데, 이때에도 git commit --amend가 쓰인다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;먼저 수정사항을 git add . 으로 staging area에 등록하고&lt;i&gt;&lt;b&gt; git commit --amend&lt;/b&gt;&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;여기서 커밋 메시지를 입력하고 저장하면 이전 commit message가 수정되면서 추가한 내용도 포함된다.&lt;/p&gt;
&lt;p&gt;&lt;figure class=&quot;imageblock alignCenter&quot; data-ke-mobileStyle=&quot;widthOrigin&quot; data-origin-width=&quot;1031&quot; data-origin-height=&quot;434&quot;&gt;&lt;span data-url=&quot;https://blog.kakaocdn.net/dn/dQTpIX/dJMb85bkwNr/fDjNtLaKsjEIsb3ObdX5LK/img.png&quot; data-phocus=&quot;https://blog.kakaocdn.net/dn/dQTpIX/dJMb85bkwNr/fDjNtLaKsjEIsb3ObdX5LK/img.png&quot;&gt;&lt;img src=&quot;https://blog.kakaocdn.net/dn/dQTpIX/dJMb85bkwNr/fDjNtLaKsjEIsb3ObdX5LK/img.png&quot; srcset=&quot;https://img1.daumcdn.net/thumb/R1280x0/?scode=mtistory2&amp;fname=https%3A%2F%2Fblog.kakaocdn.net%2Fdn%2FdQTpIX%2FdJMb85bkwNr%2FfDjNtLaKsjEIsb3ObdX5LK%2Fimg.png&quot; onerror=&quot;this.onerror=null; this.src='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png'; this.srcset='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png';&quot; loading=&quot;lazy&quot; width=&quot;1031&quot; height=&quot;434&quot; data-origin-width=&quot;1031&quot; data-origin-height=&quot;434&quot;/&gt;&lt;/span&gt;&lt;/figure&gt;
&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;br /&gt;마지막으로 커밋 메시지를 변경하는 또다른 방법은 git commit --amend -m &quot;commit message&quot;&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;649&quot; data-origin-height=&quot;327&quot;&gt;&lt;span data-url=&quot;https://blog.kakaocdn.net/dn/b8jL3E/dJMb89SkU70/lLdMsG0K1ahpxvkYDasrM0/img.png&quot; data-phocus=&quot;https://blog.kakaocdn.net/dn/b8jL3E/dJMb89SkU70/lLdMsG0K1ahpxvkYDasrM0/img.png&quot;&gt;&lt;img src=&quot;https://blog.kakaocdn.net/dn/b8jL3E/dJMb89SkU70/lLdMsG0K1ahpxvkYDasrM0/img.png&quot; srcset=&quot;https://img1.daumcdn.net/thumb/R1280x0/?scode=mtistory2&amp;fname=https%3A%2F%2Fblog.kakaocdn.net%2Fdn%2Fb8jL3E%2FdJMb89SkU70%2FlLdMsG0K1ahpxvkYDasrM0%2Fimg.png&quot; onerror=&quot;this.onerror=null; this.src='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png'; this.srcset='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png';&quot; loading=&quot;lazy&quot; width=&quot;649&quot; height=&quot;327&quot; data-origin-width=&quot;649&quot; data-origin-height=&quot;327&quot;/&gt;&lt;/span&gt;&lt;/figure&gt;
&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;br /&gt;또한 &lt;i&gt;&lt;b&gt;git add .&lt;/b&gt; &lt;/i&gt;없이 수정사항을 staging area 등록까지 일괄적으로 진행하고 싶다면&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;i&gt;&lt;b&gt;git commit --amend -am &quot;commit message&quot;&lt;/b&gt; &lt;/i&gt;를 진행하여 수정사항 저장 및 메시지 변경이 가능하다.&lt;/p&gt;
&lt;p&gt;&lt;figure class=&quot;imageblock alignCenter&quot; data-ke-mobileStyle=&quot;widthOrigin&quot; data-origin-width=&quot;708&quot; data-origin-height=&quot;612&quot;&gt;&lt;span data-url=&quot;https://blog.kakaocdn.net/dn/2JGzG/dJMb8Z91mR8/MfW53NmyDZLMBjqSW2K2C1/img.png&quot; data-phocus=&quot;https://blog.kakaocdn.net/dn/2JGzG/dJMb8Z91mR8/MfW53NmyDZLMBjqSW2K2C1/img.png&quot;&gt;&lt;img src=&quot;https://blog.kakaocdn.net/dn/2JGzG/dJMb8Z91mR8/MfW53NmyDZLMBjqSW2K2C1/img.png&quot; srcset=&quot;https://img1.daumcdn.net/thumb/R1280x0/?scode=mtistory2&amp;fname=https%3A%2F%2Fblog.kakaocdn.net%2Fdn%2F2JGzG%2FdJMb8Z91mR8%2FMfW53NmyDZLMBjqSW2K2C1%2Fimg.png&quot; onerror=&quot;this.onerror=null; this.src='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png'; this.srcset='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png';&quot; loading=&quot;lazy&quot; width=&quot;708&quot; height=&quot;612&quot; data-origin-width=&quot;708&quot; data-origin-height=&quot;612&quot;/&gt;&lt;/span&gt;&lt;/figure&gt;
&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;이렇게 과거의 여러 커밋들을 수정 뿐 아니라 삭제, 병합, 분할 등 다양하게 수정할 수도 있다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;방법은 rebase를 사용하는데 이전에 rebase 에 대해서는 commit 대상 branch에게&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;다른 branch를 옮기는 것이라고 언급한 적이 있는데, 이번에도 비슷한 개념이다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;예를 들어 과거의 어떤 내역이 변경되면, 이 이후의 변경사항들은 이전의 commit과 동일하지 않다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;즉, A-B-C-D-E-HEAD 라고 되어있는 경우, B를 수정하면 B만 수정되는 것이 아니고,&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;이후의 C, D, E, HEAD 까지 커밋을 복사한 뒤 그것을 rebase로 붙이는 식으로 동작하게 된다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;또 다른 예를 들자면, A-B-C-D-E-HEAD 상태에서 C를 수정했다고 가정해보자.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;그러면 A-B-C`-D-E-HEAD 가 아니라, A-B-C`-D`-E`-HEAD` 이러한 형식으로 변경되게 되고,&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;B 뒤에 있던 C~HEAD commit 은 사라지게 되는것과 비슷한 원리라고 보면 될것 같다.&lt;/p&gt;
&lt;p&gt;&lt;figure class=&quot;imageblock alignCenter&quot; data-ke-mobileStyle=&quot;widthOrigin&quot; data-origin-width=&quot;910&quot; data-origin-height=&quot;205&quot;&gt;&lt;span data-url=&quot;https://blog.kakaocdn.net/dn/dlRiYE/dJMb9N9EGg4/cTDRprZrwn46Tkm94s03TK/img.png&quot; data-phocus=&quot;https://blog.kakaocdn.net/dn/dlRiYE/dJMb9N9EGg4/cTDRprZrwn46Tkm94s03TK/img.png&quot;&gt;&lt;img src=&quot;https://blog.kakaocdn.net/dn/dlRiYE/dJMb9N9EGg4/cTDRprZrwn46Tkm94s03TK/img.png&quot; srcset=&quot;https://img1.daumcdn.net/thumb/R1280x0/?scode=mtistory2&amp;fname=https%3A%2F%2Fblog.kakaocdn.net%2Fdn%2FdlRiYE%2FdJMb9N9EGg4%2FcTDRprZrwn46Tkm94s03TK%2Fimg.png&quot; onerror=&quot;this.onerror=null; this.src='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png'; this.srcset='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png';&quot; loading=&quot;lazy&quot; width=&quot;910&quot; height=&quot;205&quot; data-origin-width=&quot;910&quot; data-origin-height=&quot;205&quot;/&gt;&lt;/span&gt;&lt;/figure&gt;
&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;먼저 commit 의 목록은 위와 같고 먼저 add datas에 대한 commit hash를 기억한 다음에,&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;i&gt;&lt;b&gt;git rebase -i &quot;commit hash&quot;&lt;/b&gt; &lt;/i&gt;를 입력하면 아래와 같은 화면이 나온다.&lt;/p&gt;
&lt;p&gt;&lt;figure class=&quot;imageblock alignCenter&quot; data-ke-mobileStyle=&quot;widthOrigin&quot; data-origin-width=&quot;1196&quot; data-origin-height=&quot;605&quot;&gt;&lt;span data-url=&quot;https://blog.kakaocdn.net/dn/bjywbk/dJMb9OHtPji/N9yu30P7canQlI4ph50KXk/img.png&quot; data-phocus=&quot;https://blog.kakaocdn.net/dn/bjywbk/dJMb9OHtPji/N9yu30P7canQlI4ph50KXk/img.png&quot;&gt;&lt;img src=&quot;https://blog.kakaocdn.net/dn/bjywbk/dJMb9OHtPji/N9yu30P7canQlI4ph50KXk/img.png&quot; srcset=&quot;https://img1.daumcdn.net/thumb/R1280x0/?scode=mtistory2&amp;fname=https%3A%2F%2Fblog.kakaocdn.net%2Fdn%2Fbjywbk%2FdJMb9OHtPji%2FN9yu30P7canQlI4ph50KXk%2Fimg.png&quot; onerror=&quot;this.onerror=null; this.src='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png'; this.srcset='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png';&quot; loading=&quot;lazy&quot; width=&quot;1196&quot; height=&quot;605&quot; data-origin-width=&quot;1196&quot; data-origin-height=&quot;605&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;여기서는 add datas 위에 있는 모든 커밋 목록들이 나오는데, 여기에서 pick 이라는 값을&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; data-ke-style=&quot;style12&quot;&gt;
&lt;tbody&gt;
&lt;tr&gt;
&lt;td style=&quot;width: 28.6047%;&quot;&gt;명령어&lt;/td&gt;
&lt;td style=&quot;width: 71.3953%;&quot;&gt;설명&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td style=&quot;width: 28.6047%;&quot;&gt;p, pick&lt;/td&gt;
&lt;td style=&quot;width: 71.3953%;&quot;&gt;커밋 그대로 유지하기&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td style=&quot;width: 28.6047%;&quot;&gt;r, reword&lt;/td&gt;
&lt;td style=&quot;width: 71.3953%;&quot;&gt;커밋 메시지 변경&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td style=&quot;width: 28.6047%;&quot;&gt;e, edit&lt;/td&gt;
&lt;td style=&quot;width: 71.3953%;&quot;&gt;커밋 수정을 위해 정지시키기&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td style=&quot;width: 28.6047%;&quot;&gt;d, drop&lt;/td&gt;
&lt;td style=&quot;width: 71.3953%;&quot;&gt;커밋 삭제하기&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td style=&quot;width: 28.6047%;&quot;&gt;s, squash&lt;/td&gt;
&lt;td style=&quot;width: 71.3953%;&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;그럼 여기에서 edit commit 을 수정하기 위해서는 edit commit 앞에 pick 을 r로 변경한다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;이후 :wq 를 진행하면 commit을 수정할 수 있는 레이아웃으로 변경되는데,&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;여기서 커밋 메시지를 edit complete 로 변경한 뒤에 저장하면 commit message가 변경된다.&lt;/p&gt;
&lt;p&gt;&lt;figure class=&quot;imageblock alignCenter&quot; data-ke-mobileStyle=&quot;widthOrigin&quot; data-origin-width=&quot;1262&quot; data-origin-height=&quot;882&quot;&gt;&lt;span data-url=&quot;https://blog.kakaocdn.net/dn/8C5Fa/dJMb9NIz6AU/aMxFrolPzKMlDCRny7RKCk/img.png&quot; data-phocus=&quot;https://blog.kakaocdn.net/dn/8C5Fa/dJMb9NIz6AU/aMxFrolPzKMlDCRny7RKCk/img.png&quot;&gt;&lt;img src=&quot;https://blog.kakaocdn.net/dn/8C5Fa/dJMb9NIz6AU/aMxFrolPzKMlDCRny7RKCk/img.png&quot; srcset=&quot;https://img1.daumcdn.net/thumb/R1280x0/?scode=mtistory2&amp;fname=https%3A%2F%2Fblog.kakaocdn.net%2Fdn%2F8C5Fa%2FdJMb9NIz6AU%2FaMxFrolPzKMlDCRny7RKCk%2Fimg.png&quot; onerror=&quot;this.onerror=null; this.src='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png'; this.srcset='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png';&quot; loading=&quot;lazy&quot; width=&quot;1262&quot; height=&quot;882&quot; data-origin-width=&quot;1262&quot; data-origin-height=&quot;882&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;만약 여러 동작을 동시에 진행하고 싶다면 아래와 같이 commit message 앞의 값을 변경하면 된다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;이때 combine commit은 2 에 s를 입력해야 앞에있는 combine commit1 과 합쳐질 수 있다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;즉, squash 작업은 바로 위에 있는 commit 과 합쳐진다고 생각하는 것이 조금 더 판단하기 편하다.&lt;/p&gt;
&lt;p&gt;&lt;figure class=&quot;imageblock alignCenter&quot; data-ke-mobileStyle=&quot;widthOrigin&quot; data-origin-width=&quot;739&quot; data-origin-height=&quot;425&quot;&gt;&lt;span data-url=&quot;https://blog.kakaocdn.net/dn/bCZlRj/dJMb8XqPGB1/jtwiqv5mgsOimdgpLT8KSK/img.png&quot; data-phocus=&quot;https://blog.kakaocdn.net/dn/bCZlRj/dJMb8XqPGB1/jtwiqv5mgsOimdgpLT8KSK/img.png&quot;&gt;&lt;img src=&quot;https://blog.kakaocdn.net/dn/bCZlRj/dJMb8XqPGB1/jtwiqv5mgsOimdgpLT8KSK/img.png&quot; srcset=&quot;https://img1.daumcdn.net/thumb/R1280x0/?scode=mtistory2&amp;fname=https%3A%2F%2Fblog.kakaocdn.net%2Fdn%2FbCZlRj%2FdJMb8XqPGB1%2Fjtwiqv5mgsOimdgpLT8KSK%2Fimg.png&quot; onerror=&quot;this.onerror=null; this.src='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png'; this.srcset='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png';&quot; loading=&quot;lazy&quot; width=&quot;739&quot; height=&quot;425&quot; data-origin-width=&quot;739&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;p data-ke-size=&quot;size16&quot;&gt;이후 squash 에서 commit message를 결정할 수 있는데, 둘중 하나를 지우거나 새로운 메시지를 입력하기도 한다.&lt;/p&gt;
&lt;p&gt;&lt;figure class=&quot;imageblock alignCenter&quot; data-ke-mobileStyle=&quot;widthOrigin&quot; data-origin-width=&quot;675&quot; data-origin-height=&quot;411&quot;&gt;&lt;span data-url=&quot;https://blog.kakaocdn.net/dn/Hgy9z/dJMb9QrLQoN/8aS5PkYKr8NVd0R0fToOpk/img.png&quot; data-phocus=&quot;https://blog.kakaocdn.net/dn/Hgy9z/dJMb9QrLQoN/8aS5PkYKr8NVd0R0fToOpk/img.png&quot;&gt;&lt;img src=&quot;https://blog.kakaocdn.net/dn/Hgy9z/dJMb9QrLQoN/8aS5PkYKr8NVd0R0fToOpk/img.png&quot; srcset=&quot;https://img1.daumcdn.net/thumb/R1280x0/?scode=mtistory2&amp;fname=https%3A%2F%2Fblog.kakaocdn.net%2Fdn%2FHgy9z%2FdJMb9QrLQoN%2F8aS5PkYKr8NVd0R0fToOpk%2Fimg.png&quot; onerror=&quot;this.onerror=null; this.src='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png'; this.srcset='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png';&quot; loading=&quot;lazy&quot; width=&quot;675&quot; height=&quot;411&quot; data-origin-width=&quot;675&quot; data-origin-height=&quot;411&quot;/&gt;&lt;/span&gt;&lt;/figure&gt;
&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;마지막으로 commit 을 나누는 방법은 edit 을 사용하고 저장하면 해당&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;수정사항이 생긴 시점으로 돌아와서 commit을 직접 손으로 보겠다는 의미와 같다.&lt;/p&gt;
&lt;p&gt;&lt;figure class=&quot;imageblock alignCenter&quot; data-ke-mobileStyle=&quot;widthOrigin&quot; data-origin-width=&quot;802&quot; data-origin-height=&quot;421&quot;&gt;&lt;span data-url=&quot;https://blog.kakaocdn.net/dn/bNh1jK/dJMb9aqaO6I/ZKXc5Js18UtIDIkv8yon31/img.png&quot; data-phocus=&quot;https://blog.kakaocdn.net/dn/bNh1jK/dJMb9aqaO6I/ZKXc5Js18UtIDIkv8yon31/img.png&quot;&gt;&lt;img src=&quot;https://blog.kakaocdn.net/dn/bNh1jK/dJMb9aqaO6I/ZKXc5Js18UtIDIkv8yon31/img.png&quot; srcset=&quot;https://img1.daumcdn.net/thumb/R1280x0/?scode=mtistory2&amp;fname=https%3A%2F%2Fblog.kakaocdn.net%2Fdn%2FbNh1jK%2FdJMb9aqaO6I%2FZKXc5Js18UtIDIkv8yon31%2Fimg.png&quot; onerror=&quot;this.onerror=null; this.src='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png'; this.srcset='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png';&quot; loading=&quot;lazy&quot; width=&quot;802&quot; height=&quot;421&quot; data-origin-width=&quot;802&quot; data-origin-height=&quot;421&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;이후 git reset HEAD~ 으로 커밋을 한단계 되돌린 상태로 하나하나씩 커밋을 진행한다.&lt;/p&gt;
&lt;p&gt;&lt;figure class=&quot;imageblock alignCenter&quot; data-ke-mobileStyle=&quot;widthOrigin&quot; data-origin-width=&quot;746&quot; data-origin-height=&quot;588&quot;&gt;&lt;span data-url=&quot;https://blog.kakaocdn.net/dn/bP3gJk/dJMb9aX0Jai/bK0J5okGeYuW5O7iy1Mt0k/img.png&quot; data-phocus=&quot;https://blog.kakaocdn.net/dn/bP3gJk/dJMb9aX0Jai/bK0J5okGeYuW5O7iy1Mt0k/img.png&quot;&gt;&lt;img src=&quot;https://blog.kakaocdn.net/dn/bP3gJk/dJMb9aX0Jai/bK0J5okGeYuW5O7iy1Mt0k/img.png&quot; srcset=&quot;https://img1.daumcdn.net/thumb/R1280x0/?scode=mtistory2&amp;fname=https%3A%2F%2Fblog.kakaocdn.net%2Fdn%2FbP3gJk%2FdJMb9aX0Jai%2FbK0J5okGeYuW5O7iy1Mt0k%2Fimg.png&quot; onerror=&quot;this.onerror=null; this.src='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png'; this.srcset='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png';&quot; loading=&quot;lazy&quot; width=&quot;746&quot; height=&quot;588&quot; data-origin-width=&quot;746&quot; data-origin-height=&quot;588&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;이렇게 comit 을 관리하고 한 파일에 여러 내용을 분할해서 commit하기, stash, commit 수정까지&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;다양한 내용들을 배워봤는데, 다음번에는 관리되지 않는 파일들을 삭제하거나&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;커밋하지 않은 변경사항을 되돌리는 방법에 대해서 언급하려고 한다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;</description>
      <category>git&amp;amp;github</category>
      <author>몽자비루</author>
      <guid isPermaLink="true">https://rusharp.tistory.com/203</guid>
      <comments>https://rusharp.tistory.com/203#entry203comment</comments>
      <pubDate>Fri, 24 Oct 2025 00:53:39 +0900</pubDate>
    </item>
  </channel>
</rss>