<?xml version="1.0" encoding="UTF-8"?>
<rss version="2.0">
  <channel>
    <title>잠은 죽어서 자라</title>
    <link>https://twosky.tistory.com/</link>
    <description>
</description>
    <language>ko</language>
    <pubDate>Tue, 7 Jul 2026 17:55:09 +0900</pubDate>
    <generator>TISTORY</generator>
    <ttl>100</ttl>
    <managingEditor>주인  장</managingEditor>
    <item>
      <title>[Spring] 분산 시스템에서 이벤트 발행 보장하기 (feat. Transactional Outbox Pattern)</title>
      <link>https://twosky.tistory.com/65</link>
      <description>&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;h2 data-ke-size=&quot;size26&quot;&gt;&lt;b&gt;이벤트 기반 아키텍처 구조&lt;/b&gt;&lt;/h2&gt;
&lt;hr contenteditable=&quot;false&quot; data-ke-type=&quot;horizontalRule&quot; data-ke-style=&quot;style6&quot; /&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;현재 Gloddy 서비스는 그룹 시스템과 알림 시스템으로 구성되어 있습니다. 시스템 간 소통을 위해 이벤트 드리븐 방식을 사용하고 있습니다. 아래는 Gloddy 서비스의 아키텍처를 간단히 그림으로 나타낸 것입니다. 도메인의 비관심사는 메시징 시스템으로 이벤트를 발행하는 것입니다. 따라서, Spring Application Event를 활용해 도메인은 행위에 대한 이벤트를 발행하고, 이를 구독하고 있는 Sns Publish Listener에서 메시징 시스템으로 이벤트를 발행하도록 설계했습니다. 이를 통해 도메인에 영향 없이 메시징 시스템에 대한 연결을 변경 및 확장할 수 있습니다. 메시징 시스템으로는 SNS-SQS를 사용했습니다. 여러 도메인 행위에 대해 SNS 토픽을 생성하고, SQS에서 SNS 토픽을 구독해 메시지를 가져갑니다. 알림 시스템에서는 SQS의 메시지를 consume해 알림 저장 및 알림 전송을 처리합니다.&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;1492&quot; data-origin-height=&quot;708&quot;&gt;&lt;span data-url=&quot;https://blog.kakaocdn.net/dn/lZhe4/btsBG4ePxNg/BC0txw5kThFJQbUrksLUI0/img.png&quot; data-phocus=&quot;https://blog.kakaocdn.net/dn/lZhe4/btsBG4ePxNg/BC0txw5kThFJQbUrksLUI0/img.png&quot;&gt;&lt;img src=&quot;https://blog.kakaocdn.net/dn/lZhe4/btsBG4ePxNg/BC0txw5kThFJQbUrksLUI0/img.png&quot; srcset=&quot;https://img1.daumcdn.net/thumb/R1280x0/?scode=mtistory2&amp;fname=https%3A%2F%2Fblog.kakaocdn.net%2Fdn%2FlZhe4%2FbtsBG4ePxNg%2FBC0txw5kThFJQbUrksLUI0%2Fimg.png&quot; onerror=&quot;this.onerror=null; this.src='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png'; this.srcset='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png';&quot; loading=&quot;lazy&quot; width=&quot;946&quot; height=&quot;449&quot; data-origin-width=&quot;1492&quot; data-origin-height=&quot;708&quot;/&gt;&lt;/span&gt;&lt;/figure&gt;
&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;h2 data-ke-size=&quot;size26&quot;&gt;&lt;b&gt;이벤트 기반 아키텍처 문제점&lt;/b&gt;&lt;/h2&gt;
&lt;hr contenteditable=&quot;false&quot; data-ke-type=&quot;horizontalRule&quot; data-ke-style=&quot;style6&quot; /&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;SNS-SQS는 높은 신뢰성을 제공합니다. 하지만, 그룹 시스템에서 SNS로 이벤트를 발행하는 구간은 HTTP 통신을 사용하기 때문에 이벤트를 발행하는 과정에서 문제가 발생할 수 있습니다. 이를 해결하기 위해 아래와 같이 두가지 방식으로 구현해봤습니다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;h3 data-ke-size=&quot;size23&quot;&gt;&lt;b&gt;1. 하나의 트랜잭션에서 도메인 행위와 이벤트 발행 수행하기&lt;/b&gt;&lt;/h3&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;이벤트 발행을 보장하기 위해 아래와 같이 하나의 트랜잭션에서 도메인 행위와 이벤트 발행을 수행하도록 설계했습니다. 이벤트 발행에 실패하면 도메인 행위도 실패하게 됩니다. &lt;span style=&quot;color: #333333; text-align: start;&quot;&gt;&lt;/span&gt;&lt;/p&gt;
&lt;p&gt;&lt;img src=&quot;https://blog.kakaocdn.net/dn/cQeyeg/btsBFH5Kbgc/x87BAfkQvpS5VXkyvB9JoK/img.png&quot; width=&quot;876&quot; height=&quot;428&quot; data-origin-width=&quot;1504&quot; data-origin-height=&quot;734&quot; data-is-animation=&quot;false&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;Spring Application Event는 트랜잭션 제어 기능을 제공합니다. TransactionalEventListener을 통해 트랜잭션 상태에 따라 Application Event를 consume할 수 있습니다.  따라서, 이를 활용해 트랜잭션 커밋 직전 메시징 시스템으로 이벤트가 발행되도록 구현했습니다. '지원서가 생성되면 모임장에게 알림을 전송한다' 라는 요구사항이 있다고 해보겠습니다. 이를 구현한 코드는 아래와 같습니다.&lt;/p&gt;
&lt;pre id=&quot;code_1702314553508&quot; class=&quot;java&quot; data-ke-language=&quot;java&quot; data-ke-type=&quot;codeblock&quot;&gt;&lt;code&gt;@Transactional
public void createApply(Long userId, Long groupId, ApplyRequest.Create request) {
    User user = userQueryHandler.findById(userId);
    Group group = groupQueryHandler.findById(groupId);
    Apply apply = applyCommandHandler.save(Apply.create(user, group, request.getIntroduce());
    
    applyEventProducer.produceEvent(new ApplyCreateEvent(apply.getId()));
}&lt;/code&gt;&lt;/pre&gt;
&lt;pre id=&quot;code_1702314861514&quot; class=&quot;java&quot; data-ke-language=&quot;java&quot; data-ke-type=&quot;codeblock&quot;&gt;&lt;code&gt;@TransactionalEventListener(phase = TransactionPhase.BEFORE_COMMIT)
public void handleApplyCreateEvent(ApplyCreateEvent event) {
    ApplyAdapterEvent adapterEvent = ApplyEventMapper.from(event);
    snsTemplate.sendNotification(snsProperties.getApplyTopic(), adapterEvent, subject);
}&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;지원서 저장 후 트랜잭션 커밋 직전 지원서 생성 행위에 대한 도메인 이벤트를 발행합니다. 그 후, 이벤트 구독자에서 해당 이벤트를 consume해 메시징 시스템으로 이벤트를 발행합니다. 이벤트가 정상적으로 발행되었다면 트랜잭션을 커밋합니다. 이벤트 발행에 실패했다면 도메인 행위까지 롤백됩니다.&amp;nbsp;하지만, 이는&lt;u&gt; 메시징 시스템의 장애가 시스템 전체의 장애로 번질 수 있는 문제&lt;/u&gt;가 있습니다. 또한, &lt;u&gt;발행되지 않아야할 메시지가 발행될 위험&lt;/u&gt;이 있습니다. 만약, 메시징 시스템으로 이벤트 발행 후 트랜잭션을 커밋하는 과정에서 커밋에 실패한다면 도메인 행위는 롤백되지만, 외부 이벤트는 이미 발행된 상태입니다. 따라서, 도메인 행위와 이벤트 발행의 정합성을 보장할 수 없습니다.&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;h3 data-ke-size=&quot;size23&quot;&gt;&lt;b&gt;2. 도메인 행위와 이벤트 발행 트랜잭션 분리하기&lt;/b&gt;&lt;/h3&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;메시징 시스템의 장애 전파를 막기 위해 도메인 행위와 이벤트 발행 트랜잭션을 분리했습니다. 하지만, 이 경우 메시징 시스템으로의 이벤트 발행을 보장할 수 없게됩니다. 도메인 행위는 정상적으로 처리되어 트랜잭션이 커밋되었지만, 외부 이벤트 발행에 실패하는 경우 메시지가 유실됩니다. 즉, &lt;u&gt;발행되어야 하는 메시지가 발행되지 않을 위험&lt;/u&gt;이 있습니다. 트랜잭션을 분리하는 방법은 위 코드에서 TransactionalEventListener의 phase 옵션을 AFTER_COMMIT으로 변경하면 됩니다.&lt;/p&gt;
&lt;p&gt;&lt;figure class=&quot;imageblock alignCenter&quot; data-ke-mobileStyle=&quot;widthOrigin&quot; data-origin-width=&quot;1490&quot; data-origin-height=&quot;712&quot;&gt;&lt;span data-url=&quot;https://blog.kakaocdn.net/dn/zcQ8C/btsBRRdVSxL/5qIQ6SOKKqGIidN5gFq4w1/img.png&quot; data-phocus=&quot;https://blog.kakaocdn.net/dn/zcQ8C/btsBRRdVSxL/5qIQ6SOKKqGIidN5gFq4w1/img.png&quot;&gt;&lt;img src=&quot;https://blog.kakaocdn.net/dn/zcQ8C/btsBRRdVSxL/5qIQ6SOKKqGIidN5gFq4w1/img.png&quot; srcset=&quot;https://img1.daumcdn.net/thumb/R1280x0/?scode=mtistory2&amp;fname=https%3A%2F%2Fblog.kakaocdn.net%2Fdn%2FzcQ8C%2FbtsBRRdVSxL%2F5qIQ6SOKKqGIidN5gFq4w1%2Fimg.png&quot; onerror=&quot;this.onerror=null; this.src='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png'; this.srcset='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png';&quot; loading=&quot;lazy&quot; width=&quot;1490&quot; height=&quot;712&quot; data-origin-width=&quot;1490&quot; data-origin-height=&quot;712&quot;/&gt;&lt;/span&gt;&lt;/figure&gt;
&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;도메인 행위와 이벤트 발행 트랜잭션을 분리하면서, 도메인 행위와 이벤트 발행의 정합성을 보장할 수 없게 되었습니다. 이를 해결하기 위해선 이벤트 저장소가 필요합니다.&amp;nbsp; 이벤트 저장소에 대한 자세한 내용은 아래 이어집니다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;h2 data-ke-size=&quot;size26&quot;&gt;&lt;b&gt;&lt;span style=&quot;color: #000000; text-align: start;&quot;&gt;Transactional Outbox Pattern&lt;/span&gt;&lt;/b&gt;&lt;/h2&gt;
&lt;hr contenteditable=&quot;false&quot; data-ke-type=&quot;horizontalRule&quot; data-ke-style=&quot;style6&quot; /&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;트랜잭션 내부에서 동일한 데이터베이스에 이벤트를 저장해 도메인 행위와 이벤트 발행의 정합성을 보장하는 방식입니다. 도메인 행위를 수행하는 트랜잭션에서 데이터베이스에 이벤트를 저장합니다. 트랜잭션이 커밋되면 데이터베이스에 저장된 이벤트를 조회해 메시징 시스템으로 이벤트를 발행합니다. 도메인 행위가 정상적으로 수행되었다면, 이벤트가 저장될 것이기 때문에 &lt;u&gt;이벤트 저장 여부에 따라 메시징 시스템으로의 이벤트 발행&lt;/u&gt;을 처리할 수 있습니다. 즉, 발행되지 않아야할 메시지 발행의 문제를 해결할 수 있습니다. 또한, 이벤트 발행 시 이벤트 발행 여부를 업데이트함으로써 &lt;u&gt;이벤트 발행 여부값에 따라 이벤트를 재발행&lt;/u&gt;할 수 있습니다. 이를 통해 발행되어야 하는 메시지가 발행되지 않는 문제를 해결할 수 있습니다. 이처럼, Transactional Outbox Pattern을 통해 도메인 행위와 이벤트 발행의 정합성을 보장할 수 있습니다. 구조는 아래와 같습니다.&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;1756&quot; data-origin-height=&quot;820&quot;&gt;&lt;span data-url=&quot;https://blog.kakaocdn.net/dn/cqS51B/btsBQTwg5tZ/pfppve6Gl5K4smcjzxLlGk/img.png&quot; data-phocus=&quot;https://blog.kakaocdn.net/dn/cqS51B/btsBQTwg5tZ/pfppve6Gl5K4smcjzxLlGk/img.png&quot;&gt;&lt;img src=&quot;https://blog.kakaocdn.net/dn/cqS51B/btsBQTwg5tZ/pfppve6Gl5K4smcjzxLlGk/img.png&quot; srcset=&quot;https://img1.daumcdn.net/thumb/R1280x0/?scode=mtistory2&amp;fname=https%3A%2F%2Fblog.kakaocdn.net%2Fdn%2FcqS51B%2FbtsBQTwg5tZ%2Fpfppve6Gl5K4smcjzxLlGk%2Fimg.png&quot; onerror=&quot;this.onerror=null; this.src='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png'; this.srcset='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png';&quot; loading=&quot;lazy&quot; width=&quot;1756&quot; height=&quot;820&quot; data-origin-width=&quot;1756&quot; data-origin-height=&quot;820&quot;/&gt;&lt;/span&gt;&lt;/figure&gt;
&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;h3 data-ke-size=&quot;size23&quot;&gt;&lt;b&gt;왜 동일한 데이터베이스를 사용해야 할까?&lt;/b&gt;&lt;/h3&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;도메인 행위와 이벤트 발행의 정합성을 보장하기 위해선 필수적으로 트랜잭션을 사용해야 합니다. 즉, 도메인 행위와 이벤트 저장소로의 이벤트 저장은 하나의 트랜잭션에서 수행되어야 합니다. 만약, 이벤트 저장소의 데이터베이스를 분리한다면 다중 데이터베이스의 분산 트랜잭션을 구현해야 합니다. 분산 트랜잭션을 구현하는 것은 어렵기 때문에 동일한 데이터베이스에 이벤트를 저장해 정합성을 보장하려 합니다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;h3 data-ke-size=&quot;size23&quot;&gt;&lt;b&gt;이벤트 저장소에 저장할 데이터 형태&lt;/b&gt;&lt;/h3&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;Zero Payload 방식으로 이벤트를 설계했기 때문에 이벤트의 데이터 형태를 쉽게 정의할 수 있었습니다. 추가로, 이벤트 재발행을 위해 이벤트 발행 여부, 이벤트 발행 시간 컬럼을 추가했습니다.&lt;/p&gt;
&lt;pre id=&quot;code_1702320828390&quot; class=&quot;sql&quot; data-ke-language=&quot;sql&quot; data-ke-type=&quot;codeblock&quot;&gt;&lt;code&gt;create table apply_event (
    id bigint primary key,
    apply_id bigint not null,         // 누가(식별자)
    event_type varchar(50) not null,  // 무엇을 위하여(행위)
    created_at datetime not null      // 언제(시간)

    published tinyint not null,       // 이벤트 발행여부
    published_at datetime not null    // 이벤트 발행시간
)&lt;/code&gt;&lt;/pre&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;h3 data-ke-size=&quot;size23&quot;&gt;&lt;b&gt;이벤트 발행 과정&lt;/b&gt;&lt;/h3&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;아래는 '지원서 생성 시 모임장에게 알림을 전송한다' 라는 요구사항에 대해 Transactional Outbox Pattern을 적용한 이벤트 발행 과정입니다. 1. 도메인 행위 트랜잭션에서 Spring Application Event를 발행합니다. 2. 지원서 이벤트 저장 구독자에서 해당 이벤트를 consume해 지원서 이벤트를 저장합니다. 그 후, Spring Application Event를 발행합니다. 3. 외부 이벤트 발행 구독자는 해당 이벤트를 consume해 지원서 저장 트랜잭션이 커밋된 후 메시징 시스템으로 이벤트를 발행합니다. 이벤트 발행 후 이벤트 발행 여부를 업데이트 합니다. 4. 배치 작업을 통해 이벤트 발행 여부가 false인 이벤트들에 대해 메시징 시스템으로 이벤트를 재발행합니다.&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;1682&quot; data-origin-height=&quot;540&quot;&gt;&lt;span data-url=&quot;https://blog.kakaocdn.net/dn/dkG4oD/btsBT2Ut5sB/jU1TZWz64kIKNuGDl2P9ek/img.png&quot; data-phocus=&quot;https://blog.kakaocdn.net/dn/dkG4oD/btsBT2Ut5sB/jU1TZWz64kIKNuGDl2P9ek/img.png&quot;&gt;&lt;img src=&quot;https://blog.kakaocdn.net/dn/dkG4oD/btsBT2Ut5sB/jU1TZWz64kIKNuGDl2P9ek/img.png&quot; srcset=&quot;https://img1.daumcdn.net/thumb/R1280x0/?scode=mtistory2&amp;fname=https%3A%2F%2Fblog.kakaocdn.net%2Fdn%2FdkG4oD%2FbtsBT2Ut5sB%2FjU1TZWz64kIKNuGDl2P9ek%2Fimg.png&quot; onerror=&quot;this.onerror=null; this.src='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png'; this.srcset='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png';&quot; loading=&quot;lazy&quot; width=&quot;1682&quot; height=&quot;540&quot; data-origin-width=&quot;1682&quot; data-origin-height=&quot;540&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;h3 data-ke-size=&quot;size23&quot;&gt;&lt;b&gt;References&lt;/b&gt;&lt;/h3&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;a href=&quot;https://techblog.woowahan.com/7835/&quot; target=&quot;_blank&quot; rel=&quot;noopener&amp;nbsp;noreferrer&quot;&gt;https://techblog.woowahan.com/7835/&lt;/a&gt;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;span&gt;&lt;/span&gt;&lt;a href=&quot;https://www.youtube.com/watch?v=b65zIH7sDug&amp;amp;t=1094s&quot;&gt;https://www.youtube.com/watch?v=b65zIH7sDug&amp;amp;t=1094s&lt;/a&gt;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;a href=&quot;https://blog.gangnamunni.com/post/transactional-outbox&quot; target=&quot;_blank&quot; rel=&quot;noopener&amp;nbsp;noreferrer&quot;&gt;https://blog.gangnamunni.com/post/transactional-outbox&lt;/a&gt;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;a href=&quot;https://jgrammer.tistory.com/entry/%ED%9A%8C%EC%9B%90%EA%B0%80%EC%9E%85%EC%9C%BC%EB%A1%9C-%EB%B3%B4%EB%8A%94-Event-Driven-Architecture-%EA%B5%AC%ED%98%84&quot; target=&quot;_blank&quot; rel=&quot;noopener&amp;nbsp;noreferrer&quot;&gt;https://jgrammer.tistory.com/entry/%ED%9A%8C%EC%9B%90%EA%B0%80%EC%9E%85%EC%9C%BC%EB%A1%9C-%EB%B3%B4%EB%8A%94-Event-Driven-Architecture-%EA%B5%AC%ED%98%84&lt;/a&gt;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;a href=&quot;https://github.com/jerry-ljh/event-driven-architecture&quot; target=&quot;_blank&quot; rel=&quot;noopener&amp;nbsp;noreferrer&quot;&gt;https://github.com/jerry-ljh/event-driven-architecture&lt;/a&gt;&lt;/p&gt;</description>
      <author>주인  장</author>
      <guid isPermaLink="true">https://twosky.tistory.com/65</guid>
      <comments>https://twosky.tistory.com/65#entry65comment</comments>
      <pubDate>Tue, 12 Dec 2023 04:24:00 +0900</pubDate>
    </item>
    <item>
      <title>[DB] 분산 락 구현 시 트랜잭션으로 인한 동시성 문제</title>
      <link>https://twosky.tistory.com/64</link>
      <description>&lt;h2 data-ke-size=&quot;size26&quot;&gt;&lt;b&gt;문제 상황&lt;/b&gt;&lt;/h2&gt;
&lt;hr contenteditable=&quot;false&quot; data-ke-type=&quot;horizontalRule&quot; data-ke-style=&quot;style6&quot; /&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;이전 글에서 &lt;span style=&quot;color: #000000; text-align: start;&quot;&gt;Redisson을 사용해 분산 락을 구축했습니다. 이후 테스트를 통해 동시성 문제가 해결된 것을 확인할 수 있었습니다. 하지만, 다른 서비스에서 해당 메소드를 호출한 경우 동시성 문제가 재발되었습니다. 아래와 같은 상황입니다.&lt;/span&gt;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;span style=&quot;color: #000000; text-align: start;&quot;&gt;TripMemberService에서는 트랜잭션을 시작하고, TripMemberCreateService의 create 메소드를 호출합니다. TripMemberCreateService에서는 분산 락을 획득하고, 여행 참여 인원을 1 증가시킨 뒤 여행 멤버를 생성합니다.&amp;nbsp;&lt;/span&gt;&lt;/p&gt;
&lt;pre id=&quot;code_1700586375222&quot; class=&quot;java&quot; data-ke-language=&quot;java&quot; data-ke-type=&quot;codeblock&quot;&gt;&lt;code&gt;@Service
@RequiredArgsConstructor
public class TripMemberService {

    private final TripMemberCreateService tripMemberCreateService;

    @Transactional
    public void create(Long tripId, Long memberId) throws InterruptedException {
        tripMemberCreateService.create(tripId, memberId);
    }
}&lt;/code&gt;&lt;/pre&gt;
&lt;pre id=&quot;code_1700586563809&quot; class=&quot;java&quot; data-ke-language=&quot;java&quot; data-ke-type=&quot;codeblock&quot;&gt;&lt;code&gt;@Service
@RequiredArgsConstructor
public class TripMemberCreateService {
    private final TripQueryService tripQueryService;
    private final MemberQueryService memberQueryService;
    private final RedissonClient redissonClient;
    
    public void create(Long tripId, Long memberId) {
        String key = &quot;LOCK-TRIPMEMBER-CREATE-&quot; + tripId;
        RLock lock = redissonClient.getLock(key);

        try {
            boolean availableLock = lock.tryLock(5, 3, TimeUnit.SECONDS);

            if (!availableLock) {
                System.out.println(&quot;LOCK 획득 실패&quot;);
                return;
            }
            createTripMember(tripId, memberId);
        } catch (InterruptedException e) {
            e.printStackTrace();
        } finally {
            lock.unlock();
        }
    }

    private void createTripMember(Long tripId, Long memberId) {
        Trip trip = tripQueryService.findById(tripId);
        Member member = memberQueryService.findById(memberId);
        validateJoinedAble(trip, member);
        trip.join(member);
    }
}&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;그리고, 10명의 사용자가 동시에 여행 참여 요청을 보내는 경우에 대해 테스트했습니다. &lt;span style=&quot;color: #000000; text-align: start;&quot;&gt;테스트 실행 후 여행 참여 인원 수는 10이 되어야 합니다. 하지만,&lt;span style=&quot;color: #333333; text-align: start;&quot;&gt; 아래와 같이 동시성 문제가 발생했습니다.&lt;span&gt;&amp;nbsp;&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&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;1510&quot; data-origin-height=&quot;204&quot;&gt;&lt;span data-url=&quot;https://blog.kakaocdn.net/dn/cfT99P/btsAF0kaehf/tKUyHmSptYZH4otZenu49k/img.png&quot; data-phocus=&quot;https://blog.kakaocdn.net/dn/cfT99P/btsAF0kaehf/tKUyHmSptYZH4otZenu49k/img.png&quot;&gt;&lt;img src=&quot;https://blog.kakaocdn.net/dn/cfT99P/btsAF0kaehf/tKUyHmSptYZH4otZenu49k/img.png&quot; srcset=&quot;https://img1.daumcdn.net/thumb/R1280x0/?scode=mtistory2&amp;fname=https%3A%2F%2Fblog.kakaocdn.net%2Fdn%2FcfT99P%2FbtsAF0kaehf%2FtKUyHmSptYZH4otZenu49k%2Fimg.png&quot; onerror=&quot;this.onerror=null; this.src='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png'; this.srcset='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png';&quot; loading=&quot;lazy&quot; width=&quot;1510&quot; height=&quot;204&quot; data-origin-width=&quot;1510&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;원인을 분석하기 위해 각 스레드에 대해 LOCK 획득 시, LOCK 해지 시 로그를 찍고, 각 스레드에서 조회한 여행 참여 인원 수에 대해 출력해봤습니다. 그 결과, 아래와 같이 각 스레드에서 조회한 여행 참여 인원 수가 같은 것을 확인할 수 있었습니다. 앞의 숫자는 스레드 ID 입니다.&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;1358&quot; data-origin-height=&quot;416&quot;&gt;&lt;span data-url=&quot;https://blog.kakaocdn.net/dn/KETTu/btsAF4fW3bP/GecIkRxbILTLUJzqxwllA0/img.png&quot; data-phocus=&quot;https://blog.kakaocdn.net/dn/KETTu/btsAF4fW3bP/GecIkRxbILTLUJzqxwllA0/img.png&quot;&gt;&lt;img src=&quot;https://blog.kakaocdn.net/dn/KETTu/btsAF4fW3bP/GecIkRxbILTLUJzqxwllA0/img.png&quot; srcset=&quot;https://img1.daumcdn.net/thumb/R1280x0/?scode=mtistory2&amp;fname=https%3A%2F%2Fblog.kakaocdn.net%2Fdn%2FKETTu%2FbtsAF4fW3bP%2FGecIkRxbILTLUJzqxwllA0%2Fimg.png&quot; onerror=&quot;this.onerror=null; this.src='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png'; this.srcset='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png';&quot; loading=&quot;lazy&quot; width=&quot;1358&quot; height=&quot;416&quot; data-origin-width=&quot;1358&quot; data-origin-height=&quot;416&quot;/&gt;&lt;/span&gt;&lt;/figure&gt;
&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;h2 data-ke-size=&quot;size26&quot;&gt;&lt;b&gt;문제 원인 분석&lt;/b&gt;&lt;/h2&gt;
&lt;hr contenteditable=&quot;false&quot; data-ke-type=&quot;horizontalRule&quot; data-ke-style=&quot;style6&quot; /&gt;
&lt;p style=&quot;text-align: left;&quot; data-ke-size=&quot;size16&quot;&gt;문제의 원인은 트랜잭션 커밋 시점에 있었습니다. Spring Transacitonal의 기본 전파 속성은 REQUIRED입니다. 이는 부모 트랜잭션이 존재한다면 부모 트랜잭션에 합류하고, 그렇지 않다면 새로운 트랜잭션을 만듭니다. 따라서, TripMemberCreateService의 create 메소드는 이를 호출한 TripMemberService의 트랜잭션에 합류하게 됩니다. 그렇다면, 트랜잭션과 동시성 문제의 재발은 어떠한 연관이 있을까요? &amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;b&gt;1. 락의 해제 시점이 트랜잭션 커밋 시점보다 빠른 경우&lt;/b&gt;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;table style=&quot;border-collapse: collapse; width: 100%; height: 203px;&quot; border=&quot;1&quot; data-ke-align=&quot;alignLeft&quot; data-ke-style=&quot;style15&quot;&gt;
&lt;tbody&gt;
&lt;tr style=&quot;height: 17px;&quot;&gt;
&lt;td style=&quot;width: 50%; text-align: center; height: 17px;&quot;&gt;Thread A&amp;nbsp;&lt;/td&gt;
&lt;td style=&quot;width: 50%; text-align: center; height: 17px;&quot;&gt;Thread B&lt;/td&gt;
&lt;/tr&gt;
&lt;tr style=&quot;height: 19px;&quot;&gt;
&lt;td style=&quot;width: 50%; text-align: center; height: 19px;&quot;&gt;Transaction A 시작&lt;/td&gt;
&lt;td style=&quot;width: 50%; text-align: center; height: 19px;&quot;&gt;Transaction B 시작&lt;/td&gt;
&lt;/tr&gt;
&lt;tr style=&quot;height: 19px;&quot;&gt;
&lt;td style=&quot;width: 50%; text-align: center; height: 19px;&quot;&gt;&lt;span style=&quot;color: #ee2323;&quot;&gt;Lock 획득&lt;/span&gt;&lt;/td&gt;
&lt;td style=&quot;width: 50%; text-align: center; height: 76px;&quot; rowspan=&quot;4&quot;&gt;Lock 획득 대기&lt;/td&gt;
&lt;/tr&gt;
&lt;tr style=&quot;height: 19px;&quot;&gt;
&lt;td style=&quot;width: 50%; text-align: center; height: 19px;&quot;&gt;여행 참여 인원 수 조회 -&amp;gt; currentCount = 4&lt;/td&gt;
&lt;/tr&gt;
&lt;tr style=&quot;height: 19px;&quot;&gt;
&lt;td style=&quot;width: 50%; text-align: center; height: 19px;&quot;&gt;여행 참여 인원 수 증가 -&amp;gt; currentCount = 5&lt;/td&gt;
&lt;/tr&gt;
&lt;tr style=&quot;height: 19px;&quot;&gt;
&lt;td style=&quot;width: 50%; text-align: center; height: 19px;&quot;&gt;&lt;span style=&quot;color: #ee2323;&quot;&gt;Lock 해제&lt;/span&gt;&lt;/td&gt;
&lt;/tr&gt;
&lt;tr style=&quot;height: 19px;&quot;&gt;
&lt;td style=&quot;text-align: center; height: 19px; width: 50%;&quot;&gt;&amp;nbsp;&lt;/td&gt;
&lt;td style=&quot;width: 50%; text-align: center; height: 19px;&quot;&gt;&lt;span style=&quot;color: #ee2323;&quot;&gt;Lock 획득 (Transaction A Commit 전)&lt;/span&gt;&lt;/td&gt;
&lt;/tr&gt;
&lt;tr style=&quot;height: 17px;&quot;&gt;
&lt;td style=&quot;text-align: center; width: 50%; height: 55px;&quot; rowspan=&quot;3&quot;&gt;&lt;span style=&quot;color: #333333; text-align: center;&quot;&gt;Transaction A Commit&lt;/span&gt;&lt;/td&gt;
&lt;td style=&quot;width: 50%; text-align: center; height: 17px;&quot;&gt;여행 참여 인원 수 조회 -&amp;gt; currentCount = 4&lt;/td&gt;
&lt;/tr&gt;
&lt;tr style=&quot;height: 19px;&quot;&gt;
&lt;td style=&quot;width: 50%; text-align: center; height: 19px;&quot;&gt;여행 참여 인원 수 증가 -&amp;gt; currentCount = 5&lt;/td&gt;
&lt;/tr&gt;
&lt;tr style=&quot;height: 19px;&quot;&gt;
&lt;td style=&quot;width: 50%; text-align: center; height: 19px;&quot;&gt;&lt;span style=&quot;color: #ee2323;&quot;&gt;Lock 해제&lt;/span&gt;&lt;/td&gt;
&lt;/tr&gt;
&lt;tr style=&quot;height: 17px;&quot;&gt;
&lt;td style=&quot;width: 50%; text-align: center; height: 17px;&quot;&gt;&amp;nbsp;&lt;/td&gt;
&lt;td style=&quot;width: 50%; text-align: center; height: 17px;&quot;&gt;&lt;span style=&quot;background-color: #ffffff; color: #333333; text-align: center;&quot;&gt;Transaction B Commit&lt;/span&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;1. Thread A, Thread B 두 스레드가 여행 참여를 위해 메소드에 동시에 접근한다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;2. Thread A가 간발의 차이로 락을 선점하고 여행 참여 인원 수를 조회한다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;3. Thread A는 여행 참여 인원 수를 1 증가하고, 락을 해제한다. (이때 트랜잭션은 커밋되지 않은 상태)&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;4. Thread B는 락이 해제되었다는 신호를 받고 락을 획득하여 여행 참여 인원 수 조회한다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;5. Thread A에서 여행 참여 인원 수를 증가했지만, 아직 Thread A의 트랜잭션이 커밋되지 않았으므로&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp; &amp;nbsp; Thread B는 여행 참여 인원 수를 4로 조회한다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;6. Thread B는 여행 참여 인원을 1 증가하고, 락을 해제한 뒤 커밋한다. (DB에는 4+1=5로 여행 참여 인원 수 반영)&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;두 스레드 중 Thread A가 먼저 락을 획득해 여행 참여 인원 수를 업데이트 했지만, 트랜잭션을 커밋하기 전 락을 해제했습니다. 이로 인해 업데이트 값이 DB에 반영되지 않았고, 이후 락을 획득한 Thread B는 여행 참여 인원 수를 4로 조회한 뒤 값을 1 증가시켰습니다. 이렇듯 &lt;u&gt;락의 해제가 트랜잭션 커밋보다 먼저 이뤄지면 업데이트된 값이 DB에 반영되지 않아 데이터 정합성이 깨질 수 있습니다.&lt;/u&gt;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;b&gt;2. 트랜잭션 커밋 이후 락을 해제하는 경우&lt;/b&gt;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;table style=&quot;border-collapse: collapse; width: 100%; height: 223px;&quot; border=&quot;1&quot; data-ke-align=&quot;alignLeft&quot; data-ke-style=&quot;style15&quot;&gt;
&lt;tbody&gt;
&lt;tr style=&quot;height: 17px;&quot;&gt;
&lt;td style=&quot;width: 50%; height: 17px; text-align: center;&quot;&gt;Thread A&lt;/td&gt;
&lt;td style=&quot;width: 50%; height: 17px; text-align: center;&quot;&gt;Thread B&lt;/td&gt;
&lt;/tr&gt;
&lt;tr style=&quot;height: 17px;&quot;&gt;
&lt;td style=&quot;width: 50%; height: 17px; text-align: center;&quot;&gt;&lt;span style=&quot;color: #ee2323; text-align: center;&quot;&gt;Lock 획득&lt;/span&gt;&lt;/td&gt;
&lt;td style=&quot;width: 50%; height: 102px; text-align: center;&quot; rowspan=&quot;6&quot;&gt;Lock 획득 대기&lt;/td&gt;
&lt;/tr&gt;
&lt;tr style=&quot;height: 17px;&quot;&gt;
&lt;td style=&quot;text-align: center; width: 50%; height: 17px;&quot;&gt;&lt;span style=&quot;color: #333333; text-align: center;&quot;&gt;Transaction A 시작&lt;/span&gt;&lt;/td&gt;
&lt;/tr&gt;
&lt;tr style=&quot;height: 17px;&quot;&gt;
&lt;td style=&quot;width: 50%; height: 17px; text-align: center;&quot;&gt;&lt;span style=&quot;color: #333333; text-align: center;&quot;&gt;여행 참여 인원 수 조회 -&amp;gt; currentCount = 4&lt;/span&gt;&lt;/td&gt;
&lt;/tr&gt;
&lt;tr style=&quot;height: 17px;&quot;&gt;
&lt;td style=&quot;width: 50%; height: 17px; text-align: center;&quot;&gt;&lt;span style=&quot;color: #333333; text-align: center;&quot;&gt;여행 참여 인원 수 증가 -&amp;gt; currentCount = 5&lt;/span&gt;&lt;/td&gt;
&lt;/tr&gt;
&lt;tr style=&quot;height: 17px;&quot;&gt;
&lt;td style=&quot;width: 50%; height: 17px; text-align: center;&quot;&gt;&lt;span style=&quot;color: #333333; text-align: center;&quot;&gt;Transaction A Commit&lt;/span&gt;&lt;/td&gt;
&lt;/tr&gt;
&lt;tr style=&quot;height: 17px;&quot;&gt;
&lt;td style=&quot;width: 50%; height: 17px; text-align: center;&quot;&gt;&lt;span style=&quot;color: #ee2323;&quot;&gt;Lock 해제&lt;/span&gt;&lt;/td&gt;
&lt;/tr&gt;
&lt;tr style=&quot;height: 17px;&quot;&gt;
&lt;td style=&quot;width: 50%; height: 104px; text-align: center;&quot; rowspan=&quot;6&quot;&gt;&amp;nbsp;&lt;/td&gt;
&lt;td style=&quot;width: 50%; height: 17px; text-align: center;&quot;&gt;&lt;span style=&quot;color: #ee2323; text-align: center;&quot;&gt;Lock 획득&lt;/span&gt;&lt;/td&gt;
&lt;/tr&gt;
&lt;tr style=&quot;height: 19px;&quot;&gt;
&lt;td style=&quot;width: 50%; text-align: center; height: 19px;&quot;&gt;&lt;span style=&quot;color: #333333; text-align: center;&quot;&gt;Transaction B 시작&lt;/span&gt;&lt;span style=&quot;color: #ee2323; text-align: center;&quot;&gt;&lt;br /&gt;&lt;/span&gt;&lt;/td&gt;
&lt;/tr&gt;
&lt;tr style=&quot;height: 17px;&quot;&gt;
&lt;td style=&quot;width: 50%; height: 17px; text-align: center;&quot;&gt;&lt;span style=&quot;color: #333333; text-align: center;&quot;&gt;여행 참여 인원 수 조회 -&amp;gt; currentCount = 5&lt;/span&gt;&lt;/td&gt;
&lt;/tr&gt;
&lt;tr style=&quot;height: 17px;&quot;&gt;
&lt;td style=&quot;width: 50%; height: 17px; text-align: center;&quot;&gt;&lt;span style=&quot;color: #333333; text-align: center;&quot;&gt;여행 참여 인원 수 조회 -&amp;gt; currentCount = 6&lt;/span&gt;&lt;/td&gt;
&lt;/tr&gt;
&lt;tr style=&quot;height: 17px;&quot;&gt;
&lt;td style=&quot;width: 50%; text-align: center; height: 17px;&quot;&gt;&lt;span style=&quot;color: #333333; text-align: center;&quot;&gt;&lt;span style=&quot;color: #333333; text-align: center;&quot;&gt;Transaction B Commit&lt;/span&gt;&lt;/span&gt;&lt;/td&gt;
&lt;/tr&gt;
&lt;tr style=&quot;height: 17px;&quot;&gt;
&lt;td style=&quot;width: 50%; height: 17px; text-align: center;&quot;&gt;&lt;span style=&quot;color: #ee2323; text-align: center;&quot;&gt;Lock 해제&lt;/span&gt;&lt;/td&gt;
&lt;/tr&gt;
&lt;/tbody&gt;
&lt;/table&gt;
&lt;p style=&quot;color: #000000; text-align: start;&quot; data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p style=&quot;color: #000000; text-align: start;&quot; data-ke-size=&quot;size16&quot;&gt;1. Thread A, Thread B 두 스레드가 여행 참여를 위해 메소드에 동시에 접근한다.&lt;/p&gt;
&lt;p style=&quot;color: #333333; text-align: start;&quot; data-ke-size=&quot;size16&quot;&gt;2. Thread A가 간발의 차이로 락을 선점하고 여행 참여 인원 수를 조회한다. (인원 수 = 4)&lt;/p&gt;
&lt;p style=&quot;color: #333333; text-align: start;&quot; data-ke-size=&quot;size16&quot;&gt;3. Thread A는 여행 참여 인원 수를 1 증가하고, 트랜잭션 커밋 후 락을 해제한다. (DB 인원 수 = 5)&lt;/p&gt;
&lt;p style=&quot;color: #333333; text-align: start;&quot; data-ke-size=&quot;size16&quot;&gt;4. Thread B는 락이 해제되었다는 신호를 받고 락을 획득한 뒤 여행 참여 인원 수 조회한다. (인원 수 = 5)&lt;/p&gt;
&lt;p style=&quot;color: #333333; text-align: start;&quot; data-ke-size=&quot;size16&quot;&gt;5. Thread B는 여행 참여 인원 수를 1 증가하고, 트랜잭션 커밋 후 락을 해제한다. (DB 인원 수 = 6)&lt;/p&gt;
&lt;p style=&quot;color: #333333; text-align: start;&quot; data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p style=&quot;color: #333333; text-align: start;&quot; data-ke-size=&quot;size16&quot;&gt;두 스레드가 동시에 접근한 경우에도 여행 참여 인원 수가 정상적으로 증가하게 됩니다. 트랜잭션 커밋 후 락을 해제함으로써 이후 락을 획득한 스레드에서 DB에 업데이트된 데이터를 읽어올 수 있게 되는 것입니다. &lt;u&gt;트랜잭션 커밋 후 락을 해제해 동시성 환경에서도 데이터 정합성을 보장할 수 있습니다.&lt;/u&gt; 그럼 어떻게 트랜잭션 커밋 후 락을 해제할 수 있을까요?&lt;/p&gt;
&lt;p style=&quot;color: #333333; text-align: start;&quot; data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p style=&quot;color: #333333; text-align: start;&quot; data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;h2 style=&quot;color: #333333; text-align: start;&quot; data-ke-size=&quot;size26&quot;&gt;&lt;b&gt;문제 해결&amp;nbsp;&lt;/b&gt;&lt;/h2&gt;
&lt;hr contenteditable=&quot;false&quot; data-ke-type=&quot;horizontalRule&quot; data-ke-style=&quot;style6&quot; /&gt;
&lt;p style=&quot;color: #333333; text-align: start;&quot; data-ke-size=&quot;size16&quot;&gt;트랜잭션 커밋 이후 락을 해제하는 방법은 간단합니다. 공유 자원을 사용하는 트랜잭션과 락을 획득하고 해제하는 트랜잭션을 분리하면 됩니다. @Transactional(propagation = Propagation.REQUIRES_NEW)을 사용해 트랜잭션을 분리할 수 있습니다. 트랜잭션을 분리하면 락을 획득한 뒤 새로운 트랜잭션 내에서 공유 자원을 업데이트하고 트랜잭션을 커밋합니다. 그 후, 락을 해제합니다. 이를 통해 공유 자원 의 정합성을 보장할 수 있습니다. 이를 구현한 코드는 아래와 같습니다.&lt;/p&gt;
&lt;pre id=&quot;code_1700630170540&quot; class=&quot;java&quot; data-ke-language=&quot;java&quot; data-ke-type=&quot;codeblock&quot;&gt;&lt;code&gt;@Service
@RequiredArgsConstructor
public class TripMemberFacade {

    private final TripMemberCreateService tripMemberCreateService;
    private final RedissonClient redissonClient;

    @Transactional
    public void create(Long tripId, Long memberId) throws InterruptedException {
        String key = &quot;LOCK-TRIPMEMBER-CREATE-&quot; + trip.getId();
        RLock lock = redissonClient.getLock(key);

        try {
            boolean availableLock = lock.tryLock(5, 3, TimeUnit.SECONDS);

            if (!availableLock) {
                System.out.println(&quot;LOCK 획득 실패&quot;);
                return;
            }
            tripMemberCreateService.create(tripId, memberId);
        } catch (InterruptedException e) {
            e.printStackTrace();
        } finally {
            lock.unlock();
        }
    }
}&lt;/code&gt;&lt;/pre&gt;
&lt;pre id=&quot;code_1700630267282&quot; class=&quot;java&quot; data-ke-language=&quot;java&quot; data-ke-type=&quot;codeblock&quot;&gt;&lt;code&gt;@Service
@RequiredArgsConstructor
public class TripMemberCreateService {

    private final TripQueryService tripQueryService;
    private final MemberQueryService memberQueryService;
    
    @Transactional(propagation = Propagation.REQUIRES_NEW)
    public void create(Long tripId, Long memberId) {
        Trip trip = tripQueryService.findById(tripId);
        Member member = memberQueryService.findById(memberId);
        validateJoinedAble(trip, member);
        trip.join(member);
    }
}&lt;/code&gt;&lt;/pre&gt;
&lt;p style=&quot;color: #333333; text-align: start;&quot; data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p style=&quot;color: #333333; text-align: start;&quot; data-ke-size=&quot;size16&quot;&gt;추가로, 고려해야할 사항이 한가지 더 있습니다. 데이터베이스 커넥션(DBCP) 부족으로 인한 데드락 문제입니다. 트랜잭션을 분리했기 때문에 새로운 데이터베이스 커넥션이 필요합니다. 즉, 여행 요청에 대해 2개의 데이터베이스 커넥션이 필요하게 됩니다. Spring의 HikariCP 기본값은 10입니다. 만약 동시 요청이 10개가 발생한 경우, TripMemberService에서 커넥션 10개를 모두 사용합니다. 그 후,&amp;nbsp; TripMemberCreateService의 REQUIRES_NEW 트랜잭션 전파 속성으로 인해 새로운 커넥션을 받으려고 할 것입니다.&amp;nbsp;&lt;/p&gt;
&lt;p style=&quot;color: #333333; text-align: start;&quot; data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p style=&quot;color: #333333; text-align: start;&quot; data-ke-size=&quot;size16&quot;&gt;이는, Thread간 Connection을 차지하기 위한 Race Condition이 발생한 상태입니다. 이로 인해 HikariCP에 대한 Thread간 Dead lock이 발생하게 됩니다. 이를 해결하기 위해 HikariCP의 maximum pool size를 적절히 설정해주어야 합니다.&amp;nbsp; &lt;a href=&quot;https://techblog.woowahan.com/2663/&quot; target=&quot;_blank&quot; rel=&quot;noopener&quot;&gt;HikariCP maximum pool size 공식 참고&amp;nbsp;&lt;/a&gt;&lt;/p&gt;
&lt;p style=&quot;color: #333333; text-align: start;&quot; data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p style=&quot;color: #333333; text-align: start;&quot; data-ke-size=&quot;size16&quot;&gt;&lt;span style=&quot;color: #212121; text-align: start;&quot;&gt;HikariCP wiki의&amp;nbsp;&lt;/span&gt;Dead lock을 피할 수 있는 &lt;span style=&quot;color: #212121; text-align: start;&quot;&gt;Maximum pool size 공식을 사용해 커넥션 풀 사이즈를 설정해보겠습니다. &lt;span style=&quot;color: #333333; text-align: start;&quot;&gt;현재 테스트에서는 스레드를 10개 사용할 것이기 때문에&lt;span&gt;&amp;nbsp;&lt;/span&gt;&lt;/span&gt;&lt;span style=&quot;color: #212121; text-align: start;&quot;&gt;HikariCP&lt;span&gt;&amp;nbsp;&lt;/span&gt;&lt;/span&gt;&lt;span style=&quot;color: #212121; text-align: start;&quot;&gt;Maximum pool size를 11로 설정했습니다. (&lt;/span&gt;10 x (2-1) + 1 )&lt;/span&gt;&lt;/p&gt;
&lt;p style=&quot;color: #333333; text-align: start;&quot; data-ke-size=&quot;size16&quot;&gt;&lt;span style=&quot;color: #666666; text-align: left;&quot;&gt;(Tn : 전체 Thread 갯수, &lt;/span&gt;&lt;span style=&quot;color: #666666; text-align: left;&quot;&gt;Cm : 하나의 Task에서 동시에 필요한 Connection 수)&lt;/span&gt;&lt;/p&gt;
&lt;blockquote data-ke-style=&quot;style3&quot;&gt;pool size = Tn x (Cm - 1) + 1&lt;/blockquote&gt;
&lt;pre id=&quot;code_1700633234757&quot; class=&quot;java&quot; data-ke-language=&quot;java&quot; data-ke-type=&quot;codeblock&quot;&gt;&lt;code&gt;spring:
  datasource:
    hikari:
      maximum-pool-size: 11&lt;/code&gt;&lt;/pre&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p style=&quot;color: #333333; text-align: start;&quot; data-ke-size=&quot;size16&quot;&gt;테스트 결과, 아래와 같이 동시성 문제가 해결된 것을 확인할 수 있습니다. 락과 관련된 트랜잭션과 공유자원에 접근하는 트랜잭션을 분리함으로써 데이터의 정합성을 보장해 동시성 문제를 해결했습니다.&amp;nbsp;&lt;/p&gt;
&lt;p style=&quot;color: #333333; text-align: start;&quot; data-ke-size=&quot;size16&quot;&gt;&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;1700&quot; data-origin-height=&quot;482&quot;&gt;&lt;span data-url=&quot;https://blog.kakaocdn.net/dn/drjy0q/btsAJztgNN0/ls4yKKQMSgQuCbyZjhEHsk/img.png&quot; data-phocus=&quot;https://blog.kakaocdn.net/dn/drjy0q/btsAJztgNN0/ls4yKKQMSgQuCbyZjhEHsk/img.png&quot;&gt;&lt;img src=&quot;https://blog.kakaocdn.net/dn/drjy0q/btsAJztgNN0/ls4yKKQMSgQuCbyZjhEHsk/img.png&quot; srcset=&quot;https://img1.daumcdn.net/thumb/R1280x0/?scode=mtistory2&amp;fname=https%3A%2F%2Fblog.kakaocdn.net%2Fdn%2Fdrjy0q%2FbtsAJztgNN0%2Fls4yKKQMSgQuCbyZjhEHsk%2Fimg.png&quot; onerror=&quot;this.onerror=null; this.src='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png'; this.srcset='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png';&quot; loading=&quot;lazy&quot; width=&quot;1700&quot; height=&quot;482&quot; data-origin-width=&quot;1700&quot; data-origin-height=&quot;482&quot;/&gt;&lt;/span&gt;&lt;/figure&gt;
&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;h2 data-ke-size=&quot;size26&quot;&gt;&lt;b&gt;마무리하며&lt;/b&gt;&lt;/h2&gt;
&lt;hr contenteditable=&quot;false&quot; data-ke-type=&quot;horizontalRule&quot; data-ke-style=&quot;style6&quot; /&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;트랜잭션의 전파 속성을 통해 락과 관련된 로직과 공유자원에 접근하는 로직의 트랜잭션을 분리함으로써 동시성 재발 문제를 해결했습니다. 또한, 트랜잭션 분리는 데이터베이스 커넥션이 필요한 작업이고, 하나의 작업에서 2개의 커넥션을 사용하는 것은 곧 HikariCP에 대한 Thread간 Dead lock으로 이어질 수 있다는 것을 알게되었습니다. 현재 설정한 HikariCP의 maximum pool size는 Dead lock을 피하기 위한 최소 커넥션 풀 사이즈입니다. 추후 성능 테스트를 통해 최적의 HikariCP 사이즈를 찾아볼 예정입니다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;h3 data-ke-size=&quot;size23&quot;&gt;&lt;b&gt;References&lt;/b&gt;&lt;b&gt;&lt;/b&gt;&lt;/h3&gt;
&lt;p style=&quot;color: #000000; text-align: start;&quot; data-ke-size=&quot;size16&quot;&gt;&lt;u&gt;Spring Redisson 분산 락&lt;span&gt;&amp;nbsp;&lt;/span&gt;&lt;/u&gt;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;a href=&quot;https://helloworld.kurly.com/blog/distributed-redisson-lock/#4-%ED%85%8C%EC%8A%A4%ED%8A%B8-%EC%8B%9C%EB%82%98%EB%A6%AC%EC%98%A4%EB%A5%BC-%EA%B2%80%EC%A6%9D%ED%95%B4-%EB%B3%B4%EC%9E%90&quot; target=&quot;_blank&quot; rel=&quot;noopener&amp;nbsp;noreferrer&quot;&gt;https://helloworld.kurly.com/blog/distributed-redisson-lock/#4-%ED%85%8C%EC%8A%A4%ED%8A%B8-%EC%8B%9C%EB%82%98%EB%A6%AC%EC%98%A4%EB%A5%BC-%EA%B2%80%EC%A6%9D%ED%95%B4-%EB%B3%B4%EC%9E%90&lt;/a&gt;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;a href=&quot;https://cl8d.tistory.com/112&quot; target=&quot;_blank&quot; rel=&quot;noopener&amp;nbsp;noreferrer&quot;&gt;https://cl8d.tistory.com/112&lt;/a&gt;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;a href=&quot;https://developer-nyong.tistory.com/76&quot; target=&quot;_blank&quot; rel=&quot;noopener&amp;nbsp;noreferrer&quot;&gt;https://developer-nyong.tistory.com/76&lt;/a&gt;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;a href=&quot;https://incheol-jung.gitbook.io/docs/q-and-a/spring/redisson-trylock&quot; target=&quot;_blank&quot; rel=&quot;noopener&amp;nbsp;noreferrer&quot;&gt;https://incheol-jung.gitbook.io/docs/q-and-a/spring/redisson-trylock&lt;/a&gt;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;a href=&quot;https://hyperconnect.github.io/2019/11/15/redis-distributed-lock-1.html&quot; target=&quot;_blank&quot; rel=&quot;noopener&amp;nbsp;noreferrer&quot;&gt;https://hyperconnect.github.io/2019/11/15/redis-distributed-lock-1.html&lt;/a&gt;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;a href=&quot;https://velog.io/@znftm97/%EB%8F%99%EC%8B%9C%EC%84%B1-%EB%AC%B8%EC%A0%9C-%ED%95%B4%EA%B2%B0%ED%95%98%EA%B8%B0-V3-%EB%B6%84%EC%82%B0-DB-%ED%99%98%EA%B2%BD%EC%97%90%EC%84%9C-%EB%B6%84%EC%82%B0-%EB%9D%BDDistributed-Lock-%ED%99%9C%EC%9A%A9&quot; target=&quot;_blank&quot; rel=&quot;noopener&amp;nbsp;noreferrer&quot;&gt;https://velog.io/@znftm97/%EB%8F%99%EC%8B%9C%EC%84%B1-%EB%AC%B8%EC%A0%9C-%ED%95%B4%EA%B2%B0%ED%95%98%EA%B8%B0-V3-%EB%B6%84%EC%82%B0-DB-%ED%99%98%EA%B2%BD%EC%97%90%EC%84%9C-%EB%B6%84%EC%82%B0-%EB%9D%BDDistributed-Lock-%ED%99%9C%EC%9A%A9&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;u&gt;HikariCP&lt;/u&gt;&lt;br /&gt;&lt;a href=&quot;https://techblog.woowahan.com/2664/&quot;&gt;https://techblog.woowahan.com/2664/&lt;/a&gt;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;a href=&quot;https://techblog.woowahan.com/2663/&quot; target=&quot;_blank&quot; rel=&quot;noopener&amp;nbsp;noreferrer&quot;&gt;https://techblog.woowahan.com/2663/&lt;/a&gt;&lt;/p&gt;</description>
      <author>주인  장</author>
      <guid isPermaLink="true">https://twosky.tistory.com/64</guid>
      <comments>https://twosky.tistory.com/64#entry64comment</comments>
      <pubDate>Wed, 22 Nov 2023 02:38:49 +0900</pubDate>
    </item>
    <item>
      <title>[Github] SSH를 통해 Git Clone 하기</title>
      <link>https://twosky.tistory.com/63</link>
      <description>&lt;h2 data-ke-size=&quot;size26&quot;&gt;&lt;b&gt;SSH을 통한 Git Clone&lt;/b&gt;&lt;/h2&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;SSH 프로토콜을 사용하여 Git Repository를 아래와 같이 clone할 수 있다. SSH 프로토콜을 사용하면 username과 password(token)을 입력하지 않고, push/pull 을 할 수 있다.&lt;/p&gt;
&lt;pre id=&quot;code_1698755888137&quot; class=&quot;shell&quot; data-ke-language=&quot;shell&quot; data-ke-type=&quot;codeblock&quot;&gt;&lt;code&gt; $ git clone git@host:username/repository.git&lt;/code&gt;&lt;/pre&gt;
&lt;h2 data-ke-size=&quot;size26&quot;&gt;&lt;b&gt;SSH 이란?&lt;/b&gt;&lt;/h2&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;SSH은 Secure Shell의 준말로, 공개 키와 비밀 키 한쌍(Key pair)을 이용해 인증하는 방식이다. 보통 공개 키의 경우 .pub, 개인 키의 경우 .pem의 파일 형식을 띄고 있다. 사용자의 컴퓨터에서 SSH 키를 생성하면 공개 키와 비밀 키가 생성된다. 해당 공개키를 Github에 등록해 컴퓨터를 인증할 수 있다. Github에 공개 키를 저장해놓으면, 그 뒤로 Repository에 접근할 때마다 사용자 컴퓨터의 비밀 키와 Github 서버의 공개 키를 비교한다.&amp;nbsp; 두 키가 서로 부합하다면 컴퓨터와 서버가 연결되는 것이다.&amp;nbsp; (SSH 동작 과정에 대한 자세한 설명은 &lt;a href=&quot;https://library.gabia.com/contents/infrahosting/9002/&quot; target=&quot;_blank&quot; rel=&quot;noopener&quot;&gt;링크&lt;/a&gt; 참고)&lt;b&gt;&lt;/b&gt;&lt;/p&gt;
&lt;h2 data-ke-size=&quot;size26&quot;&gt;&lt;b&gt;SSH와 HTTPS 비교&lt;/b&gt;&lt;/h2&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;SSH와 HTTPS를 사용하여 Repository를 Clone하는 것의 주요 차이점은 인증이 처리되는 방식이다. HTTPS를 사용하는 경우 Git 서버에 인증하기 위해 사용자 이름과 비밀번호(토큰)을 입력한다. 반면에, SSH는 SSH 키를 통해 인증하기 때문에 네트워크를 통해 사용자의 정보를 보낼 필요가 없다. SSH은 네트워크 전송 과정에서 유출되어도 큰 위험이 없는 공개키를 사용하기 때문에 HTTPS 인증 방식보다 안전하다. 본격적으로, SSH를 통해 Git Clone 하는 방법을 알아보자.&lt;/p&gt;
&lt;h2 data-ke-size=&quot;size26&quot;&gt;&lt;b&gt;1. SSH 키 생성&lt;/b&gt;&lt;/h2&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;ssh-keygen 명령을 사용해 SSH 키를 생성한다. 해당 명령을 입력하면 여러 질문들이 나오는데, 아무것도 입력하지 않고 Enter를 3번 누르면 된다. 질문에 대한 내용은 아래에서 좀 더 자세히 살펴보자.&lt;/p&gt;
&lt;pre id=&quot;code_1698757478396&quot; class=&quot;shell&quot; data-ke-language=&quot;shell&quot; data-ke-type=&quot;codeblock&quot;&gt;&lt;code&gt;$ ssh-keygen&lt;/code&gt;&lt;/pre&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;첫번째 질문은 SSH 키가 저장될 파일 경로를 확인 후 Enter를 누르거나, 해당 경로에 SSH 키가 존재하는 경우 다른 경로를 입력하라는 질문이다. 나는 Enter를 눌러 넘어갔다.&lt;/p&gt;
&lt;pre id=&quot;code_1698757901316&quot; class=&quot;bash&quot; data-ke-language=&quot;bash&quot; data-ke-type=&quot;codeblock&quot;&gt;&lt;code&gt;Generating public/private rsa key pair.
Enter file in which to save the key (/Users/ihaneul/.ssh/id_rsa):&lt;/code&gt;&lt;/pre&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;그 다음 질문은 암호를 입력하라는 질문과 암호 재확인 질문이다. 암호를 입력하고 싶은 경우 암호 입력 후 Enter를 누르면 되고, 암호를 사용하지 않는 경우 바로 Enter를 누르면 된다. 암호 재확인 질문은 암호를 사용하지 않는 경우 Enter를 누르면 된다.&lt;/p&gt;
&lt;pre id=&quot;code_1698758026624&quot; class=&quot;shell&quot; data-ke-language=&quot;shell&quot; data-ke-type=&quot;codeblock&quot;&gt;&lt;code&gt;Enter file in which to save the key (/Users/ihaneul/.ssh/id_rsa):
Enter passphrase (empty for no passphrase):&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;그러면, 아래와 같이 성공적으로 SSH 키가 생성되었음을 알리는 출력이 터미널 창에 나타난다.&lt;/p&gt;
&lt;pre id=&quot;code_1698758133084&quot; class=&quot;shell&quot; data-ke-language=&quot;shell&quot; data-ke-type=&quot;codeblock&quot;&gt;&lt;code&gt;The key's randomart image is:
+---[RSA 3072]----+
|        . o.*....|
|       . o * =  .|
|        o + o... |
|       . o . o+ .|
|      . S . ...o.|
|     . =.+ ......|
|      Eo*o+. o.. |
|     o.B=o o. +  |
|   .o +oo.  .o   |
+----[SHA256]-----+&lt;/code&gt;&lt;/pre&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;그리고, .ssh 디렉터리로 이동하면 id_rsa.pub 파일이 생성된 것을 확인할 수 있다. id_rsa.pub에는 공개 키가 담겨있다.&lt;/p&gt;
&lt;pre id=&quot;code_1698758251252&quot; class=&quot;shell&quot; data-ke-language=&quot;shell&quot; data-ke-type=&quot;codeblock&quot;&gt;&lt;code&gt;$ ls ~/.ssh
id_rsa.pub&lt;/code&gt;&lt;/pre&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;h2 data-ke-size=&quot;size26&quot;&gt;&lt;b&gt;2. Github 계정에 공개 키 추가&lt;/b&gt;&lt;/h2&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;cat 명령을 사용하여 위에서 생성한 공개 키 파일 내용을 화면에 나타내고, 해당 내용을 복사한다.&amp;nbsp;&lt;/p&gt;
&lt;pre id=&quot;code_1698758301492&quot; class=&quot;shell&quot; data-ke-language=&quot;shell&quot; data-ke-type=&quot;codeblock&quot;&gt;&lt;code&gt;$ cat ~/.ssh/id_rsa.pub&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;그 후, Github 계정에 로그인해 프로필을 클릭하고, Settings를 클릭한다.&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;1310&quot; data-origin-height=&quot;174&quot;&gt;&lt;span data-url=&quot;https://blog.kakaocdn.net/dn/rAk75/btszvhukk8f/rxaK9PkQtfjEZ46wJBeuv1/img.png&quot; data-phocus=&quot;https://blog.kakaocdn.net/dn/rAk75/btszvhukk8f/rxaK9PkQtfjEZ46wJBeuv1/img.png&quot;&gt;&lt;img src=&quot;https://blog.kakaocdn.net/dn/rAk75/btszvhukk8f/rxaK9PkQtfjEZ46wJBeuv1/img.png&quot; srcset=&quot;https://img1.daumcdn.net/thumb/R1280x0/?scode=mtistory2&amp;fname=https%3A%2F%2Fblog.kakaocdn.net%2Fdn%2FrAk75%2Fbtszvhukk8f%2FrxaK9PkQtfjEZ46wJBeuv1%2Fimg.png&quot; onerror=&quot;this.onerror=null; this.src='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png'; this.srcset='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png';&quot; loading=&quot;lazy&quot; width=&quot;1310&quot; height=&quot;174&quot; data-origin-width=&quot;1310&quot; data-origin-height=&quot;174&quot;/&gt;&lt;/span&gt;&lt;/figure&gt;
&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;왼쪽 메뉴에서 SSH and GPG keys 클릭하고, New SSH Key 버튼을 클릭한다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p&gt;&lt;figure class=&quot;imageblock alignLeft&quot; data-ke-mobileStyle=&quot;widthOrigin&quot; data-origin-width=&quot;584&quot; data-origin-height=&quot;836&quot;&gt;&lt;span data-url=&quot;https://blog.kakaocdn.net/dn/JSZQ8/btszyGz8H8O/u4AmpNnAZtE9kKvjQPI9IK/img.png&quot; data-phocus=&quot;https://blog.kakaocdn.net/dn/JSZQ8/btszyGz8H8O/u4AmpNnAZtE9kKvjQPI9IK/img.png&quot;&gt;&lt;img src=&quot;https://blog.kakaocdn.net/dn/JSZQ8/btszyGz8H8O/u4AmpNnAZtE9kKvjQPI9IK/img.png&quot; srcset=&quot;https://img1.daumcdn.net/thumb/R1280x0/?scode=mtistory2&amp;fname=https%3A%2F%2Fblog.kakaocdn.net%2Fdn%2FJSZQ8%2FbtszyGz8H8O%2Fu4AmpNnAZtE9kKvjQPI9IK%2Fimg.png&quot; onerror=&quot;this.onerror=null; this.src='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png'; this.srcset='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png';&quot; loading=&quot;lazy&quot; width=&quot;342&quot; height=&quot;490&quot; data-origin-width=&quot;584&quot; data-origin-height=&quot;836&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;252&quot;&gt;&lt;span data-url=&quot;https://blog.kakaocdn.net/dn/qyKFD/btszvJjQTqg/sfE8TZErZS7My6GeGLQ5zK/img.png&quot; data-phocus=&quot;https://blog.kakaocdn.net/dn/qyKFD/btszvJjQTqg/sfE8TZErZS7My6GeGLQ5zK/img.png&quot;&gt;&lt;img src=&quot;https://blog.kakaocdn.net/dn/qyKFD/btszvJjQTqg/sfE8TZErZS7My6GeGLQ5zK/img.png&quot; srcset=&quot;https://img1.daumcdn.net/thumb/R1280x0/?scode=mtistory2&amp;fname=https%3A%2F%2Fblog.kakaocdn.net%2Fdn%2FqyKFD%2FbtszvJjQTqg%2FsfE8TZErZS7My6GeGLQ5zK%2Fimg.png&quot; onerror=&quot;this.onerror=null; this.src='//t1.daumcdn.net/tistory_admin/static/images/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;252&quot; data-origin-width=&quot;1190&quot; data-origin-height=&quot;252&quot;/&gt;&lt;/span&gt;&lt;/figure&gt;
&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;Title 필드에 간단한 SSH key 이름을 추가하고, Key 필드에 위에서 복사한 SSH 공개 키를 붙여넣는다. 그리고, Add SSH Key를 클릭하면 아래와 같이 SSH Key가 등록된 것을 확인할 수 있다.&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;1236&quot; data-origin-height=&quot;1034&quot;&gt;&lt;span data-url=&quot;https://blog.kakaocdn.net/dn/FxDPN/btszuKwJglZ/eQMLX79aiDDdqPTB3fLMp1/img.png&quot; data-phocus=&quot;https://blog.kakaocdn.net/dn/FxDPN/btszuKwJglZ/eQMLX79aiDDdqPTB3fLMp1/img.png&quot;&gt;&lt;img src=&quot;https://blog.kakaocdn.net/dn/FxDPN/btszuKwJglZ/eQMLX79aiDDdqPTB3fLMp1/img.png&quot; srcset=&quot;https://img1.daumcdn.net/thumb/R1280x0/?scode=mtistory2&amp;fname=https%3A%2F%2Fblog.kakaocdn.net%2Fdn%2FFxDPN%2FbtszuKwJglZ%2FeQMLX79aiDDdqPTB3fLMp1%2Fimg.png&quot; onerror=&quot;this.onerror=null; this.src='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png'; this.srcset='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png';&quot; loading=&quot;lazy&quot; width=&quot;1236&quot; height=&quot;1034&quot; data-origin-width=&quot;1236&quot; data-origin-height=&quot;1034&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;1110&quot; data-origin-height=&quot;736&quot;&gt;&lt;span data-url=&quot;https://blog.kakaocdn.net/dn/xX7YG/btszA2WeuLP/ixztztlZBasHzEUOa2ueS0/img.png&quot; data-phocus=&quot;https://blog.kakaocdn.net/dn/xX7YG/btszA2WeuLP/ixztztlZBasHzEUOa2ueS0/img.png&quot;&gt;&lt;img src=&quot;https://blog.kakaocdn.net/dn/xX7YG/btszA2WeuLP/ixztztlZBasHzEUOa2ueS0/img.png&quot; srcset=&quot;https://img1.daumcdn.net/thumb/R1280x0/?scode=mtistory2&amp;fname=https%3A%2F%2Fblog.kakaocdn.net%2Fdn%2FxX7YG%2FbtszA2WeuLP%2FixztztlZBasHzEUOa2ueS0%2Fimg.png&quot; onerror=&quot;this.onerror=null; this.src='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png'; this.srcset='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png';&quot; loading=&quot;lazy&quot; width=&quot;1110&quot; height=&quot;736&quot; data-origin-width=&quot;1110&quot; data-origin-height=&quot;736&quot;/&gt;&lt;/span&gt;&lt;/figure&gt;
&lt;/p&gt;
&lt;h2 data-ke-size=&quot;size26&quot;&gt;&lt;b&gt;3. SSH 주소로 Github Repository Clone&lt;/b&gt;&lt;/h2&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;clone 하려는 Repository로 이동해 Code 버튼을 클릭한다. 그 후, SSH 탭을 클릭한 뒤 해당 URL을 복사해 clone하면 된다. SSH 주소로 clone 받은 Repository는 인증 정보 입력 없이 바로 push/pull 명령을 사용할 수 있다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p&gt;&lt;figure class=&quot;imageblock alignCenter&quot; data-ke-mobileStyle=&quot;widthOrigin&quot; data-origin-width=&quot;1210&quot; data-origin-height=&quot;488&quot;&gt;&lt;span data-url=&quot;https://blog.kakaocdn.net/dn/bVYQAv/btszzURGqwH/939RPU4SNZ2BDciH0JLVoK/img.png&quot; data-phocus=&quot;https://blog.kakaocdn.net/dn/bVYQAv/btszzURGqwH/939RPU4SNZ2BDciH0JLVoK/img.png&quot;&gt;&lt;img src=&quot;https://blog.kakaocdn.net/dn/bVYQAv/btszzURGqwH/939RPU4SNZ2BDciH0JLVoK/img.png&quot; srcset=&quot;https://img1.daumcdn.net/thumb/R1280x0/?scode=mtistory2&amp;fname=https%3A%2F%2Fblog.kakaocdn.net%2Fdn%2FbVYQAv%2FbtszzURGqwH%2F939RPU4SNZ2BDciH0JLVoK%2Fimg.png&quot; onerror=&quot;this.onerror=null; this.src='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png'; this.srcset='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png';&quot; loading=&quot;lazy&quot; width=&quot;1210&quot; height=&quot;488&quot; data-origin-width=&quot;1210&quot; data-origin-height=&quot;488&quot;/&gt;&lt;/span&gt;&lt;/figure&gt;
&lt;/p&gt;
&lt;pre id=&quot;code_1698759285055&quot; class=&quot;shell&quot; data-ke-language=&quot;shell&quot; data-ke-type=&quot;codeblock&quot;&gt;&lt;code&gt; $ git clone git@host:username/repository.git&lt;/code&gt;&lt;/pre&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;h4 data-ke-size=&quot;size20&quot;&gt;&lt;b&gt;References&lt;/b&gt;&lt;/h4&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;a href=&quot;https://www.warp.dev/terminus/git-clone-ssh&quot; target=&quot;_blank&quot; rel=&quot;noopener&amp;nbsp;noreferrer&quot;&gt;https://www.warp.dev/terminus/git-clone-ssh&lt;/a&gt;&lt;/p&gt;</description>
      <author>주인  장</author>
      <guid isPermaLink="true">https://twosky.tistory.com/63</guid>
      <comments>https://twosky.tistory.com/63#entry63comment</comments>
      <pubDate>Tue, 31 Oct 2023 22:43:06 +0900</pubDate>
    </item>
    <item>
      <title>[Spring] JdbcTemplate을 통해 Bulk Insert 하기</title>
      <link>https://twosky.tistory.com/62</link>
      <description>&lt;h2 data-ke-size=&quot;size26&quot;&gt;&lt;b&gt;Bulk Insert란?&lt;/b&gt;&lt;/h2&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;아래와 같이 여러 개의 데이터를 하나의 Insert 쿼리로 삽입하는 것은 Bulk Insert라고 한다. 개별 Insert의 경우 쿼리를 던지고 응답을 받은 후에야 다음 쿼리를 전달하기 때문에 지연이 발생한다. 하지만 Bulk Insert는 하나의 트랜잭션으로 묶여 하나의 쿼리문으로 수행하기 때문에 성능이 좋다.&lt;/p&gt;
&lt;pre id=&quot;code_1697736443835&quot; class=&quot;sql&quot; data-ke-language=&quot;sql&quot; data-ke-type=&quot;codeblock&quot;&gt;&lt;code&gt;-- Bulk Insert
INSERT INTO last_insert_id_test(code) VALUES ('TEST1'), ('TEST2'), ('TEST3');

-- 개별 Insert
INSERT INTO last_insert_id_test(code) VALUES ('TEST4');
INSERT INTO last_insert_id_test(code) VALUES ('TEST5');&lt;/code&gt;&lt;/pre&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;h2 data-ke-size=&quot;size26&quot;&gt;&lt;b&gt;문제 상황&amp;nbsp;&lt;/b&gt;&lt;/h2&gt;
&lt;hr contenteditable=&quot;false&quot; data-ke-type=&quot;horizontalRule&quot; data-ke-style=&quot;style6&quot; /&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;프로젝트에서 다수의 여행 데이터를 scheduler를 통해 특정 시간에 DB에 삽입해야 했다. 따라서,&amp;nbsp;다수의 데이터를 한번에 DB에 저장하기 위해 JPA의 saveAll()을 사용하였지만, Bulk Insert로 처리되지 않았다. 실제 JPA saveAll() 함수의 내부를 확인해보니 for문을 돌며 save() 함수를 호출하고 있었다.&amp;nbsp;&lt;/p&gt;
&lt;pre id=&quot;code_1697735644567&quot; class=&quot;java&quot; data-ke-language=&quot;java&quot; data-ke-type=&quot;codeblock&quot;&gt;&lt;code&gt;@Transactional
@Override
public &amp;lt;S extends T&amp;gt; List&amp;lt;S&amp;gt; saveAll(Iterable&amp;lt;S&amp;gt; entities) {

    Assert.notNull(entities, &quot;Entities must not be null&quot;);

    List&amp;lt;S&amp;gt; result = new ArrayList&amp;lt;&amp;gt;();

    for (S entity : entities) {
        result.add(save(entity));
    }

    return result;
}&lt;/code&gt;&lt;/pre&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;h3 data-ke-size=&quot;size23&quot;&gt;&lt;b&gt;1. 왜 JPA saveAll()은 Batch Insert 방식으로 작동하지 않을까?&lt;/b&gt;&lt;/h3&gt;
&lt;p style=&quot;color: #000000; text-align: start;&quot; data-ke-size=&quot;size16&quot;&gt;&lt;span style=&quot;color: #000000; text-align: start;&quot;&gt;결론부터 말하자면, JPA에서 ID 생성 전략으로 GenerateType.IDENDITY 사용 시 batch insert를 할 수 없다. &lt;/span&gt;JPA의 특성에는 영속성 컨텍스트와 쓰기 지연이 있다. 영속성 컨텍스트는 엔티티를 영구 저장하는 환경이라는 뜻으로, 애플리케이션과 데이터베이스 사이에서 객체를 보관하는 가상의 데이터베이스 같은 역할을 한다. JPA는 영속성 컨텍스트 내부에 엔티티 타입과 PK값을 저장해 엔티티를 식별한다. 쓰기 지연(transaction write-behind)은 영속성 컨텍스트에 변경사항이 발생했을 때 SQL을 버퍼에 모아뒀다가, flush하는 시점(트랜잭션이 commit 하는 시점)에 DB로 쿼리를 보내 실행하는 것이다. 그럼 왜 &lt;span style=&quot;color: #000000; text-align: start;&quot;&gt;GenerateType.IDENDITY에서&amp;nbsp;&lt;/span&gt;Batch Insert를 지원하지 않을까?&lt;/p&gt;
&lt;p style=&quot;color: #333333; text-align: start;&quot; data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p style=&quot;color: #333333; text-align: start;&quot; data-ke-size=&quot;size16&quot;&gt;&lt;a style=&quot;color: #0070d1;&quot; href=&quot;https://docs.jboss.org/hibernate/orm/5.4/userguide/html_single/Hibernate_User_Guide.html#batch-session-batch&quot;&gt;공식 문서&lt;span&gt;&amp;nbsp;&lt;/span&gt;&lt;/a&gt;를 살펴보니 아래와 같은 내용이 명시되어 있었다.&lt;/p&gt;
&lt;blockquote style=&quot;color: #666666; text-align: left;&quot; data-ke-style=&quot;style2&quot;&gt;&quot;@GeneratedValue(strategy = GenerationType.IDENTITY)의 경우&amp;nbsp;Hibernate는 JDBC 수준에서 batch insert를 비활성화한다&quot;&lt;/blockquote&gt;
&lt;p style=&quot;color: #333333; text-align: start;&quot; data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p style=&quot;color: #333333; text-align: start;&quot; data-ke-size=&quot;size16&quot;&gt;ID 생성 전략이&lt;span&gt;&amp;nbsp;&lt;/span&gt;&lt;span style=&quot;color: #c7254e;&quot;&gt;&lt;span style=&quot;color: #000000;&quot;&gt;IDENTITY인 경우&lt;span&gt;&amp;nbsp;&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span style=&quot;color: #c7254e;&quot;&gt;&lt;span style=&quot;color: #000000;&quot;&gt;Insert 쿼리를 실행하기 전에는 PK를 알 수 없다.&lt;span&gt;&lt;span&gt;&amp;nbsp;&lt;/span&gt;따라서, 즉시 Insert 쿼리를 실행해 PK값을 가져와야 한다.&lt;span&gt;&amp;nbsp;&lt;/span&gt;&lt;span style=&quot;color: #000000; text-align: start;&quot;&gt;이는&lt;span&gt;&amp;nbsp;&lt;/span&gt;&lt;/span&gt;&lt;span style=&quot;background-color: #ffffff; color: #232629; text-align: left;&quot;&gt;Hibernate가 채택한 트랜잭션 write-behind 플러싱 전략을 방해한다. &lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span style=&quot;color: #c7254e;&quot;&gt;&lt;span style=&quot;color: #000000;&quot;&gt;&lt;span&gt;&lt;span style=&quot;color: #c7254e;&quot;&gt;&lt;span style=&quot;color: #000000;&quot;&gt;&lt;span&gt;또한,&amp;nbsp;&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span style=&quot;color: #c7254e;&quot;&gt;&lt;span style=&quot;color: #000000;&quot;&gt;Bulk Insert를 실행하는 경우 즉시,&lt;span&gt;&amp;nbsp;&lt;/span&gt;&lt;span style=&quot;color: #c7254e;&quot;&gt;&lt;span style=&quot;color: #000000;&quot;&gt;DB에 Bulk Insert 쿼리를 날리고, Insert한 모든 데이터를 영속화해 영속성 컨텍스트에서 관리해야 한다. 이는 너무 많은 데이터를 영속화시켜 JVM에 할당된 메모리가 부족해질 수 있다. (&lt;span style=&quot;background-color: #ffffff; color: #333333; text-align: start;&quot;&gt;out of memory 발생)&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;/p&gt;
&lt;p style=&quot;color: #333333; text-align: start;&quot; data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p style=&quot;color: #333333; text-align: start;&quot; data-ke-size=&quot;size16&quot;&gt;&lt;span style=&quot;color: #c7254e;&quot;&gt;&lt;span style=&quot;color: #000000;&quot;&gt;&lt;span&gt;&lt;span style=&quot;color: #c7254e;&quot;&gt;&lt;span style=&quot;color: #000000;&quot;&gt;&lt;span style=&quot;color: #c7254e;&quot;&gt;&lt;span style=&quot;color: #000000;&quot;&gt;&lt;span style=&quot;background-color: #ffffff; color: #333333; text-align: start;&quot;&gt;이러한 이유로,&lt;span&gt;&amp;nbsp;&lt;/span&gt;&lt;span style=&quot;color: #666666; text-align: left;&quot;&gt;&amp;nbsp;&lt;/span&gt;&lt;span style=&quot;background-color: #ffffff; color: #333333; text-align: start;&quot;&gt;Hibernate는&lt;span&gt;&amp;nbsp;&lt;/span&gt;&lt;/span&gt;ID 생성 전략이&lt;span&gt;&amp;nbsp;&lt;/span&gt;&lt;span style=&quot;color: #666666; text-align: left;&quot;&gt;IDENTITY인 경우&lt;/span&gt;&lt;span&gt;&amp;nbsp;&lt;/span&gt;JDBC 수준에서 batch insert를 비활성화한 것이다. 따라서 JPA에서 batch insert를 사용하기 위해선 ID 생성 전략을 GenerateType.SEQUENCE나 GenerateType.TABLE을 사용해야 한다.&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;/p&gt;
&lt;h3 style=&quot;color: #333333; text-align: start;&quot; data-ke-size=&quot;size23&quot;&gt;&amp;nbsp;&lt;/h3&gt;
&lt;h3 style=&quot;color: #333333; text-align: start;&quot; data-ke-size=&quot;size23&quot;&gt;&lt;b&gt;2. GenerateType.IDENTITY에서는 Batch Insert가 불가능한 것인가?&lt;/b&gt;&lt;/h3&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;JdbcTemplate을 통해 Batch Insert로 처리할 수 있다. JDBC의 batchUpdate를 이용해 Batch Insert 형태로 쿼리를 실행시킬 수 있다. 아래는 그 예시이다. ( jdbcUrl에&amp;nbsp;&lt;b&gt;rewirteBatchedStatements=true&lt;/b&gt;&lt;span style=&quot;color: #333333; text-align: start;&quot;&gt;&lt;span&gt;&lt;span&gt;&amp;nbsp;&lt;/span&gt;을 추가해야 Bulk Insert 형식으로 쿼리가 실행된다. 아래 트러블 슈팅에서 설명할 예정)&lt;/span&gt;&lt;/span&gt;&lt;/p&gt;
&lt;pre id=&quot;code_1697737195574&quot; class=&quot;java&quot; data-ke-language=&quot;java&quot; data-ke-type=&quot;codeblock&quot;&gt;&lt;code&gt;@Repository
@RequiredArgsConstructor
public class OriginTripJdbcRepositoryImpl implements OriginTripJdbcRepository {

    private final JdbcTemplate jdbcTemplate;

    @Override
    public void saveAllTrip(List&amp;lt;OriginTripCreateDto&amp;gt; commands) {
        String TRIP_SQL = &quot;INSERT INTO trip(name, date_time, meet_place, total_time, max_cnt, current_cnt) &quot; +
              &quot;VALUES (?, ?, ?, ?, ?, ?)&quot;;
        jdbcTemplate.batchUpdate(TRIP_SQL, new BatchPreparedStatementSetter() {
                @Override
                public void setValues(PreparedStatement ps, int i) throws SQLException {
                    OriginTripCreateDto command = commands.get(i);
                    ps.setString(1, command.getName());
                    ps.setTimestamp(2, Timestamp.valueOf(command.getDateTime()));
                    ps.setString(3, command.getMeetPlace());
                    ps.setInt(4, command.getTotalTime());
                    ps.setInt(5, command.getMaxCnt());
                    ps.setInt(6, command.getCurrentCnt());
                }

                @Override
                public int getBatchSize() {
                    return commands.size();
                }
            }
        );
    }&lt;/code&gt;&lt;/pre&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;h3 data-ke-size=&quot;size23&quot;&gt;&lt;b&gt;3. 1 : N 관계에서는 어떻게 Bulk Insert를 구현하지?&lt;/b&gt;&lt;/h3&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;JdbcTemplate을 통해 Bulk Insert 구현에 성공했지만, 또 다른 문제가 있었다. 현재 프로젝트에서는 아래와 같이 trip : tripPlace = 1 : N 관계이다. trip을 Bulk Insert로 삽입했지만, ID 생성 전략이 IDENTITY이기 때문에 각 trip의 PK를 알 수 없었다. 따라서, trip_place의 외래키로 trip_id를 넣어줄 수 없었다.&lt;/p&gt;
&lt;p&gt;&lt;figure class=&quot;imageblock alignCenter&quot; data-ke-mobileStyle=&quot;widthOrigin&quot; data-origin-width=&quot;1626&quot; data-origin-height=&quot;546&quot;&gt;&lt;span data-url=&quot;https://blog.kakaocdn.net/dn/bg31Wh/btsyQQoSVzc/WBOKYtzbc1FI4Kw7IAa0t0/img.png&quot; data-phocus=&quot;https://blog.kakaocdn.net/dn/bg31Wh/btsyQQoSVzc/WBOKYtzbc1FI4Kw7IAa0t0/img.png&quot;&gt;&lt;img src=&quot;https://blog.kakaocdn.net/dn/bg31Wh/btsyQQoSVzc/WBOKYtzbc1FI4Kw7IAa0t0/img.png&quot; srcset=&quot;https://img1.daumcdn.net/thumb/R1280x0/?scode=mtistory2&amp;fname=https%3A%2F%2Fblog.kakaocdn.net%2Fdn%2Fbg31Wh%2FbtsyQQoSVzc%2FWBOKYtzbc1FI4Kw7IAa0t0%2Fimg.png&quot; onerror=&quot;this.onerror=null; this.src='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png'; this.srcset='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png';&quot; loading=&quot;lazy&quot; width=&quot;1626&quot; height=&quot;546&quot; data-origin-width=&quot;1626&quot; data-origin-height=&quot;546&quot;/&gt;&lt;/span&gt;&lt;/figure&gt;
&lt;/p&gt;
&lt;h4 data-ke-size=&quot;size20&quot;&gt;&lt;b&gt;trip의 PK를 어떻게 알 수 있을까?&lt;/b&gt;&lt;/h4&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;MySQL에서는 Auto_Increment 컬럼에 대해 중복되지 않고, 저장된 순서대로 증가하는 숫자값을 부여하기 위해 자동 증가 락(AUTO_INCREMENT LOCK) 이라는 테이블 수준의 잠금을 사용한다. 따라서, &lt;span style=&quot;color: #333333; text-align: start;&quot;&gt;PK가 Auto Increment인 테이블에서의 Bulk Insert는 값이 1씩 증가하는 순차적인 PK를 가진다는 것을 보장할 수 있다.&lt;span&gt;&amp;nbsp;&lt;/span&gt;&lt;/span&gt;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;아래와 같이 PK가 Auto Increment인 테이블에서 Bulk Insert를 통해 3개의 데이터를 삽입한다 해보자. 삽입 후, &lt;b&gt;SELECT last_insert_id()&lt;/b&gt; 를 통해 첫번째로 삽입된 데이터의 PK를 알 수 있다. 따라서, 첫번째로 삽입된 데이터의 PK를 기준으로 1씩 늘려가며 Bulk Insert된 모든 데이터의 PK를 알 수 있다.&lt;/p&gt;
&lt;pre id=&quot;code_1697738359753&quot; class=&quot;java&quot; data-ke-language=&quot;java&quot; data-ke-type=&quot;codeblock&quot;&gt;&lt;code&gt;mysql&amp;gt; INSERT INTO last_insert_id_test(code) VALUES ('TEST1'), ('TEST2'), ('TEST3');
mysql&amp;gt; SELECT last_insert_id();
+------------------+
| last_insert_id() |
+------------------+
|                1 |
+------------------+&lt;/code&gt;&lt;/pre&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;아래는 trip 데이터를 Bulk Insert 한 뒤, &lt;b&gt;SELECT last_insert_id()&lt;/b&gt;를 통해 알아낸 첫번째로 삽입된 trip의 PK를 사용해 trip_place 데이터를 삽입하는 코드이다.&amp;nbsp;&lt;/p&gt;
&lt;pre id=&quot;code_1697738739268&quot; class=&quot;java&quot; data-ke-language=&quot;java&quot; data-ke-type=&quot;codeblock&quot;&gt;&lt;code&gt;@Override
public void saveAllTripPlace(List&amp;lt;OriginTripPlaceCreateDto&amp;gt; commands) {
    String TRIP_PLACE_SQL = &quot;INSERT INTO trip_place(`order`, trip_id, place_id) VALUES (?, ?, ?)&quot;;

    jdbcTemplate.batchUpdate(TRIP_PLACE_SQL, new BatchPreparedStatementSetter() {
        @Override
        public void setValues(PreparedStatement ps, int i) throws SQLException {
            OriginTripPlaceCreateDto command = commands.get(i);
            ps.setInt(1, command.getOrder());
            ps.setLong(2, command.getTripId());
            ps.setLong(3, command.getPlaceId());
        }

        @Override
        public int getBatchSize() {
            return commands.size();
        }
    });
}

@Override
public Long lastInsertId() {
    return jdbcTemplate.queryForObject(&quot;SELECT last_insert_id()&quot;, Long.class);
}&lt;/code&gt;&lt;/pre&gt;
&lt;pre id=&quot;code_1697738814460&quot; class=&quot;java&quot; data-ke-language=&quot;java&quot; data-ke-type=&quot;codeblock&quot;&gt;&lt;code&gt;@Service
@RequiredArgsConstructor
public class OriginTripCreateService {

	private final OriginTripRepository  originTripRepository;
    
    private List&amp;lt;OriginTripPlaceCreateDto&amp;gt; createOriginTripPlaces(List&amp;lt;OriginTripCreateDto&amp;gt; originTrips) {
        Long lastInsertTripId = originTripRepository.lastInsertId();
		
        // lastInsertId기반으로 PK 연산
        IntStream.range(0, totalCnt)
                .forEach(idx -&amp;gt; {
                    Long tripId = lastInsertTripId + (long) idx;
                    originTrips.get(idx).setId(tripId);  // PK 설정
                });

        return originTrips.stream()
                .map(trip -&amp;gt; createOriginTripPlace(trip.getId(), trip.getPlaces()))
                .flatMap(Collection::stream)
                .collect(Collectors.toList());
    }
}&lt;/code&gt;&lt;/pre&gt;
&lt;h4 data-ke-size=&quot;size20&quot;&gt;&lt;b&gt;SELECT last_insert_id() 반환값&lt;/b&gt;&lt;/h4&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;1. last_insert_id()는 데이터베이스 커넥션마다 관리된다. 해당 커넥션에서 아직 삽입된 데이터가 없는 경우 0을 반환한다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;2. Bulk Insert에서는 첫번째로 삽입된 데이터의 PK를 반환하지만, 개별 Insert에서는 마지막으로 삽입된 데이터의 PK를 반환한다. 아래 트러블 슈팅 과정에서 더 자세히 설명할 예정이다.&lt;b&gt;&lt;/b&gt;&lt;/p&gt;
&lt;h2 data-ke-size=&quot;size26&quot;&gt;&amp;nbsp;&lt;/h2&gt;
&lt;h2 data-ke-size=&quot;size26&quot;&gt;&lt;b&gt;트러블 슈팅&lt;/b&gt;&lt;/h2&gt;
&lt;hr contenteditable=&quot;false&quot; data-ke-type=&quot;horizontalRule&quot; data-ke-style=&quot;style6&quot; /&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;JdbcTemplate으로 bulkInsert를 한 뒤, SELECT last_insert_id() 반환값을 보니 첫번째로 삽입된 PK가 아닌 마지막으로 삽입된 PK로 반환되었다.&lt;/p&gt;
&lt;h4 data-ke-size=&quot;size20&quot;&gt;&lt;b&gt;[ 문제 원인 가정 1 ]&lt;/b&gt;&lt;/h4&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;처음에는 bulkInsert를 실행한 DB 커넥션과 last_insert_id()를 실행한 DB 커넥션이 달라 last_insert_id()가 마지막으로 삽입된 PK값으로 나왔다고 생각했다. 하지만, Spring은 transactionManager를 통해 트랜잭션 동기화 기능을 제공하고 있다. 즉, 같은 트랜잭션 내에서는 같은 커넥션을 사용한다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;또한, DB 커넥션이 달랐다면 last_insert_id()의 값은 마지막으로 삽입된 PK가 아닌, 0이 나왔어야 한다. insert가 발생하지 않은 커넥션에서는&amp;nbsp; last_insert_id()가 0으로 반환되기 때문이다. 이러한 이유로 각 쿼리를 실행한 DB 커넥션이 다르다는 것은 잘못된 가정이였다.&lt;/p&gt;
&lt;h4 data-ke-size=&quot;size20&quot;&gt;&lt;b&gt;[ 문제 원인 가정 2 ]&lt;/b&gt;&lt;/h4&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;last_insert_id()의 반환값을 살펴보자. 아래와 같이 Bulk Insert 시에는 하나의 쿼리로 보기 때문에 첫번째로 삽입된 PK를 반환한다. 개별 Insert 하는 경우에는 마지막으로 삽입된 PK를 반환한다.&amp;nbsp;&amp;nbsp;&lt;/p&gt;
&lt;pre id=&quot;code_1697720289327&quot; class=&quot;sql&quot; data-ke-language=&quot;sql&quot; data-ke-type=&quot;codeblock&quot;&gt;&lt;code&gt;create table last_insert_id_test (
    id bigint auto_increment primary key,
    code varchar(255)
);&lt;/code&gt;&lt;/pre&gt;
&lt;pre id=&quot;code_1697720163269&quot; class=&quot;java&quot; data-ke-language=&quot;java&quot; data-ke-type=&quot;codeblock&quot;&gt;&lt;code&gt;-- Bulk Insert
mysql&amp;gt; INSERT INTO last_insert_id_test(code) VALUES ('TEST1'), ('TEST2'), ('TEST3');
mysql&amp;gt; SELECT last_insert_id();
+------------------+
| last_insert_id() |
+------------------+
|                1 |
+------------------+

-- Insert
mysql&amp;gt; INSERT INTO last_insert_id_test(code) VALUES ('TEST4');
mysql&amp;gt; INSERT INTO last_insert_id_test(code) VALUES ('TEST5');
mysql&amp;gt; SELECT last_insert_id();
+------------------+
| last_insert_id() |
+------------------+
|                5 |
+------------------+&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;last_insert_id()의 반환값이 마지막으로 삽입된 PK로 나왔다는 것은, Bulk Insert가 실행되지 않다는 뜻이다. 이를 직접 확인해보고자, Jdbc 쿼리 로그를 살펴보았다. 쿼리 로그는 아래와 같은 설정을 통해 확인할 수 있다.&amp;nbsp;&lt;b&gt;&lt;/b&gt;&lt;/p&gt;
&lt;pre id=&quot;code_1697720947627&quot; class=&quot;shell&quot; data-ke-language=&quot;shell&quot; data-ke-type=&quot;codeblock&quot;&gt;&lt;code&gt;spring:
  datasource:
    url: jdbc:mysql://localhost:3306/hibernate_batch?rewriteBatchedStatements=true&amp;amp;profileSQL=true&amp;amp;logger=Slf4JLogger&amp;amp;maxQuerySizeToLog=999999
    username: ${DATABASE_USERNAME}
    password: ${DATABASE_PASSWORD}
    driver-class-name: com.mysql.cj.jdbc.Driver&lt;/code&gt;&lt;/pre&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;profileSQL=true 와 logger=Slf4JLogger, maxQuerySizeToLog=999999 을 추가로 설정해줬다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;1. profileSQL=true : Driver에서 전송하는 쿼리를 출력하기 위한 옵션&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;2. logger=Slf4JLogger : Driver에서 쿼리 출력시 사용할 Logger를 설정하는 옵션. (MySQL 드라이버는 default로 System.err로 출력하도록 설정되어 있으므로 지정 필수)&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;3. maxQuerySizeToLog=999999 : 출력할 쿼리 길이를 설정하는 옵션 (MySQL 드라이버는 기본값이 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;그리고, 실제로 Jdbc 쿼리 로그를 확인해보니, 예상과 같이 Bulk Insert가 실행되고 있지 않았다. Insert 쿼리가 개별적으로 실행되고 있어 last_insert_id() 반환값이 마지막으로 삽입된 PK가 나왔던 것이다.&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;1503&quot; data-origin-height=&quot;540&quot;&gt;&lt;span data-url=&quot;https://blog.kakaocdn.net/dn/lydzd/btsyPCkvvcb/647Yyra6AhuHrG2Xi5QJY0/img.png&quot; data-phocus=&quot;https://blog.kakaocdn.net/dn/lydzd/btsyPCkvvcb/647Yyra6AhuHrG2Xi5QJY0/img.png&quot;&gt;&lt;img src=&quot;https://blog.kakaocdn.net/dn/lydzd/btsyPCkvvcb/647Yyra6AhuHrG2Xi5QJY0/img.png&quot; srcset=&quot;https://img1.daumcdn.net/thumb/R1280x0/?scode=mtistory2&amp;fname=https%3A%2F%2Fblog.kakaocdn.net%2Fdn%2Flydzd%2FbtsyPCkvvcb%2F647Yyra6AhuHrG2Xi5QJY0%2Fimg.png&quot; onerror=&quot;this.onerror=null; this.src='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png'; this.srcset='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png';&quot; loading=&quot;lazy&quot; width=&quot;1503&quot; height=&quot;540&quot; data-origin-width=&quot;1503&quot; data-origin-height=&quot;540&quot;/&gt;&lt;/span&gt;&lt;/figure&gt;
&lt;/p&gt;
&lt;h2 data-ke-size=&quot;size26&quot;&gt;&amp;nbsp;&lt;/h2&gt;
&lt;h2 data-ke-size=&quot;size26&quot;&gt;&lt;b&gt;문제 해결&lt;/b&gt;&lt;/h2&gt;
&lt;hr contenteditable=&quot;false&quot; data-ke-type=&quot;horizontalRule&quot; data-ke-style=&quot;style6&quot; /&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;a style=&quot;color: #99a9bf;&quot; href=&quot;https://docs.jboss.org/hibernate/orm/5.4/userguide/html_single/Hibernate_User_Guide.html#batch-jdbcbatch&quot;&gt;batch-jdbcbatch&lt;/a&gt;&lt;span&gt;&amp;nbsp;&lt;/span&gt;이 글에는 JDBC에서 제공하는 batch 처리 옵션을 알려주고 있다.&lt;/p&gt;
&lt;blockquote data-ke-style=&quot;style2&quot;&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;JDBC offers support for batching together SQL statements that can be represented as a single PreparedStatement. Implementation wise this generally means that drivers will send the batched operation to the server in one call, which can save on network calls to the database. Hibernate can leverage JDBC batching. The following settings control this behavior.&lt;/p&gt;
&lt;/blockquote&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;이를 직역해보면 아래와 같다.&amp;nbsp;&lt;/p&gt;
&lt;blockquote data-ke-style=&quot;style2&quot;&gt;JDBC는 단일 PreparedStatement로 표현될 수 있는 SQL 문을 일괄 처리할 수 있도록 지원한다. 구현 측면에서 이것은 일반적으로 드라이버가 한 번의 호출로 일괄 처리된 작업을 서버로 전송하여 데이터베이스에 대한 네트워크 호출을 절약할 수 있음을 의미한다. Hibernate는 JDBC 일괄 처리를 활용할 수 있다.&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;즉, JDBC는 Bulk Insert를 지원하는 것이 아니라, batch size 크기 만큼 개별 Insert 쿼리를 한번에 실행시켜주는 것이다. 따라서 위와 같이 개별 Insert 쿼리가 날아갔던 것이다. 그럼 Bulk Insert 형태로 쿼리를 실행하려면 어떻게 해야할까?&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;JDBC에서 Bulk Insert를 하기 위해선 database url에 &lt;b&gt;rewirteBatchedStatements=true&lt;/b&gt; 옵션을 추가해야 한다. 이 옵션을 추가하면 connector 레벨에서 insert 구문을 재작성(rewrite) 하게 되어 Bulk Insert(multi-line Insert)가 가능해진다.&lt;/p&gt;
&lt;pre id=&quot;code_1697730420138&quot; class=&quot;shell&quot; data-ke-language=&quot;shell&quot; data-ke-type=&quot;codeblock&quot;&gt;&lt;code&gt;jdbc:mysql://localhost:3306/hibernate_batch?rewriteBatchedStatements=true&amp;amp;profileSQL=true&amp;amp;logger=Slf4JLogger&amp;amp;maxQuerySizeToLog=999999&amp;amp;rewriteBatchedStatements=true&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;그리고, 다시 JdbcTemplate을 실행시켜 보니 아래와 같이 Bulk Insert 되었다! last_insert_id()도 첫번째로 삽입된 데이터의 PK으로 잘 나왔다.&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;1498&quot; data-origin-height=&quot;458&quot;&gt;&lt;span data-url=&quot;https://blog.kakaocdn.net/dn/ba1J2R/btsyNvGfqKN/q66DaNwEIL7B6Kwt7Qggg0/img.png&quot; data-phocus=&quot;https://blog.kakaocdn.net/dn/ba1J2R/btsyNvGfqKN/q66DaNwEIL7B6Kwt7Qggg0/img.png&quot;&gt;&lt;img src=&quot;https://blog.kakaocdn.net/dn/ba1J2R/btsyNvGfqKN/q66DaNwEIL7B6Kwt7Qggg0/img.png&quot; srcset=&quot;https://img1.daumcdn.net/thumb/R1280x0/?scode=mtistory2&amp;fname=https%3A%2F%2Fblog.kakaocdn.net%2Fdn%2Fba1J2R%2FbtsyNvGfqKN%2Fq66DaNwEIL7B6Kwt7Qggg0%2Fimg.png&quot; onerror=&quot;this.onerror=null; this.src='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png'; this.srcset='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png';&quot; loading=&quot;lazy&quot; width=&quot;1498&quot; height=&quot;458&quot; data-origin-width=&quot;1498&quot; data-origin-height=&quot;458&quot;/&gt;&lt;/span&gt;&lt;/figure&gt;
&lt;/p&gt;
&lt;h2 data-ke-size=&quot;size26&quot;&gt;&amp;nbsp;&lt;/h2&gt;
&lt;h2 data-ke-size=&quot;size26&quot;&gt;&lt;b&gt;성능 비교&lt;/b&gt;&lt;/h2&gt;
&lt;hr contenteditable=&quot;false&quot; data-ke-type=&quot;horizontalRule&quot; data-ke-style=&quot;style6&quot; /&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;10000건 데이터 삽입에 대해 saveAll()와 Bulk Insert 성능을 비교해봤습니다. 테스트 환경은 MySQL를 사용했습니다. 테스트 결과 &lt;b&gt;Bulk Insert의 쿼리 수행 시간은 374ms&lt;/b&gt;가 나왔고, &lt;b&gt;saveAll() 쿼리 수행 시간은 1968ms&lt;/b&gt;가 나왔습니다. 10000건의 데이터에 대해 Bulk Insert가 개별 Insert되는 saveAll()에 비해 약 5배 빠른 것을 확인할 수 있었습니다.&amp;nbsp;&lt;/p&gt;
&lt;pre id=&quot;code_1697882212764&quot; class=&quot;java&quot; data-ke-language=&quot;java&quot; data-ke-type=&quot;codeblock&quot;&gt;&lt;code&gt;@Test
void bulkInsertTest() {
    List&amp;lt;OriginTripCreateDto&amp;gt; commands = new ArrayList&amp;lt;&amp;gt;();
    for(int i = 0; i &amp;lt; 10000; i++) {
        commands.add(createOriginTrip(&quot;바다 보러 갈래?&quot;));
    }

    long startTime = System.currentTimeMillis();
    originTripRepository.saveAllTrip(commands); // Bulk Insert
    long endTime = System.currentTimeMillis();
    System.out.println(&quot;execution time = &quot; + (endTime - startTime) + &quot;ms&quot;);
}

@Test
void saveAllTest() {
    List&amp;lt;Trip&amp;gt; commands = new ArrayList&amp;lt;&amp;gt;();
    for(int i = 0; i &amp;lt; 10000; i++) {
        commands.add(createTrip());
    }

    long startTime = System.currentTimeMillis();
    tripRepository.saveAll(commands);  // saveAll()
    long endTime = System.currentTimeMillis();
    System.out.println(&quot;execution time = &quot; + (endTime - startTime) + &quot;ms&quot;);
}&lt;/code&gt;&lt;/pre&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;h2 style=&quot;color: #000000;&quot; data-ke-size=&quot;size26&quot;&gt;&lt;b&gt;Bulk Insert 주의할 점&lt;/b&gt;&lt;/h2&gt;
&lt;hr contenteditable=&quot;false&quot; data-ke-type=&quot;horizontalRule&quot; data-ke-style=&quot;style6&quot; /&gt;
&lt;h3 data-ke-size=&quot;size23&quot;&gt;&lt;b&gt;1. MySQL의 경우 max_allowed_packet 값을 고려해야 한다.&lt;/b&gt;&lt;/h3&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;MySQL의 경우 max_allowed_packet 이라는 설정값이 있다. 이 설정값은 DB server로 보낼 수 있는 최대 요청 packet size이다. 아래와 같은 SQL을 통해 확인할 수 있다. Bulk Insert를 하는 경우 한번에 많은 양의 데이터를 Insert하면 max_allowed_packet_size를 넘을 수 있기 때문에 적절한 batch_size를 고려해야 한다. 일반적으로 권장되는 배치 크기는 50-100이지만 데이터베이스 서버 구성과 각 배치 패키지의 크기에 따라달라질 수 있다.&lt;b&gt;&lt;/b&gt;&lt;/p&gt;
&lt;pre id=&quot;code_1697734075106&quot; class=&quot;sql&quot; data-ke-language=&quot;sql&quot; data-ke-type=&quot;codeblock&quot;&gt;&lt;code&gt;mysql&amp;gt; SHOW VARIABLES LIKE 'max_allowed_packet';
+--------------------+----------+
| Variable_name      | Value    |
+--------------------+----------+
| max_allowed_packet | 67108864 |
+--------------------+----------+&lt;/code&gt;&lt;/pre&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;h3 data-ke-size=&quot;size23&quot;&gt;&lt;b&gt;2. Bulk Insert는 트랜잭션 커넥션 풀을 고갈시킬 수 있다.&lt;/b&gt;&lt;/h3&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;Bulk Insert은 시간이 많이 드는 연산이다. 이는 트랜잭션 커넥션 풀을 고갈시켜 다른 트랜잭션이 실행되지 못할 수 있다. 이를 방지하기 위해 꼭 필요한 경우가 아닌 이상 트랜잭션을 사용하지 않는 것이 좋다. 아래와 같이 하나의 트랜잭션에서 10만건을 만개씩 쪼개서 Bulk Insert한다고 해보자.&lt;/p&gt;
&lt;pre id=&quot;code_1697734715503&quot; class=&quot;java&quot; data-ke-language=&quot;java&quot; data-ke-type=&quot;codeblock&quot;&gt;&lt;code&gt;@Repository
@RequiredArgsConstructor
class CsvRepository {
	
    public void saveAll(List&amp;lt;CsvDto&amp;gt; commands) {
        String SQL = &quot;INSERT INTO user(id, name, gender) VALUES (?, ?, ?)&quot;;

        jdbcTemplate.batchUpdate(SQL, new BatchPreparedStatementSetter() {
            @Override
            public void setValues(PreparedStatement ps, int i) throws SQLException {
                OriginTripPlaceCreateDto command = commands.get(i);
                ps.setLong(1, command.getId());
                ps.setString(2, command.getName());
                ps.setString(3, command.getGender());
            }

            @Override
            public int getBatchSize() {
                return commands.size();
            }
        });
    }&lt;/code&gt;&lt;/pre&gt;
&lt;pre id=&quot;code_1697734495804&quot; class=&quot;java&quot; data-ke-language=&quot;java&quot; data-ke-type=&quot;codeblock&quot;&gt;&lt;code&gt;@Service
@RequiredArgsConstructor
public class CsvService {
	
    private final CsvRepository csvRepository;

    @Transactional
    public void create() {
    	// 1만건씩 리스트로 담고 있는 10만건 리스트
        List&amp;lt;List&amp;lt;CsvDto&amp;gt;&amp;gt; data = new ArrayList&amp;lt;&amp;gt;();
		
        for(List&amp;lt;CsvDto&amp;gt; dto : data) {
        	csvRepository.saveAll(dto);
        }
    }
}&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;만약 9만건까지는 정상적으로 Insert되고, 10만건째 파일을 Insert하는 과정에서 예외가 발생했다고 한다면,&lt;span&gt;&amp;nbsp;&lt;/span&gt;트랜잭션으로 묶여있기 때문에 앞선 9만건의 데이터까지 모두 ROLLBACK 된다. @Transactional은 autoCommit을 false로 세팅해서 트랜잭션이 끝날 때 ROLLBACK/COMMIT 을 결정하게 된다. @Transactional을 사용하지 않는다면, autoCommit이 true이기 때문에 각각의 쿼리가 개별 트랜잭션으로 실행된다.&amp;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;이를 해결하기 위해선, @Transactional을 사용하지 않고, 각각의 Bulk Insert를 실행하면 된다. 모두 실행한 뒤, 예외가 발생한 연산에 대해서만 따로 처리해주면 된다. (로그를 통해 예외가 발생한 연산 확인 가능)&lt;/p&gt;
&lt;pre id=&quot;code_1697734821007&quot; class=&quot;java&quot; data-ke-language=&quot;java&quot; data-ke-type=&quot;codeblock&quot;&gt;&lt;code&gt;@Service
@Slf4j
@RequiredArgsConstructor
public class CsvService {
	
    private final CsvRepository csvRepository;

    @Transactional
    public void create() {
    	// 1만건씩 리스트로 담고 있는 10만건 리스트
        List&amp;lt;List&amp;lt;CsvDto&amp;gt;&amp;gt; data = new ArrayList&amp;lt;&amp;gt;();
		
        for(List&amp;lt;CsvDto&amp;gt; dto : data) {
            try {
                csvRepository.saveAll(dto);
            } catch(Exception e) {
            	log.error(e); // 로그 찍기
        }
    }
}&lt;/code&gt;&lt;/pre&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;h2 data-ke-size=&quot;size26&quot;&gt;&lt;b&gt;정리&lt;/b&gt;&lt;/h2&gt;
&lt;hr contenteditable=&quot;false&quot; data-ke-type=&quot;horizontalRule&quot; data-ke-style=&quot;style6&quot; /&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;b&gt;&lt;span style=&quot;color: #333333; text-align: start;&quot;&gt;JDBC Bulk Insert&lt;span&gt;&amp;nbsp;&lt;/span&gt;&lt;/span&gt;장점&lt;/b&gt;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;- 대용량 데이터 Insert 처리 속도가 대폭 개선된다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;- DB 부하도 대폭 개선된다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;b&gt;&lt;span style=&quot;color: #333333; text-align: start;&quot;&gt;JDBC Bulk Insert&lt;span&gt;&amp;nbsp;&lt;/span&gt;&lt;/span&gt;단점&lt;/b&gt;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;- DB의 외래키가 복잡하게 설정되어 있을 경우 application의 로직이 복잡해진다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;- 벌크 연산은 영속성 컨텍스트를 무시하고 DB에 직접 쿼리한다. 따라서, DB에 반영된 변경이 영속성 컨텍스트에는 반영되지 않을 수 있다. JPA와 함께 사용할 경우 영속성 컨텍스트와 DB간의 데이터 정합성 문제가 발생할 수 있으므로 주의 필요 (&lt;a href=&quot;https://dev-coco.tistory.com/169&quot; target=&quot;_blank&quot; rel=&quot;noopener&quot;&gt;참고&lt;/a&gt;)&lt;/p&gt;
&lt;p style=&quot;color: #000000; text-align: start;&quot; data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;h4 data-ke-size=&quot;size20&quot;&gt;&lt;b&gt;References&lt;/b&gt;&lt;/h4&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;b&gt;&lt;a href=&quot;https://helloworld.kurly.com/blog/bulk-performance-tuning/&quot; target=&quot;_blank&quot; rel=&quot;noopener&amp;nbsp;noreferrer&quot;&gt;https://helloworld.kurly.com/blog/bulk-performance-tuning/&lt;/a&gt;&lt;/b&gt;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;a href=&quot;https://docs.jboss.org/hibernate/orm/5.4/userguide/html_single/Hibernate_User_Guide.html#batch-session-batch&quot; target=&quot;_blank&quot; rel=&quot;noopener&amp;nbsp;noreferrer&quot;&gt;https://docs.jboss.org/hibernate/orm/5.4/userguide/html_single/Hibernate_User_Guide.html#batch-session-batch&lt;/a&gt;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;a href=&quot;https://dev.mysql.com/doc/refman/8.0/en/information-functions.html#function_last-insert-id&quot; target=&quot;_blank&quot; rel=&quot;noopener&amp;nbsp;noreferrer&quot;&gt;https://dev.mysql.com/doc/refman/8.0/en/information-functions.html#function_last-insert-id&lt;/a&gt;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;a href=&quot;https://jaehun2841.github.io/2020/11/22/2020-11-22-spring-data-jpa-batch-insert/#%EB%93%A4%EC%96%B4%EA%B0%80%EB%A9%B0&quot; target=&quot;_blank&quot; rel=&quot;noopener&amp;nbsp;noreferrer&quot;&gt;https://jaehun2841.github.io/2020/11/22/2020-11-22-spring-data-jpa-batch-insert/#%EB%93%A4%EC%96%B4%EA%B0%80%EB%A9%B0&lt;/a&gt;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;a href=&quot;https://backtony.github.io/jpa/2021-08-12-jpa-springdatajpa-2/#1-bulk-insert&quot; target=&quot;_blank&quot; rel=&quot;noopener&amp;nbsp;noreferrer&quot;&gt;https://backtony.github.io/jpa/2021-08-12-jpa-springdatajpa-2/#1-bulk-insert&lt;/a&gt;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;a href=&quot;https://cheese10yun.github.io/jpa-batch-insert/&quot; target=&quot;_blank&quot; rel=&quot;noopener&amp;nbsp;noreferrer&quot;&gt;https://cheese10yun.github.io/jpa-batch-insert/&lt;/a&gt;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;a href=&quot;https://www.youtube.com/watch?v=L9K0l65wMbQ&quot; target=&quot;_blank&quot; rel=&quot;noopener&amp;nbsp;noreferrer&quot;&gt;https://www.youtube.com/watch?v=L9K0l65wMbQ&lt;/a&gt;&lt;/p&gt;</description>
      <author>주인  장</author>
      <guid isPermaLink="true">https://twosky.tistory.com/62</guid>
      <comments>https://twosky.tistory.com/62#entry62comment</comments>
      <pubDate>Fri, 20 Oct 2023 00:31:52 +0900</pubDate>
    </item>
    <item>
      <title>[AWS] AutoScaling CodeDeploy Blue/Green CI/CD</title>
      <link>https://twosky.tistory.com/61</link>
      <description>&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;span style=&quot;color: #333333; text-align: start;&quot;&gt;Github Action, ECR, S3, CodeDeploy를 활용해 Blue/Green&lt;span&gt;&amp;nbsp;방식의&amp;nbsp;&lt;/span&gt;&lt;/span&gt;무중단 배포를 해보려 합니다. 아래는 모든 과정이 끝난 후의 아키텍처입니다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p&gt;&lt;figure class=&quot;imageblock alignCenter&quot; data-ke-mobileStyle=&quot;widthOrigin&quot; data-origin-width=&quot;1012&quot; data-origin-height=&quot;436&quot;&gt;&lt;span data-url=&quot;https://blog.kakaocdn.net/dn/bDpTtv/btsxgc1m1zZ/xWvklo7a7K5oPCrPqmrG00/img.png&quot; data-phocus=&quot;https://blog.kakaocdn.net/dn/bDpTtv/btsxgc1m1zZ/xWvklo7a7K5oPCrPqmrG00/img.png&quot;&gt;&lt;img src=&quot;https://blog.kakaocdn.net/dn/bDpTtv/btsxgc1m1zZ/xWvklo7a7K5oPCrPqmrG00/img.png&quot; srcset=&quot;https://img1.daumcdn.net/thumb/R1280x0/?scode=mtistory2&amp;fname=https%3A%2F%2Fblog.kakaocdn.net%2Fdn%2FbDpTtv%2Fbtsxgc1m1zZ%2FxWvklo7a7K5oPCrPqmrG00%2Fimg.png&quot; onerror=&quot;this.onerror=null; this.src='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png'; this.srcset='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png';&quot; loading=&quot;lazy&quot; width=&quot;1012&quot; height=&quot;436&quot; data-origin-width=&quot;1012&quot; data-origin-height=&quot;436&quot;/&gt;&lt;/span&gt;&lt;/figure&gt;
&lt;/p&gt;
&lt;h2 data-ke-size=&quot;size26&quot;&gt;&lt;b&gt;배포 시나리오&lt;/b&gt;&lt;/h2&gt;
&lt;hr contenteditable=&quot;false&quot; data-ke-type=&quot;horizontalRule&quot; data-ke-style=&quot;style6&quot; /&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;1. Github에 코드 push하면 Github Action 동작&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;2. ECR에 Docker image push&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;3. S3에 CodeDeploy script 업로드&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;4. CodeDeploy 실행해 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 Action을 통해 2,3,4 과정이 실행됩니다. ECR에는 Spring Boot docker image를 push하고, S3에는 CodeDeploy에 필요한 스크립트를 압축해 업로드 합니다. CodeDeploy에서는 S3의 스크립트를 실행해 EC2 배포를 진행합니다. 스크립트에는 ECR로부터 docker image를 pull 받고 컨테이너를 실행하는 명령이 들어있습니다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;h2 data-ke-size=&quot;size26&quot;&gt;&lt;b&gt;1. IAM 생성&lt;/b&gt;&lt;/h2&gt;
&lt;hr contenteditable=&quot;false&quot; data-ke-type=&quot;horizontalRule&quot; data-ke-style=&quot;style6&quot; /&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;IAM은 &lt;span style=&quot;background-color: #ffffff; color: #16191f; text-align: start;&quot;&gt;&lt;span&gt;&amp;nbsp;&lt;/span&gt;AWS 리소스에 대한 액세스를 안전하게 제어할 수 있는 웹 서비스입니다. 이를 통해 특정 인스턴스나 그룹 혹은 계정에 특정 권한을 부여할 수 있습니다. CI/CD를 위해서 첫번째로, EC2가 S3, ECR에 접근할 수 있도록 IAM 역할을 생성해야 합니다. 두번째로, CodeDeploy가 AutoScalingGroup에 접근하기 위한 IAM 역할을 생성해야 합니다.&amp;nbsp;&lt;/span&gt;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;h3 data-ke-size=&quot;size23&quot;&gt;&lt;b&gt;&lt;span style=&quot;background-color: #ffffff; color: #16191f; text-align: start;&quot;&gt;[ 1.&amp;nbsp; &lt;/span&gt;&lt;span style=&quot;background-color: #ffffff; color: #16191f; text-align: start;&quot;&gt;EC2 IAM 역할 생성 ]&lt;/span&gt;&lt;/b&gt;&lt;/h3&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;span style=&quot;background-color: #ffffff; color: #16191f; text-align: start;&quot;&gt;AmazonS3FullAccess, AmazonEC2ContainerRegistryFullAccess 정책을 연결합니다.&lt;/span&gt;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p&gt;&lt;figure class=&quot;imageblock alignCenter&quot; data-ke-mobileStyle=&quot;widthOrigin&quot; data-origin-width=&quot;2188&quot; data-origin-height=&quot;354&quot;&gt;&lt;span data-url=&quot;https://blog.kakaocdn.net/dn/CXcUd/btsw8WxttaY/rSe3zJxHAM6WbklvRlH5xK/img.png&quot; data-phocus=&quot;https://blog.kakaocdn.net/dn/CXcUd/btsw8WxttaY/rSe3zJxHAM6WbklvRlH5xK/img.png&quot;&gt;&lt;img src=&quot;https://blog.kakaocdn.net/dn/CXcUd/btsw8WxttaY/rSe3zJxHAM6WbklvRlH5xK/img.png&quot; srcset=&quot;https://img1.daumcdn.net/thumb/R1280x0/?scode=mtistory2&amp;fname=https%3A%2F%2Fblog.kakaocdn.net%2Fdn%2FCXcUd%2Fbtsw8WxttaY%2FrSe3zJxHAM6WbklvRlH5xK%2Fimg.png&quot; onerror=&quot;this.onerror=null; this.src='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png'; this.srcset='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png';&quot; loading=&quot;lazy&quot; width=&quot;2188&quot; height=&quot;354&quot; data-origin-width=&quot;2188&quot; data-origin-height=&quot;354&quot;/&gt;&lt;/span&gt;&lt;/figure&gt;
&lt;figure class=&quot;imageblock alignCenter&quot; data-ke-mobileStyle=&quot;widthOrigin&quot; data-filename=&quot;스크린샷 2023-09-25 오후 4.57.27.png&quot; data-origin-width=&quot;1598&quot; data-origin-height=&quot;1332&quot;&gt;&lt;span data-url=&quot;https://blog.kakaocdn.net/dn/o4Xvd/btsw7w6S8m9/EQrbfi6S359F9SeLYTt1H1/img.png&quot; data-phocus=&quot;https://blog.kakaocdn.net/dn/o4Xvd/btsw7w6S8m9/EQrbfi6S359F9SeLYTt1H1/img.png&quot;&gt;&lt;img src=&quot;https://blog.kakaocdn.net/dn/o4Xvd/btsw7w6S8m9/EQrbfi6S359F9SeLYTt1H1/img.png&quot; srcset=&quot;https://img1.daumcdn.net/thumb/R1280x0/?scode=mtistory2&amp;fname=https%3A%2F%2Fblog.kakaocdn.net%2Fdn%2Fo4Xvd%2Fbtsw7w6S8m9%2FEQrbfi6S359F9SeLYTt1H1%2Fimg.png&quot; onerror=&quot;this.onerror=null; this.src='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png'; this.srcset='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png';&quot; loading=&quot;lazy&quot; width=&quot;1598&quot; height=&quot;1332&quot; data-filename=&quot;스크린샷 2023-09-25 오후 4.57.27.png&quot; data-origin-width=&quot;1598&quot; data-origin-height=&quot;1332&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;2156&quot; data-origin-height=&quot;580&quot;&gt;&lt;span data-url=&quot;https://blog.kakaocdn.net/dn/bw9JmM/btswPUnsnkc/cJSPjxG3J9bDbEDkc0bM61/img.png&quot; data-phocus=&quot;https://blog.kakaocdn.net/dn/bw9JmM/btswPUnsnkc/cJSPjxG3J9bDbEDkc0bM61/img.png&quot;&gt;&lt;img src=&quot;https://blog.kakaocdn.net/dn/bw9JmM/btswPUnsnkc/cJSPjxG3J9bDbEDkc0bM61/img.png&quot; srcset=&quot;https://img1.daumcdn.net/thumb/R1280x0/?scode=mtistory2&amp;fname=https%3A%2F%2Fblog.kakaocdn.net%2Fdn%2Fbw9JmM%2FbtswPUnsnkc%2FcJSPjxG3J9bDbEDkc0bM61%2Fimg.png&quot; onerror=&quot;this.onerror=null; this.src='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png'; this.srcset='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png';&quot; loading=&quot;lazy&quot; width=&quot;2156&quot; height=&quot;580&quot; data-origin-width=&quot;2156&quot; data-origin-height=&quot;580&quot;/&gt;&lt;/span&gt;&lt;/figure&gt;
&lt;figure class=&quot;imageblock alignCenter&quot; data-ke-mobileStyle=&quot;widthOrigin&quot; data-filename=&quot;스크린샷 2023-09-25 오후 5.05.17.png&quot; data-origin-width=&quot;1612&quot; data-origin-height=&quot;844&quot;&gt;&lt;span data-url=&quot;https://blog.kakaocdn.net/dn/cnrklH/btswpW7vV5j/ylfZKK16gjpzu3RSmxoLPk/img.png&quot; data-phocus=&quot;https://blog.kakaocdn.net/dn/cnrklH/btswpW7vV5j/ylfZKK16gjpzu3RSmxoLPk/img.png&quot;&gt;&lt;img src=&quot;https://blog.kakaocdn.net/dn/cnrklH/btswpW7vV5j/ylfZKK16gjpzu3RSmxoLPk/img.png&quot; srcset=&quot;https://img1.daumcdn.net/thumb/R1280x0/?scode=mtistory2&amp;fname=https%3A%2F%2Fblog.kakaocdn.net%2Fdn%2FcnrklH%2FbtswpW7vV5j%2FylfZKK16gjpzu3RSmxoLPk%2Fimg.png&quot; onerror=&quot;this.onerror=null; this.src='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png'; this.srcset='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png';&quot; loading=&quot;lazy&quot; width=&quot;1612&quot; height=&quot;844&quot; data-filename=&quot;스크린샷 2023-09-25 오후 5.05.17.png&quot; data-origin-width=&quot;1612&quot; data-origin-height=&quot;844&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;&lt;b&gt;&lt;span style=&quot;background-color: #ffffff; color: #16191f; text-align: start;&quot;&gt;[ 2. &lt;span&gt;&amp;nbsp;&lt;/span&gt;&lt;/span&gt;&lt;span style=&quot;background-color: #ffffff; color: #16191f; text-align: start;&quot;&gt;CodeDeploy IAM 역할 생성 ]&lt;/span&gt;&lt;/b&gt;&lt;/h3&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;CodeDeploy IAM 역할을 생성한 뒤, AutoScalingGroup에 접근하기 위한 정책을 연결해줄 예정입니다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;1. 우선 기본적인 CodeDeploy 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-filename=&quot;스크린샷 2023-09-25 오후 5.06.32.png&quot; data-origin-width=&quot;1574&quot; data-origin-height=&quot;1226&quot;&gt;&lt;span data-url=&quot;https://blog.kakaocdn.net/dn/F1oXN/btswUWyDV9a/eMBMBBVOyQwrdscw0tVpn1/img.png&quot; data-phocus=&quot;https://blog.kakaocdn.net/dn/F1oXN/btswUWyDV9a/eMBMBBVOyQwrdscw0tVpn1/img.png&quot;&gt;&lt;img src=&quot;https://blog.kakaocdn.net/dn/F1oXN/btswUWyDV9a/eMBMBBVOyQwrdscw0tVpn1/img.png&quot; srcset=&quot;https://img1.daumcdn.net/thumb/R1280x0/?scode=mtistory2&amp;fname=https%3A%2F%2Fblog.kakaocdn.net%2Fdn%2FF1oXN%2FbtswUWyDV9a%2FeMBMBBVOyQwrdscw0tVpn1%2Fimg.png&quot; onerror=&quot;this.onerror=null; this.src='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png'; this.srcset='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png';&quot; loading=&quot;lazy&quot; width=&quot;1574&quot; height=&quot;1226&quot; data-filename=&quot;스크린샷 2023-09-25 오후 5.06.32.png&quot; data-origin-width=&quot;1574&quot; data-origin-height=&quot;1226&quot;/&gt;&lt;/span&gt;&lt;/figure&gt;
&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;2. CodeDeploy 배포 그룹에서 사용될 정책으로, CodeDeploy가 AutoScalingGroup에 접근할 수 있도록 정책을 생성합니다. JSON을 클릭하고 아래 JSON 코드를 붙여넣습니다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p&gt;&lt;figure class=&quot;imageblock alignLeft&quot; data-ke-mobileStyle=&quot;widthOrigin&quot; data-filename=&quot;edited_스크린샷 2023-09-25 오후 5.11.57.png&quot; data-origin-width=&quot;2081&quot; data-origin-height=&quot;364&quot;&gt;&lt;span data-url=&quot;https://blog.kakaocdn.net/dn/c2k9NE/btswPX5wOEk/exkxxbn35qe7qMZBP4NHn1/img.png&quot; data-phocus=&quot;https://blog.kakaocdn.net/dn/c2k9NE/btswPX5wOEk/exkxxbn35qe7qMZBP4NHn1/img.png&quot;&gt;&lt;img src=&quot;https://blog.kakaocdn.net/dn/c2k9NE/btswPX5wOEk/exkxxbn35qe7qMZBP4NHn1/img.png&quot; srcset=&quot;https://img1.daumcdn.net/thumb/R1280x0/?scode=mtistory2&amp;fname=https%3A%2F%2Fblog.kakaocdn.net%2Fdn%2Fc2k9NE%2FbtswPX5wOEk%2Fexkxxbn35qe7qMZBP4NHn1%2Fimg.png&quot; onerror=&quot;this.onerror=null; this.src='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png'; this.srcset='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png';&quot; loading=&quot;lazy&quot; width=&quot;2081&quot; height=&quot;364&quot; data-filename=&quot;edited_스크린샷 2023-09-25 오후 5.11.57.png&quot; data-origin-width=&quot;2081&quot; data-origin-height=&quot;364&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 alignLeft&quot; data-ke-mobileStyle=&quot;widthOrigin&quot; data-filename=&quot;edited_스크린샷 2023-09-25 오후 5.12.58.png&quot; data-origin-width=&quot;2001&quot; data-origin-height=&quot;918&quot;&gt;&lt;span data-url=&quot;https://blog.kakaocdn.net/dn/7tyX8/btswJCN7V86/XB4EELON7NJxnqJZUmmp8k/img.png&quot; data-phocus=&quot;https://blog.kakaocdn.net/dn/7tyX8/btswJCN7V86/XB4EELON7NJxnqJZUmmp8k/img.png&quot;&gt;&lt;img src=&quot;https://blog.kakaocdn.net/dn/7tyX8/btswJCN7V86/XB4EELON7NJxnqJZUmmp8k/img.png&quot; srcset=&quot;https://img1.daumcdn.net/thumb/R1280x0/?scode=mtistory2&amp;fname=https%3A%2F%2Fblog.kakaocdn.net%2Fdn%2F7tyX8%2FbtswJCN7V86%2FXB4EELON7NJxnqJZUmmp8k%2Fimg.png&quot; onerror=&quot;this.onerror=null; this.src='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png'; this.srcset='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png';&quot; loading=&quot;lazy&quot; width=&quot;2001&quot; height=&quot;918&quot; data-filename=&quot;edited_스크린샷 2023-09-25 오후 5.12.58.png&quot; data-origin-width=&quot;2001&quot; data-origin-height=&quot;918&quot;/&gt;&lt;/span&gt;&lt;/figure&gt;
&lt;/p&gt;
&lt;pre id=&quot;code_1696423961274&quot; class=&quot;json&quot; data-ke-type=&quot;codeblock&quot; data-ke-language=&quot;shell&quot;&gt;&lt;code&gt;{
    &quot;Version&quot;: &quot;2012-10-17&quot;,
    &quot;Statement&quot;: [
        {
            &quot;Sid&quot;: &quot;VisualEditor0&quot;,
            &quot;Effect&quot;: &quot;Allow&quot;,
            &quot;Action&quot;: [
                &quot;iam:PassRole&quot;,
                &quot;ec2:CreateTags&quot;,
                &quot;ec2:RunInstances&quot;
            ],
            &quot;Resource&quot;: &quot;*&quot;
        }
    ]
}&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;&lt;figure class=&quot;imageblock alignCenter&quot; data-ke-mobileStyle=&quot;widthOrigin&quot; data-filename=&quot;스크린샷 2023-09-25 오후 5.15.04.png&quot; data-origin-width=&quot;1268&quot; data-origin-height=&quot;644&quot;&gt;&lt;span data-url=&quot;https://blog.kakaocdn.net/dn/Q80xJ/btsw67TEJgh/AVEmgi5cYKVSLzP37q9m8k/img.png&quot; data-phocus=&quot;https://blog.kakaocdn.net/dn/Q80xJ/btsw67TEJgh/AVEmgi5cYKVSLzP37q9m8k/img.png&quot;&gt;&lt;img src=&quot;https://blog.kakaocdn.net/dn/Q80xJ/btsw67TEJgh/AVEmgi5cYKVSLzP37q9m8k/img.png&quot; srcset=&quot;https://img1.daumcdn.net/thumb/R1280x0/?scode=mtistory2&amp;fname=https%3A%2F%2Fblog.kakaocdn.net%2Fdn%2FQ80xJ%2Fbtsw67TEJgh%2FAVEmgi5cYKVSLzP37q9m8k%2Fimg.png&quot; onerror=&quot;this.onerror=null; this.src='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png'; this.srcset='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png';&quot; loading=&quot;lazy&quot; width=&quot;1268&quot; height=&quot;644&quot; data-filename=&quot;스크린샷 2023-09-25 오후 5.15.04.png&quot; data-origin-width=&quot;1268&quot; data-origin-height=&quot;644&quot;/&gt;&lt;/span&gt;&lt;/figure&gt;
&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;3. 첫번째로 생성한 CodeDeploy 역할에 위에서 생성한 정책을 연결해줍니다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p&gt;&lt;figure class=&quot;imageblock alignCenter&quot; data-ke-mobileStyle=&quot;widthOrigin&quot; data-filename=&quot;스크린샷 2023-09-25 오후 5.16.35.png&quot; data-origin-width=&quot;2212&quot; data-origin-height=&quot;1368&quot;&gt;&lt;span data-url=&quot;https://blog.kakaocdn.net/dn/cgclXt/btswpUV8qJB/1yEHx4blLVb0zTcGkG9NMK/img.png&quot; data-phocus=&quot;https://blog.kakaocdn.net/dn/cgclXt/btswpUV8qJB/1yEHx4blLVb0zTcGkG9NMK/img.png&quot;&gt;&lt;img src=&quot;https://blog.kakaocdn.net/dn/cgclXt/btswpUV8qJB/1yEHx4blLVb0zTcGkG9NMK/img.png&quot; srcset=&quot;https://img1.daumcdn.net/thumb/R1280x0/?scode=mtistory2&amp;fname=https%3A%2F%2Fblog.kakaocdn.net%2Fdn%2FcgclXt%2FbtswpUV8qJB%2F1yEHx4blLVb0zTcGkG9NMK%2Fimg.png&quot; onerror=&quot;this.onerror=null; this.src='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png'; this.srcset='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png';&quot; loading=&quot;lazy&quot; width=&quot;2212&quot; height=&quot;1368&quot; data-filename=&quot;스크린샷 2023-09-25 오후 5.16.35.png&quot; data-origin-width=&quot;2212&quot; data-origin-height=&quot;1368&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;1490&quot; data-origin-height=&quot;454&quot;&gt;&lt;span data-url=&quot;https://blog.kakaocdn.net/dn/bpOnRS/btswbCIGDGF/hHGT67x5IrDOMiPzwLEZM1/img.png&quot; data-phocus=&quot;https://blog.kakaocdn.net/dn/bpOnRS/btswbCIGDGF/hHGT67x5IrDOMiPzwLEZM1/img.png&quot;&gt;&lt;img src=&quot;https://blog.kakaocdn.net/dn/bpOnRS/btswbCIGDGF/hHGT67x5IrDOMiPzwLEZM1/img.png&quot; srcset=&quot;https://img1.daumcdn.net/thumb/R1280x0/?scode=mtistory2&amp;fname=https%3A%2F%2Fblog.kakaocdn.net%2Fdn%2FbpOnRS%2FbtswbCIGDGF%2FhHGT67x5IrDOMiPzwLEZM1%2Fimg.png&quot; onerror=&quot;this.onerror=null; this.src='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png'; this.srcset='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png';&quot; loading=&quot;lazy&quot; width=&quot;1490&quot; height=&quot;454&quot; data-origin-width=&quot;1490&quot; data-origin-height=&quot;454&quot;/&gt;&lt;/span&gt;&lt;/figure&gt;
&lt;/p&gt;
&lt;h2 data-ke-size=&quot;size26&quot;&gt;&amp;nbsp;&lt;/h2&gt;
&lt;h2 data-ke-size=&quot;size26&quot;&gt;&lt;b&gt;2. S3, ECR 생성&lt;/b&gt;&lt;/h2&gt;
&lt;hr contenteditable=&quot;false&quot; data-ke-type=&quot;horizontalRule&quot; data-ke-style=&quot;style6&quot; /&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;codeDeploy 스크립트를 업로드할 S3 bucket을 생성합니다. 이름과 리전 설정 후 나머지는 기본값으로 두면 됩니다.&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;1636&quot; data-origin-height=&quot;728&quot;&gt;&lt;span data-url=&quot;https://blog.kakaocdn.net/dn/dekBnc/btsvMHwGG82/K69vzMHr1jzmuQriK1KYj0/img.png&quot; data-phocus=&quot;https://blog.kakaocdn.net/dn/dekBnc/btsvMHwGG82/K69vzMHr1jzmuQriK1KYj0/img.png&quot;&gt;&lt;img src=&quot;https://blog.kakaocdn.net/dn/dekBnc/btsvMHwGG82/K69vzMHr1jzmuQriK1KYj0/img.png&quot; srcset=&quot;https://img1.daumcdn.net/thumb/R1280x0/?scode=mtistory2&amp;fname=https%3A%2F%2Fblog.kakaocdn.net%2Fdn%2FdekBnc%2FbtsvMHwGG82%2FK69vzMHr1jzmuQriK1KYj0%2Fimg.png&quot; onerror=&quot;this.onerror=null; this.src='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png'; this.srcset='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png';&quot; loading=&quot;lazy&quot; width=&quot;1636&quot; height=&quot;728&quot; data-origin-width=&quot;1636&quot; data-origin-height=&quot;728&quot;/&gt;&lt;/span&gt;&lt;/figure&gt;
&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;docker image 레지스트리인 ECR Repository를 생성합니다. Repository 이름만 설정해 생성하면 됩니다.&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;1626&quot; data-origin-height=&quot;1094&quot;&gt;&lt;span data-url=&quot;https://blog.kakaocdn.net/dn/buJdzM/btsvVLSjdnw/KRfoV8I3Tj1hZVCM6IuaGk/img.png&quot; data-phocus=&quot;https://blog.kakaocdn.net/dn/buJdzM/btsvVLSjdnw/KRfoV8I3Tj1hZVCM6IuaGk/img.png&quot;&gt;&lt;img src=&quot;https://blog.kakaocdn.net/dn/buJdzM/btsvVLSjdnw/KRfoV8I3Tj1hZVCM6IuaGk/img.png&quot; srcset=&quot;https://img1.daumcdn.net/thumb/R1280x0/?scode=mtistory2&amp;fname=https%3A%2F%2Fblog.kakaocdn.net%2Fdn%2FbuJdzM%2FbtsvVLSjdnw%2FKRfoV8I3Tj1hZVCM6IuaGk%2Fimg.png&quot; onerror=&quot;this.onerror=null; this.src='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png'; this.srcset='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png';&quot; loading=&quot;lazy&quot; width=&quot;1626&quot; height=&quot;1094&quot; data-origin-width=&quot;1626&quot; data-origin-height=&quot;1094&quot;/&gt;&lt;/span&gt;&lt;/figure&gt;
&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;h2 data-ke-size=&quot;size26&quot;&gt;&lt;b&gt;3. AMI 생성&lt;/b&gt;&lt;/h2&gt;
&lt;hr contenteditable=&quot;false&quot; data-ke-type=&quot;horizontalRule&quot; data-ke-style=&quot;style6&quot; /&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;span style=&quot;background-color: #ffffff; color: #212529; text-align: start;&quot;&gt;AMI는 Amazon Machine Image로, AWS 인스턴스를 생성할 때 base가 되는 이미지입니다. AWS에서 기본으로 제공해주는 AMI에는 ubuntu, centos 등이 있으며, 필요한 운영체제, 소프트웨어가 세팅되어 있는 AMI를 직접 구성할 수도 있습니다.&amp;nbsp;&lt;/span&gt;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;h3 data-ke-size=&quot;size23&quot;&gt;&lt;b&gt;&lt;span style=&quot;background-color: #ffffff; color: #212529; text-align: start;&quot;&gt;[ 3-1. EC2 인스턴스 생성 ]&lt;/span&gt;&lt;/b&gt;&lt;/h3&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;span style=&quot;background-color: #ffffff; color: #212529; text-align: start;&quot;&gt;AutoScaling에 의해 생성되는 EC2의 AMI를 생성하기 위해 인스턴스를 생성합니다.&lt;span&gt;&amp;nbsp;&lt;/span&gt;&lt;/span&gt;&lt;span style=&quot;background-color: #ffffff; color: #212529; text-align: start;&quot;&gt;해당 인스턴스에는 Docker, Docker-compose, CodeDeploy Agent를 설치할 예정입니다.&lt;/span&gt;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p&gt;&lt;figure class=&quot;imageblock alignCenter&quot; data-ke-mobileStyle=&quot;widthOrigin&quot; data-origin-width=&quot;2380&quot; data-origin-height=&quot;246&quot;&gt;&lt;span data-url=&quot;https://blog.kakaocdn.net/dn/blHtfP/btsvvE7uoDF/Dflne4ZtM7kghsZ55CMxfk/img.png&quot; data-phocus=&quot;https://blog.kakaocdn.net/dn/blHtfP/btsvvE7uoDF/Dflne4ZtM7kghsZ55CMxfk/img.png&quot;&gt;&lt;img src=&quot;https://blog.kakaocdn.net/dn/blHtfP/btsvvE7uoDF/Dflne4ZtM7kghsZ55CMxfk/img.png&quot; srcset=&quot;https://img1.daumcdn.net/thumb/R1280x0/?scode=mtistory2&amp;fname=https%3A%2F%2Fblog.kakaocdn.net%2Fdn%2FblHtfP%2FbtsvvE7uoDF%2FDflne4ZtM7kghsZ55CMxfk%2Fimg.png&quot; onerror=&quot;this.onerror=null; this.src='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png'; this.srcset='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png';&quot; loading=&quot;lazy&quot; width=&quot;2380&quot; height=&quot;246&quot; data-origin-width=&quot;2380&quot; data-origin-height=&quot;246&quot;/&gt;&lt;/span&gt;&lt;/figure&gt;
&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;인스턴스 이름 설정 후 AMI는 ubuntu를 선택해줍니다. 나머지는 기본값으로 간단하게 EC2를 생성해줍니다. 추후 생성할 시작템플릿에서 인스턴스의 용량, 보안그룹 등을 다시 설정할 예정입니다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p&gt;&lt;figure class=&quot;imageblock alignCenter&quot; data-ke-mobileStyle=&quot;widthOrigin&quot; data-origin-width=&quot;1750&quot; data-origin-height=&quot;1176&quot;&gt;&lt;span data-url=&quot;https://blog.kakaocdn.net/dn/dEAsIk/btsvneO8pcV/rpjkdsIIvW3oUuhwqPtSi1/img.png&quot; data-phocus=&quot;https://blog.kakaocdn.net/dn/dEAsIk/btsvneO8pcV/rpjkdsIIvW3oUuhwqPtSi1/img.png&quot;&gt;&lt;img src=&quot;https://blog.kakaocdn.net/dn/dEAsIk/btsvneO8pcV/rpjkdsIIvW3oUuhwqPtSi1/img.png&quot; srcset=&quot;https://img1.daumcdn.net/thumb/R1280x0/?scode=mtistory2&amp;fname=https%3A%2F%2Fblog.kakaocdn.net%2Fdn%2FdEAsIk%2FbtsvneO8pcV%2FrpjkdsIIvW3oUuhwqPtSi1%2Fimg.png&quot; onerror=&quot;this.onerror=null; this.src='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png'; this.srcset='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png';&quot; loading=&quot;lazy&quot; width=&quot;1750&quot; height=&quot;1176&quot; data-origin-width=&quot;1750&quot; data-origin-height=&quot;1176&quot;/&gt;&lt;/span&gt;&lt;/figure&gt;
&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;h3 data-ke-size=&quot;size23&quot;&gt;&lt;b&gt;[ 2-2. CodeDeploy Agent, Docker, AWS CLI 설치 ]&lt;/b&gt;&lt;/h3&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&gt;&lt;figure class=&quot;imageblock alignCenter&quot; data-ke-mobileStyle=&quot;widthOrigin&quot; data-origin-width=&quot;1870&quot; data-origin-height=&quot;396&quot;&gt;&lt;span data-url=&quot;https://blog.kakaocdn.net/dn/PVDDH/btsvvFyywW8/taj2MgAck9E3jF3S0qOVK0/img.png&quot; data-phocus=&quot;https://blog.kakaocdn.net/dn/PVDDH/btsvvFyywW8/taj2MgAck9E3jF3S0qOVK0/img.png&quot;&gt;&lt;img src=&quot;https://blog.kakaocdn.net/dn/PVDDH/btsvvFyywW8/taj2MgAck9E3jF3S0qOVK0/img.png&quot; srcset=&quot;https://img1.daumcdn.net/thumb/R1280x0/?scode=mtistory2&amp;fname=https%3A%2F%2Fblog.kakaocdn.net%2Fdn%2FPVDDH%2FbtsvvFyywW8%2Ftaj2MgAck9E3jF3S0qOVK0%2Fimg.png&quot; onerror=&quot;this.onerror=null; this.src='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png'; this.srcset='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png';&quot; loading=&quot;lazy&quot; width=&quot;1870&quot; height=&quot;396&quot; data-origin-width=&quot;1870&quot; data-origin-height=&quot;396&quot;/&gt;&lt;/span&gt;&lt;/figure&gt;
&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;EC2에 접속이 되셨다면, 아래의 명령어를 차례대로 입력해 Docker, Docker-compose, CodeDeploy Agent, AWS CLI, redis-server를 설치합니다.&lt;/p&gt;
&lt;pre id=&quot;code_1695635274119&quot; class=&quot;shell&quot; data-ke-language=&quot;shell&quot; data-ke-type=&quot;codeblock&quot;&gt;&lt;code&gt;-- docker 설치
$ sudo apt update
$ sudo apt install apt-transport-https ca-certificates curl software-properties-common
$ curl -fsSL&amp;nbsp;https://download.docker.com/linux/ubuntu/gpg&amp;nbsp;| sudo apt-key add -
$ sudo add-apt-repository &quot;deb [arch=amd64]&amp;nbsp;https://download.docker.com/linux/ubuntu&amp;nbsp;focal stable&quot;
$ sudo apt update
$ sudo apt install docker-ce
$ sudo usermod -aG docker ubuntu

-- docker-compose 설치
$ sudo curl -L https://github.com/docker/compose/releases/latest/download/docker-compose-$(uname -s)-$(uname -m) -o /usr/local/bin/docker-compose
$ sudo chmod +x /usr/local/bin/docker-compose&lt;/code&gt;&lt;/pre&gt;
&lt;pre id=&quot;code_1695635409334&quot; class=&quot;shell&quot; data-ke-language=&quot;shell&quot; data-ke-type=&quot;codeblock&quot;&gt;&lt;code&gt;-- CodeDeploy Agent 설치
$ sudo apt update
$ sudo apt install ruby-full
$ sudo apt install wget
$ cd /home/ubuntu
$ wget https://aws-codedeploy-ap-northeast-2.s3.ap-northeast-2.amazonaws.com/latest/install
$ chmod +x ./install
$ sudo ./install auto &amp;gt; /tmp/logfile
$ sudo service codedeploy-agent status&lt;/code&gt;&lt;/pre&gt;
&lt;pre id=&quot;code_1696176859744&quot; class=&quot;shell&quot; data-ke-language=&quot;shell&quot; data-ke-type=&quot;codeblock&quot;&gt;&lt;code&gt;-- AWS CLI 설치
$ sudo apt install curl unzip
$ curl &quot;https://awscli.amazonaws.com/awscli-exe-linux-x86_64.zip&quot; -o &quot;awscliv2.zip&quot;
$ unzip awscliv2.zip
$ sudo ./aws/install&lt;/code&gt;&lt;/pre&gt;
&lt;pre id=&quot;code_1697698457703&quot; class=&quot;shell&quot; data-ke-language=&quot;shell&quot; data-ke-type=&quot;codeblock&quot;&gt;&lt;code&gt;$ sudo apt-get update
$ sudo apt-get install redis-server&lt;/code&gt;&lt;/pre&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;정상적으로 설치가 되었다면, 아래와 같은 응답이 나와야합니다.&lt;/p&gt;
&lt;p&gt;&lt;figure class=&quot;imageblock alignCenter&quot; data-ke-mobileStyle=&quot;widthOrigin&quot; data-filename=&quot;blob&quot; data-origin-width=&quot;1858&quot; data-origin-height=&quot;671&quot;&gt;&lt;span data-url=&quot;https://blog.kakaocdn.net/dn/bhSwoL/btsvqAxdhGv/PMS9pWaePiu3Y6apVB9m8K/img.png&quot; data-phocus=&quot;https://blog.kakaocdn.net/dn/bhSwoL/btsvqAxdhGv/PMS9pWaePiu3Y6apVB9m8K/img.png&quot;&gt;&lt;img src=&quot;https://blog.kakaocdn.net/dn/bhSwoL/btsvqAxdhGv/PMS9pWaePiu3Y6apVB9m8K/img.png&quot; srcset=&quot;https://img1.daumcdn.net/thumb/R1280x0/?scode=mtistory2&amp;fname=https%3A%2F%2Fblog.kakaocdn.net%2Fdn%2FbhSwoL%2FbtsvqAxdhGv%2FPMS9pWaePiu3Y6apVB9m8K%2Fimg.png&quot; onerror=&quot;this.onerror=null; this.src='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png'; this.srcset='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png';&quot; loading=&quot;lazy&quot; width=&quot;1858&quot; height=&quot;671&quot; data-filename=&quot;blob&quot; data-origin-width=&quot;1858&quot; data-origin-height=&quot;671&quot;/&gt;&lt;/span&gt;&lt;/figure&gt;
&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;h3 data-ke-size=&quot;size23&quot;&gt;&lt;b&gt;[ 3-3. AMI 생성 ]&lt;/b&gt;&lt;/h3&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;해당 인스턴스에서 작업 - 이미지 및 템플릿 - 이미지 생성 버튼을 클릭합니다.&amp;nbsp;&lt;/p&gt;
&lt;p&gt;&lt;figure class=&quot;imageblock alignCenter&quot; data-ke-mobileStyle=&quot;widthOrigin&quot; data-origin-width=&quot;2362&quot; data-origin-height=&quot;822&quot;&gt;&lt;span data-url=&quot;https://blog.kakaocdn.net/dn/Lk5To/btsvMES08IC/4o3eVAeuxkXTufYaJ8POk0/img.png&quot; data-phocus=&quot;https://blog.kakaocdn.net/dn/Lk5To/btsvMES08IC/4o3eVAeuxkXTufYaJ8POk0/img.png&quot;&gt;&lt;img src=&quot;https://blog.kakaocdn.net/dn/Lk5To/btsvMES08IC/4o3eVAeuxkXTufYaJ8POk0/img.png&quot; srcset=&quot;https://img1.daumcdn.net/thumb/R1280x0/?scode=mtistory2&amp;fname=https%3A%2F%2Fblog.kakaocdn.net%2Fdn%2FLk5To%2FbtsvMES08IC%2F4o3eVAeuxkXTufYaJ8POk0%2Fimg.png&quot; onerror=&quot;this.onerror=null; this.src='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png'; this.srcset='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png';&quot; loading=&quot;lazy&quot; width=&quot;2362&quot; height=&quot;822&quot; data-origin-width=&quot;2362&quot; data-origin-height=&quot;822&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;2474&quot; data-origin-height=&quot;1278&quot;&gt;&lt;span data-url=&quot;https://blog.kakaocdn.net/dn/OyPT5/btsvluLmpDP/RBmBOoASxbP6jFVx3aXNM0/img.png&quot; data-phocus=&quot;https://blog.kakaocdn.net/dn/OyPT5/btsvluLmpDP/RBmBOoASxbP6jFVx3aXNM0/img.png&quot;&gt;&lt;img src=&quot;https://blog.kakaocdn.net/dn/OyPT5/btsvluLmpDP/RBmBOoASxbP6jFVx3aXNM0/img.png&quot; srcset=&quot;https://img1.daumcdn.net/thumb/R1280x0/?scode=mtistory2&amp;fname=https%3A%2F%2Fblog.kakaocdn.net%2Fdn%2FOyPT5%2FbtsvluLmpDP%2FRBmBOoASxbP6jFVx3aXNM0%2Fimg.png&quot; onerror=&quot;this.onerror=null; this.src='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png'; this.srcset='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png';&quot; loading=&quot;lazy&quot; width=&quot;2474&quot; height=&quot;1278&quot; data-origin-width=&quot;2474&quot; data-origin-height=&quot;1278&quot;/&gt;&lt;/span&gt;&lt;/figure&gt;
&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;h2 data-ke-size=&quot;size26&quot;&gt;&lt;b&gt;4. 시작템플릿 생성&lt;/b&gt;&lt;/h2&gt;
&lt;hr contenteditable=&quot;false&quot; data-ke-type=&quot;horizontalRule&quot; data-ke-style=&quot;style6&quot; /&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;시작템플릿은 인스턴스 시작에 필요한 구성 정보를 모아둔 템플릿입니다. 시작템플릿을 만들어두면, 시작 템플릿의 내용대로 바로 인스턴스를 생성할 수 있습니다. 여기서는 &lt;span style=&quot;color: #333333; text-align: start;&quot;&gt;Auto Scaling Group에서 사용할 시작템플릿을 생성해보겠습니다.&lt;/span&gt;&lt;/p&gt;
&lt;p style=&quot;background-color: #ffffff; color: #212529; text-align: start;&quot; data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p&gt;&lt;figure class=&quot;imageblock alignCenter&quot; data-ke-mobileStyle=&quot;widthOrigin&quot; data-origin-width=&quot;1598&quot; data-origin-height=&quot;868&quot;&gt;&lt;span data-url=&quot;https://blog.kakaocdn.net/dn/zoUCN/btsvMJtiZfL/eoIqhHBC9kuxaIaSsFCoB0/img.png&quot; data-phocus=&quot;https://blog.kakaocdn.net/dn/zoUCN/btsvMJtiZfL/eoIqhHBC9kuxaIaSsFCoB0/img.png&quot;&gt;&lt;img src=&quot;https://blog.kakaocdn.net/dn/zoUCN/btsvMJtiZfL/eoIqhHBC9kuxaIaSsFCoB0/img.png&quot; srcset=&quot;https://img1.daumcdn.net/thumb/R1280x0/?scode=mtistory2&amp;fname=https%3A%2F%2Fblog.kakaocdn.net%2Fdn%2FzoUCN%2FbtsvMJtiZfL%2FeoIqhHBC9kuxaIaSsFCoB0%2Fimg.png&quot; onerror=&quot;this.onerror=null; this.src='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png'; this.srcset='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png';&quot; loading=&quot;lazy&quot; width=&quot;1598&quot; height=&quot;868&quot; data-origin-width=&quot;1598&quot; data-origin-height=&quot;868&quot;/&gt;&lt;/span&gt;&lt;/figure&gt;
&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;위에서 생성한 AMI를 선택합니다. 이를 통해 docker, docker-compose, codeDeploy agent가 설치된 환경의 인스턴스를 바로 생성할 수 있습니다.&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;1594&quot; data-origin-height=&quot;1068&quot;&gt;&lt;span data-url=&quot;https://blog.kakaocdn.net/dn/G6Vzj/btsvlxVGGd1/uh1LSeiBai2NDMGTus74m0/img.png&quot; data-phocus=&quot;https://blog.kakaocdn.net/dn/G6Vzj/btsvlxVGGd1/uh1LSeiBai2NDMGTus74m0/img.png&quot;&gt;&lt;img src=&quot;https://blog.kakaocdn.net/dn/G6Vzj/btsvlxVGGd1/uh1LSeiBai2NDMGTus74m0/img.png&quot; srcset=&quot;https://img1.daumcdn.net/thumb/R1280x0/?scode=mtistory2&amp;fname=https%3A%2F%2Fblog.kakaocdn.net%2Fdn%2FG6Vzj%2FbtsvlxVGGd1%2Fuh1LSeiBai2NDMGTus74m0%2Fimg.png&quot; onerror=&quot;this.onerror=null; this.src='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png'; this.srcset='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png';&quot; loading=&quot;lazy&quot; width=&quot;1594&quot; height=&quot;1068&quot; data-origin-width=&quot;1594&quot; data-origin-height=&quot;1068&quot;/&gt;&lt;/span&gt;&lt;/figure&gt;
&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;인스턴스 유형은 프리티어로 제공해주는 t2.micro를 사용하겠습니다. (t2.micro는 가용영역 2a, 2c에서만 사용할 수 있습니다. 다른 가용영역을 사용해야하는 경우 t3.micro를 선택하시기 바랍니다.)&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;1578&quot; data-origin-height=&quot;490&quot;&gt;&lt;span data-url=&quot;https://blog.kakaocdn.net/dn/vBINj/btsvNqG13EW/6hTkzVIcQwb816fCg1P9B1/img.png&quot; data-phocus=&quot;https://blog.kakaocdn.net/dn/vBINj/btsvNqG13EW/6hTkzVIcQwb816fCg1P9B1/img.png&quot;&gt;&lt;img src=&quot;https://blog.kakaocdn.net/dn/vBINj/btsvNqG13EW/6hTkzVIcQwb816fCg1P9B1/img.png&quot; srcset=&quot;https://img1.daumcdn.net/thumb/R1280x0/?scode=mtistory2&amp;fname=https%3A%2F%2Fblog.kakaocdn.net%2Fdn%2FvBINj%2FbtsvNqG13EW%2F6hTkzVIcQwb816fCg1P9B1%2Fimg.png&quot; onerror=&quot;this.onerror=null; this.src='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png'; this.srcset='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png';&quot; loading=&quot;lazy&quot; width=&quot;1578&quot; height=&quot;490&quot; data-origin-width=&quot;1578&quot; data-origin-height=&quot;490&quot;/&gt;&lt;/span&gt;&lt;/figure&gt;
&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;키페어를 생성합니다. 이 키페어는 앞으로 Auto Scaling Group으로 설정되어 생성될 ec2 그룹들에 접속하기 위해 사용됩니다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p&gt;&lt;figure class=&quot;imageblock alignCenter&quot; data-ke-mobileStyle=&quot;widthOrigin&quot; data-origin-width=&quot;1582&quot; data-origin-height=&quot;478&quot;&gt;&lt;span data-url=&quot;https://blog.kakaocdn.net/dn/wn7cs/btsvJMDTYBG/VjVjgHgjoSGzLFF3jLRSZK/img.png&quot; data-phocus=&quot;https://blog.kakaocdn.net/dn/wn7cs/btsvJMDTYBG/VjVjgHgjoSGzLFF3jLRSZK/img.png&quot;&gt;&lt;img src=&quot;https://blog.kakaocdn.net/dn/wn7cs/btsvJMDTYBG/VjVjgHgjoSGzLFF3jLRSZK/img.png&quot; srcset=&quot;https://img1.daumcdn.net/thumb/R1280x0/?scode=mtistory2&amp;fname=https%3A%2F%2Fblog.kakaocdn.net%2Fdn%2Fwn7cs%2FbtsvJMDTYBG%2FVjVjgHgjoSGzLFF3jLRSZK%2Fimg.png&quot; onerror=&quot;this.onerror=null; this.src='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png'; this.srcset='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png';&quot; loading=&quot;lazy&quot; width=&quot;1582&quot; height=&quot;478&quot; data-origin-width=&quot;1582&quot; data-origin-height=&quot;478&quot;/&gt;&lt;/span&gt;&lt;/figure&gt;
&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;보안 그룹은 기존의 보안그룹을 사용해도 되고, 생성하셔도 됩니다. 저는 Spring Boot를 배포할 것이기에 인바운드로 8080, 22 포트를 허용해주었습니다.&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;1584&quot; data-origin-height=&quot;1068&quot;&gt;&lt;span data-url=&quot;https://blog.kakaocdn.net/dn/JBhw6/btsvltMuuUI/XpWGCulLKDIdluiR61TDe0/img.png&quot; data-phocus=&quot;https://blog.kakaocdn.net/dn/JBhw6/btsvltMuuUI/XpWGCulLKDIdluiR61TDe0/img.png&quot;&gt;&lt;img src=&quot;https://blog.kakaocdn.net/dn/JBhw6/btsvltMuuUI/XpWGCulLKDIdluiR61TDe0/img.png&quot; srcset=&quot;https://img1.daumcdn.net/thumb/R1280x0/?scode=mtistory2&amp;fname=https%3A%2F%2Fblog.kakaocdn.net%2Fdn%2FJBhw6%2FbtsvltMuuUI%2FXpWGCulLKDIdluiR61TDe0%2Fimg.png&quot; onerror=&quot;this.onerror=null; this.src='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png'; this.srcset='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png';&quot; loading=&quot;lazy&quot; width=&quot;1584&quot; height=&quot;1068&quot; data-origin-width=&quot;1584&quot; data-origin-height=&quot;1068&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;1586&quot; data-origin-height=&quot;1046&quot;&gt;&lt;span data-url=&quot;https://blog.kakaocdn.net/dn/beCxAQ/btsvncXMdg8/E1VbggQkkk39wgup3zxr91/img.png&quot; data-phocus=&quot;https://blog.kakaocdn.net/dn/beCxAQ/btsvncXMdg8/E1VbggQkkk39wgup3zxr91/img.png&quot;&gt;&lt;img src=&quot;https://blog.kakaocdn.net/dn/beCxAQ/btsvncXMdg8/E1VbggQkkk39wgup3zxr91/img.png&quot; srcset=&quot;https://img1.daumcdn.net/thumb/R1280x0/?scode=mtistory2&amp;fname=https%3A%2F%2Fblog.kakaocdn.net%2Fdn%2FbeCxAQ%2FbtsvncXMdg8%2FE1VbggQkkk39wgup3zxr91%2Fimg.png&quot; onerror=&quot;this.onerror=null; this.src='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png'; this.srcset='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png';&quot; loading=&quot;lazy&quot; width=&quot;1586&quot; height=&quot;1046&quot; data-origin-width=&quot;1586&quot; data-origin-height=&quot;1046&quot;/&gt;&lt;/span&gt;&lt;/figure&gt;
&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;Auto Scaling으로 인해 생성되는 EC2에 public IP를 부여하고 싶은 경우, 네트워크 인터페이스를 추가해 아래와 같이 설정해주면 됩니다.&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;1588&quot; data-origin-height=&quot;1274&quot;&gt;&lt;span data-url=&quot;https://blog.kakaocdn.net/dn/zR4HD/btswgWteCBd/Az5TFtJZiVYPkslMjeWuf1/img.png&quot; data-phocus=&quot;https://blog.kakaocdn.net/dn/zR4HD/btswgWteCBd/Az5TFtJZiVYPkslMjeWuf1/img.png&quot;&gt;&lt;img src=&quot;https://blog.kakaocdn.net/dn/zR4HD/btswgWteCBd/Az5TFtJZiVYPkslMjeWuf1/img.png&quot; srcset=&quot;https://img1.daumcdn.net/thumb/R1280x0/?scode=mtistory2&amp;fname=https%3A%2F%2Fblog.kakaocdn.net%2Fdn%2FzR4HD%2FbtswgWteCBd%2FAz5TFtJZiVYPkslMjeWuf1%2Fimg.png&quot; onerror=&quot;this.onerror=null; this.src='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png'; this.srcset='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png';&quot; loading=&quot;lazy&quot; width=&quot;1588&quot; height=&quot;1274&quot; data-origin-width=&quot;1588&quot; data-origin-height=&quot;1274&quot;/&gt;&lt;/span&gt;&lt;/figure&gt;
&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;IAM 인스턴스 프로파일로 위에서 생성한 IAM 역할을 연결해줍니다. 해당 역할은 EC2에 S3, ECR FullAccess 권한을 부여합니다. (실제 운영에서는 FullAccess 권한을 주면 안됩니다! 정책 생성을 통해 꼭 필요한 권한만 부여해야합니다.) 이 역할을 통해 EC2에 설치되어 있는 CodeDeploy Agent가 S3 버켓의 스크립트를 읽어오고, ECR에 있는 docker image를 pull 받을 수 있게 됩니다.&lt;/p&gt;
&lt;p style=&quot;color: #333333; text-align: left;&quot; data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p&gt;&lt;figure class=&quot;imageblock alignCenter&quot; data-ke-mobileStyle=&quot;widthOrigin&quot; data-origin-width=&quot;1588&quot; data-origin-height=&quot;496&quot;&gt;&lt;span data-url=&quot;https://blog.kakaocdn.net/dn/XgIHZ/btsvvDHDclz/daVFSFomiGiRNAPjAmeomK/img.png&quot; data-phocus=&quot;https://blog.kakaocdn.net/dn/XgIHZ/btsvvDHDclz/daVFSFomiGiRNAPjAmeomK/img.png&quot;&gt;&lt;img src=&quot;https://blog.kakaocdn.net/dn/XgIHZ/btsvvDHDclz/daVFSFomiGiRNAPjAmeomK/img.png&quot; srcset=&quot;https://img1.daumcdn.net/thumb/R1280x0/?scode=mtistory2&amp;fname=https%3A%2F%2Fblog.kakaocdn.net%2Fdn%2FXgIHZ%2FbtsvvDHDclz%2FdaVFSFomiGiRNAPjAmeomK%2Fimg.png&quot; onerror=&quot;this.onerror=null; this.src='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png'; this.srcset='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png';&quot; loading=&quot;lazy&quot; width=&quot;1588&quot; height=&quot;496&quot; data-origin-width=&quot;1588&quot; data-origin-height=&quot;496&quot;/&gt;&lt;/span&gt;&lt;/figure&gt;
&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;h2 data-ke-size=&quot;size26&quot;&gt;&lt;b&gt;5. Target Group(대상 그룹) 생성&lt;/b&gt;&lt;/h2&gt;
&lt;hr contenteditable=&quot;false&quot; data-ke-type=&quot;horizontalRule&quot; data-ke-style=&quot;style6&quot; /&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;span style=&quot;background-color: #ffffff; color: #16191f; text-align: start;&quot;&gt;대상 그룹은 지정한 프로토콜과 포트 번호를 사용하여 등록된 대상(EC2 인스턴스)으로 요청을 라우팅합니다. 대상 그룹들은 로드밸런서에 의해 로드밸런싱 됩니다.&amp;nbsp;&lt;/span&gt;&lt;span style=&quot;background-color: #ffffff; color: #16191f; text-align: start;&quot;&gt;아래에서 생성한 대상 그룹은 추후 Auto Scaling Group을 연결할 예정입니다. (즉, 로드밸런서는 Auto Scaling Group에서 생성된 EC2들에 트래픽을 분산하게 됩니다.)&lt;/span&gt;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p&gt;&lt;figure class=&quot;imageblock alignCenter&quot; data-ke-mobileStyle=&quot;widthOrigin&quot; data-origin-width=&quot;2264&quot; data-origin-height=&quot;318&quot;&gt;&lt;span data-url=&quot;https://blog.kakaocdn.net/dn/mR7AX/btsvM1ABf5h/0lgkrQklkEEbYGNBDRsVnk/img.png&quot; data-phocus=&quot;https://blog.kakaocdn.net/dn/mR7AX/btsvM1ABf5h/0lgkrQklkEEbYGNBDRsVnk/img.png&quot;&gt;&lt;img src=&quot;https://blog.kakaocdn.net/dn/mR7AX/btsvM1ABf5h/0lgkrQklkEEbYGNBDRsVnk/img.png&quot; srcset=&quot;https://img1.daumcdn.net/thumb/R1280x0/?scode=mtistory2&amp;fname=https%3A%2F%2Fblog.kakaocdn.net%2Fdn%2FmR7AX%2FbtsvM1ABf5h%2F0lgkrQklkEEbYGNBDRsVnk%2Fimg.png&quot; onerror=&quot;this.onerror=null; this.src='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png'; this.srcset='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png';&quot; loading=&quot;lazy&quot; width=&quot;2264&quot; height=&quot;318&quot; data-origin-width=&quot;2264&quot; data-origin-height=&quot;318&quot;/&gt;&lt;/span&gt;&lt;/figure&gt;
&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;Spring Boot의 기본 포트인 8080을 매핑해줍니다. 추가로, 대상 그룹에 포함할 인스턴스가 있는 VPC를 선택해주어야 합니다.&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;1636&quot; data-origin-height=&quot;500&quot;&gt;&lt;span data-url=&quot;https://blog.kakaocdn.net/dn/cl2xvt/btsvmasoFzR/CwY6Lo90mHxKWvASCVCzn1/img.png&quot; data-phocus=&quot;https://blog.kakaocdn.net/dn/cl2xvt/btsvmasoFzR/CwY6Lo90mHxKWvASCVCzn1/img.png&quot;&gt;&lt;img src=&quot;https://blog.kakaocdn.net/dn/cl2xvt/btsvmasoFzR/CwY6Lo90mHxKWvASCVCzn1/img.png&quot; srcset=&quot;https://img1.daumcdn.net/thumb/R1280x0/?scode=mtistory2&amp;fname=https%3A%2F%2Fblog.kakaocdn.net%2Fdn%2Fcl2xvt%2FbtsvmasoFzR%2FCwY6Lo90mHxKWvASCVCzn1%2Fimg.png&quot; onerror=&quot;this.onerror=null; this.src='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png'; this.srcset='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png';&quot; loading=&quot;lazy&quot; width=&quot;1636&quot; height=&quot;500&quot; data-origin-width=&quot;1636&quot; data-origin-height=&quot;500&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;1646&quot; data-origin-height=&quot;800&quot;&gt;&lt;span data-url=&quot;https://blog.kakaocdn.net/dn/be67nN/btsvOBVRRTF/ER12Sy9r7MZPC7RFeJyojK/img.png&quot; data-phocus=&quot;https://blog.kakaocdn.net/dn/be67nN/btsvOBVRRTF/ER12Sy9r7MZPC7RFeJyojK/img.png&quot;&gt;&lt;img src=&quot;https://blog.kakaocdn.net/dn/be67nN/btsvOBVRRTF/ER12Sy9r7MZPC7RFeJyojK/img.png&quot; srcset=&quot;https://img1.daumcdn.net/thumb/R1280x0/?scode=mtistory2&amp;fname=https%3A%2F%2Fblog.kakaocdn.net%2Fdn%2Fbe67nN%2FbtsvOBVRRTF%2FER12Sy9r7MZPC7RFeJyojK%2Fimg.png&quot; onerror=&quot;this.onerror=null; this.src='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png'; this.srcset='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png';&quot; loading=&quot;lazy&quot; width=&quot;1646&quot; height=&quot;800&quot; data-origin-width=&quot;1646&quot; data-origin-height=&quot;800&quot;/&gt;&lt;/span&gt;&lt;/figure&gt;
&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;대상 그룹은 특정 엔드포인트에 대해 health check를 실행합니다. 상태 코드 200을 반환하는 health check api 경로를 지정해줍니다. 개인적으로는, health check를 위한 endpoint를 만들어 사용하는 것을 권장합니다. health check는 몇 초에 한번씩 이뤄지기 때문에 기존 api 경로를 사용하는 경우 성능에 지장이 가거나 로그 및 메트릭이 오염될 수 있습니다.&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;1642&quot; data-origin-height=&quot;584&quot;&gt;&lt;span data-url=&quot;https://blog.kakaocdn.net/dn/cLjgbT/btsvMFkaxEL/n1ZMLWEZSh8KIpxMY0ekv0/img.png&quot; data-phocus=&quot;https://blog.kakaocdn.net/dn/cLjgbT/btsvMFkaxEL/n1ZMLWEZSh8KIpxMY0ekv0/img.png&quot;&gt;&lt;img src=&quot;https://blog.kakaocdn.net/dn/cLjgbT/btsvMFkaxEL/n1ZMLWEZSh8KIpxMY0ekv0/img.png&quot; srcset=&quot;https://img1.daumcdn.net/thumb/R1280x0/?scode=mtistory2&amp;fname=https%3A%2F%2Fblog.kakaocdn.net%2Fdn%2FcLjgbT%2FbtsvMFkaxEL%2Fn1ZMLWEZSh8KIpxMY0ekv0%2Fimg.png&quot; onerror=&quot;this.onerror=null; this.src='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png'; this.srcset='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png';&quot; loading=&quot;lazy&quot; width=&quot;1642&quot; height=&quot;584&quot; data-origin-width=&quot;1642&quot; data-origin-height=&quot;584&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;1632&quot; data-origin-height=&quot;1380&quot;&gt;&lt;span data-url=&quot;https://blog.kakaocdn.net/dn/pMW4o/btsvDknq2xm/919skGwnDEQeKrNcE2Zvd0/img.png&quot; data-phocus=&quot;https://blog.kakaocdn.net/dn/pMW4o/btsvDknq2xm/919skGwnDEQeKrNcE2Zvd0/img.png&quot;&gt;&lt;img src=&quot;https://blog.kakaocdn.net/dn/pMW4o/btsvDknq2xm/919skGwnDEQeKrNcE2Zvd0/img.png&quot; srcset=&quot;https://img1.daumcdn.net/thumb/R1280x0/?scode=mtistory2&amp;fname=https%3A%2F%2Fblog.kakaocdn.net%2Fdn%2FpMW4o%2FbtsvDknq2xm%2F919skGwnDEQeKrNcE2Zvd0%2Fimg.png&quot; onerror=&quot;this.onerror=null; this.src='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png'; this.srcset='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png';&quot; loading=&quot;lazy&quot; width=&quot;1632&quot; height=&quot;1380&quot; data-origin-width=&quot;1632&quot; data-origin-height=&quot;1380&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;- 비정상 임계값: 연속으로 몇 번 비정상 응답을 해야만 정상 상태로 볼 것인지 지정하는 항목&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;값을 작게 설정 해주어야 CodeDeploy 배포 과정인 AllowTraffic 구간에서 빠르게 진행될 수 있습니다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;(현재 설정에서는 배포 시 한 인스턴스당 90초 (30초 * 3회) + a 가 소요됩니다. 인스턴스의 개수가 많을 경우 몇십분의 시간이 소요될 수 있으므로, 헬스체크 간격과 정상 임계값을 적절하게 설정하는 것이 좋습니다)&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;그리고 다음 대상 등록에서는 인스턴스를 지금 등록하지 않고 Auto Scaling Group을 대상그룹으로 연결할 예정이니 넘어가겠습니다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;h2 data-ke-size=&quot;size26&quot;&gt;&lt;b&gt;6. 로드밸런서 생성&lt;/b&gt;&lt;/h2&gt;
&lt;hr contenteditable=&quot;false&quot; data-ke-type=&quot;horizontalRule&quot; data-ke-style=&quot;style6&quot; /&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;인스턴스들에게 트래픽을 분산하기 위해 로드밸런서를 생성합니다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p&gt;&lt;figure class=&quot;imageblock alignCenter&quot; data-ke-mobileStyle=&quot;widthOrigin&quot; data-origin-width=&quot;2342&quot; data-origin-height=&quot;348&quot;&gt;&lt;span data-url=&quot;https://blog.kakaocdn.net/dn/dcmDoe/btsvMJfN4eA/9DIUyyc0OHKMsEKw1GreP1/img.png&quot; data-phocus=&quot;https://blog.kakaocdn.net/dn/dcmDoe/btsvMJfN4eA/9DIUyyc0OHKMsEKw1GreP1/img.png&quot;&gt;&lt;img src=&quot;https://blog.kakaocdn.net/dn/dcmDoe/btsvMJfN4eA/9DIUyyc0OHKMsEKw1GreP1/img.png&quot; srcset=&quot;https://img1.daumcdn.net/thumb/R1280x0/?scode=mtistory2&amp;fname=https%3A%2F%2Fblog.kakaocdn.net%2Fdn%2FdcmDoe%2FbtsvMJfN4eA%2F9DIUyyc0OHKMsEKw1GreP1%2Fimg.png&quot; onerror=&quot;this.onerror=null; this.src='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png'; this.srcset='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png';&quot; loading=&quot;lazy&quot; width=&quot;2342&quot; height=&quot;348&quot; data-origin-width=&quot;2342&quot; data-origin-height=&quot;348&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;2038&quot; data-origin-height=&quot;1070&quot;&gt;&lt;span data-url=&quot;https://blog.kakaocdn.net/dn/EkHKd/btsvlBqhe1a/pEPgtDFonPNSw6mMHyX601/img.png&quot; data-phocus=&quot;https://blog.kakaocdn.net/dn/EkHKd/btsvlBqhe1a/pEPgtDFonPNSw6mMHyX601/img.png&quot;&gt;&lt;img src=&quot;https://blog.kakaocdn.net/dn/EkHKd/btsvlBqhe1a/pEPgtDFonPNSw6mMHyX601/img.png&quot; srcset=&quot;https://img1.daumcdn.net/thumb/R1280x0/?scode=mtistory2&amp;fname=https%3A%2F%2Fblog.kakaocdn.net%2Fdn%2FEkHKd%2FbtsvlBqhe1a%2FpEPgtDFonPNSw6mMHyX601%2Fimg.png&quot; onerror=&quot;this.onerror=null; this.src='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png'; this.srcset='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png';&quot; loading=&quot;lazy&quot; width=&quot;2038&quot; height=&quot;1070&quot; data-origin-width=&quot;2038&quot; data-origin-height=&quot;1070&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;1688&quot; data-origin-height=&quot;1002&quot;&gt;&lt;span data-url=&quot;https://blog.kakaocdn.net/dn/sFFFy/btsvHhKyvJw/PyN3AszRwnZvgKz45sxiLK/img.png&quot; data-phocus=&quot;https://blog.kakaocdn.net/dn/sFFFy/btsvHhKyvJw/PyN3AszRwnZvgKz45sxiLK/img.png&quot;&gt;&lt;img src=&quot;https://blog.kakaocdn.net/dn/sFFFy/btsvHhKyvJw/PyN3AszRwnZvgKz45sxiLK/img.png&quot; srcset=&quot;https://img1.daumcdn.net/thumb/R1280x0/?scode=mtistory2&amp;fname=https%3A%2F%2Fblog.kakaocdn.net%2Fdn%2FsFFFy%2FbtsvHhKyvJw%2FPyN3AszRwnZvgKz45sxiLK%2Fimg.png&quot; onerror=&quot;this.onerror=null; this.src='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png'; this.srcset='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png';&quot; loading=&quot;lazy&quot; width=&quot;1688&quot; height=&quot;1002&quot; data-origin-width=&quot;1688&quot; data-origin-height=&quot;1002&quot;/&gt;&lt;/span&gt;&lt;/figure&gt;
&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;VPC는 위의 대상그룹과 동일한 VPC를 선택해주어야 합니다. 그리고, ALB가 트래픽을 분산할 가용영역을 선택해줍니다. 각 가용영역에서는 외부와 연결된 public 서브넷을 선택해야 합니다. 저는 2a, 2c 가용영역의 public 서브넷을 선택했습니다.&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;1758&quot; data-origin-height=&quot;856&quot;&gt;&lt;span data-url=&quot;https://blog.kakaocdn.net/dn/bnXhQ2/btsvluLrTic/5xkPIALq7fIygCkaLYxcK1/img.png&quot; data-phocus=&quot;https://blog.kakaocdn.net/dn/bnXhQ2/btsvluLrTic/5xkPIALq7fIygCkaLYxcK1/img.png&quot;&gt;&lt;img src=&quot;https://blog.kakaocdn.net/dn/bnXhQ2/btsvluLrTic/5xkPIALq7fIygCkaLYxcK1/img.png&quot; srcset=&quot;https://img1.daumcdn.net/thumb/R1280x0/?scode=mtistory2&amp;fname=https%3A%2F%2Fblog.kakaocdn.net%2Fdn%2FbnXhQ2%2FbtsvluLrTic%2F5xkPIALq7fIygCkaLYxcK1%2Fimg.png&quot; onerror=&quot;this.onerror=null; this.src='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png'; this.srcset='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png';&quot; loading=&quot;lazy&quot; width=&quot;1758&quot; height=&quot;856&quot; data-origin-width=&quot;1758&quot; data-origin-height=&quot;856&quot;/&gt;&lt;/span&gt;&lt;/figure&gt;
&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;보안 그룹은 80 포트에 대해 인바운드를 허용해주고, 아웃바운드는 대상그룹의 포트인 8080포트에 대해 허용해줬습니다.&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;2046&quot; data-origin-height=&quot;582&quot;&gt;&lt;span data-url=&quot;https://blog.kakaocdn.net/dn/bKMIFg/btswpY5gEh2/zVZC4aBF5IPa1GlPhdzyN1/img.png&quot; data-phocus=&quot;https://blog.kakaocdn.net/dn/bKMIFg/btswpY5gEh2/zVZC4aBF5IPa1GlPhdzyN1/img.png&quot;&gt;&lt;img src=&quot;https://blog.kakaocdn.net/dn/bKMIFg/btswpY5gEh2/zVZC4aBF5IPa1GlPhdzyN1/img.png&quot; srcset=&quot;https://img1.daumcdn.net/thumb/R1280x0/?scode=mtistory2&amp;fname=https%3A%2F%2Fblog.kakaocdn.net%2Fdn%2FbKMIFg%2FbtswpY5gEh2%2FzVZC4aBF5IPa1GlPhdzyN1%2Fimg.png&quot; onerror=&quot;this.onerror=null; this.src='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png'; this.srcset='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png';&quot; loading=&quot;lazy&quot; width=&quot;2046&quot; height=&quot;582&quot; data-origin-width=&quot;2046&quot; data-origin-height=&quot;582&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;2034&quot; data-origin-height=&quot;586&quot;&gt;&lt;span data-url=&quot;https://blog.kakaocdn.net/dn/bUiH9h/btswwRrkToR/SMgOJjyWYXS6ZDfy3lQ8i0/img.png&quot; data-phocus=&quot;https://blog.kakaocdn.net/dn/bUiH9h/btswwRrkToR/SMgOJjyWYXS6ZDfy3lQ8i0/img.png&quot;&gt;&lt;img src=&quot;https://blog.kakaocdn.net/dn/bUiH9h/btswwRrkToR/SMgOJjyWYXS6ZDfy3lQ8i0/img.png&quot; srcset=&quot;https://img1.daumcdn.net/thumb/R1280x0/?scode=mtistory2&amp;fname=https%3A%2F%2Fblog.kakaocdn.net%2Fdn%2FbUiH9h%2FbtswwRrkToR%2FSMgOJjyWYXS6ZDfy3lQ8i0%2Fimg.png&quot; onerror=&quot;this.onerror=null; this.src='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png'; this.srcset='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png';&quot; loading=&quot;lazy&quot; width=&quot;2034&quot; height=&quot;586&quot; data-origin-width=&quot;2034&quot; data-origin-height=&quot;586&quot;/&gt;&lt;/span&gt;&lt;/figure&gt;
&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;리스너는 위에서 생성한 대상그룹에 80포트를 연결해줍니다. 즉, 로드밸런서의 80포트로 들어오는 요청은 해당 대상그룹으로 전달됩니다.&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;2188&quot; data-origin-height=&quot;724&quot;&gt;&lt;span data-url=&quot;https://blog.kakaocdn.net/dn/bHiLwA/btsvJMYcX7s/HkvQEBawEnraWc6G0oLcsk/img.png&quot; data-phocus=&quot;https://blog.kakaocdn.net/dn/bHiLwA/btsvJMYcX7s/HkvQEBawEnraWc6G0oLcsk/img.png&quot;&gt;&lt;img src=&quot;https://blog.kakaocdn.net/dn/bHiLwA/btsvJMYcX7s/HkvQEBawEnraWc6G0oLcsk/img.png&quot; srcset=&quot;https://img1.daumcdn.net/thumb/R1280x0/?scode=mtistory2&amp;fname=https%3A%2F%2Fblog.kakaocdn.net%2Fdn%2FbHiLwA%2FbtsvJMYcX7s%2FHkvQEBawEnraWc6G0oLcsk%2Fimg.png&quot; onerror=&quot;this.onerror=null; this.src='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png'; this.srcset='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png';&quot; loading=&quot;lazy&quot; width=&quot;2188&quot; height=&quot;724&quot; data-origin-width=&quot;2188&quot; data-origin-height=&quot;724&quot;/&gt;&lt;/span&gt;&lt;/figure&gt;
&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;h2 data-ke-size=&quot;size26&quot;&gt;&lt;b&gt;7. Auto Scaling Group 생성&lt;/b&gt;&lt;/h2&gt;
&lt;hr contenteditable=&quot;false&quot; data-ke-type=&quot;horizontalRule&quot; data-ke-style=&quot;style6&quot; /&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;span style=&quot;background-color: #ffffff; color: #212529; text-align: start;&quot;&gt;Auto Scaling Group은 트래픽 혹은 하드웨어 사용량에 따라 인스턴스의 개수를 줄이거나 늘릴 수 있게 해주는 서비스입니다.&lt;/span&gt;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;span style=&quot;background-color: #ffffff; color: #212529; text-align: start;&quot;&gt;시작 템플릿은 위에서 생성한 시작템플릿을 선택해줍니다. AutoScaling 시 해당 시작템플릿 내용으로 EC2가 생성됩니다.&lt;/span&gt;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p&gt;&lt;figure class=&quot;imageblock alignCenter&quot; data-ke-mobileStyle=&quot;widthOrigin&quot; data-origin-width=&quot;1626&quot; data-origin-height=&quot;740&quot;&gt;&lt;span data-url=&quot;https://blog.kakaocdn.net/dn/xz4WM/btsvOyx453k/oi8P7PtJRIFDKS6ARN7C1K/img.png&quot; data-phocus=&quot;https://blog.kakaocdn.net/dn/xz4WM/btsvOyx453k/oi8P7PtJRIFDKS6ARN7C1K/img.png&quot;&gt;&lt;img src=&quot;https://blog.kakaocdn.net/dn/xz4WM/btsvOyx453k/oi8P7PtJRIFDKS6ARN7C1K/img.png&quot; srcset=&quot;https://img1.daumcdn.net/thumb/R1280x0/?scode=mtistory2&amp;fname=https%3A%2F%2Fblog.kakaocdn.net%2Fdn%2Fxz4WM%2FbtsvOyx453k%2Foi8P7PtJRIFDKS6ARN7C1K%2Fimg.png&quot; onerror=&quot;this.onerror=null; this.src='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png'; this.srcset='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png';&quot; loading=&quot;lazy&quot; width=&quot;1626&quot; height=&quot;740&quot; data-origin-width=&quot;1626&quot; data-origin-height=&quot;740&quot;/&gt;&lt;/span&gt;&lt;/figure&gt;
&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;Auto Scaling에 의해 생성될 EC2를 배치할 VPC와 가용영역을 선택합니다. private 서브넷도 선택가능합니다. (private 서브넷에 EC2를 배치하고, ALB를 통해 트래픽 분산 가능) 저는 2a, 2c 가용영역을 선택했습니다.&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;1630&quot; data-origin-height=&quot;1062&quot;&gt;&lt;span data-url=&quot;https://blog.kakaocdn.net/dn/bcOJsc/btsvvFrYCb0/qY8WUmoib4U4iQJXTaYXJ1/img.png&quot; data-phocus=&quot;https://blog.kakaocdn.net/dn/bcOJsc/btsvvFrYCb0/qY8WUmoib4U4iQJXTaYXJ1/img.png&quot;&gt;&lt;img src=&quot;https://blog.kakaocdn.net/dn/bcOJsc/btsvvFrYCb0/qY8WUmoib4U4iQJXTaYXJ1/img.png&quot; srcset=&quot;https://img1.daumcdn.net/thumb/R1280x0/?scode=mtistory2&amp;fname=https%3A%2F%2Fblog.kakaocdn.net%2Fdn%2FbcOJsc%2FbtsvvFrYCb0%2FqY8WUmoib4U4iQJXTaYXJ1%2Fimg.png&quot; onerror=&quot;this.onerror=null; this.src='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png'; this.srcset='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png';&quot; loading=&quot;lazy&quot; width=&quot;1630&quot; height=&quot;1062&quot; data-origin-width=&quot;1630&quot; data-origin-height=&quot;1062&quot;/&gt;&lt;/span&gt;&lt;/figure&gt;
&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;위에서 Load Balancer와 대상그룹을 생성했으므로, 해당 대상 그룹과 Auto Scaling Group을 연결해주었습니다.&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;1636&quot; data-origin-height=&quot;1162&quot;&gt;&lt;span data-url=&quot;https://blog.kakaocdn.net/dn/dm6j65/btsvHdg6xsQ/qT7z9iSmAfyyK6CkXrnoJ0/img.png&quot; data-phocus=&quot;https://blog.kakaocdn.net/dn/dm6j65/btsvHdg6xsQ/qT7z9iSmAfyyK6CkXrnoJ0/img.png&quot;&gt;&lt;img src=&quot;https://blog.kakaocdn.net/dn/dm6j65/btsvHdg6xsQ/qT7z9iSmAfyyK6CkXrnoJ0/img.png&quot; srcset=&quot;https://img1.daumcdn.net/thumb/R1280x0/?scode=mtistory2&amp;fname=https%3A%2F%2Fblog.kakaocdn.net%2Fdn%2Fdm6j65%2FbtsvHdg6xsQ%2FqT7z9iSmAfyyK6CkXrnoJ0%2Fimg.png&quot; onerror=&quot;this.onerror=null; this.src='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png'; this.srcset='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png';&quot; loading=&quot;lazy&quot; width=&quot;1636&quot; height=&quot;1162&quot; data-origin-width=&quot;1636&quot; data-origin-height=&quot;1162&quot;/&gt;&lt;/span&gt;&lt;/figure&gt;
&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;해당 옵션은 Elastic Load Balancer의 health check를 반영해 Auto Scaling하겠다는 의미입니다. Auto Scaling Group은 인스턴스의 상태를 기준으로 health check를 합니다. 즉, 인스턴스가 실행 중이면 실제 WAS가 작동하지 않는 경우에도 정상이라고 판단합니다. 이에 반해, Elastic Load Balancer는 health check endpoint로 요청을 보내 상태를 확인하기 때문에 WAS가 작동하지 않는 경우도 탐지할 수 있습니다. 따라서, Elastic Load Balancer의 상태를 반영해 WAS가 작동하지 않는 경우에도 Auto Scaling하기 위해 해당 옵션을 활성화했습니다.&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;1638&quot; data-origin-height=&quot;970&quot;&gt;&lt;span data-url=&quot;https://blog.kakaocdn.net/dn/cNut67/btsvOBn2qpd/gYUTlBT3DorkuCvC90PfN1/img.png&quot; data-phocus=&quot;https://blog.kakaocdn.net/dn/cNut67/btsvOBn2qpd/gYUTlBT3DorkuCvC90PfN1/img.png&quot;&gt;&lt;img src=&quot;https://blog.kakaocdn.net/dn/cNut67/btsvOBn2qpd/gYUTlBT3DorkuCvC90PfN1/img.png&quot; srcset=&quot;https://img1.daumcdn.net/thumb/R1280x0/?scode=mtistory2&amp;fname=https%3A%2F%2Fblog.kakaocdn.net%2Fdn%2FcNut67%2FbtsvOBn2qpd%2FgYUTlBT3DorkuCvC90PfN1%2Fimg.png&quot; onerror=&quot;this.onerror=null; this.src='//t1.daumcdn.net/tistory_admin/static/images/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;970&quot; data-origin-width=&quot;1638&quot; data-origin-height=&quot;970&quot;/&gt;&lt;/span&gt;&lt;/figure&gt;
&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;Auto Scaling Group에 의해 생성될 인스턴스들의 최소 값, 최대 값, 평상시 원하는 값을 설정해야합니다. 저는 각 가용영역에 1개씩 분배하기 위해 원하는 용량 2, 최소 용량 2를 지정하였고, 트래픽이 더 들어올 시 최대 4개까지 인스턴스가 생성되도록 설정했습니다.&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;1640&quot; data-origin-height=&quot;792&quot;&gt;&lt;span data-url=&quot;https://blog.kakaocdn.net/dn/dU3EVe/btsvM0BIvGy/P3WKVppN7ymPb46H9qdQdK/img.png&quot; data-phocus=&quot;https://blog.kakaocdn.net/dn/dU3EVe/btsvM0BIvGy/P3WKVppN7ymPb46H9qdQdK/img.png&quot;&gt;&lt;img src=&quot;https://blog.kakaocdn.net/dn/dU3EVe/btsvM0BIvGy/P3WKVppN7ymPb46H9qdQdK/img.png&quot; srcset=&quot;https://img1.daumcdn.net/thumb/R1280x0/?scode=mtistory2&amp;fname=https%3A%2F%2Fblog.kakaocdn.net%2Fdn%2FdU3EVe%2FbtsvM0BIvGy%2FP3WKVppN7ymPb46H9qdQdK%2Fimg.png&quot; onerror=&quot;this.onerror=null; this.src='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png'; this.srcset='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png';&quot; loading=&quot;lazy&quot; width=&quot;1640&quot; height=&quot;792&quot; data-origin-width=&quot;1640&quot; data-origin-height=&quot;792&quot;/&gt;&lt;/span&gt;&lt;/figure&gt;
&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;Auto Scaling Group - 인스턴스 관리 에 들어가면 &lt;span style=&quot;background-color: #ffffff; color: #212529; text-align: start;&quot;&gt;Auto Scaling Group에 의해 관리되는 인스턴스들을 확인할 수 있습니다.&amp;nbsp;&lt;/span&gt;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;h2 data-ke-size=&quot;size26&quot;&gt;&lt;b&gt;8. CodeDeploy 생성&lt;/b&gt;&lt;/h2&gt;
&lt;hr contenteditable=&quot;false&quot; data-ke-type=&quot;horizontalRule&quot; data-ke-style=&quot;style6&quot; /&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;CodeDeploy는 appspec.yml 이라는 yml 파일에 의해 작동하는 도구로, 원하는 스크립트를 실행하거나 동작 시킬 수 있습니다. CodeDeploy를 통해 S3에 업로드한 script를 EC2 인스턴스에 다운받아 실행시킬 예정입니다.&amp;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;먼저 CodeDeploy 애플리케이션을 생성합니다. 간단하게 이름, 컴퓨팅 플랫폼을 선택한 뒤 생성해줍니다.&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;1642&quot; data-origin-height=&quot;808&quot;&gt;&lt;span data-url=&quot;https://blog.kakaocdn.net/dn/vCvwb/btsvnhyuTen/19TSa3GK5HfWlJzBEXzRQK/img.png&quot; data-phocus=&quot;https://blog.kakaocdn.net/dn/vCvwb/btsvnhyuTen/19TSa3GK5HfWlJzBEXzRQK/img.png&quot;&gt;&lt;img src=&quot;https://blog.kakaocdn.net/dn/vCvwb/btsvnhyuTen/19TSa3GK5HfWlJzBEXzRQK/img.png&quot; srcset=&quot;https://img1.daumcdn.net/thumb/R1280x0/?scode=mtistory2&amp;fname=https%3A%2F%2Fblog.kakaocdn.net%2Fdn%2FvCvwb%2FbtsvnhyuTen%2F19TSa3GK5HfWlJzBEXzRQK%2Fimg.png&quot; onerror=&quot;this.onerror=null; this.src='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png'; this.srcset='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png';&quot; loading=&quot;lazy&quot; width=&quot;1642&quot; height=&quot;808&quot; data-origin-width=&quot;1642&quot; data-origin-height=&quot;808&quot;/&gt;&lt;/span&gt;&lt;/figure&gt;
&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;위에서 생성한 CodeDeploy 애플리케이션에 들어가 배포 그룹 생성을 클릭합니다.&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;2168&quot; data-origin-height=&quot;370&quot;&gt;&lt;span data-url=&quot;https://blog.kakaocdn.net/dn/DkjMO/btsvlwJfFD6/EdKRmwQfhHm0ZpmosMSyOK/img.png&quot; data-phocus=&quot;https://blog.kakaocdn.net/dn/DkjMO/btsvlwJfFD6/EdKRmwQfhHm0ZpmosMSyOK/img.png&quot;&gt;&lt;img src=&quot;https://blog.kakaocdn.net/dn/DkjMO/btsvlwJfFD6/EdKRmwQfhHm0ZpmosMSyOK/img.png&quot; srcset=&quot;https://img1.daumcdn.net/thumb/R1280x0/?scode=mtistory2&amp;fname=https%3A%2F%2Fblog.kakaocdn.net%2Fdn%2FDkjMO%2FbtsvlwJfFD6%2FEdKRmwQfhHm0ZpmosMSyOK%2Fimg.png&quot; onerror=&quot;this.onerror=null; this.src='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png'; this.srcset='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png';&quot; loading=&quot;lazy&quot; width=&quot;2168&quot; height=&quot;370&quot; data-origin-width=&quot;2168&quot; data-origin-height=&quot;370&quot;/&gt;&lt;/span&gt;&lt;/figure&gt;
&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;CodeDeploy 역할은 맨 위에서 생성한 CodeDeploy 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;1626&quot; data-origin-height=&quot;774&quot;&gt;&lt;span data-url=&quot;https://blog.kakaocdn.net/dn/2gShH/btsvMVtDfhX/bFkwDm9fzMXMTDN1HtSSk0/img.png&quot; data-phocus=&quot;https://blog.kakaocdn.net/dn/2gShH/btsvMVtDfhX/bFkwDm9fzMXMTDN1HtSSk0/img.png&quot;&gt;&lt;img src=&quot;https://blog.kakaocdn.net/dn/2gShH/btsvMVtDfhX/bFkwDm9fzMXMTDN1HtSSk0/img.png&quot; srcset=&quot;https://img1.daumcdn.net/thumb/R1280x0/?scode=mtistory2&amp;fname=https%3A%2F%2Fblog.kakaocdn.net%2Fdn%2F2gShH%2FbtsvMVtDfhX%2FbFkwDm9fzMXMTDN1HtSSk0%2Fimg.png&quot; onerror=&quot;this.onerror=null; this.src='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png'; this.srcset='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png';&quot; loading=&quot;lazy&quot; width=&quot;1626&quot; height=&quot;774&quot; data-origin-width=&quot;1626&quot; data-origin-height=&quot;774&quot;/&gt;&lt;/span&gt;&lt;/figure&gt;
&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;배포 유형은 무중단 배포 및 Rollback이 가능한 블루/그린 배포 유형을 선택합니다. 환경 구성은 Auto Scaling 그룹 자동 복사를 선택합니다. 인스턴스 수동 프로비저닝을 선택하면 직접 새로운 Auto Scaling Group을 만들어야 하지만, 그룹 자동 복사를 선택하면 자동으로 Auto Scaling Group (Green 그룹)이 생성되고, Blue Group 삭제까지 진행됩니다.&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;1628&quot; data-origin-height=&quot;1154&quot;&gt;&lt;span data-url=&quot;https://blog.kakaocdn.net/dn/lGPWT/btsvP8Ts3Tk/FROwFI31CdCSkKdQue3oQ0/img.png&quot; data-phocus=&quot;https://blog.kakaocdn.net/dn/lGPWT/btsvP8Ts3Tk/FROwFI31CdCSkKdQue3oQ0/img.png&quot;&gt;&lt;img src=&quot;https://blog.kakaocdn.net/dn/lGPWT/btsvP8Ts3Tk/FROwFI31CdCSkKdQue3oQ0/img.png&quot; srcset=&quot;https://img1.daumcdn.net/thumb/R1280x0/?scode=mtistory2&amp;fname=https%3A%2F%2Fblog.kakaocdn.net%2Fdn%2FlGPWT%2FbtsvP8Ts3Tk%2FFROwFI31CdCSkKdQue3oQ0%2Fimg.png&quot; onerror=&quot;this.onerror=null; this.src='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png'; this.srcset='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png';&quot; loading=&quot;lazy&quot; width=&quot;1628&quot; height=&quot;1154&quot; data-origin-width=&quot;1628&quot; data-origin-height=&quot;1154&quot;/&gt;&lt;/span&gt;&lt;/figure&gt;
&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;배포 성공시 트래픽을 Green Group으로 라우팅할 수 있도록, 트래픽 재 라우팅은 즉시 라우팅으로 설정해줍니다. 그리고, 원본 인스턴스(Blue Group) 종료 시간을 설정해줍니다. 필요한 경우 Blue Group으로 롤백할 수 있도록, 적절한 시간을 설정해줍니다. 아래에서는 Green Group 배포 후 1시간이 지난 뒤 원본 인스턴스들이 종료됩니다.&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;1632&quot; data-origin-height=&quot;952&quot;&gt;&lt;span data-url=&quot;https://blog.kakaocdn.net/dn/QlqJo/btsvNvO9val/updNpgNQ8xHlzxuSkuSQFk/img.png&quot; data-phocus=&quot;https://blog.kakaocdn.net/dn/QlqJo/btsvNvO9val/updNpgNQ8xHlzxuSkuSQFk/img.png&quot;&gt;&lt;img src=&quot;https://blog.kakaocdn.net/dn/QlqJo/btsvNvO9val/updNpgNQ8xHlzxuSkuSQFk/img.png&quot; srcset=&quot;https://img1.daumcdn.net/thumb/R1280x0/?scode=mtistory2&amp;fname=https%3A%2F%2Fblog.kakaocdn.net%2Fdn%2FQlqJo%2FbtsvNvO9val%2FupdNpgNQ8xHlzxuSkuSQFk%2Fimg.png&quot; onerror=&quot;this.onerror=null; this.src='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png'; this.srcset='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png';&quot; loading=&quot;lazy&quot; width=&quot;1632&quot; height=&quot;952&quot; data-origin-width=&quot;1632&quot; data-origin-height=&quot;952&quot;/&gt;&lt;/span&gt;&lt;/figure&gt;
&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;로드 밸런서는 위에서 생성한 대상그룹으로 연결해줍니다. 배포에 성공해 Green Group으로 트래픽 라우팅이 가능해지면 대상 그룹으로 인스턴스들을 자동 등록해줍니다.&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;1626&quot; data-origin-height=&quot;706&quot;&gt;&lt;span data-url=&quot;https://blog.kakaocdn.net/dn/ebAu02/btsvnfm4XoM/1qJmMf41ddYvFcTFzFfg1k/img.png&quot; data-phocus=&quot;https://blog.kakaocdn.net/dn/ebAu02/btsvnfm4XoM/1qJmMf41ddYvFcTFzFfg1k/img.png&quot;&gt;&lt;img src=&quot;https://blog.kakaocdn.net/dn/ebAu02/btsvnfm4XoM/1qJmMf41ddYvFcTFzFfg1k/img.png&quot; srcset=&quot;https://img1.daumcdn.net/thumb/R1280x0/?scode=mtistory2&amp;fname=https%3A%2F%2Fblog.kakaocdn.net%2Fdn%2FebAu02%2Fbtsvnfm4XoM%2F1qJmMf41ddYvFcTFzFfg1k%2Fimg.png&quot; onerror=&quot;this.onerror=null; this.src='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png'; this.srcset='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png';&quot; loading=&quot;lazy&quot; width=&quot;1626&quot; height=&quot;706&quot; data-origin-width=&quot;1626&quot; data-origin-height=&quot;706&quot;/&gt;&lt;/span&gt;&lt;/figure&gt;
&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;h2 data-ke-size=&quot;size26&quot;&gt;&lt;b&gt;9. IAM 사용자&lt;/b&gt;&lt;/h2&gt;
&lt;hr contenteditable=&quot;false&quot; data-ke-type=&quot;horizontalRule&quot; data-ke-style=&quot;style6&quot; /&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;Github Action에서 AWS 리소스에 접근하기 위해 IAM 사용자를 생성해야 합니다. 사용자 생성 후 발급받은 access key, secret key로 AWS 리소스에 접근할 수 있습니다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p&gt;&lt;figure class=&quot;imageblock alignCenter&quot; data-ke-mobileStyle=&quot;widthOrigin&quot; data-filename=&quot;스크린샷 2023-09-28 오후 1.20.14.png&quot; data-origin-width=&quot;2194&quot; data-origin-height=&quot;278&quot;&gt;&lt;span data-url=&quot;https://blog.kakaocdn.net/dn/tViI4/btsw8kLTupD/XETqk89KkNDCURPrkLwbsK/img.png&quot; data-phocus=&quot;https://blog.kakaocdn.net/dn/tViI4/btsw8kLTupD/XETqk89KkNDCURPrkLwbsK/img.png&quot;&gt;&lt;img src=&quot;https://blog.kakaocdn.net/dn/tViI4/btsw8kLTupD/XETqk89KkNDCURPrkLwbsK/img.png&quot; srcset=&quot;https://img1.daumcdn.net/thumb/R1280x0/?scode=mtistory2&amp;fname=https%3A%2F%2Fblog.kakaocdn.net%2Fdn%2FtViI4%2Fbtsw8kLTupD%2FXETqk89KkNDCURPrkLwbsK%2Fimg.png&quot; onerror=&quot;this.onerror=null; this.src='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png'; this.srcset='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png';&quot; loading=&quot;lazy&quot; width=&quot;2194&quot; height=&quot;278&quot; data-filename=&quot;스크린샷 2023-09-28 오후 1.20.14.png&quot; data-origin-width=&quot;2194&quot; data-origin-height=&quot;278&quot;/&gt;&lt;/span&gt;&lt;/figure&gt;
&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;AWS 외부에서 실행되는 애플리케이션을 선택 후, 적절히 사용자 이름을 설정해주고 ECR, S3, CodeDeploy에 대한 FullAccess 권한을 부여합니다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p&gt;&lt;figure class=&quot;imageblock alignCenter&quot; data-ke-mobileStyle=&quot;widthOrigin&quot; data-filename=&quot;스크린샷 2023-09-28 오후 1.35.01.png&quot; data-origin-width=&quot;1652&quot; data-origin-height=&quot;1442&quot;&gt;&lt;span data-url=&quot;https://blog.kakaocdn.net/dn/00GbD/btswPXdk5Vm/kjNdJKDyHyfDiF37x9A2zK/img.png&quot; data-phocus=&quot;https://blog.kakaocdn.net/dn/00GbD/btswPXdk5Vm/kjNdJKDyHyfDiF37x9A2zK/img.png&quot;&gt;&lt;img src=&quot;https://blog.kakaocdn.net/dn/00GbD/btswPXdk5Vm/kjNdJKDyHyfDiF37x9A2zK/img.png&quot; srcset=&quot;https://img1.daumcdn.net/thumb/R1280x0/?scode=mtistory2&amp;fname=https%3A%2F%2Fblog.kakaocdn.net%2Fdn%2F00GbD%2FbtswPXdk5Vm%2FkjNdJKDyHyfDiF37x9A2zK%2Fimg.png&quot; onerror=&quot;this.onerror=null; this.src='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png'; this.srcset='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png';&quot; loading=&quot;lazy&quot; width=&quot;1652&quot; height=&quot;1442&quot; data-filename=&quot;스크린샷 2023-09-28 오후 1.35.01.png&quot; data-origin-width=&quot;1652&quot; data-origin-height=&quot;1442&quot;/&gt;&lt;/span&gt;&lt;/figure&gt;
&lt;figure class=&quot;imageblock alignCenter&quot; data-ke-mobileStyle=&quot;widthOrigin&quot; data-filename=&quot;스크린샷 2023-09-28 오후 1.22.15.png&quot; data-origin-width=&quot;1552&quot; data-origin-height=&quot;472&quot;&gt;&lt;span data-url=&quot;https://blog.kakaocdn.net/dn/qUhff/btswbe2b5mT/W1WiN8QC7kvoepn2xtfJRk/img.png&quot; data-phocus=&quot;https://blog.kakaocdn.net/dn/qUhff/btswbe2b5mT/W1WiN8QC7kvoepn2xtfJRk/img.png&quot;&gt;&lt;img src=&quot;https://blog.kakaocdn.net/dn/qUhff/btswbe2b5mT/W1WiN8QC7kvoepn2xtfJRk/img.png&quot; srcset=&quot;https://img1.daumcdn.net/thumb/R1280x0/?scode=mtistory2&amp;fname=https%3A%2F%2Fblog.kakaocdn.net%2Fdn%2FqUhff%2Fbtswbe2b5mT%2FW1WiN8QC7kvoepn2xtfJRk%2Fimg.png&quot; onerror=&quot;this.onerror=null; this.src='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png'; this.srcset='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png';&quot; loading=&quot;lazy&quot; width=&quot;1552&quot; height=&quot;472&quot; data-filename=&quot;스크린샷 2023-09-28 오후 1.22.15.png&quot; data-origin-width=&quot;1552&quot; data-origin-height=&quot;472&quot;/&gt;&lt;/span&gt;&lt;/figure&gt;
&lt;figure class=&quot;imageblock alignCenter&quot; data-ke-mobileStyle=&quot;widthOrigin&quot; data-filename=&quot;스크린샷 2023-09-28 오후 1.26.00.png&quot; data-origin-width=&quot;1846&quot; data-origin-height=&quot;482&quot;&gt;&lt;span data-url=&quot;https://blog.kakaocdn.net/dn/wo18d/btswcFZwhHB/0RRq9ErylkMZPX6YctTW40/img.png&quot; data-phocus=&quot;https://blog.kakaocdn.net/dn/wo18d/btswcFZwhHB/0RRq9ErylkMZPX6YctTW40/img.png&quot;&gt;&lt;img src=&quot;https://blog.kakaocdn.net/dn/wo18d/btswcFZwhHB/0RRq9ErylkMZPX6YctTW40/img.png&quot; srcset=&quot;https://img1.daumcdn.net/thumb/R1280x0/?scode=mtistory2&amp;fname=https%3A%2F%2Fblog.kakaocdn.net%2Fdn%2Fwo18d%2FbtswcFZwhHB%2F0RRq9ErylkMZPX6YctTW40%2Fimg.png&quot; onerror=&quot;this.onerror=null; this.src='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png'; this.srcset='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png';&quot; loading=&quot;lazy&quot; width=&quot;1846&quot; height=&quot;482&quot; data-filename=&quot;스크린샷 2023-09-28 오후 1.26.00.png&quot; data-origin-width=&quot;1846&quot; data-origin-height=&quot;482&quot;/&gt;&lt;/span&gt;&lt;/figure&gt;
&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;IAM 사용자를 생성했으면, 생성한 IAM 사용자에 들어가 access key, secret key 를 발급받습니다. 이 key들은 한번 발급 후 다시 조회할 수 없으니 꼭 로컬에 저장해두시는 것을 추천드립니다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p&gt;&lt;figure class=&quot;imageblock alignCenter&quot; data-ke-mobileStyle=&quot;widthOrigin&quot; data-filename=&quot;스크린샷 2023-09-28 오후 1.34.32.png&quot; data-origin-width=&quot;2174&quot; data-origin-height=&quot;510&quot;&gt;&lt;span data-url=&quot;https://blog.kakaocdn.net/dn/HhCs4/btswUVNcN7w/tA3mkAqQluDKZlxhDXtkt0/img.png&quot; data-phocus=&quot;https://blog.kakaocdn.net/dn/HhCs4/btswUVNcN7w/tA3mkAqQluDKZlxhDXtkt0/img.png&quot;&gt;&lt;img src=&quot;https://blog.kakaocdn.net/dn/HhCs4/btswUVNcN7w/tA3mkAqQluDKZlxhDXtkt0/img.png&quot; srcset=&quot;https://img1.daumcdn.net/thumb/R1280x0/?scode=mtistory2&amp;fname=https%3A%2F%2Fblog.kakaocdn.net%2Fdn%2FHhCs4%2FbtswUVNcN7w%2FtA3mkAqQluDKZlxhDXtkt0%2Fimg.png&quot; onerror=&quot;this.onerror=null; this.src='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png'; this.srcset='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png';&quot; loading=&quot;lazy&quot; width=&quot;2174&quot; height=&quot;510&quot; data-filename=&quot;스크린샷 2023-09-28 오후 1.34.32.png&quot; data-origin-width=&quot;2174&quot; data-origin-height=&quot;510&quot;/&gt;&lt;/span&gt;&lt;/figure&gt;
&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;h2 data-ke-size=&quot;size26&quot;&gt;&lt;b&gt;10. Github Action CI/CD&lt;/b&gt;&lt;/h2&gt;
&lt;hr contenteditable=&quot;false&quot; data-ke-type=&quot;horizontalRule&quot; data-ke-style=&quot;style6&quot; /&gt;
&lt;p style=&quot;color: #000000; text-align: start;&quot; data-ke-size=&quot;size16&quot;&gt;Github Action CI/CD를 위해선 Dockerfile, CodeDeploy 배포를 위한 스크립트 파일이 필요합니다. 먼저 필요한 파일들을 생성한 뒤, Github Action CI/CD workflow에 대해 살펴보겠습니다.&lt;/p&gt;
&lt;h3 style=&quot;color: #000000; text-align: start;&quot; data-ke-size=&quot;size23&quot;&gt;&lt;b&gt;&lt;span&gt;[ 10-1. Dockerfile 작성&lt;/span&gt;&amp;nbsp;]&lt;/b&gt;&lt;/h3&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;아래와 같이 Spring Boot Dockerfile을 작성합니다. 저는 멀티모듈을 사용하고 있습니다. Dockerfile은 자신의 환경에 맞게 작성해주시면 됩니다.&lt;span&gt;&amp;nbsp;&lt;/span&gt;해당 Dockerfile은 Github Action을 통해 build해 docker image를 생성하고, ECR에 push합니다.&lt;/p&gt;
&lt;pre id=&quot;code_1696418089287&quot; class=&quot;dockerfile&quot; style=&quot;background-color: #f8f8f8; color: #383a42;&quot; data-ke-type=&quot;codeblock&quot; data-ke-language=&quot;shell&quot;&gt;&lt;code&gt;FROM openjdk:17-ea-11-jdk-slim AS BUILDER
RUN mkdir /app_source
COPY .. /app_source
WORKDIR /app_source
RUN chmod +x ./gradlew
RUN ./gradlew :api:bootJar

FROM openjdk:17-ea-11-jdk-slim AS RUNNER
RUN mkdir /app
COPY --from=BUILDER /app_source/api/build/libs /app
WORKDIR /app
ENV TZ=Asia/Seoul
EXPOSE 8080
ENTRYPOINT [&quot;java&quot;,&quot;-jar&quot;,&quot;/app/api-0.0.1-SNAPSHOT.jar&quot;]&lt;/code&gt;&lt;/pre&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;저는 EC2에 redis, spring boot 컨테이너를 띄울 것이기 때문에 다수의 컨테이너를 실행하기 위한 docker-compose 파일도 작성했습니다.docker-compose 파일은 S3에 업로드 후, codeDeploy에 의해 실행될 예정입니다. (codeDeploy가 S3로 부터 파일을 가져와&lt;span&gt;&amp;nbsp;&lt;/span&gt;&lt;span style=&quot;color: #333333; text-align: start;&quot;&gt;EC2에&lt;span&gt;&amp;nbsp;&lt;/span&gt;&lt;/span&gt;압축을 해제한 뒤, appspec.yml에 정의된 스크립트를 실행시켜 docker-compose에 정의된 컨테이너 실행)&lt;/p&gt;
&lt;pre id=&quot;code_1696418089289&quot; class=&quot;http&quot; style=&quot;background-color: #f8f8f8; color: #383a42;&quot; data-ke-type=&quot;codeblock&quot; data-ke-language=&quot;bash&quot;&gt;&lt;code&gt;version: '3.7'

services:
  redis:
    image: redis:latest
    command: redis-server
    container_name: tago-redis
    ports:
      - &quot;6379:6379&quot;
  spring:
    # ECR로부터 Spring Boot 이미지를 pull 받아 컨테이너 실행
    image: 121284569119.dkr.ecr.ap-northeast-2.amazonaws.com/tago-dev:latest
    restart: always
    depends_on:
      - redis
    ports:
      - &quot;8080:8080&quot;
    container_name: tago-spring
    environment:
      DATABASE_URL: ${DATABASE_URL}
      DATABASE_USERNAME: ${DATABASE_USERNAME}
      DATABASE_PASSWORD: ${DATABASE_PASSWORD}
      REDIS_HOST: ${REDIS_HOST}
      REDIS_PORT: ${REDIS_PORT}&lt;/code&gt;&lt;/pre&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;h3 style=&quot;color: #000000;&quot; data-ke-size=&quot;size23&quot;&gt;&lt;b&gt;[ 9-4. CodeDeploy script 작성 ]&lt;/b&gt;&lt;/h3&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;CodeDeploy는 S3에 업로드한 압축파일을 EC2로 가져와 압축을 해제한 뒤, appspec.yml에 정의되어 있는 스크립트를 실행합니다. CodeDeploy 배포에 필요한 파일의 구조는 아래와 같습니다.&lt;/p&gt;
&lt;p&gt;&lt;figure class=&quot;imageblock alignLeft&quot; data-ke-mobileStyle=&quot;widthOrigin&quot; data-origin-width=&quot;630&quot; data-origin-height=&quot;254&quot;&gt;&lt;span data-url=&quot;https://blog.kakaocdn.net/dn/bSj84Q/btsw2Fcezdn/OYrvjqkSxW8u3r0phfYQb0/img.png&quot; data-phocus=&quot;https://blog.kakaocdn.net/dn/bSj84Q/btsw2Fcezdn/OYrvjqkSxW8u3r0phfYQb0/img.png&quot;&gt;&lt;img src=&quot;https://blog.kakaocdn.net/dn/bSj84Q/btsw2Fcezdn/OYrvjqkSxW8u3r0phfYQb0/img.png&quot; srcset=&quot;https://img1.daumcdn.net/thumb/R1280x0/?scode=mtistory2&amp;fname=https%3A%2F%2Fblog.kakaocdn.net%2Fdn%2FbSj84Q%2Fbtsw2Fcezdn%2FOYrvjqkSxW8u3r0phfYQb0%2Fimg.png&quot; onerror=&quot;this.onerror=null; this.src='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png'; this.srcset='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png';&quot; loading=&quot;lazy&quot; width=&quot;543&quot; height=&quot;219&quot; data-origin-width=&quot;630&quot; data-origin-height=&quot;254&quot;/&gt;&lt;/span&gt;&lt;/figure&gt;
&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;1. afterInstall.yml&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;ECR에 로그인 후, ECR로 부터 docker image를 pull 받은 뒤, 컨테이너를 실행하는 스크립트입니다.&lt;/p&gt;
&lt;pre id=&quot;code_1696418089290&quot; class=&quot;bash&quot; style=&quot;background-color: #f8f8f8; color: #383a42;&quot; data-ke-type=&quot;codeblock&quot; data-ke-language=&quot;bash&quot;&gt;&lt;code&gt;#!/bin/bash
REPOSITORY=/home/ubuntu/tago
CONTAINER_NAME=tago-spring
ECR_REGISTRY=121284569119.dkr.ecr.ap-northeast-2.amazonaws.com

cd $REPOSITORY

echo &quot;&amp;gt;   PULL DOCKER IMAGE FROM ECR&quot;
aws ecr get-login-password --region ap-northeast-2 | docker login --username AWS --password-stdin $ECR_REGISTRY

echo &quot;&amp;gt;   RUN APPLICATION CONTAINER&quot;
docker-compose pull spring
docker-compose up -d&lt;/code&gt;&lt;/pre&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;2. appspec.yml&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;위에서 정의한 스크립트를 S3로 부터 가져오고 스크립트의 실행권한과, 실행 시기를 정의해놓은 파일입니다.&lt;/p&gt;
&lt;pre id=&quot;code_1696418089291&quot; class=&quot;yaml&quot; style=&quot;background-color: #f8f8f8; color: #383a42;&quot; data-ke-type=&quot;codeblock&quot; data-ke-language=&quot;bash&quot;&gt;&lt;code&gt;version: 0.0
os: linux
files:
  - source: /
    destination: /home/ubuntu/tago
    overwrite: yes

permissions:
  - object: /
    pattern: &quot;**&quot;
    owner: ubuntu
    group: ubuntu
    mode: 755

hooks:
  AfterInstall:
    - location: scripts/afterInstall.sh
      timeout: 60
      runas: ubuntu&lt;/code&gt;&lt;/pre&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;간단하게 살펴보면 appspec.yml은 3가지 작업을 수행합니다.&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;- files : S3에 업로드된 zip 파일을 EC2에 다운받아 압축 해제 후&lt;span&gt;&amp;nbsp;&lt;/span&gt;/home/ubuntu/tago 경로로 옮긴다&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;- permissions : 옮기기 전 파일들의 권한을 설정한다. (shell script 실행을 위한 권한 부여)&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;- hooks : 압축 해제한 스크립트 파일을 실행한다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;hooks에서 해당 스크립트 파일에 정의된 명령들을 실행한다. 여기서는 afterInstall.sh 스크립트 내 docker-compose up 명령에 의해 EC2에 Spring boot, redis 컨테이너가 실행된다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;h3 data-ke-size=&quot;size23&quot;&gt;&lt;b&gt;[ 9-2. Github Action ]&lt;/b&gt;&lt;/h3&gt;
&lt;pre id=&quot;code_1696415097408&quot; class=&quot;bash&quot; data-ke-language=&quot;bash&quot; data-ke-type=&quot;codeblock&quot;&gt;&lt;code&gt;name: cd-dev

on:
  pull_request:
    branches: [ develop ]
    types: [ closed ]

env:
  AWS_REGION: ap-northeast-2
  S3_BUCKET_NAME: S3 버켓 이름
  CODE_DEPLOY_APPLICATION_NAME: CodeDeploy 애플리케이션 이름
  CODE_DEPLOY_DEPLOYMENT_GROUP_NAME: CodeDeploy 배포 그룹 이름

permissions:
  contents: read

jobs:
  build:
    runs-on: ubuntu-latest
    if: github.event.pull_request.merged
    steps:
      - uses: actions/checkout@v3
      - name: Set up JDK 17
        uses: actions/setup-java@v2
        with:
          java-version: '17'
          distribution: 'temurin'

      - name: Grant execute permission for gradlew
        run: chmod +x gradlew

      - name: Create .env file
        env:
          ENV_PATH: .env
        run: |
          touch $ENV_PATH
          echo DATABASE_URL=${{ secrets.DEV_DATABASE_URL }} &amp;gt;&amp;gt; $ENV_PATH
          echo DATABASE_USERNAME=${{ secrets.DEV_DATABASE_USERNAME }} &amp;gt;&amp;gt; $ENV_PATH
          echo DATABASE_PASSWORD=${{ secrets.DEV_DATABASE_PASSWORD }} &amp;gt;&amp;gt; $ENV_PATH
          echo REDIS_HOST=${{ secrets.DEV_REDIS_HOST }} &amp;gt;&amp;gt; $ENV_PATH
          echo REDIS_PORT=${{ secrets.DEV_REDIS_PORT }} &amp;gt;&amp;gt; $ENV_PATH

      - name: Configure AWS credentials
        uses: aws-actions/configure-aws-credentials@v1
        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: Login to Amazon ECR
        id: login-ecr
        uses: aws-actions/amazon-ecr-login@v1

      - name: Build, tag, and push image to Amazon ECR
        env:
          ECR_REGISTRY: ${{ steps.login-ecr.outputs.registry }}
          ECR_REPOSITORY: tago-dev
          IMAGE_TAG: latest
        run: |
          docker build -t $ECR_REGISTRY/$ECR_REPOSITORY:$IMAGE_TAG .
          docker push $ECR_REGISTRY/$ECR_REPOSITORY:$IMAGE_TAG

      - name: Upload to S3
        run: |
          zip -r ./$GITHUB_SHA.zip ./scripts appspec.yml docker-compose.yml .env
          aws s3 cp --region ap-northeast-2 ./$GITHUB_SHA.zip s3://$S3_BUCKET_NAME

      - name: Deploy to EC2 with CodeDeploy
        run: |
          aws deploy create-deployment \
          --application-name ${{ env.CODE_DEPLOY_APPLICATION_NAME }} \
          --deployment-config-name CodeDeployDefault.AllAtOnce \
          --deployment-group-name ${{ env.CODE_DEPLOY_DEPLOYMENT_GROUP_NAME }} \
          --s3-location bucket=$S3_BUCKET_NAME,key=$GITHUB_SHA.zip,bundleType=zip&lt;/code&gt;&lt;/pre&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;Github Action의 과정을 간단하게 소개하면 아래와 같습니다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;- 1. docker-compose 환경변수 파일 세팅&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;- 2. AWS IAM 로그인&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;- 3. ECR에 docker image push&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;- 4. CodeDeploy에 필요한 스크립트 압축해 S3에 업로드&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;- 5. CodeDeploy 실행해 배포 시작&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;2단계는 위에서 생성한 IAM 사용자의 access key, secret key를 github secret 변수로 등록해 AWS에 로그인했습니다. 이를 통해 Github Action은 ECR, S3, CodeDeploy에 접근할 수 있게 됩니다. 3단계부터는 좀 더 자세히 살펴보겠습니다.&lt;b&gt;&lt;span&gt;&lt;/span&gt;&lt;/b&gt;&lt;b&gt;&lt;span&gt;&lt;/span&gt;&lt;/b&gt;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;- ECR에 docker image push&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;정의한 Dockerfile을 build해 docker image 생성한 뒤, 해당 docker image를 ECR에 push합니다.&lt;/p&gt;
&lt;pre id=&quot;code_1696418296569&quot; class=&quot;bash&quot; data-ke-language=&quot;bash&quot; data-ke-type=&quot;codeblock&quot;&gt;&lt;code&gt;  - name: Build, tag, and push image to Amazon ECR
    env:
      ECR_REGISTRY: ${{ steps.login-ecr.outputs.registry }}
      ECR_REPOSITORY: tago-dev
      IMAGE_TAG: latest
    run: |
      docker build -t $ECR_REGISTRY/$ECR_REPOSITORY:$IMAGE_TAG .
      docker push $ECR_REGISTRY/$ECR_REPOSITORY:$IMAGE_TAG&lt;/code&gt;&lt;/pre&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;- CodeDeploy에 필요한 스크립트 압축해 S3에 업로드&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;appspec.yml 파일과 스크립트, docker-compose 파일을 압축해 S3에 업로드합니다.&lt;/p&gt;
&lt;pre id=&quot;code_1696418476846&quot; class=&quot;bash&quot; data-ke-language=&quot;bash&quot; data-ke-type=&quot;codeblock&quot;&gt;&lt;code&gt;  - name: Upload to S3
    run: |
      zip -r ./$GITHUB_SHA.zip ./scripts appspec.yml docker-compose.yml .env
      aws s3 cp --region ap-northeast-2 ./$GITHUB_SHA.zip s3://$S3_BUCKET_NAME&lt;/code&gt;&lt;/pre&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;- CodeDeploy 실행해 배포 시작&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;해당 S3 버켓으로 부터 zip 파일을 가져오고, CodeDeploy 애플리케이션의 특정 배포그룹에 배포를 진행합니다.&amp;nbsp;&lt;/p&gt;
&lt;pre id=&quot;code_1696418592085&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 with CodeDeploy
  run: |
    aws deploy create-deployment \
    --application-name ${{ env.CODE_DEPLOY_APPLICATION_NAME }} \
    --deployment-config-name CodeDeployDefault.AllAtOnce \
    --deployment-group-name ${{ env.CODE_DEPLOY_DEPLOYMENT_GROUP_NAME }} \
    --s3-location bucket=$S3_BUCKET_NAME,key=$GITHUB_SHA.zip,bundleType=zip&lt;/code&gt;&lt;/pre&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;h2 data-ke-size=&quot;size26&quot;&gt;&lt;b&gt;11. CI/CD 결과 확인&lt;/b&gt;&lt;/h2&gt;
&lt;hr contenteditable=&quot;false&quot; data-ke-type=&quot;horizontalRule&quot; data-ke-style=&quot;style6&quot; /&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;Github Action이 정상동작 했다면, 아래와 같이 CodeDeploy의 Blue/Green 배포가 실행될 것입니다. 3단계까지 완료가 되었다면, Green Group의 AutoScaling Group이 생성되고, 대상 그룹에 Auto Scaling에 의해 생성된 인스턴스(green)가 등록됩니다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p&gt;&lt;figure class=&quot;imageblock alignLeft&quot; data-ke-mobileStyle=&quot;widthOrigin&quot; data-filename=&quot;스크린샷 2023-10-04 오후 8.42.38.png&quot; data-origin-width=&quot;1080&quot; data-origin-height=&quot;972&quot;&gt;&lt;span data-url=&quot;https://blog.kakaocdn.net/dn/coAla2/btswcJ1VjC2/ajJSgZwAMC9erbt8xNohm1/img.png&quot; data-phocus=&quot;https://blog.kakaocdn.net/dn/coAla2/btswcJ1VjC2/ajJSgZwAMC9erbt8xNohm1/img.png&quot;&gt;&lt;img src=&quot;https://blog.kakaocdn.net/dn/coAla2/btswcJ1VjC2/ajJSgZwAMC9erbt8xNohm1/img.png&quot; srcset=&quot;https://img1.daumcdn.net/thumb/R1280x0/?scode=mtistory2&amp;fname=https%3A%2F%2Fblog.kakaocdn.net%2Fdn%2FcoAla2%2FbtswcJ1VjC2%2FajJSgZwAMC9erbt8xNohm1%2Fimg.png&quot; onerror=&quot;this.onerror=null; this.src='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png'; this.srcset='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png';&quot; loading=&quot;lazy&quot; width=&quot;562&quot; height=&quot;506&quot; data-filename=&quot;스크린샷 2023-10-04 오후 8.42.38.png&quot; data-origin-width=&quot;1080&quot; data-origin-height=&quot;972&quot;/&gt;&lt;/span&gt;&lt;/figure&gt;
&lt;figure class=&quot;imageblock alignLeft&quot; data-ke-mobileStyle=&quot;widthOrigin&quot; data-filename=&quot;스크린샷 2023-10-04 오후 8.35.40.png&quot; data-origin-width=&quot;1286&quot; data-origin-height=&quot;590&quot;&gt;&lt;span data-url=&quot;https://blog.kakaocdn.net/dn/LKxdR/btswwP8bKdT/O6IT7R5txlgOk9Bqw6KEyK/img.png&quot; data-phocus=&quot;https://blog.kakaocdn.net/dn/LKxdR/btswwP8bKdT/O6IT7R5txlgOk9Bqw6KEyK/img.png&quot;&gt;&lt;img src=&quot;https://blog.kakaocdn.net/dn/LKxdR/btswwP8bKdT/O6IT7R5txlgOk9Bqw6KEyK/img.png&quot; srcset=&quot;https://img1.daumcdn.net/thumb/R1280x0/?scode=mtistory2&amp;fname=https%3A%2F%2Fblog.kakaocdn.net%2Fdn%2FLKxdR%2FbtswwP8bKdT%2FO6IT7R5txlgOk9Bqw6KEyK%2Fimg.png&quot; onerror=&quot;this.onerror=null; this.src='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png'; this.srcset='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png';&quot; loading=&quot;lazy&quot; width=&quot;749&quot; height=&quot;344&quot; data-filename=&quot;스크린샷 2023-10-04 오후 8.35.40.png&quot; data-origin-width=&quot;1286&quot; data-origin-height=&quot;590&quot;/&gt;&lt;/span&gt;&lt;/figure&gt;
&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;4단계까지 완료가 되면, 원본 인스턴스(Blue)가 종료됩니다. 저는 CodeDeploy 애플리케이션 생성 시 원본 인스턴스를 1시간 뒤에 삭제한다고 설정했기 때문에, 1시간 뒤 원본 인스턴스가 종료되고, 원본 Auto Scaling Group도 삭제됩니다. 아래와 같이 원본 AutoScaling Group(Blue Group)이 삭제 중인 것을 확인할 수 있습니다.&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;2150&quot; data-origin-height=&quot;994&quot;&gt;&lt;span data-url=&quot;https://blog.kakaocdn.net/dn/bKqfyo/btswbdWxfJO/PjIKz8bCNOiLCpSuS56K41/img.png&quot; data-phocus=&quot;https://blog.kakaocdn.net/dn/bKqfyo/btswbdWxfJO/PjIKz8bCNOiLCpSuS56K41/img.png&quot;&gt;&lt;img src=&quot;https://blog.kakaocdn.net/dn/bKqfyo/btswbdWxfJO/PjIKz8bCNOiLCpSuS56K41/img.png&quot; srcset=&quot;https://img1.daumcdn.net/thumb/R1280x0/?scode=mtistory2&amp;fname=https%3A%2F%2Fblog.kakaocdn.net%2Fdn%2FbKqfyo%2FbtswbdWxfJO%2FPjIKz8bCNOiLCpSuS56K41%2Fimg.png&quot; onerror=&quot;this.onerror=null; this.src='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png'; this.srcset='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png';&quot; loading=&quot;lazy&quot; width=&quot;2150&quot; height=&quot;994&quot; data-origin-width=&quot;2150&quot; data-origin-height=&quot;994&quot;/&gt;&lt;/span&gt;&lt;/figure&gt;
&lt;figure class=&quot;imageblock alignCenter&quot; data-ke-mobileStyle=&quot;widthOrigin&quot; data-filename=&quot;스크린샷 2023-10-04 오후 8.45.51.png&quot; data-origin-width=&quot;1792&quot; data-origin-height=&quot;596&quot;&gt;&lt;span data-url=&quot;https://blog.kakaocdn.net/dn/dcKsjE/btsw670oJw0/iDQgcXeRSsQo2lhBldjaXK/img.png&quot; data-phocus=&quot;https://blog.kakaocdn.net/dn/dcKsjE/btsw670oJw0/iDQgcXeRSsQo2lhBldjaXK/img.png&quot;&gt;&lt;img src=&quot;https://blog.kakaocdn.net/dn/dcKsjE/btsw670oJw0/iDQgcXeRSsQo2lhBldjaXK/img.png&quot; srcset=&quot;https://img1.daumcdn.net/thumb/R1280x0/?scode=mtistory2&amp;fname=https%3A%2F%2Fblog.kakaocdn.net%2Fdn%2FdcKsjE%2Fbtsw670oJw0%2FiDQgcXeRSsQo2lhBldjaXK%2Fimg.png&quot; onerror=&quot;this.onerror=null; this.src='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png'; this.srcset='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png';&quot; loading=&quot;lazy&quot; width=&quot;1792&quot; height=&quot;596&quot; data-filename=&quot;스크린샷 2023-10-04 오후 8.45.51.png&quot; data-origin-width=&quot;1792&quot; data-origin-height=&quot;596&quot;/&gt;&lt;/span&gt;&lt;/figure&gt;
&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;대상 그룹에도 새로 생성된 인스턴스(Green Group)만 등록되어 있는 것을 확인할 수 있습니다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p&gt;&lt;figure class=&quot;imageblock alignCenter&quot; data-ke-mobileStyle=&quot;widthOrigin&quot; data-filename=&quot;스크린샷 2023-10-04 오후 8.41.06.png&quot; data-origin-width=&quot;1982&quot; data-origin-height=&quot;406&quot;&gt;&lt;span data-url=&quot;https://blog.kakaocdn.net/dn/c2EXjr/btswcJ1VsRf/EOi6S1Rkd2mxKVnFR9TlR1/img.png&quot; data-phocus=&quot;https://blog.kakaocdn.net/dn/c2EXjr/btswcJ1VsRf/EOi6S1Rkd2mxKVnFR9TlR1/img.png&quot;&gt;&lt;img src=&quot;https://blog.kakaocdn.net/dn/c2EXjr/btswcJ1VsRf/EOi6S1Rkd2mxKVnFR9TlR1/img.png&quot; srcset=&quot;https://img1.daumcdn.net/thumb/R1280x0/?scode=mtistory2&amp;fname=https%3A%2F%2Fblog.kakaocdn.net%2Fdn%2Fc2EXjr%2FbtswcJ1VsRf%2FEOi6S1Rkd2mxKVnFR9TlR1%2Fimg.png&quot; onerror=&quot;this.onerror=null; this.src='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png'; this.srcset='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png';&quot; loading=&quot;lazy&quot; width=&quot;1982&quot; height=&quot;406&quot; data-filename=&quot;스크린샷 2023-10-04 오후 8.41.06.png&quot; data-origin-width=&quot;1982&quot; data-origin-height=&quot;406&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;마지막으로! 제대로 배포가 되었는지 확인해봅시다. ELB DNS로 접속하면 아래와 같이 응답이 정상적으로 오는 것을 확인할 수 있습니다.&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;1616&quot; data-origin-height=&quot;238&quot;&gt;&lt;span data-url=&quot;https://blog.kakaocdn.net/dn/bqLIHi/btswbci4pn6/kVL6ldUSy0tN3LVnFAApVk/img.png&quot; data-phocus=&quot;https://blog.kakaocdn.net/dn/bqLIHi/btswbci4pn6/kVL6ldUSy0tN3LVnFAApVk/img.png&quot;&gt;&lt;img src=&quot;https://blog.kakaocdn.net/dn/bqLIHi/btswbci4pn6/kVL6ldUSy0tN3LVnFAApVk/img.png&quot; srcset=&quot;https://img1.daumcdn.net/thumb/R1280x0/?scode=mtistory2&amp;fname=https%3A%2F%2Fblog.kakaocdn.net%2Fdn%2FbqLIHi%2Fbtswbci4pn6%2FkVL6ldUSy0tN3LVnFAApVk%2Fimg.png&quot; onerror=&quot;this.onerror=null; this.src='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png'; this.srcset='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png';&quot; loading=&quot;lazy&quot; width=&quot;1616&quot; height=&quot;238&quot; data-origin-width=&quot;1616&quot; data-origin-height=&quot;238&quot;/&gt;&lt;/span&gt;&lt;/figure&gt;
&lt;/p&gt;
&lt;h3 data-ke-size=&quot;size23&quot;&gt;&lt;b&gt;삽질 Log .....&lt;/b&gt;&lt;/h3&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;ELB의 아웃바운드 규칙으로 8080포트를 허용하지 않아서 트래픽이 EC2로 전달되지 않았다. 대상 그룹의 health check도 당연히 실패했고, codeDeploy 배포도 실패했다. codeDeploy의 &lt;span style=&quot;background-color: #ffffff; color: #16191f; text-align: left;&quot;&gt;AllowTraffic 단계에서 계속 실패했는데, 이는 대상 그룹의 health check가 비정상인 경우 실패처리 된다고 한다. 보안 그룹을 제대로 확인하자!&lt;/span&gt;&lt;/p&gt;
&lt;figure contenteditable=&quot;false&quot; data-ke-type=&quot;emoticon&quot; data-ke-align=&quot;alignCenter&quot; data-emoticon-type=&quot;friends1&quot; data-emoticon-name=&quot;016&quot; data-emoticon-isanimation=&quot;false&quot; data-emoticon-src=&quot;https://t1.daumcdn.net/keditor/emoticon/friends1/large/016.gif&quot;&gt;&lt;img src=&quot;https://t1.daumcdn.net/keditor/emoticon/friends1/large/016.gif&quot; width=&quot;150&quot; /&gt;&lt;/figure&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;</description>
      <category>DevOps/AWS</category>
      <author>주인  장</author>
      <guid isPermaLink="true">https://twosky.tistory.com/61</guid>
      <comments>https://twosky.tistory.com/61#entry61comment</comments>
      <pubDate>Tue, 26 Sep 2023 03:33:35 +0900</pubDate>
    </item>
    <item>
      <title>[Spring] oneToMany 관계에서 fetch join, limit 같이 사용하는 경우</title>
      <link>https://twosky.tistory.com/60</link>
      <description>&lt;h2 data-ke-size=&quot;size26&quot;&gt;&lt;b&gt;문제 상황&lt;/b&gt;&lt;/h2&gt;
&lt;hr contenteditable=&quot;false&quot; data-ke-type=&quot;horizontalRule&quot; data-ke-style=&quot;style6&quot; /&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;oneToMany 관계에서 Fetch Join과 Limit을 같이 사용해 아래와 같은 경고 메시지가 발생했다.&lt;/p&gt;
&lt;pre id=&quot;code_1693916873795&quot; class=&quot;shell&quot; data-ke-language=&quot;shell&quot; data-ke-type=&quot;codeblock&quot;&gt;&lt;code&gt;HHH90003004: firstResult/maxResults specified with collection fetch; applying in memory&lt;/code&gt;&lt;/pre&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;쿼리 결과를 전부 메모리에 적재한 뒤 Pagination 작업을 어플리케이션 레벨에서 하기 때문에 위험하다는 로그이다. limit에 의해 제한된 개수의 쿼리 결과를 가져올 것이라는 예상과는 다르게 작동하고 있었다. 이처럼, One에서 Many를 fetch 하는 경우 limit절 (Pagination을 위해) 포함 시 원하는 대로 결과가 나오지 않고, 성능상 문제가 발생할 수 있다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;h2 data-ke-size=&quot;size26&quot;&gt;&lt;b&gt;문제 상황 재현&lt;/b&gt;&lt;/h2&gt;
&lt;hr contenteditable=&quot;false&quot; data-ke-type=&quot;horizontalRule&quot; data-ke-style=&quot;style6&quot; /&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;문제 상황 재현을 위한 ERD이다. trip, place, trip_place 테이블이 있고, trip_place는 tripId, placeId를 외래키로 갖고 있다. trip과 place는 N:N 관계이므로, trip_place 테이블을 중간에 두어 일대다, 다대일 관계로 풀어주었다.&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;1550&quot; data-origin-height=&quot;522&quot;&gt;&lt;span data-url=&quot;https://blog.kakaocdn.net/dn/O3rzT/btss9hfOWJD/kJ9oHQ04SMn0lBZE9o4vMK/img.png&quot; data-phocus=&quot;https://blog.kakaocdn.net/dn/O3rzT/btss9hfOWJD/kJ9oHQ04SMn0lBZE9o4vMK/img.png&quot;&gt;&lt;img src=&quot;https://blog.kakaocdn.net/dn/O3rzT/btss9hfOWJD/kJ9oHQ04SMn0lBZE9o4vMK/img.png&quot; srcset=&quot;https://img1.daumcdn.net/thumb/R1280x0/?scode=mtistory2&amp;fname=https%3A%2F%2Fblog.kakaocdn.net%2Fdn%2FO3rzT%2Fbtss9hfOWJD%2FkJ9oHQ04SMn0lBZE9o4vMK%2Fimg.png&quot; onerror=&quot;this.onerror=null; this.src='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png'; this.srcset='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png';&quot; loading=&quot;lazy&quot; width=&quot;1550&quot; height=&quot;522&quot; data-origin-width=&quot;1550&quot; data-origin-height=&quot;522&quot;/&gt;&lt;/span&gt;&lt;/figure&gt;
&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;trip 5개와 해당 trip의 place 들을 조회해야하는 요구사항이 있다고 가정하자. 아래는 요구사항을 위해 작성한 쿼리이다. &lt;span style=&quot;color: #000000; text-align: start;&quot;&gt;Cursor based Pagination 방식을 사용해 cursorId로 offset을 구현했다.&lt;/span&gt;&lt;/p&gt;
&lt;pre id=&quot;code_1693918291211&quot; class=&quot;java&quot; data-ke-language=&quot;java&quot; data-ke-type=&quot;codeblock&quot;&gt;&lt;code&gt;public List&amp;lt;Trip&amp;gt; findAllFetch(Long cursorId, LocalDateTime cursorDate) {
    return queryFactory.selectFrom(trip)
            .innerJoin(trip.tripPlaces, tripPlace).fetchJoin()
            .innerJoin(tripPlace.place, place).fetchJoin()
            .where(cursorGt(cursorId, cursorDate))
            .limit(5)
            .fetch();
}&lt;/code&gt;&lt;/pre&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;실행 후 console 로그를 보니 아래와 같은 경고 로그가 발생했다.&amp;nbsp; &lt;b&gt;HHH90003004:&amp;nbsp;firstResult/maxResults&amp;nbsp;specified&amp;nbsp;with&amp;nbsp;collection&amp;nbsp;fetch;&amp;nbsp;applying&amp;nbsp;in&amp;nbsp;memory&lt;/b&gt;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;쿼리 결과를 전부 메모리에 적재한 뒤 Limit 작업을 어플리케이션 레벨에서 하기 때문에 위험하다는 로그이다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;1개의 trip은 여러 개의 place를 가질 수 있기 때문에 Join 시 아래와 같이 데이터가 반환된다.&lt;/p&gt;
&lt;pre id=&quot;code_1693919768955&quot; class=&quot;sql&quot; data-ke-language=&quot;sql&quot; data-ke-type=&quot;codeblock&quot;&gt;&lt;code&gt;select t.id, t.name, t.meet_place, p.id, p.title, p.address
from trip t
inner join trip_place tp on t.id = tp.trip_id
inner join place p on tp.place_id = p.id&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;1672&quot; data-origin-height=&quot;188&quot;&gt;&lt;span data-url=&quot;https://blog.kakaocdn.net/dn/oYoAF/btss923MqSI/UNjeOEft8RWNaZ4Y2rYor0/img.png&quot; data-phocus=&quot;https://blog.kakaocdn.net/dn/oYoAF/btss923MqSI/UNjeOEft8RWNaZ4Y2rYor0/img.png&quot;&gt;&lt;img src=&quot;https://blog.kakaocdn.net/dn/oYoAF/btss923MqSI/UNjeOEft8RWNaZ4Y2rYor0/img.png&quot; srcset=&quot;https://img1.daumcdn.net/thumb/R1280x0/?scode=mtistory2&amp;fname=https%3A%2F%2Fblog.kakaocdn.net%2Fdn%2FoYoAF%2Fbtss923MqSI%2FUNjeOEft8RWNaZ4Y2rYor0%2Fimg.png&quot; onerror=&quot;this.onerror=null; this.src='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png'; this.srcset='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png';&quot; loading=&quot;lazy&quot; width=&quot;1672&quot; height=&quot;188&quot; data-origin-width=&quot;1672&quot; data-origin-height=&quot;188&quot;/&gt;&lt;/span&gt;&lt;/figure&gt;
&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;trip 데이터가 중복되어 반환되기 때문에 limit으로 원하는 개수의 trip을 가져올 수 없다. 위와 같이 반환된 데이터에서 limit 3을 한다면 1개의 trip만 조회될 것이다. 따라서, Hibernate는 데이터를 모두 가져온 뒤, JVM 메모리 상에서 필터링을 수행한다. 실제로, 실행된 sql 문을 확인해보니 limit절이 존재하지 않는다.&lt;/p&gt;
&lt;pre id=&quot;code_1693919072857&quot; class=&quot;java&quot; data-ke-language=&quot;java&quot; data-ke-type=&quot;codeblock&quot;&gt;&lt;code&gt;Hibernate: 
    select
        t1_0.id,
        t1_0.date_time,
        t1_0.meet_time,
        t1_0.name,
        t2_0.trip_id,
        t2_0.id,
        p1_0.id,
        p1_0.address,
        p1_0.title
    from
        trip t1_0 
    join
        trip_place t2_0 
            on t1_0.id=t2_0.trip_id 
    join
        place p1_0 
            on p1_0.id=t2_0.place_id 
    where
        t1_0.id&amp;gt;?&lt;/code&gt;&lt;/pre&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;h2 data-ke-size=&quot;size26&quot;&gt;&lt;b&gt;문제 해결&lt;/b&gt;&lt;/h2&gt;
&lt;hr contenteditable=&quot;false&quot; data-ke-type=&quot;horizontalRule&quot; data-ke-style=&quot;style6&quot; /&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;그러면, OneToMany 관계에서 fetch Join과 limit 같이 사용할 수 없는 것인가? 사용 가능하다. 아래와 같이 쿼리를 두개로 나누면 사용 가능하다. 첫번째 쿼리에서는 One에 해당하는 객체의 주식별자를 Pagination 으로 가져온다. 두번째 쿼리에서는 IN 절을 사용해 필터링하고, fetch join을 실행한다. 직접 적용해보자.&lt;/p&gt;
&lt;pre id=&quot;code_1693920636899&quot; class=&quot;java&quot; data-ke-language=&quot;java&quot; data-ke-type=&quot;codeblock&quot;&gt;&lt;code&gt;public List&amp;lt;Trip&amp;gt; findAllFetch(Long cursorId, LocalDateTime cursorDate) {
        
        // 1. One에 해당하는 객체의 주식별자를 Pagination으로 가져온다.
        List&amp;lt;Long&amp;gt; ids = queryFactory.select(trip.id)
                .from(trip)
                .where(cursorGt(cursorId, cursorDate))
                .limit(5)
                .fetch();

	// 2. IN 절을 사용해 필터링하고, fetch join을 실행한다.
        return queryFactory.selectFrom(trip)
                .innerJoin(trip.tripPlaces, tripPlace).fetchJoin()
                .innerJoin(tripPlace.place, place).fetchJoin()
                .where(trip.id.in(ids))
                .fetch();
}&lt;/code&gt;&lt;/pre&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;실행된 sql은 아래와 같다. 쿼리 개수는 1개 늘었지만, limit 절이 정상작동해 필요한 개수의 데이터만 가져올 수 있게 되었다.&lt;/p&gt;
&lt;pre id=&quot;code_1693920996051&quot; class=&quot;shell&quot; data-ke-language=&quot;shell&quot; data-ke-type=&quot;codeblock&quot;&gt;&lt;code&gt;Hibernate: 
    select
        t1_0.id 
    from
        trip t1_0 
    where
        t1_0.date_time&amp;gt;? 
        and (
            t1_0.id&amp;gt;? 
            and t1_0.date_time=? 
            or t1_0.date_time&amp;gt;?
        ) limit ?
Hibernate: 
    select
        t1_0.id,
        t1_0.date_time,
        t1_0.meet_place,
        t1_0.name,
        t2_0.trip_id,
        t2_0.id,
        p1_0.id,
        p1_0.address,
        p1_0.title,
    from
        trip t1_0 
    join
        trip_place t2_0 
            on t1_0.id=t2_0.trip_id 
    join
        place p1_0 
            on p1_0.id=t2_0.place_id 
    where
        t1_0.id in (?,?)&lt;/code&gt;&lt;/pre&gt;
&lt;h2 data-ke-size=&quot;size26&quot;&gt;&lt;b&gt;결론&lt;/b&gt;&lt;/h2&gt;
&lt;hr contenteditable=&quot;false&quot; data-ke-type=&quot;horizontalRule&quot; data-ke-style=&quot;style6&quot; /&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;OneToMany 관계에서 fetch join과 limit을 함께 사용해 Pagination 하는 경우 쿼리 결과를 전부 메모리에 적재한 뒤 limit 작업을 어플리케이션 레벨에서 한다. 성능상 문제가 될 수 있으므로, pagination과 fetch join 쿼리를 분리해 limit절이 실행되도록 쿼리를 개선해보자.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;h3 data-ke-size=&quot;size23&quot;&gt;&lt;b&gt;References&lt;/b&gt;&lt;/h3&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;b&gt;&lt;a href=&quot;https://github.com/pci2676/Spring-Data-JPA-Lab/tree/master/fetch-limit&quot; target=&quot;_blank&quot; rel=&quot;noopener&quot;&gt;https://github.com/pci2676/Spring-Data-JPA-Lab/tree/master/fetch-limit&lt;/a&gt;&lt;/b&gt;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;a href=&quot;https://velog.io/@cksdnr066/WARN-firstResultmaxResults-specified-with-collection-fetch-applying-in-memory&quot; target=&quot;_blank&quot; rel=&quot;noopener&quot;&gt;https://velog.io/@cksdnr066/WARN-firstResultmaxResults-specified-with-collection-fetch-applying-in-memory&lt;/a&gt;&lt;/p&gt;</description>
      <category>Server/Spring</category>
      <category>spring</category>
      <author>주인  장</author>
      <guid isPermaLink="true">https://twosky.tistory.com/60</guid>
      <comments>https://twosky.tistory.com/60#entry60comment</comments>
      <pubDate>Tue, 5 Sep 2023 22:47:39 +0900</pubDate>
    </item>
    <item>
      <title>[AWS] Auto Scaling, Elastic Load Balancer 구성해보기</title>
      <link>https://twosky.tistory.com/59</link>
      <description>&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;Elastic Load Balancer의 종류인 Application Load Balancer를 생성해 트래픽을 분산해보자. 첫번째로는, 대상 그룹을 생성 후 인스턴스 2개를 대상으로 지정해 트래픽을 분산해보려 한다. 두번째로는, Auto Scaling Group을 대상 그룹에 연결해 Auto Scaling에 의해 생성된 모든 인스턴스들에 트래픽을 분산해보려 한다. 최종 아키텍처는 아래와 같다.&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;979&quot; data-origin-height=&quot;499&quot;&gt;&lt;span data-url=&quot;https://blog.kakaocdn.net/dn/eGZSoT/btsm9gfD9C1/C4yb3R2ShA24jvTPApP981/img.png&quot; data-phocus=&quot;https://blog.kakaocdn.net/dn/eGZSoT/btsm9gfD9C1/C4yb3R2ShA24jvTPApP981/img.png&quot;&gt;&lt;img src=&quot;https://blog.kakaocdn.net/dn/eGZSoT/btsm9gfD9C1/C4yb3R2ShA24jvTPApP981/img.png&quot; srcset=&quot;https://img1.daumcdn.net/thumb/R1280x0/?scode=mtistory2&amp;fname=https%3A%2F%2Fblog.kakaocdn.net%2Fdn%2FeGZSoT%2Fbtsm9gfD9C1%2FC4yb3R2ShA24jvTPApP981%2Fimg.png&quot; onerror=&quot;this.onerror=null; this.src='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png'; this.srcset='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png';&quot; loading=&quot;lazy&quot; width=&quot;979&quot; height=&quot;499&quot; data-origin-width=&quot;979&quot; data-origin-height=&quot;499&quot;/&gt;&lt;/span&gt;&lt;/figure&gt;
&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;h2 data-ke-size=&quot;size26&quot;&gt;&lt;b&gt;Load Balancer Hands-On&lt;/b&gt;&lt;/h2&gt;
&lt;ol style=&quot;list-style-type: decimal;&quot; data-ke-list-type=&quot;decimal&quot;&gt;
&lt;li&gt;시작 템플릿 버전 수정&lt;/li&gt;
&lt;li&gt;Auto Scaling Group 시작 템플릿 변경&lt;/li&gt;
&lt;li&gt;Target Group 생성&lt;/li&gt;
&lt;li&gt;Application Load Balancer 생성&lt;/li&gt;
&lt;li&gt;Auto Scaling Group을 대상 그룹으로 설정&lt;/li&gt;
&lt;li&gt;Load Balancer 트래픽 분산 테스트&lt;/li&gt;
&lt;/ol&gt;
&lt;h2 data-ke-size=&quot;size26&quot;&gt;&amp;nbsp;&lt;/h2&gt;
&lt;h2 data-ke-size=&quot;size26&quot;&gt;&lt;b&gt;1. 시작 템플릿 생성&lt;/b&gt;&lt;/h2&gt;
&lt;hr contenteditable=&quot;false&quot; data-ke-type=&quot;horizontalRule&quot; data-ke-style=&quot;style6&quot; /&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;시작 템플릿은 Auto Scaling에 의해 생성될 인스턴스의 환경을 구성하는 템플릿이다. 시작 템플릿은 수정할 수 없다. 해당 템플릿에 대해 새로운 버전으로 생성해야 한다.&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 widthContent&quot; data-ke-mobileStyle=&quot;widthOrigin&quot; data-origin-width=&quot;828&quot; data-origin-height=&quot;626&quot;&gt;&lt;span data-url=&quot;https://blog.kakaocdn.net/dn/IZr0N/btsnbJgSI8L/6MsktcpJpWVPH8YTpQv96k/img.png&quot; data-phocus=&quot;https://blog.kakaocdn.net/dn/IZr0N/btsnbJgSI8L/6MsktcpJpWVPH8YTpQv96k/img.png&quot;&gt;&lt;img src=&quot;https://blog.kakaocdn.net/dn/IZr0N/btsnbJgSI8L/6MsktcpJpWVPH8YTpQv96k/img.png&quot; srcset=&quot;https://img1.daumcdn.net/thumb/R1280x0/?scode=mtistory2&amp;fname=https%3A%2F%2Fblog.kakaocdn.net%2Fdn%2FIZr0N%2FbtsnbJgSI8L%2F6MsktcpJpWVPH8YTpQv96k%2Fimg.png&quot; onerror=&quot;this.onerror=null; this.src='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png'; this.srcset='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png';&quot; loading=&quot;lazy&quot; width=&quot;828&quot; height=&quot;626&quot; data-origin-width=&quot;828&quot; data-origin-height=&quot;626&quot;/&gt;&lt;/span&gt;&lt;/figure&gt;
&lt;figure class=&quot;imageblock widthContent&quot; data-ke-mobileStyle=&quot;widthOrigin&quot; data-filename=&quot;blob&quot; data-origin-width=&quot;810&quot; data-origin-height=&quot;530&quot;&gt;&lt;span data-url=&quot;https://blog.kakaocdn.net/dn/b2FOak/btsm9wvPKnQ/1sVE72EeAevPFUECznkNVK/img.png&quot; data-phocus=&quot;https://blog.kakaocdn.net/dn/b2FOak/btsm9wvPKnQ/1sVE72EeAevPFUECznkNVK/img.png&quot;&gt;&lt;img src=&quot;https://blog.kakaocdn.net/dn/b2FOak/btsm9wvPKnQ/1sVE72EeAevPFUECznkNVK/img.png&quot; srcset=&quot;https://img1.daumcdn.net/thumb/R1280x0/?scode=mtistory2&amp;fname=https%3A%2F%2Fblog.kakaocdn.net%2Fdn%2Fb2FOak%2Fbtsm9wvPKnQ%2F1sVE72EeAevPFUECznkNVK%2Fimg.png&quot; onerror=&quot;this.onerror=null; this.src='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png'; this.srcset='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png';&quot; loading=&quot;lazy&quot; width=&quot;810&quot; height=&quot;530&quot; data-filename=&quot;blob&quot; data-origin-width=&quot;810&quot; data-origin-height=&quot;530&quot;/&gt;&lt;/span&gt;&lt;/figure&gt;
&lt;figure class=&quot;imageblock widthContent&quot; data-ke-mobileStyle=&quot;widthOrigin&quot; data-origin-width=&quot;811&quot; data-origin-height=&quot;464&quot;&gt;&lt;span data-url=&quot;https://blog.kakaocdn.net/dn/c6diKd/btsm2Ij0CpP/AeVPsETErV502Nkm1kKOGK/img.png&quot; data-phocus=&quot;https://blog.kakaocdn.net/dn/c6diKd/btsm2Ij0CpP/AeVPsETErV502Nkm1kKOGK/img.png&quot;&gt;&lt;img src=&quot;https://blog.kakaocdn.net/dn/c6diKd/btsm2Ij0CpP/AeVPsETErV502Nkm1kKOGK/img.png&quot; srcset=&quot;https://img1.daumcdn.net/thumb/R1280x0/?scode=mtistory2&amp;fname=https%3A%2F%2Fblog.kakaocdn.net%2Fdn%2Fc6diKd%2Fbtsm2Ij0CpP%2FAeVPsETErV502Nkm1kKOGK%2Fimg.png&quot; onerror=&quot;this.onerror=null; this.src='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png'; this.srcset='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png';&quot; loading=&quot;lazy&quot; width=&quot;811&quot; height=&quot;464&quot; data-origin-width=&quot;811&quot; data-origin-height=&quot;464&quot;/&gt;&lt;/span&gt;&lt;/figure&gt;
&lt;figure class=&quot;imageblock widthContent&quot; data-ke-mobileStyle=&quot;widthOrigin&quot; data-origin-width=&quot;813&quot; data-origin-height=&quot;401&quot;&gt;&lt;span data-url=&quot;https://blog.kakaocdn.net/dn/bq8Mpz/btsm9vRcG4t/92tkWJ1UA4avAJAMAz2mF1/img.png&quot; data-phocus=&quot;https://blog.kakaocdn.net/dn/bq8Mpz/btsm9vRcG4t/92tkWJ1UA4avAJAMAz2mF1/img.png&quot;&gt;&lt;img src=&quot;https://blog.kakaocdn.net/dn/bq8Mpz/btsm9vRcG4t/92tkWJ1UA4avAJAMAz2mF1/img.png&quot; srcset=&quot;https://img1.daumcdn.net/thumb/R1280x0/?scode=mtistory2&amp;fname=https%3A%2F%2Fblog.kakaocdn.net%2Fdn%2Fbq8Mpz%2Fbtsm9vRcG4t%2F92tkWJ1UA4avAJAMAz2mF1%2Fimg.png&quot; onerror=&quot;this.onerror=null; this.src='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png'; this.srcset='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png';&quot; loading=&quot;lazy&quot; width=&quot;813&quot; height=&quot;401&quot; data-origin-width=&quot;813&quot; data-origin-height=&quot;401&quot;/&gt;&lt;/span&gt;&lt;/figure&gt;
&lt;figure class=&quot;imageblock widthContent&quot; data-ke-mobileStyle=&quot;widthOrigin&quot; data-origin-width=&quot;814&quot; data-origin-height=&quot;695&quot;&gt;&lt;span data-url=&quot;https://blog.kakaocdn.net/dn/bPJjAk/btsnaSrA4JE/H9Aq2ptbkbm0qVgyed0tw1/img.png&quot; data-phocus=&quot;https://blog.kakaocdn.net/dn/bPJjAk/btsnaSrA4JE/H9Aq2ptbkbm0qVgyed0tw1/img.png&quot;&gt;&lt;img src=&quot;https://blog.kakaocdn.net/dn/bPJjAk/btsnaSrA4JE/H9Aq2ptbkbm0qVgyed0tw1/img.png&quot; srcset=&quot;https://img1.daumcdn.net/thumb/R1280x0/?scode=mtistory2&amp;fname=https%3A%2F%2Fblog.kakaocdn.net%2Fdn%2FbPJjAk%2FbtsnaSrA4JE%2FH9Aq2ptbkbm0qVgyed0tw1%2Fimg.png&quot; onerror=&quot;this.onerror=null; this.src='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png'; this.srcset='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png';&quot; loading=&quot;lazy&quot; width=&quot;814&quot; height=&quot;695&quot; data-origin-width=&quot;814&quot; data-origin-height=&quot;695&quot;/&gt;&lt;/span&gt;&lt;/figure&gt;
&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;고급 세부 정보의 사용자 데이터 값에 아래 코드를 붙여넣자. 아래 코드는 EC2 인스턴스에 apache2 서버를 설치하고, index.html을 수정해 요청이 전달된 인스턴스의 ID를 화면에 보여주는 코드이다.&lt;/p&gt;
&lt;p&gt;&lt;figure class=&quot;imageblock widthContent&quot; data-ke-mobileStyle=&quot;widthOrigin&quot; data-filename=&quot;스크린샷 2023-07-03 오후 7.17.49.png&quot; data-origin-width=&quot;1592&quot; data-origin-height=&quot;326&quot;&gt;&lt;span data-url=&quot;https://blog.kakaocdn.net/dn/buLSb0/btsm9u5R1UQ/00CYC2Q4zyhDZIUReq69bk/img.png&quot; data-phocus=&quot;https://blog.kakaocdn.net/dn/buLSb0/btsm9u5R1UQ/00CYC2Q4zyhDZIUReq69bk/img.png&quot;&gt;&lt;img src=&quot;https://blog.kakaocdn.net/dn/buLSb0/btsm9u5R1UQ/00CYC2Q4zyhDZIUReq69bk/img.png&quot; srcset=&quot;https://img1.daumcdn.net/thumb/R1280x0/?scode=mtistory2&amp;fname=https%3A%2F%2Fblog.kakaocdn.net%2Fdn%2FbuLSb0%2Fbtsm9u5R1UQ%2F00CYC2Q4zyhDZIUReq69bk%2Fimg.png&quot; onerror=&quot;this.onerror=null; this.src='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png'; this.srcset='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png';&quot; loading=&quot;lazy&quot; width=&quot;1592&quot; height=&quot;326&quot; data-filename=&quot;스크린샷 2023-07-03 오후 7.17.49.png&quot; data-origin-width=&quot;1592&quot; data-origin-height=&quot;326&quot;/&gt;&lt;/span&gt;&lt;/figure&gt;
&lt;/p&gt;
&lt;pre id=&quot;code_1693209474379&quot; class=&quot;shell&quot; data-ke-language=&quot;shell&quot; data-ke-type=&quot;codeblock&quot;&gt;&lt;code&gt;#!/bin/bash

# 자신의 인스턴스 ID 문자값을 변수에 저장
INSTANCE_ID=$(curl -s http://169.254.169.254/latest/meta-data/instance-id)

# 아파치 서버 설치
sudo apt-get install apache2 -y

# 쓰기 권한 부여
sudo chmod -R 777 /var/www/html

# 기존 index.html 삭제
rm -rf /var/www/html/index.html

# INSTANCE_ID 변수값을 html에 이어쓰기
echo &quot;&quot;$INSTANCE_ID&quot;&quot; &amp;gt;&amp;gt; /var/www/html/index.html

# 아파치 시작
sudo service apache2 start&lt;/code&gt;&lt;/pre&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;h2 data-ke-size=&quot;size26&quot;&gt;&lt;b&gt;2.&amp;nbsp; Auto Scaling Group 생성&lt;/b&gt;&lt;/h2&gt;
&lt;hr contenteditable=&quot;false&quot; data-ke-type=&quot;horizontalRule&quot; data-ke-style=&quot;style6&quot; /&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;1. Auto Scaling Group 생성&lt;/p&gt;
&lt;p&gt;&lt;figure class=&quot;imageblock widthContent&quot; data-ke-mobileStyle=&quot;widthOrigin&quot; data-origin-width=&quot;746&quot; data-origin-height=&quot;526&quot;&gt;&lt;span data-url=&quot;https://blog.kakaocdn.net/dn/TuAbA/btsm82PhCtj/VW1pKY0OEETkofkUFuL3t0/img.png&quot; data-phocus=&quot;https://blog.kakaocdn.net/dn/TuAbA/btsm82PhCtj/VW1pKY0OEETkofkUFuL3t0/img.png&quot;&gt;&lt;img src=&quot;https://blog.kakaocdn.net/dn/TuAbA/btsm82PhCtj/VW1pKY0OEETkofkUFuL3t0/img.png&quot; srcset=&quot;https://img1.daumcdn.net/thumb/R1280x0/?scode=mtistory2&amp;fname=https%3A%2F%2Fblog.kakaocdn.net%2Fdn%2FTuAbA%2Fbtsm82PhCtj%2FVW1pKY0OEETkofkUFuL3t0%2Fimg.png&quot; onerror=&quot;this.onerror=null; this.src='//t1.daumcdn.net/tistory_admin/static/images/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;526&quot; data-origin-width=&quot;746&quot; data-origin-height=&quot;526&quot;/&gt;&lt;/span&gt;&lt;/figure&gt;
&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;VPC와 가용영역을 선택한다. 아래의 경우 가용 영역 2a, 2b, 2c에 Auto Scaling에 의해 생성된 인스턴스들이 배치된다.&lt;/p&gt;
&lt;p&gt;&lt;figure class=&quot;imageblock widthContent&quot; data-ke-mobileStyle=&quot;widthOrigin&quot; data-origin-width=&quot;734&quot; data-origin-height=&quot;741&quot;&gt;&lt;span data-url=&quot;https://blog.kakaocdn.net/dn/bAwVnr/btsm8QuNCej/VK6cjvQ0RczJInN4PJmXrK/img.png&quot; data-phocus=&quot;https://blog.kakaocdn.net/dn/bAwVnr/btsm8QuNCej/VK6cjvQ0RczJInN4PJmXrK/img.png&quot;&gt;&lt;img src=&quot;https://blog.kakaocdn.net/dn/bAwVnr/btsm8QuNCej/VK6cjvQ0RczJInN4PJmXrK/img.png&quot; srcset=&quot;https://img1.daumcdn.net/thumb/R1280x0/?scode=mtistory2&amp;fname=https%3A%2F%2Fblog.kakaocdn.net%2Fdn%2FbAwVnr%2Fbtsm8QuNCej%2FVK6cjvQ0RczJInN4PJmXrK%2Fimg.png&quot; onerror=&quot;this.onerror=null; this.src='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png'; this.srcset='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png';&quot; loading=&quot;lazy&quot; width=&quot;734&quot; height=&quot;741&quot; data-origin-width=&quot;734&quot; data-origin-height=&quot;741&quot;/&gt;&lt;/span&gt;&lt;/figure&gt;
&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;로드 밸런서는 추후 생성 후 Auto Scaling 그룹과 연결시켜줄 예정이다. 일단은 없음으로 체크&lt;/p&gt;
&lt;p&gt;&lt;figure class=&quot;imageblock widthContent&quot; data-ke-mobileStyle=&quot;widthOrigin&quot; data-origin-width=&quot;735&quot; data-origin-height=&quot;419&quot;&gt;&lt;span data-url=&quot;https://blog.kakaocdn.net/dn/bY8uuD/btsm8NLEmF7/mRCrQGyPtoBDxk1fpyKEp1/img.png&quot; data-phocus=&quot;https://blog.kakaocdn.net/dn/bY8uuD/btsm8NLEmF7/mRCrQGyPtoBDxk1fpyKEp1/img.png&quot;&gt;&lt;img src=&quot;https://blog.kakaocdn.net/dn/bY8uuD/btsm8NLEmF7/mRCrQGyPtoBDxk1fpyKEp1/img.png&quot; srcset=&quot;https://img1.daumcdn.net/thumb/R1280x0/?scode=mtistory2&amp;fname=https%3A%2F%2Fblog.kakaocdn.net%2Fdn%2FbY8uuD%2Fbtsm8NLEmF7%2FmRCrQGyPtoBDxk1fpyKEp1%2Fimg.png&quot; onerror=&quot;this.onerror=null; this.src='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png'; this.srcset='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png';&quot; loading=&quot;lazy&quot; width=&quot;735&quot; height=&quot;419&quot; data-origin-width=&quot;735&quot; data-origin-height=&quot;419&quot;/&gt;&lt;/span&gt;&lt;/figure&gt;
&lt;figure class=&quot;imageblock alignLeft&quot; data-ke-mobileStyle=&quot;widthOrigin&quot; data-origin-width=&quot;735&quot; data-origin-height=&quot;507&quot;&gt;&lt;span data-url=&quot;https://blog.kakaocdn.net/dn/E0F6G/btsm8IKv6i4/pJFqHGidkwcaxgmObo9vP1/img.png&quot; data-phocus=&quot;https://blog.kakaocdn.net/dn/E0F6G/btsm8IKv6i4/pJFqHGidkwcaxgmObo9vP1/img.png&quot;&gt;&lt;img src=&quot;https://blog.kakaocdn.net/dn/E0F6G/btsm8IKv6i4/pJFqHGidkwcaxgmObo9vP1/img.png&quot; srcset=&quot;https://img1.daumcdn.net/thumb/R1280x0/?scode=mtistory2&amp;fname=https%3A%2F%2Fblog.kakaocdn.net%2Fdn%2FE0F6G%2Fbtsm8IKv6i4%2FpJFqHGidkwcaxgmObo9vP1%2Fimg.png&quot; onerror=&quot;this.onerror=null; this.src='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png'; this.srcset='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png';&quot; loading=&quot;lazy&quot; width=&quot;735&quot; height=&quot;507&quot; data-origin-width=&quot;735&quot; data-origin-height=&quot;507&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;2. Auto Scaling을 통해 생성된 인스턴스 확인&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;위에서 설정한 인스턴스 개수에 따라 2개의 인스턴스가 생성된 것을 확인할 수 있다.&lt;/p&gt;
&lt;p&gt;&lt;figure class=&quot;imageblock widthContent&quot; data-ke-mobileStyle=&quot;widthOrigin&quot; data-origin-width=&quot;2232&quot; data-origin-height=&quot;770&quot;&gt;&lt;span data-url=&quot;https://blog.kakaocdn.net/dn/cCLAJv/btsnaTjJ65E/Z5qh3k6F1ronSyc2cUENSk/img.png&quot; data-phocus=&quot;https://blog.kakaocdn.net/dn/cCLAJv/btsnaTjJ65E/Z5qh3k6F1ronSyc2cUENSk/img.png&quot;&gt;&lt;img src=&quot;https://blog.kakaocdn.net/dn/cCLAJv/btsnaTjJ65E/Z5qh3k6F1ronSyc2cUENSk/img.png&quot; srcset=&quot;https://img1.daumcdn.net/thumb/R1280x0/?scode=mtistory2&amp;fname=https%3A%2F%2Fblog.kakaocdn.net%2Fdn%2FcCLAJv%2FbtsnaTjJ65E%2FZ5qh3k6F1ronSyc2cUENSk%2Fimg.png&quot; onerror=&quot;this.onerror=null; this.src='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png'; this.srcset='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png';&quot; loading=&quot;lazy&quot; width=&quot;2232&quot; height=&quot;770&quot; data-origin-width=&quot;2232&quot; data-origin-height=&quot;770&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;3. 인스턴스 IP로 접속해 아래와 같이 해당 인스턴스 ID가 뜨는지 확인&lt;/p&gt;
&lt;p&gt;&lt;figure class=&quot;imageblock alignLeft&quot; data-ke-mobileStyle=&quot;widthOrigin&quot; data-filename=&quot;스크린샷 2023-07-03 오후 10.49.45.png&quot; data-origin-width=&quot;696&quot; data-origin-height=&quot;180&quot;&gt;&lt;span data-url=&quot;https://blog.kakaocdn.net/dn/XmUbb/btsm8g8n9qP/DaORGfs9BtiuQJT6PzJ2P1/img.png&quot; data-phocus=&quot;https://blog.kakaocdn.net/dn/XmUbb/btsm8g8n9qP/DaORGfs9BtiuQJT6PzJ2P1/img.png&quot;&gt;&lt;img src=&quot;https://blog.kakaocdn.net/dn/XmUbb/btsm8g8n9qP/DaORGfs9BtiuQJT6PzJ2P1/img.png&quot; srcset=&quot;https://img1.daumcdn.net/thumb/R1280x0/?scode=mtistory2&amp;fname=https%3A%2F%2Fblog.kakaocdn.net%2Fdn%2FXmUbb%2Fbtsm8g8n9qP%2FDaORGfs9BtiuQJT6PzJ2P1%2Fimg.png&quot; onerror=&quot;this.onerror=null; this.src='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png'; this.srcset='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png';&quot; loading=&quot;lazy&quot; width=&quot;646&quot; height=&quot;167&quot; data-filename=&quot;스크린샷 2023-07-03 오후 10.49.45.png&quot; data-origin-width=&quot;696&quot; data-origin-height=&quot;180&quot;/&gt;&lt;/span&gt;&lt;/figure&gt;
&lt;/p&gt;
&lt;h2 data-ke-size=&quot;size26&quot;&gt;&lt;b&gt;3. Target Group 생성&lt;/b&gt;&lt;/h2&gt;
&lt;hr contenteditable=&quot;false&quot; data-ke-type=&quot;horizontalRule&quot; data-ke-style=&quot;style6&quot; /&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;1. 대상 그룹 생성&lt;/p&gt;
&lt;p&gt;&lt;figure class=&quot;imageblock widthContent&quot; data-ke-mobileStyle=&quot;widthOrigin&quot; data-filename=&quot;스크린샷 2023-07-03 오후 10.53.06.png&quot; data-origin-width=&quot;2282&quot; data-origin-height=&quot;458&quot;&gt;&lt;span data-url=&quot;https://blog.kakaocdn.net/dn/bIrlwo/btsm8OX4OiG/rQuYNYqdS56rJnJQVBKuT0/img.png&quot; data-phocus=&quot;https://blog.kakaocdn.net/dn/bIrlwo/btsm8OX4OiG/rQuYNYqdS56rJnJQVBKuT0/img.png&quot;&gt;&lt;img src=&quot;https://blog.kakaocdn.net/dn/bIrlwo/btsm8OX4OiG/rQuYNYqdS56rJnJQVBKuT0/img.png&quot; srcset=&quot;https://img1.daumcdn.net/thumb/R1280x0/?scode=mtistory2&amp;fname=https%3A%2F%2Fblog.kakaocdn.net%2Fdn%2FbIrlwo%2Fbtsm8OX4OiG%2FrQuYNYqdS56rJnJQVBKuT0%2Fimg.png&quot; onerror=&quot;this.onerror=null; this.src='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png'; this.srcset='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png';&quot; loading=&quot;lazy&quot; width=&quot;2282&quot; height=&quot;458&quot; data-filename=&quot;스크린샷 2023-07-03 오후 10.53.06.png&quot; data-origin-width=&quot;2282&quot; data-origin-height=&quot;458&quot;/&gt;&lt;/span&gt;&lt;/figure&gt;
&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;Instance 선택, 대상 그룹 이름 설정&lt;/p&gt;
&lt;p&gt;&lt;figure class=&quot;imageblock widthContent&quot; data-ke-mobileStyle=&quot;widthOrigin&quot; data-filename=&quot;스크린샷 2023-07-03 오후 10.54.52.png&quot; data-origin-width=&quot;1644&quot; data-origin-height=&quot;1448&quot;&gt;&lt;span data-url=&quot;https://blog.kakaocdn.net/dn/cMvXBF/btsm82BLxMV/ZHKFzOXK7kHJ6ql6VlSkAk/img.png&quot; data-phocus=&quot;https://blog.kakaocdn.net/dn/cMvXBF/btsm82BLxMV/ZHKFzOXK7kHJ6ql6VlSkAk/img.png&quot;&gt;&lt;img src=&quot;https://blog.kakaocdn.net/dn/cMvXBF/btsm82BLxMV/ZHKFzOXK7kHJ6ql6VlSkAk/img.png&quot; srcset=&quot;https://img1.daumcdn.net/thumb/R1280x0/?scode=mtistory2&amp;fname=https%3A%2F%2Fblog.kakaocdn.net%2Fdn%2FcMvXBF%2Fbtsm82BLxMV%2FZHKFzOXK7kHJ6ql6VlSkAk%2Fimg.png&quot; onerror=&quot;this.onerror=null; this.src='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png'; this.srcset='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png';&quot; loading=&quot;lazy&quot; width=&quot;1644&quot; height=&quot;1448&quot; data-filename=&quot;스크린샷 2023-07-03 오후 10.54.52.png&quot; data-origin-width=&quot;1644&quot; data-origin-height=&quot;1448&quot;/&gt;&lt;/span&gt;&lt;/figure&gt;
&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;health check 경로 설정&lt;/p&gt;
&lt;p&gt;&lt;figure class=&quot;imageblock widthContent&quot; data-ke-mobileStyle=&quot;widthOrigin&quot; data-origin-width=&quot;2084&quot; data-origin-height=&quot;682&quot;&gt;&lt;span data-url=&quot;https://blog.kakaocdn.net/dn/cn8K3X/btsnalt3hEd/pAH5Xhsrh6x1oMW7lZVK0K/img.png&quot; data-phocus=&quot;https://blog.kakaocdn.net/dn/cn8K3X/btsnalt3hEd/pAH5Xhsrh6x1oMW7lZVK0K/img.png&quot;&gt;&lt;img src=&quot;https://blog.kakaocdn.net/dn/cn8K3X/btsnalt3hEd/pAH5Xhsrh6x1oMW7lZVK0K/img.png&quot; srcset=&quot;https://img1.daumcdn.net/thumb/R1280x0/?scode=mtistory2&amp;fname=https%3A%2F%2Fblog.kakaocdn.net%2Fdn%2Fcn8K3X%2Fbtsnalt3hEd%2FpAH5Xhsrh6x1oMW7lZVK0K%2Fimg.png&quot; onerror=&quot;this.onerror=null; this.src='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png'; this.srcset='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png';&quot; loading=&quot;lazy&quot; width=&quot;2084&quot; height=&quot;682&quot; data-origin-width=&quot;2084&quot; data-origin-height=&quot;682&quot;/&gt;&lt;/span&gt;&lt;/figure&gt;
&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;현재 존재하는 인스턴스 2개 선택 후 보류 그룹으로 포함 버튼 클릭 (이전에 Auto Scaling에 의해 생성된 인스턴스)&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;외부의 트래픽을 80포트로 연결 (아래에서 트래픽을 전달할 포트를 80으로 설정했으므로)&lt;/p&gt;
&lt;p&gt;&lt;figure class=&quot;imageblock widthContent&quot; data-ke-mobileStyle=&quot;widthOrigin&quot; data-filename=&quot;스크린샷 2023-07-03 오후 10.55.55.png&quot; data-origin-width=&quot;1894&quot; data-origin-height=&quot;1130&quot;&gt;&lt;span data-url=&quot;https://blog.kakaocdn.net/dn/d7G4C5/btsnaL65iEr/DrCkk5gq89bhxFK6uZFU9k/img.png&quot; data-phocus=&quot;https://blog.kakaocdn.net/dn/d7G4C5/btsnaL65iEr/DrCkk5gq89bhxFK6uZFU9k/img.png&quot;&gt;&lt;img src=&quot;https://blog.kakaocdn.net/dn/d7G4C5/btsnaL65iEr/DrCkk5gq89bhxFK6uZFU9k/img.png&quot; srcset=&quot;https://img1.daumcdn.net/thumb/R1280x0/?scode=mtistory2&amp;fname=https%3A%2F%2Fblog.kakaocdn.net%2Fdn%2Fd7G4C5%2FbtsnaL65iEr%2FDrCkk5gq89bhxFK6uZFU9k%2Fimg.png&quot; onerror=&quot;this.onerror=null; this.src='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png'; this.srcset='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png';&quot; loading=&quot;lazy&quot; width=&quot;1894&quot; height=&quot;1130&quot; data-filename=&quot;스크린샷 2023-07-03 오후 10.55.55.png&quot; data-origin-width=&quot;1894&quot; data-origin-height=&quot;1130&quot;/&gt;&lt;/span&gt;&lt;/figure&gt;
&lt;figure class=&quot;imageblock widthContent&quot; data-ke-mobileStyle=&quot;widthOrigin&quot; data-filename=&quot;스크린샷 2023-07-03 오후 10.56.25.png&quot; data-origin-width=&quot;2168&quot; data-origin-height=&quot;718&quot;&gt;&lt;span data-url=&quot;https://blog.kakaocdn.net/dn/bVOsq8/btsnak9JO0b/KP26cLVTkjBYpUJZfrKgNK/img.png&quot; data-phocus=&quot;https://blog.kakaocdn.net/dn/bVOsq8/btsnak9JO0b/KP26cLVTkjBYpUJZfrKgNK/img.png&quot;&gt;&lt;img src=&quot;https://blog.kakaocdn.net/dn/bVOsq8/btsnak9JO0b/KP26cLVTkjBYpUJZfrKgNK/img.png&quot; srcset=&quot;https://img1.daumcdn.net/thumb/R1280x0/?scode=mtistory2&amp;fname=https%3A%2F%2Fblog.kakaocdn.net%2Fdn%2FbVOsq8%2Fbtsnak9JO0b%2FKP26cLVTkjBYpUJZfrKgNK%2Fimg.png&quot; onerror=&quot;this.onerror=null; this.src='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png'; this.srcset='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png';&quot; loading=&quot;lazy&quot; width=&quot;2168&quot; height=&quot;718&quot; data-filename=&quot;스크린샷 2023-07-03 오후 10.56.25.png&quot; data-origin-width=&quot;2168&quot; data-origin-height=&quot;718&quot;/&gt;&lt;/span&gt;&lt;/figure&gt;
&lt;/p&gt;
&lt;h2 data-ke-size=&quot;size26&quot;&gt;&amp;nbsp;&lt;/h2&gt;
&lt;h2 data-ke-size=&quot;size26&quot;&gt;&lt;b&gt;4.&amp;nbsp; Application Load Balancer 생성&lt;/b&gt;&lt;/h2&gt;
&lt;hr contenteditable=&quot;false&quot; data-ke-type=&quot;horizontalRule&quot; data-ke-style=&quot;style6&quot; /&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;1. 로드밸런서 생성&lt;/p&gt;
&lt;p&gt;&lt;figure class=&quot;imageblock widthContent&quot; data-ke-mobileStyle=&quot;widthOrigin&quot; data-filename=&quot;스크린샷 2023-07-03 오후 10.59.25.png&quot; data-origin-width=&quot;2194&quot; data-origin-height=&quot;466&quot;&gt;&lt;span data-url=&quot;https://blog.kakaocdn.net/dn/kw5MX/btsm5s85IAY/0khiROgqnaEF0ky7KoW5zk/img.png&quot; data-phocus=&quot;https://blog.kakaocdn.net/dn/kw5MX/btsm5s85IAY/0khiROgqnaEF0ky7KoW5zk/img.png&quot;&gt;&lt;img src=&quot;https://blog.kakaocdn.net/dn/kw5MX/btsm5s85IAY/0khiROgqnaEF0ky7KoW5zk/img.png&quot; srcset=&quot;https://img1.daumcdn.net/thumb/R1280x0/?scode=mtistory2&amp;fname=https%3A%2F%2Fblog.kakaocdn.net%2Fdn%2Fkw5MX%2Fbtsm5s85IAY%2F0khiROgqnaEF0ky7KoW5zk%2Fimg.png&quot; onerror=&quot;this.onerror=null; this.src='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png'; this.srcset='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png';&quot; loading=&quot;lazy&quot; width=&quot;2194&quot; height=&quot;466&quot; data-filename=&quot;스크린샷 2023-07-03 오후 10.59.25.png&quot; data-origin-width=&quot;2194&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;Application Load Balancer 선택&lt;/p&gt;
&lt;p&gt;&lt;figure class=&quot;imageblock widthContent&quot; data-ke-mobileStyle=&quot;widthOrigin&quot; data-filename=&quot;스크린샷 2023-07-03 오후 10.59.56.png&quot; data-origin-width=&quot;1648&quot; data-origin-height=&quot;1250&quot;&gt;&lt;span data-url=&quot;https://blog.kakaocdn.net/dn/b0pDsc/btsm9gNy2R2/IC7wD5sySbHSKqv4FqRSk0/img.png&quot; data-phocus=&quot;https://blog.kakaocdn.net/dn/b0pDsc/btsm9gNy2R2/IC7wD5sySbHSKqv4FqRSk0/img.png&quot;&gt;&lt;img src=&quot;https://blog.kakaocdn.net/dn/b0pDsc/btsm9gNy2R2/IC7wD5sySbHSKqv4FqRSk0/img.png&quot; srcset=&quot;https://img1.daumcdn.net/thumb/R1280x0/?scode=mtistory2&amp;fname=https%3A%2F%2Fblog.kakaocdn.net%2Fdn%2Fb0pDsc%2Fbtsm9gNy2R2%2FIC7wD5sySbHSKqv4FqRSk0%2Fimg.png&quot; onerror=&quot;this.onerror=null; this.src='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png'; this.srcset='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png';&quot; loading=&quot;lazy&quot; width=&quot;1648&quot; height=&quot;1250&quot; data-filename=&quot;스크린샷 2023-07-03 오후 10.59.56.png&quot; data-origin-width=&quot;1648&quot; data-origin-height=&quot;1250&quot;/&gt;&lt;/span&gt;&lt;/figure&gt;
&lt;figure class=&quot;imageblock widthContent&quot; data-ke-mobileStyle=&quot;widthOrigin&quot; data-filename=&quot;스크린샷 2023-07-03 오후 11.01.18.png&quot; data-origin-width=&quot;1746&quot; data-origin-height=&quot;680&quot;&gt;&lt;span data-url=&quot;https://blog.kakaocdn.net/dn/ADNw4/btsnaml9W7i/D30mikS9R7VekdZYSmmSf0/img.png&quot; data-phocus=&quot;https://blog.kakaocdn.net/dn/ADNw4/btsnaml9W7i/D30mikS9R7VekdZYSmmSf0/img.png&quot;&gt;&lt;img src=&quot;https://blog.kakaocdn.net/dn/ADNw4/btsnaml9W7i/D30mikS9R7VekdZYSmmSf0/img.png&quot; srcset=&quot;https://img1.daumcdn.net/thumb/R1280x0/?scode=mtistory2&amp;fname=https%3A%2F%2Fblog.kakaocdn.net%2Fdn%2FADNw4%2Fbtsnaml9W7i%2FD30mikS9R7VekdZYSmmSf0%2Fimg.png&quot; onerror=&quot;this.onerror=null; this.src='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png'; this.srcset='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png';&quot; loading=&quot;lazy&quot; width=&quot;1746&quot; height=&quot;680&quot; data-filename=&quot;스크린샷 2023-07-03 오후 11.01.18.png&quot; data-origin-width=&quot;1746&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;로드 밸런서 가용영역 선택 (2a, 2b, 2c)&lt;/p&gt;
&lt;p&gt;&lt;figure class=&quot;imageblock widthContent&quot; data-ke-mobileStyle=&quot;widthOrigin&quot; data-filename=&quot;스크린샷 2023-07-03 오후 11.02.03.png&quot; data-origin-width=&quot;1526&quot; data-origin-height=&quot;1440&quot;&gt;&lt;span data-url=&quot;https://blog.kakaocdn.net/dn/bqMPV4/btsm8gN4mfN/SLmkakGY1HhgDKhFkCdekK/img.png&quot; data-phocus=&quot;https://blog.kakaocdn.net/dn/bqMPV4/btsm8gN4mfN/SLmkakGY1HhgDKhFkCdekK/img.png&quot;&gt;&lt;img src=&quot;https://blog.kakaocdn.net/dn/bqMPV4/btsm8gN4mfN/SLmkakGY1HhgDKhFkCdekK/img.png&quot; srcset=&quot;https://img1.daumcdn.net/thumb/R1280x0/?scode=mtistory2&amp;fname=https%3A%2F%2Fblog.kakaocdn.net%2Fdn%2FbqMPV4%2Fbtsm8gN4mfN%2FSLmkakGY1HhgDKhFkCdekK%2Fimg.png&quot; onerror=&quot;this.onerror=null; this.src='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png'; this.srcset='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png';&quot; loading=&quot;lazy&quot; width=&quot;1526&quot; height=&quot;1440&quot; data-filename=&quot;스크린샷 2023-07-03 오후 11.02.03.png&quot; data-origin-width=&quot;1526&quot; data-origin-height=&quot;1440&quot;/&gt;&lt;/span&gt;&lt;/figure&gt;
&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;보안 그룹 default 선택&lt;/p&gt;
&lt;p&gt;&lt;figure class=&quot;imageblock widthContent&quot; data-ke-mobileStyle=&quot;widthOrigin&quot; data-filename=&quot;스크린샷 2023-07-03 오후 11.02.40.png&quot; data-origin-width=&quot;1438&quot; data-origin-height=&quot;538&quot;&gt;&lt;span data-url=&quot;https://blog.kakaocdn.net/dn/dbefsb/btsm4CYpmyj/txNZeVhVteRHTAwEs76Lc0/img.png&quot; data-phocus=&quot;https://blog.kakaocdn.net/dn/dbefsb/btsm4CYpmyj/txNZeVhVteRHTAwEs76Lc0/img.png&quot;&gt;&lt;img src=&quot;https://blog.kakaocdn.net/dn/dbefsb/btsm4CYpmyj/txNZeVhVteRHTAwEs76Lc0/img.png&quot; srcset=&quot;https://img1.daumcdn.net/thumb/R1280x0/?scode=mtistory2&amp;fname=https%3A%2F%2Fblog.kakaocdn.net%2Fdn%2Fdbefsb%2Fbtsm4CYpmyj%2FtxNZeVhVteRHTAwEs76Lc0%2Fimg.png&quot; onerror=&quot;this.onerror=null; this.src='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png'; this.srcset='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png';&quot; loading=&quot;lazy&quot; width=&quot;1438&quot; height=&quot;538&quot; data-filename=&quot;스크린샷 2023-07-03 오후 11.02.40.png&quot; data-origin-width=&quot;1438&quot; data-origin-height=&quot;538&quot;/&gt;&lt;/span&gt;&lt;/figure&gt;
&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;80 포트로 들어오는 HTTP 요청은 위에서 생성한 대상그룹으로 전달하도록 리스너 설정&lt;/p&gt;
&lt;p&gt;&lt;figure class=&quot;imageblock widthContent&quot; data-ke-mobileStyle=&quot;widthOrigin&quot; data-filename=&quot;스크린샷 2023-07-03 오후 11.03.23.png&quot; data-origin-width=&quot;2186&quot; data-origin-height=&quot;616&quot;&gt;&lt;span data-url=&quot;https://blog.kakaocdn.net/dn/bjcCzY/btsnaTKNJd4/Xak0BsVA2qt1cvpPUX9lzk/img.png&quot; data-phocus=&quot;https://blog.kakaocdn.net/dn/bjcCzY/btsnaTKNJd4/Xak0BsVA2qt1cvpPUX9lzk/img.png&quot;&gt;&lt;img src=&quot;https://blog.kakaocdn.net/dn/bjcCzY/btsnaTKNJd4/Xak0BsVA2qt1cvpPUX9lzk/img.png&quot; srcset=&quot;https://img1.daumcdn.net/thumb/R1280x0/?scode=mtistory2&amp;fname=https%3A%2F%2Fblog.kakaocdn.net%2Fdn%2FbjcCzY%2FbtsnaTKNJd4%2FXak0BsVA2qt1cvpPUX9lzk%2Fimg.png&quot; onerror=&quot;this.onerror=null; this.src='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png'; this.srcset='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png';&quot; loading=&quot;lazy&quot; width=&quot;2186&quot; height=&quot;616&quot; data-filename=&quot;스크린샷 2023-07-03 오후 11.03.23.png&quot; data-origin-width=&quot;2186&quot; data-origin-height=&quot;616&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;2. 생성한 Application Load Balancer의 DNS로 접속&lt;/p&gt;
&lt;p&gt;&lt;figure class=&quot;imageblock widthContent&quot; data-ke-mobileStyle=&quot;widthOrigin&quot; data-filename=&quot;스크린샷 2023-07-03 오후 11.09.05.png&quot; data-origin-width=&quot;2350&quot; data-origin-height=&quot;992&quot;&gt;&lt;span data-url=&quot;https://blog.kakaocdn.net/dn/dYEwPC/btsm8g1AFks/tqhwogKy8udrDrONMdaBi1/img.png&quot; data-phocus=&quot;https://blog.kakaocdn.net/dn/dYEwPC/btsm8g1AFks/tqhwogKy8udrDrONMdaBi1/img.png&quot;&gt;&lt;img src=&quot;https://blog.kakaocdn.net/dn/dYEwPC/btsm8g1AFks/tqhwogKy8udrDrONMdaBi1/img.png&quot; srcset=&quot;https://img1.daumcdn.net/thumb/R1280x0/?scode=mtistory2&amp;fname=https%3A%2F%2Fblog.kakaocdn.net%2Fdn%2FdYEwPC%2Fbtsm8g1AFks%2FtqhwogKy8udrDrONMdaBi1%2Fimg.png&quot; onerror=&quot;this.onerror=null; this.src='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png'; this.srcset='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png';&quot; loading=&quot;lazy&quot; width=&quot;2350&quot; height=&quot;992&quot; data-filename=&quot;스크린샷 2023-07-03 오후 11.09.05.png&quot; data-origin-width=&quot;2350&quot; data-origin-height=&quot;992&quot;/&gt;&lt;/span&gt;&lt;/figure&gt;
&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;아래와 같이 대상 그룹으로 설정한 인스턴스 2개에 대해서 트래픽이 분산되는 것을 확인&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;아래 화면은 요청이 전달된 인스턴스의 ID 이다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p&gt;&lt;figure class=&quot;imageblock alignLeft&quot; data-ke-mobileStyle=&quot;widthOrigin&quot; data-filename=&quot;스크린샷 2023-07-03 오후 11.10.35.png&quot; data-origin-width=&quot;1306&quot; data-origin-height=&quot;186&quot;&gt;&lt;span data-url=&quot;https://blog.kakaocdn.net/dn/titRK/btsnbJgSYwg/vs9q4jAywcQVGxyo3CewQK/img.png&quot; data-phocus=&quot;https://blog.kakaocdn.net/dn/titRK/btsnbJgSYwg/vs9q4jAywcQVGxyo3CewQK/img.png&quot;&gt;&lt;img src=&quot;https://blog.kakaocdn.net/dn/titRK/btsnbJgSYwg/vs9q4jAywcQVGxyo3CewQK/img.png&quot; srcset=&quot;https://img1.daumcdn.net/thumb/R1280x0/?scode=mtistory2&amp;fname=https%3A%2F%2Fblog.kakaocdn.net%2Fdn%2FtitRK%2FbtsnbJgSYwg%2Fvs9q4jAywcQVGxyo3CewQK%2Fimg.png&quot; onerror=&quot;this.onerror=null; this.src='//t1.daumcdn.net/tistory_admin/static/images/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;102&quot; data-filename=&quot;스크린샷 2023-07-03 오후 11.10.35.png&quot; data-origin-width=&quot;1306&quot; data-origin-height=&quot;186&quot;/&gt;&lt;/span&gt;&lt;/figure&gt;
&lt;figure class=&quot;imageblock alignLeft&quot; data-ke-mobileStyle=&quot;widthOrigin&quot; data-filename=&quot;스크린샷 2023-07-03 오후 11.10.41.png&quot; data-origin-width=&quot;1246&quot; data-origin-height=&quot;176&quot;&gt;&lt;span data-url=&quot;https://blog.kakaocdn.net/dn/dGFpdX/btsm8QauByR/SM7LWUPCTAkyab5Y0UlDsk/img.png&quot; data-phocus=&quot;https://blog.kakaocdn.net/dn/dGFpdX/btsm8QauByR/SM7LWUPCTAkyab5Y0UlDsk/img.png&quot;&gt;&lt;img src=&quot;https://blog.kakaocdn.net/dn/dGFpdX/btsm8QauByR/SM7LWUPCTAkyab5Y0UlDsk/img.png&quot; srcset=&quot;https://img1.daumcdn.net/thumb/R1280x0/?scode=mtistory2&amp;fname=https%3A%2F%2Fblog.kakaocdn.net%2Fdn%2FdGFpdX%2Fbtsm8QauByR%2FSM7LWUPCTAkyab5Y0UlDsk%2Fimg.png&quot; onerror=&quot;this.onerror=null; this.src='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png'; this.srcset='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png';&quot; loading=&quot;lazy&quot; width=&quot;718&quot; height=&quot;176&quot; data-filename=&quot;스크린샷 2023-07-03 오후 11.10.41.png&quot; data-origin-width=&quot;1246&quot; data-origin-height=&quot;176&quot;/&gt;&lt;/span&gt;&lt;/figure&gt;
&lt;/p&gt;
&lt;h2 data-ke-size=&quot;size26&quot;&gt;&lt;b&gt;5.&amp;nbsp; Auto Scaling Group을 대상 그룹으로 설정&lt;/b&gt;&lt;/h2&gt;
&lt;hr contenteditable=&quot;false&quot; data-ke-type=&quot;horizontalRule&quot; data-ke-style=&quot;style6&quot; /&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;1. Auto Scaling Group에서 로드밸런싱 편집 클릭&lt;/p&gt;
&lt;p&gt;&lt;figure class=&quot;imageblock widthContent&quot; data-ke-mobileStyle=&quot;widthOrigin&quot; data-filename=&quot;스크린샷 2023-07-03 오후 11.15.09.png&quot; data-origin-width=&quot;1526&quot; data-origin-height=&quot;480&quot;&gt;&lt;span data-url=&quot;https://blog.kakaocdn.net/dn/JHpB6/btsm8X8xdLM/IGHkU6kXc9MVhdotkLlnak/img.png&quot; data-phocus=&quot;https://blog.kakaocdn.net/dn/JHpB6/btsm8X8xdLM/IGHkU6kXc9MVhdotkLlnak/img.png&quot;&gt;&lt;img src=&quot;https://blog.kakaocdn.net/dn/JHpB6/btsm8X8xdLM/IGHkU6kXc9MVhdotkLlnak/img.png&quot; srcset=&quot;https://img1.daumcdn.net/thumb/R1280x0/?scode=mtistory2&amp;fname=https%3A%2F%2Fblog.kakaocdn.net%2Fdn%2FJHpB6%2Fbtsm8X8xdLM%2FIGHkU6kXc9MVhdotkLlnak%2Fimg.png&quot; onerror=&quot;this.onerror=null; this.src='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png'; this.srcset='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png';&quot; loading=&quot;lazy&quot; width=&quot;1526&quot; height=&quot;480&quot; data-filename=&quot;스크린샷 2023-07-03 오후 11.15.09.png&quot; data-origin-width=&quot;1526&quot; data-origin-height=&quot;480&quot;/&gt;&lt;/span&gt;&lt;/figure&gt;
&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;위에서 생성한 대상 그룹 선택 후 업데이트 버튼 클릭&lt;/p&gt;
&lt;p&gt;&lt;figure class=&quot;imageblock widthContent&quot; data-ke-mobileStyle=&quot;widthOrigin&quot; data-filename=&quot;스크린샷 2023-07-03 오후 11.15.41.png&quot; data-origin-width=&quot;1646&quot; data-origin-height=&quot;570&quot;&gt;&lt;span data-url=&quot;https://blog.kakaocdn.net/dn/I9z6M/btsm9287npt/fddwmCMwttesv35dw312U0/img.png&quot; data-phocus=&quot;https://blog.kakaocdn.net/dn/I9z6M/btsm9287npt/fddwmCMwttesv35dw312U0/img.png&quot;&gt;&lt;img src=&quot;https://blog.kakaocdn.net/dn/I9z6M/btsm9287npt/fddwmCMwttesv35dw312U0/img.png&quot; srcset=&quot;https://img1.daumcdn.net/thumb/R1280x0/?scode=mtistory2&amp;fname=https%3A%2F%2Fblog.kakaocdn.net%2Fdn%2FI9z6M%2Fbtsm9287npt%2FfddwmCMwttesv35dw312U0%2Fimg.png&quot; onerror=&quot;this.onerror=null; this.src='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png'; this.srcset='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png';&quot; loading=&quot;lazy&quot; width=&quot;1646&quot; height=&quot;570&quot; data-filename=&quot;스크린샷 2023-07-03 오후 11.15.41.png&quot; data-origin-width=&quot;1646&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;결과적으로, 해당 Auto Scaling Group에서 Auto Scaling에 의해 생성되는 모든 인스턴스들은 대상 그룹(asc-target-group)에 들어가게 된다. ALB(Application Load Balancer) 생성시 80포트로 들어오는 요청은 asc-target-group 대상 그룹으로 전달하도록 리스너를 설정했다. 해당 대상 그룹은 auto scaling group과 연결되어 있다. 따라서, 80 포트로 들어오는 요청은 ALB를 거쳐 Auto Scaling Group 내 인스턴스들에 트래픽이 분산된다.&lt;/p&gt;
&lt;h3 data-ke-size=&quot;size23&quot;&gt;&amp;nbsp;&lt;/h3&gt;
&lt;h2 data-ke-size=&quot;size26&quot;&gt;&lt;b&gt;6.&amp;nbsp; Load Balancer 트래픽 분산 테스트&lt;/b&gt;&lt;/h2&gt;
&lt;hr contenteditable=&quot;false&quot; data-ke-type=&quot;horizontalRule&quot; data-ke-style=&quot;style6&quot; /&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;1. Auto Scaling Group의 원하는 용량, 최소 용량을 3으로 늘려보자&lt;/p&gt;
&lt;p&gt;&lt;figure class=&quot;imageblock alignLeft&quot; data-ke-mobileStyle=&quot;widthOrigin&quot; data-filename=&quot;스크린샷 2023-07-03 오후 11.28.17.png&quot; data-origin-width=&quot;1230&quot; data-origin-height=&quot;900&quot;&gt;&lt;span data-url=&quot;https://blog.kakaocdn.net/dn/CNqxY/btsm8Nx5pYq/cQS07wKJ1P7xuKYKrnrIf1/img.png&quot; data-phocus=&quot;https://blog.kakaocdn.net/dn/CNqxY/btsm8Nx5pYq/cQS07wKJ1P7xuKYKrnrIf1/img.png&quot;&gt;&lt;img src=&quot;https://blog.kakaocdn.net/dn/CNqxY/btsm8Nx5pYq/cQS07wKJ1P7xuKYKrnrIf1/img.png&quot; srcset=&quot;https://img1.daumcdn.net/thumb/R1280x0/?scode=mtistory2&amp;fname=https%3A%2F%2Fblog.kakaocdn.net%2Fdn%2FCNqxY%2Fbtsm8Nx5pYq%2FcQS07wKJ1P7xuKYKrnrIf1%2Fimg.png&quot; onerror=&quot;this.onerror=null; this.src='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png'; this.srcset='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png';&quot; loading=&quot;lazy&quot; width=&quot;583&quot; height=&quot;427&quot; data-filename=&quot;스크린샷 2023-07-03 오후 11.28.17.png&quot; data-origin-width=&quot;1230&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;Auto Scaling Group에 아래와 같이 인스턴스가 1개 더 생성된다.&lt;/p&gt;
&lt;p&gt;&lt;figure class=&quot;imageblock widthContent&quot; data-ke-mobileStyle=&quot;widthOrigin&quot; data-filename=&quot;스크린샷 2023-07-03 오후 11.29.32.png&quot; data-origin-width=&quot;2254&quot; data-origin-height=&quot;766&quot;&gt;&lt;span data-url=&quot;https://blog.kakaocdn.net/dn/dt4oV6/btsm83HshJK/XUN8wj2dF0DJ77fUJ2b8jk/img.png&quot; data-phocus=&quot;https://blog.kakaocdn.net/dn/dt4oV6/btsm83HshJK/XUN8wj2dF0DJ77fUJ2b8jk/img.png&quot;&gt;&lt;img src=&quot;https://blog.kakaocdn.net/dn/dt4oV6/btsm83HshJK/XUN8wj2dF0DJ77fUJ2b8jk/img.png&quot; srcset=&quot;https://img1.daumcdn.net/thumb/R1280x0/?scode=mtistory2&amp;fname=https%3A%2F%2Fblog.kakaocdn.net%2Fdn%2Fdt4oV6%2Fbtsm83HshJK%2FXUN8wj2dF0DJ77fUJ2b8jk%2Fimg.png&quot; onerror=&quot;this.onerror=null; this.src='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png'; this.srcset='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png';&quot; loading=&quot;lazy&quot; width=&quot;2254&quot; height=&quot;766&quot; data-filename=&quot;스크린샷 2023-07-03 오후 11.29.32.png&quot; data-origin-width=&quot;2254&quot; data-origin-height=&quot;766&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 widthContent&quot; data-ke-mobileStyle=&quot;widthOrigin&quot; data-filename=&quot;스크린샷 2023-07-03 오후 11.30.32.png&quot; data-origin-width=&quot;1574&quot; data-origin-height=&quot;884&quot;&gt;&lt;span data-url=&quot;https://blog.kakaocdn.net/dn/cPdf1b/btsm9gUfUKD/DzYz5VGfe4hBZR817aQ8Ek/img.png&quot; data-phocus=&quot;https://blog.kakaocdn.net/dn/cPdf1b/btsm9gUfUKD/DzYz5VGfe4hBZR817aQ8Ek/img.png&quot;&gt;&lt;img src=&quot;https://blog.kakaocdn.net/dn/cPdf1b/btsm9gUfUKD/DzYz5VGfe4hBZR817aQ8Ek/img.png&quot; srcset=&quot;https://img1.daumcdn.net/thumb/R1280x0/?scode=mtistory2&amp;fname=https%3A%2F%2Fblog.kakaocdn.net%2Fdn%2FcPdf1b%2Fbtsm9gUfUKD%2FDzYz5VGfe4hBZR817aQ8Ek%2Fimg.png&quot; onerror=&quot;this.onerror=null; this.src='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png'; this.srcset='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png';&quot; loading=&quot;lazy&quot; width=&quot;1574&quot; height=&quot;884&quot; data-filename=&quot;스크린샷 2023-07-03 오후 11.30.32.png&quot; data-origin-width=&quot;1574&quot; data-origin-height=&quot;884&quot;/&gt;&lt;/span&gt;&lt;/figure&gt;
&lt;figure class=&quot;imageblock widthContent&quot; data-ke-mobileStyle=&quot;widthOrigin&quot; data-filename=&quot;스크린샷 2023-07-03 오후 11.30.45.png&quot; data-origin-width=&quot;1882&quot; data-origin-height=&quot;694&quot;&gt;&lt;span data-url=&quot;https://blog.kakaocdn.net/dn/b77y87/btsm9BcR801/KhnnqJcSGKKqd0mL0GwmJk/img.png&quot; data-phocus=&quot;https://blog.kakaocdn.net/dn/b77y87/btsm9BcR801/KhnnqJcSGKKqd0mL0GwmJk/img.png&quot;&gt;&lt;img src=&quot;https://blog.kakaocdn.net/dn/b77y87/btsm9BcR801/KhnnqJcSGKKqd0mL0GwmJk/img.png&quot; srcset=&quot;https://img1.daumcdn.net/thumb/R1280x0/?scode=mtistory2&amp;fname=https%3A%2F%2Fblog.kakaocdn.net%2Fdn%2Fb77y87%2Fbtsm9BcR801%2FKhnnqJcSGKKqd0mL0GwmJk%2Fimg.png&quot; onerror=&quot;this.onerror=null; this.src='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png'; this.srcset='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png';&quot; loading=&quot;lazy&quot; width=&quot;1882&quot; height=&quot;694&quot; data-filename=&quot;스크린샷 2023-07-03 오후 11.30.45.png&quot; data-origin-width=&quot;1882&quot; data-origin-height=&quot;694&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;2. Application Load Balancer의 DNS에 접속해 3개의 인스턴스로 트래픽이 분산되는지 확인&lt;/p&gt;
&lt;p&gt;&lt;figure class=&quot;imageblock alignLeft&quot; data-ke-mobileStyle=&quot;widthOrigin&quot; data-filename=&quot;스크린샷 2023-07-03 오후 11.31.05.png&quot; data-origin-width=&quot;1250&quot; data-origin-height=&quot;184&quot;&gt;&lt;span data-url=&quot;https://blog.kakaocdn.net/dn/cdQOED/btsm9gtdoS3/EjUHWypz0CLOTeNUUTmQD0/img.png&quot; data-phocus=&quot;https://blog.kakaocdn.net/dn/cdQOED/btsm9gtdoS3/EjUHWypz0CLOTeNUUTmQD0/img.png&quot;&gt;&lt;img src=&quot;https://blog.kakaocdn.net/dn/cdQOED/btsm9gtdoS3/EjUHWypz0CLOTeNUUTmQD0/img.png&quot; srcset=&quot;https://img1.daumcdn.net/thumb/R1280x0/?scode=mtistory2&amp;fname=https%3A%2F%2Fblog.kakaocdn.net%2Fdn%2FcdQOED%2Fbtsm9gtdoS3%2FEjUHWypz0CLOTeNUUTmQD0%2Fimg.png&quot; onerror=&quot;this.onerror=null; this.src='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png'; this.srcset='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png';&quot; loading=&quot;lazy&quot; width=&quot;697&quot; height=&quot;103&quot; data-filename=&quot;스크린샷 2023-07-03 오후 11.31.05.png&quot; data-origin-width=&quot;1250&quot; data-origin-height=&quot;184&quot;/&gt;&lt;/span&gt;&lt;/figure&gt;
&lt;figure class=&quot;imageblock alignLeft&quot; data-ke-mobileStyle=&quot;widthOrigin&quot; data-origin-width=&quot;1246&quot; data-origin-height=&quot;176&quot;&gt;&lt;span data-url=&quot;https://blog.kakaocdn.net/dn/tbzvP/btsm8PvU47P/jonLcIjbRvTJNaJfMfAg00/img.png&quot; data-phocus=&quot;https://blog.kakaocdn.net/dn/tbzvP/btsm8PvU47P/jonLcIjbRvTJNaJfMfAg00/img.png&quot;&gt;&lt;img src=&quot;https://blog.kakaocdn.net/dn/tbzvP/btsm8PvU47P/jonLcIjbRvTJNaJfMfAg00/img.png&quot; srcset=&quot;https://img1.daumcdn.net/thumb/R1280x0/?scode=mtistory2&amp;fname=https%3A%2F%2Fblog.kakaocdn.net%2Fdn%2FtbzvP%2Fbtsm8PvU47P%2FjonLcIjbRvTJNaJfMfAg00%2Fimg.png&quot; onerror=&quot;this.onerror=null; this.src='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png'; this.srcset='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png';&quot; loading=&quot;lazy&quot; width=&quot;697&quot; height=&quot;98&quot; data-origin-width=&quot;1246&quot; data-origin-height=&quot;176&quot;/&gt;&lt;/span&gt;&lt;/figure&gt;
&lt;figure class=&quot;imageblock alignLeft&quot; data-ke-mobileStyle=&quot;widthOrigin&quot; data-filename=&quot;스크린샷 2023-07-03 오후 11.10.35.png&quot; data-origin-width=&quot;1306&quot; data-origin-height=&quot;186&quot;&gt;&lt;span data-url=&quot;https://blog.kakaocdn.net/dn/bHKM8F/btsnbO3zfzz/4nUxG4JdcLaYeJZVTAIjvk/img.png&quot; data-phocus=&quot;https://blog.kakaocdn.net/dn/bHKM8F/btsnbO3zfzz/4nUxG4JdcLaYeJZVTAIjvk/img.png&quot;&gt;&lt;img src=&quot;https://blog.kakaocdn.net/dn/bHKM8F/btsnbO3zfzz/4nUxG4JdcLaYeJZVTAIjvk/img.png&quot; srcset=&quot;https://img1.daumcdn.net/thumb/R1280x0/?scode=mtistory2&amp;fname=https%3A%2F%2Fblog.kakaocdn.net%2Fdn%2FbHKM8F%2FbtsnbO3zfzz%2F4nUxG4JdcLaYeJZVTAIjvk%2Fimg.png&quot; onerror=&quot;this.onerror=null; this.src='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png'; this.srcset='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png';&quot; loading=&quot;lazy&quot; width=&quot;698&quot; height=&quot;99&quot; data-filename=&quot;스크린샷 2023-07-03 오후 11.10.35.png&quot; data-origin-width=&quot;1306&quot; data-origin-height=&quot;186&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;위와 같이 나오면 정상적으로 트래픽이 3개의 인스턴스에 분산되고 있는 것이다. 이로써, ELB의 대상 그룹으로 Auto Scaling Group을 지정해 스케일링된 EC2 인스턴스들에 트래픽을 분산해봤다. 다음으로는 ELB와 Auto Scaling의 Health Check 기능에 대해 좀 더 자세히 알아보자.&lt;/p&gt;</description>
      <category>DevOps/AWS</category>
      <category>AWS</category>
      <author>주인  장</author>
      <guid isPermaLink="true">https://twosky.tistory.com/59</guid>
      <comments>https://twosky.tistory.com/59#entry59comment</comments>
      <pubDate>Tue, 11 Jul 2023 01:09:09 +0900</pubDate>
    </item>
    <item>
      <title>[Spring] HikariCP 에러 핸들링</title>
      <link>https://twosky.tistory.com/58</link>
      <description>&lt;h2 data-ke-size=&quot;size26&quot;&gt;&lt;b&gt;[ 개요 ]&lt;/b&gt;&lt;/h2&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;서버를 운영하다 보면 hikari connection과 관련된 에러를 겪게 된다. 빈번하게 발생 가능한 에러라 정리해보려 한다.&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;먼저 ConnectionPool이 뭔지, HikariCP는 어떻게 동작하는지에 대해 알아보자.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;h2 data-ke-size=&quot;size26&quot;&gt;&lt;span style=&quot;font-family: AppleSDGothicNeo-Regular, 'Malgun Gothic', '맑은 고딕', dotum, 돋움, sans-serif;&quot;&gt;&lt;b&gt;[ ConnectionPool ]&lt;/b&gt;&lt;/span&gt;&lt;/h2&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;ConnectionPool 이란 커넥션을 미리 생성해두고 사용하는 방법이다. 데이터베이스 커넥션을 새로 생성하는 것은 과정도 복잡하고, 많은 시간이 소요되므로 커넥션 풀을 사용한다.&lt;/p&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;커넥션 풀 오픈 소스에는 여러가지 있지만, 스프링 부트 2.0부터는 기본 커넥션 풀로 hikariCP를 제공한다. 그럼 hikariCP 동작원리에 대해 알아보자.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;hr contenteditable=&quot;false&quot; data-ke-type=&quot;horizontalRule&quot; data-ke-style=&quot;style6&quot; /&gt;
&lt;h2 data-ke-size=&quot;size26&quot;&gt;&lt;b&gt;[&amp;nbsp; HikariCP에서 하나의 쿼리가 실행되는 과정 ]&lt;/b&gt;&lt;/h2&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;커넥션을 얻는 방법은 (DriverManager, HikariCP ..) 다양하기 때문에 커넥션을 획득하는 방법을 추상화한 인터페이스인 DataSource를 사용한다. HikariDataSource는 HikariCP에서 커넥션을 획득하는 방법을 구현한 DataSource의 구현체이다.&lt;/p&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_1681889774967&quot; class=&quot;java&quot; data-ke-language=&quot;java&quot; data-ke-type=&quot;codeblock&quot;&gt;&lt;code&gt;Connection connection = null;
PreparedStatement preparedStatement = null

try {
    connection = hikariDataSource.getConnection();  // 1. connection pool로부터 커넥션 획득
    preparedStatement = connection.preparedStatement(sql);  // 2. SQL을 넣은 statement 객체 반환
    preparedStatement.executeQuery();  // 3. 쿼리 실행
} catch(Throwable e) {
    throw new RuntimeException(e);
} finally {
    if (preparedStatement != null) {
        preparedStatement.close();
    }
    if (connection != null) {
        connection.close(); // 4. connection pool에 커넥션 반납
    }
}&lt;/code&gt;&lt;/pre&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p style=&quot;color: #212121; text-align: start;&quot; data-ke-size=&quot;size16&quot;&gt;&lt;span&gt;HikariCP에서 쿼리가 실행되는 과정을 알아봤으니,&amp;nbsp;&lt;/span&gt;hikariDataSource.getConnection() 과&lt;span&gt;&amp;nbsp;&lt;/span&gt;connection.close() 의 내부 동작 원리를 살펴보자.&lt;/p&gt;
&lt;p style=&quot;color: #212121; text-align: start;&quot; data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;hr contenteditable=&quot;false&quot; data-ke-type=&quot;horizontalRule&quot; data-ke-style=&quot;style6&quot; /&gt;
&lt;h2 data-ke-size=&quot;size26&quot;&gt;&lt;b&gt;[ HikariCP 동작 원리 ]&lt;/b&gt;&lt;/h2&gt;
&lt;h4 data-ke-size=&quot;size20&quot;&gt;&lt;b&gt;1. 커넥션 요청 : &lt;span style=&quot;color: #212121; text-align: start;&quot;&gt;hikariDataSource.getConnection()&lt;/span&gt;&lt;/b&gt;&lt;/h4&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;쓰레드가 커넥션을 요청하면 이전에 사용했던 Connection이 존재하는지 확인하고, 이를 우선적으로 반환한다.&lt;/p&gt;
&lt;p&gt;&lt;figure class=&quot;imageblock alignCenter&quot; data-ke-mobileStyle=&quot;widthOrigin&quot; data-filename=&quot;edited_download (11).png&quot; data-origin-width=&quot;798&quot; data-origin-height=&quot;374&quot;&gt;&lt;span data-url=&quot;https://blog.kakaocdn.net/dn/sJJU1/btsblzjDeWs/qX8DdukUYYPAaJVJ0ctAy0/img.png&quot; data-phocus=&quot;https://blog.kakaocdn.net/dn/sJJU1/btsblzjDeWs/qX8DdukUYYPAaJVJ0ctAy0/img.png&quot;&gt;&lt;img src=&quot;https://blog.kakaocdn.net/dn/sJJU1/btsblzjDeWs/qX8DdukUYYPAaJVJ0ctAy0/img.png&quot; srcset=&quot;https://img1.daumcdn.net/thumb/R1280x0/?scode=mtistory2&amp;fname=https%3A%2F%2Fblog.kakaocdn.net%2Fdn%2FsJJU1%2FbtsblzjDeWs%2FqX8DdukUYYPAaJVJ0ctAy0%2Fimg.png&quot; onerror=&quot;this.onerror=null; this.src='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png'; this.srcset='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png';&quot; loading=&quot;lazy&quot; width=&quot;726&quot; height=&quot;340&quot; data-filename=&quot;edited_download (11).png&quot; data-origin-width=&quot;798&quot; data-origin-height=&quot;374&quot;/&gt;&lt;/span&gt;&lt;/figure&gt;
&lt;/p&gt;
&lt;h4 data-ke-size=&quot;size20&quot;&gt;&amp;nbsp;&lt;/h4&gt;
&lt;h4 data-ke-size=&quot;size20&quot;&gt;&lt;b&gt;1-2. 사용 가능한 커넥션이 없는 경우&lt;/b&gt;&lt;/h4&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;사용 가능한 커넥션이 존재하지 않으면, HandOffQueue를 Polling하면서 다른 쓰레드가 커넥션을 반납하기를 기다린다. (지정한 TimeOut 시간까지 대기하다가 시간이 만료되면 예외를 던진다. )&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p&gt;&lt;figure class=&quot;imageblock alignCenter&quot; data-ke-mobileStyle=&quot;widthOrigin&quot; data-filename=&quot;download (12).png&quot; data-origin-width=&quot;821&quot; data-origin-height=&quot;427&quot;&gt;&lt;span data-url=&quot;https://blog.kakaocdn.net/dn/qFURs/btsbmYXrUR6/yKC7VpbcfWyVRSqcy1qy30/img.png&quot; data-phocus=&quot;https://blog.kakaocdn.net/dn/qFURs/btsbmYXrUR6/yKC7VpbcfWyVRSqcy1qy30/img.png&quot;&gt;&lt;img src=&quot;https://blog.kakaocdn.net/dn/qFURs/btsbmYXrUR6/yKC7VpbcfWyVRSqcy1qy30/img.png&quot; srcset=&quot;https://img1.daumcdn.net/thumb/R1280x0/?scode=mtistory2&amp;fname=https%3A%2F%2Fblog.kakaocdn.net%2Fdn%2FqFURs%2FbtsbmYXrUR6%2FyKC7VpbcfWyVRSqcy1qy30%2Fimg.png&quot; onerror=&quot;this.onerror=null; this.src='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png'; this.srcset='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png';&quot; loading=&quot;lazy&quot; width=&quot;774&quot; height=&quot;403&quot; data-filename=&quot;download (12).png&quot; data-origin-width=&quot;821&quot; data-origin-height=&quot;427&quot;/&gt;&lt;/span&gt;&lt;/figure&gt;
&lt;/p&gt;
&lt;h4 data-ke-size=&quot;size20&quot;&gt;&lt;b&gt;2. 커넥션 사용 후 커넥션 풀에 반납 : &lt;span style=&quot;color: #212121; text-align: start;&quot;&gt;connection.close()&lt;/span&gt;&lt;/b&gt;&lt;/h4&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;쓰레드가 커넥션을 반납하면 Connection Pool은 커넥션 사용 내역을 기록하고,&amp;nbsp; 반납된 커넥션을 HandOffQueue에 삽입한다. 그 후 HandOffQueue를 Polling하던 쓰레드는 커넥션을 획득하고 작업을 이어나간다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p&gt;&lt;figure class=&quot;imageblock alignCenter&quot; data-ke-mobileStyle=&quot;widthOrigin&quot; data-filename=&quot;download (13).png&quot; data-origin-width=&quot;848&quot; data-origin-height=&quot;494&quot;&gt;&lt;span data-url=&quot;https://blog.kakaocdn.net/dn/cyCfwH/btsbmTV9uCp/zviIFNKSVvlB2zqUMmN6eK/img.png&quot; data-phocus=&quot;https://blog.kakaocdn.net/dn/cyCfwH/btsbmTV9uCp/zviIFNKSVvlB2zqUMmN6eK/img.png&quot;&gt;&lt;img src=&quot;https://blog.kakaocdn.net/dn/cyCfwH/btsbmTV9uCp/zviIFNKSVvlB2zqUMmN6eK/img.png&quot; srcset=&quot;https://img1.daumcdn.net/thumb/R1280x0/?scode=mtistory2&amp;fname=https%3A%2F%2Fblog.kakaocdn.net%2Fdn%2FcyCfwH%2FbtsbmTV9uCp%2FzviIFNKSVvlB2zqUMmN6eK%2Fimg.png&quot; onerror=&quot;this.onerror=null; this.src='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png'; this.srcset='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png';&quot; loading=&quot;lazy&quot; width=&quot;778&quot; height=&quot;453&quot; data-filename=&quot;download (13).png&quot; data-origin-width=&quot;848&quot; data-origin-height=&quot;494&quot;/&gt;&lt;/span&gt;&lt;/figure&gt;
&lt;/p&gt;
&lt;hr contenteditable=&quot;false&quot; data-ke-type=&quot;horizontalRule&quot; data-ke-style=&quot;style6&quot; /&gt;
&lt;h2 data-ke-size=&quot;size26&quot;&gt;&lt;b&gt;[ Hikari Connection Pool 에러 핸들링 ]&lt;/b&gt;&lt;/h2&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;Connection 관련 에러는 보통 아래의 에러 메시지와 함께 문제가 발생한다.&lt;/p&gt;
&lt;blockquote data-ke-style=&quot;style3&quot;&gt;- o.h.engine.jdbc.spi.SqlExceptionHelper : SQL Error: 0, SQLState: null &lt;br /&gt;- o.h.engine.jdbc.spi.SqlExceptionHelper : hikari-pool-1 &amp;ndash; Connection is not available, request timed out after 30000ms. &lt;br /&gt;- org.hibernate.exception.JDBCConnectionException: unable to obtain isolated JDBC connection &lt;br /&gt;- Could not open JPA EntityManager for transaction; nested exception is org.hibernate.exception.JDBCConnectionException: Unable to acquire JDBC Connection&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;에러의 내용은 트랜잭션을 만드는데 실패했고, JDBC Connection을 획득할 수 없다. 즉, hikariPool 에서 커넥션을 사용할 수 없어 요청 시간이 만료되었다는 의미이다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;그렇다면, 왜 커넥션을 사용할 수 없는지 간단한 예시로 상황을 재현해보자.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;h2 data-ke-size=&quot;size26&quot;&gt;&lt;b&gt;[ Connection 에러 상황 재현 ]&lt;/b&gt;&lt;/h2&gt;
&lt;h4 data-ke-size=&quot;size20&quot;&gt;&lt;b&gt;1. hikari 관련 옵션 설정&lt;/b&gt;&lt;/h4&gt;
&lt;p data-ke-size=&quot;size18&quot;&gt;application.yml&lt;/p&gt;
&lt;pre id=&quot;code_1681888324646&quot; class=&quot;bash&quot; data-ke-language=&quot;bash&quot; data-ke-type=&quot;codeblock&quot;&gt;&lt;code&gt;spring:
  datasource:
    hikari:
      jdbc-url: jdbc:h2:mem:testdb;MODE=MySQL;DATABASE_TO_LOWER=TRUE
      username: sa
      password:
      connection-timeout: 3000
      maximum-pool-size: 5
      max-lifetime: 30000
      driver-class-name: org.h2.Driver

  jpa:
    hibernate:
      ddl-auto: create
    properties:
      hibernate:
        dialect: org.hibernate.dialect.H2Dialect&lt;/code&gt;&lt;/pre&gt;
&lt;ul style=&quot;list-style-type: disc;&quot; data-ke-list-type=&quot;disc&quot;&gt;
&lt;li&gt;maximum-pool-size : hikari pool에서 관리하는 최대 커넥션 수&lt;/li&gt;
&lt;li&gt;connection-timeout : 사용가능한 커넥션이 없는 경우 커넥션을 얻기 위해 대기하는 시간&lt;/li&gt;
&lt;/ul&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;connection-timeout 으로 설정한 시간 안에 커넥션을 얻지 못하면 에러가 발생한다. 위 application.yml 파일을 test/resources 경로에 저장하자.&lt;/p&gt;
&lt;p data-ke-size=&quot;size18&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;h4 data-ke-size=&quot;size20&quot;&gt;&lt;b&gt;2. 비즈니스 로직 작성&lt;/b&gt;&lt;b&gt;&lt;/b&gt;&lt;/h4&gt;
&lt;p data-ke-size=&quot;size18&quot;&gt;Order&lt;/p&gt;
&lt;pre id=&quot;code_1681892382602&quot; class=&quot;java&quot; data-ke-language=&quot;java&quot; data-ke-type=&quot;codeblock&quot;&gt;&lt;code&gt;@Entity
@Getter
@AllArgsConstructor
@NoArgsConstructor
@Table(name = &quot;orders&quot;)
public class Order {
    @Id
    @GeneratedValue(strategy = GenerationType.IDENTITY)
    private Long id;
}&lt;/code&gt;&lt;/pre&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;OrderRepository&lt;/p&gt;
&lt;pre id=&quot;code_1681892574958&quot; class=&quot;java&quot; data-ke-language=&quot;java&quot; data-ke-type=&quot;codeblock&quot;&gt;&lt;code&gt;public interface OrderJpaRepository extends JpaRepository&amp;lt;Order, Long&amp;gt; {
}&lt;/code&gt;&lt;/pre&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;OrderService&lt;/p&gt;
&lt;pre id=&quot;code_1681892595718&quot; class=&quot;java&quot; data-ke-language=&quot;java&quot; data-ke-type=&quot;codeblock&quot;&gt;&lt;code&gt;@Slf4j
@Service
@RequiredArgsConstructor
public class OrderService {

    private final OrderJpaRepository orderJpaRepository;

    @Transactional
    public void saveWithDelay(Order order) throws InterruptedException {
        log.info(&quot;saveWithDelay 호출 : {}&quot;, order.getId());
        Thread.sleep(10000);
        orderJpaRepository.save(order);
    }
}&lt;/code&gt;&lt;/pre&gt;
&lt;ul style=&quot;list-style-type: disc;&quot; data-ke-list-type=&quot;disc&quot;&gt;
&lt;li&gt;saveWithDelay는 Order 엔티티를 저장하고, 10초간 delay하는 메서드이다.&lt;/li&gt;
&lt;li&gt;여기서 중요한 점은 saveWithDelay가 트랜잭션으로 묶여있다는 점이다.&lt;/li&gt;
&lt;li&gt;따라서 saveWithDelay가 호출되면, 트랜잭션은 10초간 유지될 것이다.&lt;/li&gt;
&lt;/ul&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;h4 data-ke-size=&quot;size20&quot;&gt;&lt;b&gt;3. 테스트&lt;/b&gt;&lt;b&gt;&lt;/b&gt;&lt;/h4&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;멀티 스레드를 사용해 각 스레드가 커넥션을 제대로 획득하는지 테스트해보자. 테스트 시나리오는 아래와 같다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;ol style=&quot;list-style-type: decimal;&quot; data-ke-list-type=&quot;decimal&quot;&gt;
&lt;li&gt;멀티 스레드를 사용해 동시에 10개의 스레드를 만든다.&lt;/li&gt;
&lt;li&gt;saveWithDelay를 호출한다. (10초의 delay)&lt;/li&gt;
&lt;/ol&gt;
&lt;pre id=&quot;code_1681893036617&quot; class=&quot;java&quot; data-ke-language=&quot;java&quot; data-ke-type=&quot;codeblock&quot;&gt;&lt;code&gt;@SpringBootTest
public class OrderServiceTest {

    @Autowired
    private OrderService orderService;

    @Test
    void 동시에_10개의_order를_저장한다() throws InterruptedException {
        int threadCount = 10;
        ExecutorService executorService = Executors.newFixedThreadPool(threadCount);
        CountDownLatch countDownLatch = new CountDownLatch(threadCount);

        for(int i = 0; i &amp;lt; threadCount; i++) {
            long id = i;
            executorService.submit(() -&amp;gt; {
                try {
                    Order order = new Order(id);
                    orderService.saveWithDelay(order);
                } catch (InterruptedException e) {
                    e.printStackTrace();
                } finally {
                    countDownLatch.countDown();
                }
            });
        }
        countDownLatch.await();
    }
}&lt;/code&gt;&lt;/pre&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;테스트 실행 결과는 다음과 같다.&lt;/p&gt;
&lt;pre id=&quot;code_1681893457977&quot; class=&quot;java&quot; data-ke-language=&quot;java&quot; data-ke-type=&quot;codeblock&quot;&gt;&lt;code&gt;INFO 864755 --- [pool-1-thread-9] com.example.springevent.OrderService     : saveWithDelay 호출 : 8
INFO 864755 --- [pool-1-thread-8] com.example.springevent.OrderService     : saveWithDelay 호출 : 7
INFO 864755 --- [pool-1-thread-4] com.example.springevent.OrderService     : saveWithDelay 호출 : 3
INFO 864755 --- [pool-1-thread-5] com.example.springevent.OrderService     : saveWithDelay 호출 : 4
INFO 864755 --- [ool-1-thread-10] com.example.springevent.OrderService     : saveWithDelay 호출 : 9
WARN 864755 --- [pool-1-thread-1] o.h.engine.jdbc.spi.SqlExceptionHelper   : SQL Error: 0, SQLState: null
WARN 864755 --- [pool-1-thread-6] o.h.engine.jdbc.spi.SqlExceptionHelper   : SQL Error: 0, SQLState: null
WARN 864755 --- [pool-1-thread-3] o.h.engine.jdbc.spi.SqlExceptionHelper   : SQL Error: 0, SQLState: null
WARN 864755 --- [pool-1-thread-7] o.h.engine.jdbc.spi.SqlExceptionHelper   : SQL Error: 0, SQLState: null
WARN 864755 --- [pool-1-thread-2] o.h.engine.jdbc.spi.SqlExceptionHelper   : SQL Error: 0, SQLState: null
ERROR 864755 --- [pool-1-thread-1] o.h.engine.jdbc.spi.SqlExceptionHelper   : HikariPool-1 - Connection is not available, request timed out after 3000ms.
ERROR 864755 --- [pool-1-thread-6] o.h.engine.jdbc.spi.SqlExceptionHelper   : HikariPool-1 - Connection is not available, request timed out after 3001ms.
ERROR 864755 --- [pool-1-thread-3] o.h.engine.jdbc.spi.SqlExceptionHelper   : HikariPool-1 - Connection is not available, request timed out after 3000ms.
ERROR 864755 --- [pool-1-thread-7] o.h.engine.jdbc.spi.SqlExceptionHelper   : HikariPool-1 - Connection is not available, request timed out after 3000ms.
ERROR 864755 --- [pool-1-thread-2] o.h.engine.jdbc.spi.SqlExceptionHelper   : HikariPool-1 - Connection is not available, request timed out after 3000ms.
org.springframework.transaction.CannotCreateTransactionException: Could not open JPA EntityManager for transaction; nested exception is org.hibernate.exception.JDBCConnectionException: Unable to acquire JDBC Connection
java.util.concurrent.CompletionException: org.springframework.transaction.CannotCreateTransactionException: Could not open JPA EntityManager for transaction; nested exception is org.hibernate.exception.JDBCConnectionException: Unable to acquire JDBC Connection&lt;/code&gt;&lt;/pre&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;이러한 결과가 발생한 이유는 다음과 같다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;ol style=&quot;list-style-type: decimal;&quot; data-ke-list-type=&quot;decimal&quot;&gt;
&lt;li&gt;max-pool-size를 5로 설정했기에 커넥션 풀에 5개의 커넥션이 존재한다.&lt;/li&gt;
&lt;li&gt;saveWithDelay가 호출되면 트랜잭션에 의해 약 10초간 커넥션을 잡고 있는다.&lt;/li&gt;
&lt;li&gt;먼저 실행된 5개의 saveWithDelay는 커넥션을 획득하고, 나머지 5개의 스레드는 커넥션 풀에 사용 가능한 커넥션이 존재하지 않아 대기한다.&lt;/li&gt;
&lt;li&gt;커넥션 풀로부터 커넥션 획득을 기다리는 시간인 connection-timeout 으로 설정한 3초가 지나 예외가 발생한다.&lt;/li&gt;
&lt;/ol&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;즉, 커넥션 풀에 존재하는 모든 커넥션이 사용 중인 상황에서 커넥션 획득을 위해 대기하다 일정시간이 지나면 예외가 발생하는 것이다. 커넥션 관련 에러는 어떻게 해결해야 할까?&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;hr contenteditable=&quot;false&quot; data-ke-type=&quot;horizontalRule&quot; data-ke-style=&quot;style6&quot; /&gt;
&lt;h2 data-ke-size=&quot;size26&quot;&gt;&lt;b&gt;[ 해결 방법 ]&lt;/b&gt;&lt;/h2&gt;
&lt;h4 data-ke-size=&quot;size20&quot;&gt;&lt;b&gt;1. Connection Pool 사이즈를 늘린다.&lt;/b&gt;&lt;/h4&gt;
&lt;pre id=&quot;code_1681894774933&quot; class=&quot;java&quot; data-ke-language=&quot;java&quot; data-ke-type=&quot;codeblock&quot;&gt;&lt;code&gt;spring:
  datasource:
    hikari:
      jdbc-url: jdbc:h2:mem:testdb;MODE=MySQL;DATABASE_TO_LOWER=TRUE
      username: sa
      password:
      connection-timeout: 3000
      maximum-pool-size: 10  // 5 -&amp;gt; 10
      max-lifetime: 30000
      driver-class-name: org.h2.Driver&lt;/code&gt;&lt;/pre&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;테스트 결과는 당연히 성공한다. 10개의 스레드 모두 커넥션을 획득하여 saveWithDelay 메서드를 실행한 것을 볼 수 있다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p&gt;&lt;figure class=&quot;imageblock alignCenter&quot; data-ke-mobileStyle=&quot;widthOrigin&quot; data-filename=&quot;Screenshot from 2023-04-19 17-58-55.png&quot; data-origin-width=&quot;1266&quot; data-origin-height=&quot;384&quot;&gt;&lt;span data-url=&quot;https://blog.kakaocdn.net/dn/bBFbrB/btsbmTPSukn/9nZ3CYfR3z8MmCC3bTgEq0/img.png&quot; data-phocus=&quot;https://blog.kakaocdn.net/dn/bBFbrB/btsbmTPSukn/9nZ3CYfR3z8MmCC3bTgEq0/img.png&quot;&gt;&lt;img src=&quot;https://blog.kakaocdn.net/dn/bBFbrB/btsbmTPSukn/9nZ3CYfR3z8MmCC3bTgEq0/img.png&quot; srcset=&quot;https://img1.daumcdn.net/thumb/R1280x0/?scode=mtistory2&amp;fname=https%3A%2F%2Fblog.kakaocdn.net%2Fdn%2FbBFbrB%2FbtsbmTPSukn%2F9nZ3CYfR3z8MmCC3bTgEq0%2Fimg.png&quot; onerror=&quot;this.onerror=null; this.src='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png'; this.srcset='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png';&quot; loading=&quot;lazy&quot; width=&quot;1266&quot; height=&quot;384&quot; data-filename=&quot;Screenshot from 2023-04-19 17-58-55.png&quot; data-origin-width=&quot;1266&quot; data-origin-height=&quot;384&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;Connection Pool 사이즈를 늘리는 것이 무조건적으로 에러를 해결해줄 수 있는 것은 아니다. 트래픽이 증가한다면, 커넥션 풀 사이즈를 늘린다고 하더라도 금방 커넥션이 고갈될 것이다. 또한, Connection Pool 사이즈를 너무 크게 설정하는 것은 그만큼 커넥션을 미리 생성해 확보해놓는 것이므로 리소스 낭비가 될 수 있다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;h4 data-ke-size=&quot;size20&quot;&gt;&lt;b&gt;2. Connection timeout 시간을 늘린다.&lt;/b&gt;&lt;/h4&gt;
&lt;pre id=&quot;code_1681895213856&quot; class=&quot;java&quot; data-ke-language=&quot;java&quot; data-ke-type=&quot;codeblock&quot;&gt;&lt;code&gt;spring:
  datasource:
    hikari:
      jdbc-url: jdbc:h2:mem:testdb;MODE=MySQL;DATABASE_TO_LOWER=TRUE
      username: sa
      password:
      connection-timeout: 15000  // 3000 -&amp;gt; 15000
      maximum-pool-size: 5
      max-lifetime: 30000
      driver-class-name: org.h2.Driver&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;테스트 결과는 당연히 성공한다. saveWithDelay 호출 시 약 10초간 커넥션을 사용하고, 커넥션 풀에 반납한다. 대기하던 스레드들은 약 10초 뒤 커넥션을 획득해 saveWithDelay를 실행할 수 있게 된다. timeout 시간인 15초를 넘기지 않았으므로 에러는 발생하지 않는다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p&gt;&lt;figure class=&quot;imageblock alignCenter&quot; data-ke-mobileStyle=&quot;widthOrigin&quot; data-filename=&quot;Screenshot from 2023-04-19 18-06-24.png&quot; data-origin-width=&quot;1243&quot; data-origin-height=&quot;294&quot;&gt;&lt;span data-url=&quot;https://blog.kakaocdn.net/dn/bcyx0Q/btsbmYp8EiT/PdG6koXwJJ3nyEkifz2FS0/img.png&quot; data-phocus=&quot;https://blog.kakaocdn.net/dn/bcyx0Q/btsbmYp8EiT/PdG6koXwJJ3nyEkifz2FS0/img.png&quot;&gt;&lt;img src=&quot;https://blog.kakaocdn.net/dn/bcyx0Q/btsbmYp8EiT/PdG6koXwJJ3nyEkifz2FS0/img.png&quot; srcset=&quot;https://img1.daumcdn.net/thumb/R1280x0/?scode=mtistory2&amp;fname=https%3A%2F%2Fblog.kakaocdn.net%2Fdn%2Fbcyx0Q%2FbtsbmYp8EiT%2FPdG6koXwJJ3nyEkifz2FS0%2Fimg.png&quot; onerror=&quot;this.onerror=null; this.src='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png'; this.srcset='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png';&quot; loading=&quot;lazy&quot; width=&quot;1243&quot; height=&quot;294&quot; data-filename=&quot;Screenshot from 2023-04-19 18-06-24.png&quot; data-origin-width=&quot;1243&quot; data-origin-height=&quot;294&quot;/&gt;&lt;/span&gt;&lt;/figure&gt;
&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;connection-timeout 시간을 늘리는 것은 일시적인 해소는 될 수 있지만, 오랜 시간 커넥션 대기로 인해 클라이언트에게 응답이 늦어질 수 있다.&lt;/p&gt;
&lt;blockquote data-ke-style=&quot;style2&quot;&gt;해결방법 1과 2는 임시 방편으로 사용해볼 수 있겠으나, 근본적인 해결방법은 아닐 수 있다.&lt;/blockquote&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;h4 data-ke-size=&quot;size20&quot;&gt;&lt;b&gt;3. 트랜잭션 범위를 작게 설정&lt;/b&gt;&lt;/h4&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;connection pool 사이즈와 timeout 시간은 처음과 같이 가져가고, saveWithDelay 메서드에 @Transactional 애노테이션을 제거해주었다.&lt;/p&gt;
&lt;pre id=&quot;code_1681896372283&quot; class=&quot;java&quot; data-ke-language=&quot;java&quot; data-ke-type=&quot;codeblock&quot;&gt;&lt;code&gt;spring:
  datasource:
    hikari:
      jdbc-url: jdbc:h2:mem:testdb;MODE=MySQL;DATABASE_TO_LOWER=TRUE
      username: sa
      password:
      connection-timeout: 3000
      maximum-pool-size: 5
      max-lifetime: 30000
      driver-class-name: org.h2.Driver&lt;/code&gt;&lt;/pre&gt;
&lt;pre id=&quot;code_1681896299761&quot; class=&quot;java&quot; data-ke-language=&quot;java&quot; data-ke-type=&quot;codeblock&quot;&gt;&lt;code&gt;// @Transactional
public void saveWithDelay(Order order) throws InterruptedException {
    log.info(&quot;saveWithDelay 호출 : {}&quot;, order.getId());
    Thread.sleep(10000);
    orderJpaRepository.save(order);
}&lt;/code&gt;&lt;/pre&gt;
&lt;ul style=&quot;list-style-type: disc;&quot; data-ke-list-type=&quot;disc&quot;&gt;
&lt;li&gt;saveWithDelay 메서드 자체에 걸려있는 트랜잭션을 빼주면, 10초간 트랜잭션을 가져가는 것이 아닌, &lt;b&gt;orderJpaRepository.save(order)가 호출되는 순간에만 트랜잭션을 가져간다&lt;/b&gt;.&lt;/li&gt;
&lt;li&gt;따라서, 커넥션을 사용해도 save 작업이 끝나면 바로 반납하기 때문에 connection-timeout이 발생하지 않는다.&lt;/li&gt;
&lt;/ul&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;Connection 관련 에러가 발생하면 먼저, 트랜잭션의 범위를 너무 넓게 잡아둔 곳이 있는지 확인해보자.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;테스트는 아래와 같이 성공한다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p&gt;&lt;figure class=&quot;imageblock alignCenter&quot; data-ke-mobileStyle=&quot;widthOrigin&quot; data-filename=&quot;Screenshot from 2023-04-19 18-24-37.png&quot; data-origin-width=&quot;1177&quot; data-origin-height=&quot;288&quot;&gt;&lt;span data-url=&quot;https://blog.kakaocdn.net/dn/9wfRR/btsbglG5Sen/W9Q5ubYTUL7ILk7T9peE31/img.png&quot; data-phocus=&quot;https://blog.kakaocdn.net/dn/9wfRR/btsbglG5Sen/W9Q5ubYTUL7ILk7T9peE31/img.png&quot;&gt;&lt;img src=&quot;https://blog.kakaocdn.net/dn/9wfRR/btsbglG5Sen/W9Q5ubYTUL7ILk7T9peE31/img.png&quot; srcset=&quot;https://img1.daumcdn.net/thumb/R1280x0/?scode=mtistory2&amp;fname=https%3A%2F%2Fblog.kakaocdn.net%2Fdn%2F9wfRR%2FbtsbglG5Sen%2FW9Q5ubYTUL7ILk7T9peE31%2Fimg.png&quot; onerror=&quot;this.onerror=null; this.src='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png'; this.srcset='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png';&quot; loading=&quot;lazy&quot; width=&quot;1177&quot; height=&quot;288&quot; data-filename=&quot;Screenshot from 2023-04-19 18-24-37.png&quot; data-origin-width=&quot;1177&quot; data-origin-height=&quot;288&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;hr contenteditable=&quot;false&quot; data-ke-type=&quot;horizontalRule&quot; data-ke-style=&quot;style6&quot; /&gt;
&lt;h2 data-ke-size=&quot;size26&quot;&gt;&lt;b&gt;마무리하며&lt;/b&gt;&lt;/h2&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;Hikari Connection pool 동작 원리, Connection 관련 에러 원인 및 해결 방법에 대해 간단히 알아보았다. 해결 방법은 많지만, 상황에 따라 적절히 사용하는 것이 중요하다. Connection pool 사이즈를 늘리는 것이 근본적인 해결방법이라 할 수 없다. 사이즈를 늘린다 하더라도 트래픽이 몰리는 경우 똑같은 에러 상황이 발생할 수 있고, 하나의 스레드에서 하나의 커넥션만 사용할 것이라는 보장이 없기 때문에 언제든지 커넥션으로 인한 에러가 발생할 수 있다. 이러한 많은 상황을 고려해 Connection pool 사이즈를 지정해야 할 것이다.&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;트랜잭션 범위가 넓어 커넥션을 오래 잡고 있는 코드가 있는지 살펴보는 것도 중요하다.&amp;nbsp;DB와 밀접한 연관이 있는 부분만 트랜잭션 범위로 설정하고, 나머지는 트랜잭션 밖으로 분리하자.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;h2 data-ke-size=&quot;size26&quot;&gt;&lt;b&gt;Reference&lt;/b&gt;&lt;/h2&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;a href=&quot;https://techblog.woowahan.com/2664/&quot; target=&quot;_blank&quot; rel=&quot;noopener&quot;&gt;https://techblog.woowahan.com/2664/&lt;/a&gt;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;a href=&quot;https://steady-coding.tistory.com/564&quot; target=&quot;_blank&quot; rel=&quot;noopener&quot;&gt;https://steady-coding.tistory.com/564&lt;/a&gt;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;a href=&quot;https://jgrammer.tistory.com/entry/Spring-Boot-Hikari-Connection-Pool-%EC%97%90%EB%9F%AC-%ED%95%B8%EB%93%A4%EB%A7%81&quot; target=&quot;_blank&quot; rel=&quot;noopener&quot;&gt;https://jgrammer.tistory.com/entry/Spring-Boot-Hikari-Connection-Pool-%EC%97%90%EB%9F%AC-%ED%95%B8%EB%93%A4%EB%A7%81&lt;/a&gt;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;a href=&quot;https://velog.io/@kdohyeon/Error-Could-not-open-JPA-EntityManager-for-transaction&quot; target=&quot;_blank&quot; rel=&quot;noopener&quot;&gt;https://velog.io/@kdohyeon/Error-Could-not-open-JPA-EntityManager-for-transaction&lt;/a&gt;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;</description>
      <category>Server/Spring</category>
      <category>spring</category>
      <author>주인  장</author>
      <guid isPermaLink="true">https://twosky.tistory.com/58</guid>
      <comments>https://twosky.tistory.com/58#entry58comment</comments>
      <pubDate>Wed, 19 Apr 2023 19:09:57 +0900</pubDate>
    </item>
    <item>
      <title>[AWS] Github Action + AWS Elastic Beanstalk을 사용한 CI/CD (3)</title>
      <link>https://twosky.tistory.com/56</link>
      <description>&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;저번 글에서 AWS Elastic Beanstalk, RDS 환경 세팅 및 인스턴스 연결을 해주었다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;이번 글에서는 Github Action yml 파일에 배포 스크립트를 추가하고, Github Action을 통한 AWS Elastic Beanstalk 배포 작업을 마무리해보자.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;글은 다음과 같은 순서로 진행된다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;ol style=&quot;list-style-type: decimal;&quot; data-ke-list-type=&quot;decimal&quot;&gt;
&lt;li&gt;docker-compose 파일 작성&lt;/li&gt;
&lt;li&gt;IAM 인증키 발급받기&lt;/li&gt;
&lt;li&gt;Github Action yml에 배포 스크립트 추가&lt;/li&gt;
&lt;/ol&gt;
&lt;hr contenteditable=&quot;false&quot; data-ke-type=&quot;horizontalRule&quot; data-ke-style=&quot;style6&quot; /&gt;
&lt;h2 data-ke-size=&quot;size26&quot;&gt;&lt;b&gt;&lt;b&gt;1. docker-compose 파일 작성&lt;/b&gt;&lt;/b&gt;&lt;/h2&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;Elastic beanstalk 배포를 위해 필요한 설정 파일인 docker-compose 파일을 작성해보자. docker-compose 파일을 이용해 Docker hub에 빌드되어 있는 이미지를 가져오고, 포트 맵핑, 환경변수 세팅 등을 해줄 수 있다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;pre class=&quot;bash&quot; data-ke-language=&quot;bash&quot;&gt;&lt;code&gt;version: &quot;3.8&quot;
services:
  springboot:
    image: &quot;twoosky/springboot-demo&quot;
    environment:
       - RDS_HOSTNAME=
       - RDS_USERNAME=admin
       - RDS_PASSWORD=1234
       - RDS_DB_NAME=myapp
       - RDS_PORT=3306
    restart: &quot;always&quot;

  nginx:
    image: &quot;twoosky/nginx-demo&quot;
    ports:
      - &quot;80:80&quot;
    restart: &quot;always&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;springboot&lt;/p&gt;
&lt;ul style=&quot;list-style-type: disc;&quot; data-ke-list-type=&quot;disc&quot;&gt;
&lt;li&gt;RDS_HOSTNAME : RDS 앤드포인트를 넣어주면 된다.&lt;/li&gt;
&lt;li&gt;RDS_USERNAME : RDS 마스터 사용자 이름&lt;/li&gt;
&lt;li&gt;RDS_PASSWORD : RDS 마스터 사용자 암호&lt;/li&gt;
&lt;li&gt;RDS_DB_NAME : 사용할 DB 이름&lt;/li&gt;
&lt;li&gt;RDS_PORT : 3306으로 설정&lt;/li&gt;
&lt;/ul&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;nginx&lt;/p&gt;
&lt;ul style=&quot;list-style-type: disc;&quot; data-ke-list-type=&quot;disc&quot;&gt;
&lt;li&gt;ports : 80 포트로 요청을 받기 위해 80포트 개방&lt;/li&gt;
&lt;li&gt;restart : nginx는 reverse proxy 역할을 하기 때문에 nginx 컨테이너가 다운되면 아무런 요청을 받을 수 없다. always로 설정&lt;/li&gt;
&lt;/ul&gt;
&lt;h2 data-ke-size=&quot;size26&quot;&gt;&amp;nbsp;&lt;/h2&gt;
&lt;h2 data-ke-size=&quot;size26&quot;&gt;&lt;b&gt;&lt;b&gt;2. IAM 인증키 발급받기&lt;/b&gt;&lt;/b&gt;&lt;/h2&gt;
&lt;h4 style=&quot;color: #000000; text-align: start;&quot; data-ke-size=&quot;size20&quot;&gt;&lt;b&gt;[ IAM ]&lt;/b&gt;&lt;/h4&gt;
&lt;p style=&quot;color: #333333; text-align: start;&quot; data-ke-size=&quot;size16&quot;&gt;IAM은 리소스에 대한 액세스를 안전하게 제어할 수 있는 웹 서비스이다. 이를 통해 리소스를 사용&lt;span style=&quot;color: #333333; text-align: start;&quot;&gt;&lt;span&gt;&amp;nbsp;&lt;/span&gt;&lt;/span&gt;인증 및 권한 부여 대상을 제어한다.&lt;span&gt;&amp;nbsp;&lt;/span&gt;&lt;i&gt;Root 사용자&lt;span&gt;&amp;nbsp;&lt;/span&gt;&lt;/i&gt;는 AWS 서비스 및 리소스에 대한 완전한 엑세스 권한이 있다.&amp;nbsp; IAM 사용자는 Root 사용자가 부여한 권한만 가지고 있다.&lt;span&gt;&amp;nbsp;&lt;/span&gt;보안을 위해서는 항상 IAM 유저를 생성해 사용하는 것이 좋다.&lt;/p&gt;
&lt;p style=&quot;color: #333333; text-align: start;&quot; data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p style=&quot;color: #333333; text-align: start;&quot; data-ke-size=&quot;size16&quot;&gt;같은 AWS 서비스가 아닌 경우 AWS 서비스에 명령을 줄 수 있는 권한을 받아야 한다. 권한을 받는 방법으로는 IAM 사용자를 이용한 인증키(accessKey, secretKey)가 있다.&amp;nbsp;&lt;/p&gt;
&lt;p style=&quot;color: #333333; text-align: start;&quot; data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;h4 style=&quot;color: #333333; text-align: start;&quot; data-ke-size=&quot;size20&quot;&gt;&lt;b&gt;2-1. IAM 사용자 추가&lt;/b&gt;&lt;/h4&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p&gt;&lt;figure class=&quot;imageblock alignCenter&quot; data-ke-mobileStyle=&quot;widthOrigin&quot; data-filename=&quot;Group 62.png&quot; data-origin-width=&quot;1473&quot; data-origin-height=&quot;241&quot;&gt;&lt;span data-url=&quot;https://blog.kakaocdn.net/dn/cru2RV/btr6Fg28LF5/qHH3Cl6nsYm3IQqJj7toP0/img.png&quot; data-phocus=&quot;https://blog.kakaocdn.net/dn/cru2RV/btr6Fg28LF5/qHH3Cl6nsYm3IQqJj7toP0/img.png&quot;&gt;&lt;img src=&quot;https://blog.kakaocdn.net/dn/cru2RV/btr6Fg28LF5/qHH3Cl6nsYm3IQqJj7toP0/img.png&quot; srcset=&quot;https://img1.daumcdn.net/thumb/R1280x0/?scode=mtistory2&amp;fname=https%3A%2F%2Fblog.kakaocdn.net%2Fdn%2Fcru2RV%2Fbtr6Fg28LF5%2FqHH3Cl6nsYm3IQqJj7toP0%2Fimg.png&quot; onerror=&quot;this.onerror=null; this.src='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png'; this.srcset='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png';&quot; loading=&quot;lazy&quot; width=&quot;1473&quot; height=&quot;241&quot; data-filename=&quot;Group 62.png&quot; data-origin-width=&quot;1473&quot; data-origin-height=&quot;241&quot;/&gt;&lt;/span&gt;&lt;/figure&gt;
&lt;figure class=&quot;imageblock alignLeft&quot; data-ke-mobileStyle=&quot;widthOrigin&quot; data-filename=&quot;edited_Group 61.png&quot; data-origin-width=&quot;704&quot; data-origin-height=&quot;331&quot;&gt;&lt;span data-url=&quot;https://blog.kakaocdn.net/dn/buQN6Z/btr6GVdmavD/F1cGmxcbT2K8l7er1PrAwk/img.png&quot; data-phocus=&quot;https://blog.kakaocdn.net/dn/buQN6Z/btr6GVdmavD/F1cGmxcbT2K8l7er1PrAwk/img.png&quot;&gt;&lt;img src=&quot;https://blog.kakaocdn.net/dn/buQN6Z/btr6GVdmavD/F1cGmxcbT2K8l7er1PrAwk/img.png&quot; srcset=&quot;https://img1.daumcdn.net/thumb/R1280x0/?scode=mtistory2&amp;fname=https%3A%2F%2Fblog.kakaocdn.net%2Fdn%2FbuQN6Z%2Fbtr6GVdmavD%2FF1cGmxcbT2K8l7er1PrAwk%2Fimg.png&quot; onerror=&quot;this.onerror=null; this.src='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png'; this.srcset='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png';&quot; loading=&quot;lazy&quot; width=&quot;602&quot; height=&quot;344&quot; data-filename=&quot;edited_Group 61.png&quot; data-origin-width=&quot;704&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;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;직접 정책 연결 클릭 후, Elastic BeanStalk을 제어하기 위한&lt;b&gt;&amp;nbsp;AdministratorAccess-AWSElasticBeanstalk&amp;nbsp;&lt;/b&gt;권한 부여&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p&gt;&lt;figure class=&quot;imageblock alignCenter&quot; data-ke-mobileStyle=&quot;widthOrigin&quot; data-filename=&quot;edited_Group 67.png&quot; data-origin-width=&quot;1210&quot; data-origin-height=&quot;640&quot;&gt;&lt;span data-url=&quot;https://blog.kakaocdn.net/dn/cuSpC3/btr6BgpAfVY/s2spF1y4EoxEdX19VhcoXK/img.png&quot; data-phocus=&quot;https://blog.kakaocdn.net/dn/cuSpC3/btr6BgpAfVY/s2spF1y4EoxEdX19VhcoXK/img.png&quot;&gt;&lt;img src=&quot;https://blog.kakaocdn.net/dn/cuSpC3/btr6BgpAfVY/s2spF1y4EoxEdX19VhcoXK/img.png&quot; srcset=&quot;https://img1.daumcdn.net/thumb/R1280x0/?scode=mtistory2&amp;fname=https%3A%2F%2Fblog.kakaocdn.net%2Fdn%2FcuSpC3%2Fbtr6BgpAfVY%2Fs2spF1y4EoxEdX19VhcoXK%2Fimg.png&quot; onerror=&quot;this.onerror=null; this.src='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png'; this.srcset='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png';&quot; loading=&quot;lazy&quot; width=&quot;1210&quot; height=&quot;640&quot; data-filename=&quot;edited_Group 67.png&quot; data-origin-width=&quot;1210&quot; data-origin-height=&quot;640&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;아래와 같이 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 alignLeft&quot; data-ke-mobileStyle=&quot;widthOrigin&quot; data-filename=&quot;Group 68.png&quot; data-origin-width=&quot;543&quot; data-origin-height=&quot;276&quot;&gt;&lt;span data-url=&quot;https://blog.kakaocdn.net/dn/bWXNy9/btr6Cf4Q0lh/gFDBzqDlOFvrKEANaPY4M1/img.png&quot; data-phocus=&quot;https://blog.kakaocdn.net/dn/bWXNy9/btr6Cf4Q0lh/gFDBzqDlOFvrKEANaPY4M1/img.png&quot;&gt;&lt;img src=&quot;https://blog.kakaocdn.net/dn/bWXNy9/btr6Cf4Q0lh/gFDBzqDlOFvrKEANaPY4M1/img.png&quot; srcset=&quot;https://img1.daumcdn.net/thumb/R1280x0/?scode=mtistory2&amp;fname=https%3A%2F%2Fblog.kakaocdn.net%2Fdn%2FbWXNy9%2Fbtr6Cf4Q0lh%2FgFDBzqDlOFvrKEANaPY4M1%2Fimg.png&quot; onerror=&quot;this.onerror=null; this.src='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png'; this.srcset='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png';&quot; loading=&quot;lazy&quot; width=&quot;464&quot; height=&quot;236&quot; data-filename=&quot;Group 68.png&quot; data-origin-width=&quot;543&quot; data-origin-height=&quot;276&quot;/&gt;&lt;/span&gt;&lt;/figure&gt;
&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;h4 data-ke-size=&quot;size20&quot;&gt;&lt;b&gt;2-2. Secrets key 발급&lt;/b&gt;&lt;/h4&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;[ 해당 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 alignLeft&quot; data-ke-mobileStyle=&quot;widthOrigin&quot; data-filename=&quot;Group 65.png&quot; data-origin-width=&quot;836&quot; data-origin-height=&quot;539&quot;&gt;&lt;span data-url=&quot;https://blog.kakaocdn.net/dn/cKJtpp/btr6DKXK4Yj/WzvFH6e4wyLejqUKojQbo1/img.png&quot; data-phocus=&quot;https://blog.kakaocdn.net/dn/cKJtpp/btr6DKXK4Yj/WzvFH6e4wyLejqUKojQbo1/img.png&quot;&gt;&lt;img src=&quot;https://blog.kakaocdn.net/dn/cKJtpp/btr6DKXK4Yj/WzvFH6e4wyLejqUKojQbo1/img.png&quot; srcset=&quot;https://img1.daumcdn.net/thumb/R1280x0/?scode=mtistory2&amp;fname=https%3A%2F%2Fblog.kakaocdn.net%2Fdn%2FcKJtpp%2Fbtr6DKXK4Yj%2FWzvFH6e4wyLejqUKojQbo1%2Fimg.png&quot; onerror=&quot;this.onerror=null; this.src='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png'; this.srcset='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png';&quot; loading=&quot;lazy&quot; width=&quot;670&quot; height=&quot;432&quot; data-filename=&quot;Group 65.png&quot; data-origin-width=&quot;836&quot; data-origin-height=&quot;539&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&gt;&lt;figure class=&quot;imageblock alignLeft&quot; data-ke-mobileStyle=&quot;widthOrigin&quot; data-filename=&quot;Group 66.png&quot; data-origin-width=&quot;615&quot; data-origin-height=&quot;241&quot;&gt;&lt;span data-url=&quot;https://blog.kakaocdn.net/dn/cGzvkV/btr6BgC9N3f/2pce9f7uAQNPThzY8U4CX1/img.png&quot; data-phocus=&quot;https://blog.kakaocdn.net/dn/cGzvkV/btr6BgC9N3f/2pce9f7uAQNPThzY8U4CX1/img.png&quot;&gt;&lt;img src=&quot;https://blog.kakaocdn.net/dn/cGzvkV/btr6BgC9N3f/2pce9f7uAQNPThzY8U4CX1/img.png&quot; srcset=&quot;https://img1.daumcdn.net/thumb/R1280x0/?scode=mtistory2&amp;fname=https%3A%2F%2Fblog.kakaocdn.net%2Fdn%2FcGzvkV%2Fbtr6BgC9N3f%2F2pce9f7uAQNPThzY8U4CX1%2Fimg.png&quot; onerror=&quot;this.onerror=null; this.src='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png'; this.srcset='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png';&quot; loading=&quot;lazy&quot; width=&quot;462&quot; height=&quot;181&quot; data-filename=&quot;Group 66.png&quot; data-origin-width=&quot;615&quot; data-origin-height=&quot;241&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;위에서 발급한 access key 와 secret access key를&lt;span&gt;&amp;nbsp;&lt;/span&gt;&lt;span style=&quot;color: #333333; text-align: start;&quot;&gt;Github 레포에 secrets key로&lt;span&gt;&lt;span&gt;&amp;nbsp;&lt;/span&gt;등록한다.&lt;/span&gt;&lt;/span&gt;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p&gt;&lt;figure class=&quot;imageblock alignCenter&quot; data-ke-mobileStyle=&quot;widthOrigin&quot; data-filename=&quot;Group 63.png&quot; data-origin-width=&quot;1139&quot; data-origin-height=&quot;345&quot;&gt;&lt;span data-url=&quot;https://blog.kakaocdn.net/dn/pXfZc/btr6AN83Fsa/b04SGKkFfdmth4S08FhynK/img.png&quot; data-phocus=&quot;https://blog.kakaocdn.net/dn/pXfZc/btr6AN83Fsa/b04SGKkFfdmth4S08FhynK/img.png&quot;&gt;&lt;img src=&quot;https://blog.kakaocdn.net/dn/pXfZc/btr6AN83Fsa/b04SGKkFfdmth4S08FhynK/img.png&quot; srcset=&quot;https://img1.daumcdn.net/thumb/R1280x0/?scode=mtistory2&amp;fname=https%3A%2F%2Fblog.kakaocdn.net%2Fdn%2FpXfZc%2Fbtr6AN83Fsa%2Fb04SGKkFfdmth4S08FhynK%2Fimg.png&quot; onerror=&quot;this.onerror=null; this.src='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png'; this.srcset='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png';&quot; loading=&quot;lazy&quot; width=&quot;1139&quot; height=&quot;345&quot; data-filename=&quot;Group 63.png&quot; data-origin-width=&quot;1139&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;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;h2 data-ke-size=&quot;size26&quot;&gt;&lt;b&gt;3. Github Action yml 수정&lt;/b&gt;&lt;/h2&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;이전 글에서 작성한 Github Action yml 파일에 아래 코드를 추가한다. Elastic BeanStalk 플러그인을 사용하는 코드이다. 앞에서 생성한 IAM 인증키를 사용한다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;h4 data-ke-size=&quot;size20&quot;&gt;&lt;b&gt;3-1. deploy 추가&lt;/b&gt;&lt;/h4&gt;
&lt;pre id=&quot;code_1680017336632&quot; class=&quot;bash&quot; data-ke-language=&quot;bash&quot; data-ke-type=&quot;codeblock&quot;&gt;&lt;code&gt;      - name: Get current time
        uses: 1466587594/get-current-time@v2
        id: current-time
        with:
          format: YYYY-MM-DDTHH-mm-ss
          utcOffset: &quot;+09:00&quot;

      - name: Deploy to EB
        uses: einaregilsson/beanstalk-deploy@v18
        with:
          aws_access_key: ${{ secrets.AWS_ACCESS_KEY_ID }}
          aws_secret_key: ${{ secrets.AWS_SECRET_ACCESS_KEY }}
          application_name: docker-springboot
          environment_name: Dockerspringboot-env
          version_label: deploy-${{steps.current-time.outputs.formattedTime}}
          region: ap-northeast-2
          deployment_package: demo/docker-compose.yml&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;name: Get current time&lt;/p&gt;
&lt;ul style=&quot;list-style-type: disc;&quot; data-ke-list-type=&quot;disc&quot;&gt;
&lt;li&gt;uses:&amp;nbsp;1466587594/get-current-time@v2&amp;nbsp;: 배포될때마다 유니크한 버저닝을 하기 위해 현재시간 플러그인 사용&lt;/li&gt;
&lt;/ul&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;name: Deploy to EB&lt;/p&gt;
&lt;ul style=&quot;list-style-type: disc;&quot; data-ke-list-type=&quot;disc&quot;&gt;
&lt;li&gt;uses: einaregilsson/beanstalk-deploy@v18 : EB 배포를 위한 플러그인 사용 (&lt;a href=&quot;https://github.com/einaregilsson/beanstalk-deploy&quot; target=&quot;_blank&quot; rel=&quot;noopener&quot;&gt;참고&lt;/a&gt;)&lt;/li&gt;
&lt;li&gt;미리 생성해둔 IAM 인증키 사용&lt;/li&gt;
&lt;li&gt;application_name :&amp;nbsp; 앞서 생성한 EB 애플리케이션 이름 작성&lt;/li&gt;
&lt;li&gt;environment_name : EB 애플리케이션 환경 이름 작성&lt;/li&gt;
&lt;li&gt;version_label : 위에서 정의한 현재시간 플러그인 사용해 버전 라벨 설정&lt;/li&gt;
&lt;li&gt;deployment_package : 위에서 작성한 docker-compose 파일 경로 작성 (중요)&lt;/li&gt;
&lt;/ul&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;h4 data-ke-size=&quot;size20&quot;&gt;&lt;b&gt;3-2. 최종 Github Action yml 파일&lt;/b&gt;&lt;/h4&gt;
&lt;pre id=&quot;code_1680018534813&quot; class=&quot;bash&quot; data-ke-language=&quot;bash&quot; data-ke-type=&quot;codeblock&quot;&gt;&lt;code&gt;name: deploy

on:
  push:
    branches:
      - 'main'

jobs:
  build:
    runs-on: ubuntu-latest

    steps:
      - name: Checkout
        uses: actions/checkout@v3

      - name: Set up JDK 17
        uses: actions/setup-java@v3
        with:
          java-version: '17'
          distribution: 'temurin'

      - name: Grant execute permission for gradlew
        run: |
          cd demo
          chmod +x ./gradlew

      - name: Build with Gradle
        run: |
          cd demo
          ./gradlew clean build -x test

      - name: Login to Docker Hub
        uses: docker/login-action@v2
        with:
          username: ${{ secrets.DOCKERHUB_USERNAME }}
          password: ${{ secrets.DOCKERHUB_TOKEN }}


      - name: Build Docker Image &amp;amp; Push to Docker Hub
        run: |
          docker build ./demo/ -t twoosky/springboot-demo
          docker build ./demo/nginx -t twoosky/nginx-demo
          docker push twoosky/springboot-demo
          docker push twoosky/nginx-demo

      - name: Get current time
        uses: 1466587594/get-current-time@v2
        id: current-time
        with:
          format: YYYY-MM-DDTHH-mm-ss
          utcOffset: &quot;+09:00&quot;

      - name: Deploy to EB
        uses: einaregilsson/beanstalk-deploy@v18
        with:
          aws_access_key: ${{ secrets.AWS_ACCESS_KEY_ID }}
          aws_secret_key: ${{ secrets.AWS_SECRET_ACCESS_KEY }}
          application_name: docker-springboot
          environment_name: Dockerspringboot-env
          version_label: deploy-${{steps.current-time.outputs.formattedTime}}
          region: ap-northeast-2
          deployment_package: demo/docker-compose.yml&lt;/code&gt;&lt;/pre&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;Github Action에서 실행하는 job은 아래와 같다.&lt;/p&gt;
&lt;ul style=&quot;list-style-type: disc;&quot; data-ke-list-type=&quot;disc&quot;&gt;
&lt;li&gt;Spring boot 프로젝트 빌드&lt;/li&gt;
&lt;li&gt;Dockerfile 빌드해 docker image 생성&lt;/li&gt;
&lt;li&gt;Docker hub에 docker image 푸시&lt;/li&gt;
&lt;li&gt;Elastic Beanstalk에 배포&lt;/li&gt;
&lt;/ul&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;h2 data-ke-size=&quot;size26&quot;&gt;&lt;b&gt;3. Github Action 실행&lt;/b&gt;&lt;/h2&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;마지막으로, 해당 Repository의 main 브랜치에 푸시해 Github Action을 실행시켜보자. 그 후 Elastic Beanstalk에 제대로 배포되는지 확인해보자.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;h4 data-ke-size=&quot;size20&quot;&gt;&lt;b&gt;3-1. Github Action 실행&lt;/b&gt;&lt;/h4&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;Repository - Actions 탭에 아래와 같이 녹색불이 떴으면 Github Action이 정상적으로 실행된 것이다.&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 alignLeft&quot; data-ke-mobileStyle=&quot;widthOrigin&quot; data-filename=&quot;Screenshot from 2023-03-29 01-28-18.png&quot; data-origin-width=&quot;791&quot; data-origin-height=&quot;169&quot;&gt;&lt;span data-url=&quot;https://blog.kakaocdn.net/dn/dvK5Is/btr6AZ9rJ8E/CwSMfr0aoCCfXIByASvpV0/img.png&quot; data-phocus=&quot;https://blog.kakaocdn.net/dn/dvK5Is/btr6AZ9rJ8E/CwSMfr0aoCCfXIByASvpV0/img.png&quot;&gt;&lt;img src=&quot;https://blog.kakaocdn.net/dn/dvK5Is/btr6AZ9rJ8E/CwSMfr0aoCCfXIByASvpV0/img.png&quot; srcset=&quot;https://img1.daumcdn.net/thumb/R1280x0/?scode=mtistory2&amp;fname=https%3A%2F%2Fblog.kakaocdn.net%2Fdn%2FdvK5Is%2Fbtr6AZ9rJ8E%2FCwSMfr0aoCCfXIByASvpV0%2Fimg.png&quot; onerror=&quot;this.onerror=null; this.src='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png'; this.srcset='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png';&quot; loading=&quot;lazy&quot; width=&quot;650&quot; height=&quot;139&quot; data-filename=&quot;Screenshot from 2023-03-29 01-28-18.png&quot; data-origin-width=&quot;791&quot; data-origin-height=&quot;169&quot;/&gt;&lt;/span&gt;&lt;/figure&gt;
&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;각 Job 별로 Github Action 빌드 로그를 볼 수 있다. 정상적으로 배포가 되었다면 아래와 같이 Deploy to EB 로그에서 배포 성공 메시지를 볼 수 있다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p&gt;&lt;figure class=&quot;imageblock alignLeft&quot; data-ke-mobileStyle=&quot;widthOrigin&quot; data-filename=&quot;Group 82.png&quot; data-origin-width=&quot;875&quot; data-origin-height=&quot;747&quot;&gt;&lt;span data-url=&quot;https://blog.kakaocdn.net/dn/sPJPs/btr6BTVax72/BcPP4D2BNkwzaRo5gRF3e1/img.png&quot; data-phocus=&quot;https://blog.kakaocdn.net/dn/sPJPs/btr6BTVax72/BcPP4D2BNkwzaRo5gRF3e1/img.png&quot;&gt;&lt;img src=&quot;https://blog.kakaocdn.net/dn/sPJPs/btr6BTVax72/BcPP4D2BNkwzaRo5gRF3e1/img.png&quot; srcset=&quot;https://img1.daumcdn.net/thumb/R1280x0/?scode=mtistory2&amp;fname=https%3A%2F%2Fblog.kakaocdn.net%2Fdn%2FsPJPs%2Fbtr6BTVax72%2FBcPP4D2BNkwzaRo5gRF3e1%2Fimg.png&quot; onerror=&quot;this.onerror=null; this.src='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png'; this.srcset='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png';&quot; loading=&quot;lazy&quot; width=&quot;651&quot; height=&quot;556&quot; data-filename=&quot;Group 82.png&quot; data-origin-width=&quot;875&quot; data-origin-height=&quot;747&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;h4 data-ke-size=&quot;size20&quot;&gt;&lt;b&gt;3-2. Elastic Beanstalk 배포 확인&lt;/b&gt;&lt;/h4&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;배포에 성공했다면, AWS Elastic Beanstalk 대시보드에 있는 URL을 클릭해보자.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p&gt;&lt;figure class=&quot;imageblock alignLeft&quot; data-ke-mobileStyle=&quot;widthOrigin&quot; data-filename=&quot;Group 81.png&quot; data-origin-width=&quot;805&quot; data-origin-height=&quot;430&quot;&gt;&lt;span data-url=&quot;https://blog.kakaocdn.net/dn/bDtEHL/btr6DYhougx/rdTEhSKdPjfI0C7B6TNUG1/img.png&quot; data-phocus=&quot;https://blog.kakaocdn.net/dn/bDtEHL/btr6DYhougx/rdTEhSKdPjfI0C7B6TNUG1/img.png&quot;&gt;&lt;img src=&quot;https://blog.kakaocdn.net/dn/bDtEHL/btr6DYhougx/rdTEhSKdPjfI0C7B6TNUG1/img.png&quot; srcset=&quot;https://img1.daumcdn.net/thumb/R1280x0/?scode=mtistory2&amp;fname=https%3A%2F%2Fblog.kakaocdn.net%2Fdn%2FbDtEHL%2Fbtr6DYhougx%2FrdTEhSKdPjfI0C7B6TNUG1%2Fimg.png&quot; onerror=&quot;this.onerror=null; this.src='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png'; this.srcset='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png';&quot; loading=&quot;lazy&quot; width=&quot;673&quot; height=&quot;359&quot; data-filename=&quot;Group 81.png&quot; data-origin-width=&quot;805&quot; data-origin-height=&quot;430&quot;/&gt;&lt;/span&gt;&lt;/figure&gt;
&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;/hello 경로에 들어가면 User id와 name이 나오는 것을 확인할 수 있다. (배포 성공 ! )&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p&gt;&lt;figure class=&quot;imageblock alignLeft&quot; data-ke-mobileStyle=&quot;widthOrigin&quot; data-filename=&quot;Group 79.png&quot; data-origin-width=&quot;747&quot; data-origin-height=&quot;98&quot;&gt;&lt;span data-url=&quot;https://blog.kakaocdn.net/dn/6t9VV/btr6Ce5S3rx/jKksVhvtayWiqucRZO5lKk/img.png&quot; data-phocus=&quot;https://blog.kakaocdn.net/dn/6t9VV/btr6Ce5S3rx/jKksVhvtayWiqucRZO5lKk/img.png&quot;&gt;&lt;img src=&quot;https://blog.kakaocdn.net/dn/6t9VV/btr6Ce5S3rx/jKksVhvtayWiqucRZO5lKk/img.png&quot; srcset=&quot;https://img1.daumcdn.net/thumb/R1280x0/?scode=mtistory2&amp;fname=https%3A%2F%2Fblog.kakaocdn.net%2Fdn%2F6t9VV%2Fbtr6Ce5S3rx%2FjKksVhvtayWiqucRZO5lKk%2Fimg.png&quot; onerror=&quot;this.onerror=null; this.src='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png'; this.srcset='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png';&quot; loading=&quot;lazy&quot; width=&quot;678&quot; height=&quot;89&quot; data-filename=&quot;Group 79.png&quot; data-origin-width=&quot;747&quot; data-origin-height=&quot;98&quot;/&gt;&lt;/span&gt;&lt;/figure&gt;
&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;ec2에 들어가 nginx 컨테이너의 log를 확인하면 아래와 같이 health check가 정상적으로 되고 있는 것을 볼 수 있다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p&gt;&lt;figure class=&quot;imageblock alignLeft&quot; data-ke-mobileStyle=&quot;widthOrigin&quot; data-filename=&quot;Group 84.png&quot; data-origin-width=&quot;943&quot; data-origin-height=&quot;188&quot;&gt;&lt;span data-url=&quot;https://blog.kakaocdn.net/dn/c3zFH6/btr6GrckchS/NNPLxul2KR5qx7UiqAFVo0/img.png&quot; data-phocus=&quot;https://blog.kakaocdn.net/dn/c3zFH6/btr6GrckchS/NNPLxul2KR5qx7UiqAFVo0/img.png&quot;&gt;&lt;img src=&quot;https://blog.kakaocdn.net/dn/c3zFH6/btr6GrckchS/NNPLxul2KR5qx7UiqAFVo0/img.png&quot; srcset=&quot;https://img1.daumcdn.net/thumb/R1280x0/?scode=mtistory2&amp;fname=https%3A%2F%2Fblog.kakaocdn.net%2Fdn%2Fc3zFH6%2Fbtr6GrckchS%2FNNPLxul2KR5qx7UiqAFVo0%2Fimg.png&quot; onerror=&quot;this.onerror=null; this.src='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png'; this.srcset='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png';&quot; loading=&quot;lazy&quot; width=&quot;785&quot; height=&quot;157&quot; data-filename=&quot;Group 84.png&quot; data-origin-width=&quot;943&quot; data-origin-height=&quot;188&quot;/&gt;&lt;/span&gt;&lt;/figure&gt;
&lt;figure class=&quot;imageblock alignLeft&quot; data-ke-mobileStyle=&quot;widthOrigin&quot; data-filename=&quot;Group 80.png&quot; data-origin-width=&quot;713&quot; data-origin-height=&quot;92&quot;&gt;&lt;span data-url=&quot;https://blog.kakaocdn.net/dn/n6oo2/btr6A0tJaK6/NhWz8lBtdBTjEjeCUyvvZ0/img.png&quot; data-phocus=&quot;https://blog.kakaocdn.net/dn/n6oo2/btr6A0tJaK6/NhWz8lBtdBTjEjeCUyvvZ0/img.png&quot;&gt;&lt;img src=&quot;https://blog.kakaocdn.net/dn/n6oo2/btr6A0tJaK6/NhWz8lBtdBTjEjeCUyvvZ0/img.png&quot; srcset=&quot;https://img1.daumcdn.net/thumb/R1280x0/?scode=mtistory2&amp;fname=https%3A%2F%2Fblog.kakaocdn.net%2Fdn%2Fn6oo2%2Fbtr6A0tJaK6%2FNhWz8lBtdBTjEjeCUyvvZ0%2Fimg.png&quot; onerror=&quot;this.onerror=null; this.src='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png'; this.srcset='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png';&quot; loading=&quot;lazy&quot; width=&quot;690&quot; height=&quot;89&quot; data-filename=&quot;Group 80.png&quot; data-origin-width=&quot;713&quot; data-origin-height=&quot;92&quot;/&gt;&lt;/span&gt;&lt;/figure&gt;
&lt;/p&gt;
&lt;blockquote data-ke-style=&quot;style2&quot;&gt;ssh 을 사용해 ec2에 접속하려면 보안그룹 인바운드 규칙으로 22 포트 0.0.0.0/0 을 추가해줘야 한다.&lt;/blockquote&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;이로써 Docker + Github Action + AWS EB 를 사용한 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;&amp;nbsp;&lt;/p&gt;
&lt;h3 data-ke-size=&quot;size23&quot;&gt;&lt;b&gt;Reference&lt;/b&gt;&lt;/h3&gt;
&lt;ul style=&quot;list-style-type: disc;&quot; data-ke-list-type=&quot;disc&quot;&gt;
&lt;li&gt;&lt;a href=&quot;https://github.com/twoosky/spring-lab/tree/main/demo&quot; target=&quot;_blank&quot; rel=&quot;noopener&quot;&gt;Github 전체 코드&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;&lt;a href=&quot;https://jojoldu.tistory.com/549&quot; target=&quot;_blank&quot; rel=&quot;noopener&quot;&gt;https://jojoldu.tistory.com/549&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;&lt;a href=&quot;https://kitty-geno.tistory.com/30&quot; target=&quot;_blank&quot; rel=&quot;noopener&quot;&gt;https://kitty-geno.tistory.com/30&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;&lt;a href=&quot;https://velog.io/@woohobi/3.-%EB%8F%84%EC%BB%A4%EB%A5%BC-%EC%9D%B4%EC%9A%A9%ED%95%9C-beanstalk-%EB%B0%B0%ED%8F%AC%ED%95%98%EA%B8%B0-with-Github-Actions&quot; target=&quot;_blank&quot; rel=&quot;noopener&quot;&gt;https://velog.io/@woohobi/3.-%EB%8F%84%EC%BB%A4%EB%A5%BC-%EC%9D%B4%EC%9A%A9%ED%95%9C-beanstalk-%EB%B0%B0%ED%8F%AC%ED%95%98%EA%B8%B0-with-Github-Actions&lt;/a&gt;&lt;/li&gt;
&lt;/ul&gt;</description>
      <category>DevOps/AWS</category>
      <author>주인  장</author>
      <guid isPermaLink="true">https://twosky.tistory.com/56</guid>
      <comments>https://twosky.tistory.com/56#entry56comment</comments>
      <pubDate>Wed, 29 Mar 2023 00:41:40 +0900</pubDate>
    </item>
    <item>
      <title>[AWS] Spring Boot 프로젝트 Elastic beanstalk에 수동 배포하기</title>
      <link>https://twosky.tistory.com/55</link>
      <description>&lt;p data-ke-size=&quot;size16&quot;&gt;Docker + Github Action + Elastic beanstalk을 사용한 CI/CD를 구축하다 알 수 없는 오류가 발생해서 처음부터 차근차근해보고자 이 글을 쓰게 되었다. 그럼 Spring boot 프로젝트를 Elastic beanstalk에 수동 배포해보자.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;h2 data-ke-size=&quot;size26&quot;&gt;&lt;b&gt;1. Spring boot 프로젝트 생성&lt;/b&gt;&lt;/h2&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;Spring boot 3.0.5 사용&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;Spring&amp;nbsp; web 의존성만 추가해 프로젝트를 생성했다.&lt;/p&gt;
&lt;pre class=&quot;clean&quot;&gt;&lt;code&gt;implementation 'org.springframework.boot:spring-boot-starter-web'&lt;/code&gt;&lt;/pre&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;h2 data-ke-size=&quot;size26&quot;&gt;&lt;b&gt;2. Controller 작성&lt;/b&gt;&lt;/h2&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;간단히 hello world를 반환하는 api를 작성했다.&lt;/p&gt;
&lt;pre class=&quot;kotlin&quot;&gt;&lt;code&gt;@RestController
public class HelloController {

    @GetMapping(&quot;/&quot;)
    public String hello() {
        return &quot;hello world!&quot;;
    }
}&lt;/code&gt;&lt;/pre&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;h2 data-ke-size=&quot;size26&quot;&gt;&lt;b&gt;3. Dockerfile 작성 및 빌드&lt;/b&gt;&lt;/h2&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;1. Docker 이미지를 Docker hub에 올릴 것이므로, Dockerfile을 작성했다.&lt;/p&gt;
&lt;pre class=&quot;dockerfile&quot;&gt;&lt;code&gt;FROM openjdk:17-jdk-slim-buster

WORKDIR /app

COPY build/libs/demo-0.0.1-SNAPSHOT.jar app.jar

CMD [&quot;java&quot;, &quot;-jar&quot;, &quot;app.jar&quot;]&lt;/code&gt;&lt;/pre&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;2. Dockerfile 빌드해 Dcoker 이미지 생성&lt;/p&gt;
&lt;pre id=&quot;code_1679912094097&quot; class=&quot;bash&quot; data-ke-language=&quot;bash&quot; data-ke-type=&quot;codeblock&quot;&gt;&lt;code&gt;docker build . -t twoosky/springboot-demo&lt;/code&gt;&lt;/pre&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;3. Docker 컨테이너 실행 (정상 작동하는지 확인 해보기)&lt;/p&gt;
&lt;pre id=&quot;code_1679912197753&quot; class=&quot;bash&quot; data-ke-language=&quot;bash&quot; data-ke-type=&quot;codeblock&quot;&gt;&lt;code&gt; docker run -p 8080:8080 twoosky/springboot-demo&lt;/code&gt;&lt;/pre&gt;
&lt;h2 data-ke-size=&quot;size26&quot;&gt;&amp;nbsp;&lt;/h2&gt;
&lt;h2 data-ke-size=&quot;size26&quot;&gt;&lt;b&gt;4.&amp;nbsp; Docker hub에 푸시&lt;/b&gt;&lt;/h2&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;1. Docker hub 로그인&lt;/p&gt;
&lt;pre id=&quot;code_1679912292981&quot; class=&quot;bash&quot; data-ke-language=&quot;bash&quot; data-ke-type=&quot;codeblock&quot;&gt;&lt;code&gt;docker login&lt;/code&gt;&lt;/pre&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;2. Docker hub에 Docker 이미지 푸시&lt;/p&gt;
&lt;pre id=&quot;code_1679912315838&quot; class=&quot;bash&quot; data-ke-language=&quot;bash&quot; data-ke-type=&quot;codeblock&quot;&gt;&lt;code&gt; docker push twoosky/springboot-demo&lt;/code&gt;&lt;/pre&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p&gt;&lt;figure class=&quot;imageblock alignLeft&quot; data-ke-mobileStyle=&quot;widthOrigin&quot; data-filename=&quot;Group 74.png&quot; data-origin-width=&quot;840&quot; data-origin-height=&quot;178&quot;&gt;&lt;span data-url=&quot;https://blog.kakaocdn.net/dn/WjC5x/btr53D0Ja98/H4iqMIJQJpjkfSm1krnqnk/img.png&quot; data-phocus=&quot;https://blog.kakaocdn.net/dn/WjC5x/btr53D0Ja98/H4iqMIJQJpjkfSm1krnqnk/img.png&quot;&gt;&lt;img src=&quot;https://blog.kakaocdn.net/dn/WjC5x/btr53D0Ja98/H4iqMIJQJpjkfSm1krnqnk/img.png&quot; srcset=&quot;https://img1.daumcdn.net/thumb/R1280x0/?scode=mtistory2&amp;fname=https%3A%2F%2Fblog.kakaocdn.net%2Fdn%2FWjC5x%2Fbtr53D0Ja98%2FH4iqMIJQJpjkfSm1krnqnk%2Fimg.png&quot; onerror=&quot;this.onerror=null; this.src='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png'; this.srcset='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png';&quot; loading=&quot;lazy&quot; width=&quot;717&quot; height=&quot;152&quot; data-filename=&quot;Group 74.png&quot; data-origin-width=&quot;840&quot; data-origin-height=&quot;178&quot;/&gt;&lt;/span&gt;&lt;/figure&gt;
&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;3. Docker hub에 제대로 이미지가 푸시되었는지 확인&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p&gt;&lt;figure class=&quot;imageblock alignLeft&quot; data-ke-mobileStyle=&quot;widthOrigin&quot; data-filename=&quot;Group 73.png&quot; data-origin-width=&quot;488&quot; data-origin-height=&quot;261&quot;&gt;&lt;span data-url=&quot;https://blog.kakaocdn.net/dn/bCXfW1/btr6otI0LO1/2IyKGGLklK4E2aUki3kN81/img.png&quot; data-phocus=&quot;https://blog.kakaocdn.net/dn/bCXfW1/btr6otI0LO1/2IyKGGLklK4E2aUki3kN81/img.png&quot;&gt;&lt;img src=&quot;https://blog.kakaocdn.net/dn/bCXfW1/btr6otI0LO1/2IyKGGLklK4E2aUki3kN81/img.png&quot; srcset=&quot;https://img1.daumcdn.net/thumb/R1280x0/?scode=mtistory2&amp;fname=https%3A%2F%2Fblog.kakaocdn.net%2Fdn%2FbCXfW1%2Fbtr6otI0LO1%2F2IyKGGLklK4E2aUki3kN81%2Fimg.png&quot; onerror=&quot;this.onerror=null; this.src='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png'; this.srcset='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png';&quot; loading=&quot;lazy&quot; width=&quot;460&quot; height=&quot;246&quot; data-filename=&quot;Group 73.png&quot; data-origin-width=&quot;488&quot; data-origin-height=&quot;261&quot;/&gt;&lt;/span&gt;&lt;/figure&gt;
&lt;/p&gt;
&lt;h2 data-ke-size=&quot;size26&quot;&gt;&amp;nbsp;&lt;/h2&gt;
&lt;h2 data-ke-size=&quot;size26&quot;&gt;&lt;b&gt;5. docker-compose.yml 작성&lt;/b&gt;&lt;/h2&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;image는 Docker hub 저장소의 이미지 이름으로 지정한다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;ports 는 tcp port 80과 컨테이너 port 8080을 연결한다는 의미이다.&lt;/p&gt;
&lt;pre class=&quot;dts&quot;&gt;&lt;code&gt;version: &quot;3.8&quot;
services:
  springboot:
    image: &quot;twoosky/springboot-demo&quot;
    ports:
      - &quot;80:8080&quot;
    restart: &quot;always&quot;&lt;/code&gt;&lt;/pre&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;h2 data-ke-size=&quot;size26&quot;&gt;&lt;b&gt;5. AWS Elastic Beanstalk 애플리케이션 생성&lt;/b&gt;&lt;/h2&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;아래 부분만 입력하고, 나머지는 기본설정으로 해주었다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p&gt;&lt;figure class=&quot;imageblock alignLeft&quot; data-ke-mobileStyle=&quot;widthOrigin&quot; data-filename=&quot;Group 71.png&quot; data-origin-width=&quot;921&quot; data-origin-height=&quot;667&quot;&gt;&lt;span data-url=&quot;https://blog.kakaocdn.net/dn/ccQLFy/btr6oZnkZvq/Gr3WRKgU2hgRPGnKYqIHyk/img.png&quot; data-phocus=&quot;https://blog.kakaocdn.net/dn/ccQLFy/btr6oZnkZvq/Gr3WRKgU2hgRPGnKYqIHyk/img.png&quot;&gt;&lt;img src=&quot;https://blog.kakaocdn.net/dn/ccQLFy/btr6oZnkZvq/Gr3WRKgU2hgRPGnKYqIHyk/img.png&quot; srcset=&quot;https://img1.daumcdn.net/thumb/R1280x0/?scode=mtistory2&amp;fname=https%3A%2F%2Fblog.kakaocdn.net%2Fdn%2FccQLFy%2Fbtr6oZnkZvq%2FGr3WRKgU2hgRPGnKYqIHyk%2Fimg.png&quot; onerror=&quot;this.onerror=null; this.src='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png'; this.srcset='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png';&quot; loading=&quot;lazy&quot; width=&quot;740&quot; height=&quot;536&quot; data-filename=&quot;Group 71.png&quot; data-origin-width=&quot;921&quot; data-origin-height=&quot;667&quot;/&gt;&lt;/span&gt;&lt;/figure&gt;
&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;h2 data-ke-size=&quot;size26&quot;&gt;&lt;b&gt;6. AWS Elastic beanstalk에 docker-compose 배포&lt;/b&gt;&lt;/h2&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;애플리케이션 생성 후 애플리케이션 환경에 들어가면 아래와 같은 화면이 나온다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;업로드 및 배포를 클릭해 위에서 작성한 docker-compose 파일을 elastic beanstalk에 배포해보자&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;1. 업로드 및 배포 클릭&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p&gt;&lt;figure class=&quot;imageblock alignCenter&quot; data-ke-mobileStyle=&quot;widthOrigin&quot; data-filename=&quot;Group 77.png&quot; data-origin-width=&quot;970&quot; data-origin-height=&quot;420&quot;&gt;&lt;span data-url=&quot;https://blog.kakaocdn.net/dn/cCrNXl/btr6eSXcDO5/dwlhlx1pJs8k6N605c4Ofk/img.png&quot; data-phocus=&quot;https://blog.kakaocdn.net/dn/cCrNXl/btr6eSXcDO5/dwlhlx1pJs8k6N605c4Ofk/img.png&quot;&gt;&lt;img src=&quot;https://blog.kakaocdn.net/dn/cCrNXl/btr6eSXcDO5/dwlhlx1pJs8k6N605c4Ofk/img.png&quot; srcset=&quot;https://img1.daumcdn.net/thumb/R1280x0/?scode=mtistory2&amp;fname=https%3A%2F%2Fblog.kakaocdn.net%2Fdn%2FcCrNXl%2Fbtr6eSXcDO5%2Fdwlhlx1pJs8k6N605c4Ofk%2Fimg.png&quot; onerror=&quot;this.onerror=null; this.src='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png'; this.srcset='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png';&quot; loading=&quot;lazy&quot; width=&quot;970&quot; height=&quot;420&quot; data-filename=&quot;Group 77.png&quot; data-origin-width=&quot;970&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;p data-ke-size=&quot;size16&quot;&gt;2. docker-compose 파일 업로드&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p&gt;&lt;figure class=&quot;imageblock alignLeft&quot; data-ke-mobileStyle=&quot;widthOrigin&quot; data-filename=&quot;Group 76.png&quot; data-origin-width=&quot;589&quot; data-origin-height=&quot;481&quot;&gt;&lt;span data-url=&quot;https://blog.kakaocdn.net/dn/cSbuPX/btr6ouumWZ1/8rrcfUNwaq7kZZRh0KFlEk/img.png&quot; data-phocus=&quot;https://blog.kakaocdn.net/dn/cSbuPX/btr6ouumWZ1/8rrcfUNwaq7kZZRh0KFlEk/img.png&quot;&gt;&lt;img src=&quot;https://blog.kakaocdn.net/dn/cSbuPX/btr6ouumWZ1/8rrcfUNwaq7kZZRh0KFlEk/img.png&quot; srcset=&quot;https://img1.daumcdn.net/thumb/R1280x0/?scode=mtistory2&amp;fname=https%3A%2F%2Fblog.kakaocdn.net%2Fdn%2FcSbuPX%2Fbtr6ouumWZ1%2F8rrcfUNwaq7kZZRh0KFlEk%2Fimg.png&quot; onerror=&quot;this.onerror=null; this.src='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png'; this.srcset='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png';&quot; loading=&quot;lazy&quot; width=&quot;502&quot; height=&quot;410&quot; data-filename=&quot;Group 76.png&quot; data-origin-width=&quot;589&quot; data-origin-height=&quot;481&quot;/&gt;&lt;/span&gt;&lt;/figure&gt;
&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;배포가 끝난 뒤 애플리케이션 환경에서 위 링크를 클릭하면 아래와 같이 &lt;b&gt;hello world!&lt;/b&gt; 가 나온다. (배포 성공!)&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p&gt;&lt;figure class=&quot;imageblock alignLeft&quot; data-ke-mobileStyle=&quot;widthOrigin&quot; data-filename=&quot;Screenshot from 2023-03-27 19-35-31.png&quot; data-origin-width=&quot;513&quot; data-origin-height=&quot;87&quot;&gt;&lt;span data-url=&quot;https://blog.kakaocdn.net/dn/eOnDq6/btr6btcep1V/LVjeH4CQ0Xz73EAT6Q8N71/img.png&quot; data-phocus=&quot;https://blog.kakaocdn.net/dn/eOnDq6/btr6btcep1V/LVjeH4CQ0Xz73EAT6Q8N71/img.png&quot;&gt;&lt;img src=&quot;https://blog.kakaocdn.net/dn/eOnDq6/btr6btcep1V/LVjeH4CQ0Xz73EAT6Q8N71/img.png&quot; srcset=&quot;https://img1.daumcdn.net/thumb/R1280x0/?scode=mtistory2&amp;fname=https%3A%2F%2Fblog.kakaocdn.net%2Fdn%2FeOnDq6%2Fbtr6btcep1V%2FLVjeH4CQ0Xz73EAT6Q8N71%2Fimg.png&quot; onerror=&quot;this.onerror=null; this.src='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png'; this.srcset='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png';&quot; loading=&quot;lazy&quot; width=&quot;513&quot; height=&quot;87&quot; data-filename=&quot;Screenshot from 2023-03-27 19-35-31.png&quot; data-origin-width=&quot;513&quot; data-origin-height=&quot;87&quot;/&gt;&lt;/span&gt;&lt;/figure&gt;
&lt;/p&gt;</description>
      <category>DevOps/AWS</category>
      <author>주인  장</author>
      <guid isPermaLink="true">https://twosky.tistory.com/55</guid>
      <comments>https://twosky.tistory.com/55#entry55comment</comments>
      <pubDate>Mon, 27 Mar 2023 19:40:59 +0900</pubDate>
    </item>
  </channel>
</rss>