<?xml version="1.0" encoding="utf-8"?>
<rss version="2.0" xmlns:atom="http://www.w3.org/2005/Atom">
    <channel>
        <title>hajun-ryu.log</title>
        <link>https://velog.io/</link>
        <description></description>
        <lastBuildDate>Tue, 16 Apr 2024 13:01:47 GMT</lastBuildDate>
        <docs>https://validator.w3.org/feed/docs/rss2.html</docs>
        <generator>https://github.com/jpmonette/feed</generator>
        <image>
            <title>hajun-ryu.log</title>
            <url>https://images.velog.io/images/hajun-ryu/profile/4a259bb6-75cc-46f4-a6fb-b376ce9a7e92/social.jpeg</url>
            <link>https://velog.io/</link>
        </image>
        <copyright>Copyright (C) 2019. hajun-ryu.log. All rights reserved.</copyright>
        <atom:link href="https://v2.velog.io/rss/hajun-ryu" rel="self" type="application/rss+xml"/>
        <item>
            <title><![CDATA[Initial Mount - React Core Deep Dive]]></title>
            <link>https://velog.io/@hajun-ryu/Initial-Mount-React-Core-Deep-Dive</link>
            <guid>https://velog.io/@hajun-ryu/Initial-Mount-React-Core-Deep-Dive</guid>
            <pubDate>Tue, 16 Apr 2024 13:01:47 GMT</pubDate>
            <description><![CDATA[<blockquote>
<p><a href="https://jser.dev/2023-07-14-initial-mount">jser.dev 블로그의 Initial Mount 챕터</a>를 보며 번역하고 이해한것을 정리하였습니다. 해당 내용에 틀린점이나 뇌피셜이 포함되어있을 수 있습니다.</p>
</blockquote>
<p>이전 Overview에서 React가 내부적으로 Fiber Tree를 사용하여 최소한의 DOM 업데이트를 계산하고 커밋 단계에서 실제 업데이트를 진행한다고 간략하게만 이야기했다. 이번에는 React가 초기 마운트(초기 렌더링)시에 정확히 어떤 동작을 하는지 알아보자.</p>
<h1 id="1-fiber-architecture에-대한-간략한-소개">1. Fiber Architecture에 대한 간략한 소개</h1>
<p><img src="https://velog.velcdn.com/images/hajun-ryu/post/ff22a2b6-a3af-4993-892b-a12e66dedd20/image.png" alt="">
(이미지 출처: <a href="https://jser.dev/2023-07-14-initial-mount">jser.dev - Initial Mount</a>)  </p>
<p>Fiber는 간단하게는 React가 내부적으로 앱 상태를 어떻게 관리하는지에 대한 아키텍처이다. FiberRootNode와 FiberNodes로 구성된 트리형 구조이다. Fiber Tree에서 FiberRootNode를 제외한 모든 Node는 FiberNode이다. FiberRootNode와 FiberNode에 대해 더 자세히 살펴보자.</p>
<h2 id="11-fiberrootnode">1.1 FiberRootNode</h2>
<p>FiberRootNode는 React Root 역할을 하는 특수한 Node로, 전체 앱에 대한 필수 메타 정보를 보유한다. FiberRootNode의 <code>current</code>는 실제 Fiber Tree를 가리키며 새로운 Fiber Tree가 구성될 때마다 <code>current</code>는 새로운 HostRoot를 다시 가리키게 된다.</p>
<h2 id="12-fibernode">1.2 FiberNode</h2>
<p>위에서 말했듯 FiberNode는 FiberRootNode 이외의 모든 Node를 의미한다. 이제 FiberNode의 중요한 프로퍼티들을 알아보자.</p>
<ul>
<li>tag: FiberNode에는 tag로 구분되는 많은 하위 유형들이 있다. 예를 들면 <code>FunctionComponent</code>, <code>HostRoot</code>, <code>ContextConsumer</code>, <code>MemoComponent</code>, <code>SUspenseComponent</code> 등이 있다.</li>
<li>stateNode: <code>stateNode</code>는 다른 백업 데이터를 가리킨다. <code>HostComponent</code>의 경우를 살펴보자면 <code>HostComponent</code>의 <code>stateNode</code>는 실제 백업 DOM Node를 가리킨다.</li>
<li>child, sibling, return: 해당 요소들은 함께 트리 구조를 형성한다.
  (이 부분은 정확히 이해하지 못함)</li>
<li>elementType: 우리가 작성하는 함수 컴포넌트 또는 HTML tag이다.
  (이해하기로는 함수 컴포넌트명 또는 JSX에서 작성한 기본 HTML tag명)</li>
<li>flags: 커밋 단계에서 적용 할 업데이트를 나타낸다. <code>subtreeFlags</code>는 해당 Node의 하위 트리를 위한것이다.</li>
<li>lanes: 보류중인 업데이트의 우선순위를 나타낸다. <code>childLanes이다</code>는 해당 Node의 하위 트리를 위한것이다.</li>
<li>memoizedState: 추상적이지만 중요한 데이터를 가리키고 있다. 함수 컴포넌트에서는 hooks를 의미한다.
  (정확히 어떤 역할을 하는지는 정확히 이해하지 못했다. 추후 업데이트 해보자)</li>
</ul>
<h1 id="2-trigger-단계의-initial-mount">2. Trigger 단계의 Initial mount</h1>
<p>우리가 잘 알고있는 React의 <code>createRoot()</code>함수는 더미 HostRoot FiberNode가 있는 React Root를 생성한다. 이 더미 HostRoot는 createRoot()가 생성한 root의 <code>current</code>로 참조할 수 있다.</p>
<p>자세한건 아래 ReactDOMRoot.js에서 주요 로직만을 골라낸 코드를 직접 살펴보자. 함수의 선언부와 호출 구문이 순서대로 되어있지는 않으나 주요한 내용은 모두 있으니 주석을 활용하며 꼼꼼히 살펴보자.</p>
<pre><code class="language-typescript">export function createRoot(
  container: Element | Document | DocumentFragment,
  options?: CreateRootOptions,
): RootType {
  let isStrictMode = false;
  let concurrentUpdatesByDefaultOverride = false;
  let identifierPrefix = &#39;&#39;;
  let onRecoverableError = defaultOnRecoverableError;
  let transitionCallbacks = null;

  // 여기서 root 변수에는 FiberRootNode의 참조값이 담긴다.
  const root = createContainer(
    container,
    ConcurrentRoot,
    null,
    isStrictMode,
    concurrentUpdatesByDefaultOverride,
    identifierPrefix,
    onRecoverableError,
    transitionCallbacks,
  );
  markContainerAsRoot(root.current, container);
  Dispatcher.current = ReactDOMClientDispatcher;

  const rootContainerElement: Document | Element | DocumentFragment =
    container.nodeType === COMMENT_NODE
      ? (container.parentNode: any)
      : container;
  listenToAllSupportedEvents(rootContainerElement);
  return new ReactDOMRoot(root);
}

export function createContainer(
  containerInfo: Container,
  tag: RootTag,
  hydrationCallbacks: null | SuspenseHydrationCallbacks,
  isStrictMode: boolean,
  concurrentUpdatesByDefaultOverride: null | boolean,
  identifierPrefix: string,
  onRecoverableError: (error: mixed) =&gt; void,
  transitionCallbacks: null | TransitionTracingCallbacks,
): OpaqueRoot {
  const hydrate = false;
  const initialChildren = null;
  // 위 코드에서 createContainer함수를 호출할때 createFiberRoot 함수의 return값이 반환된다.
  return createFiberRoot(
    containerInfo,
    tag,
    hydrate,
    initialChildren,
    hydrationCallbacks,
    isStrictMode,
    concurrentUpdatesByDefaultOverride,
    identifierPrefix,
    onRecoverableError,
    transitionCallbacks,
  );
}

export function createFiberRoot(
  containerInfo: Container,
  tag: RootTag,
  hydrate: boolean,
  initialChildren: ReactNodeList,
  hydrationCallbacks: null | SuspenseHydrationCallbacks,
  isStrictMode: boolean,
  concurrentUpdatesByDefaultOverride: null | boolean,
  identifierPrefix: string,
  onRecoverableError: null | ((error: mixed) =&gt; void),
  transitionCallbacks: null | TransitionTracingCallbacks,
): FiberRoot {
  // 실제로 FiberRootNode의 인스턴스가 생성되는 부분이다.
  const root: FiberRoot = (new FiberRootNode(
    containerInfo,
    tag,
    hydrate,
    identifierPrefix,
    onRecoverableError,
  ): any);
  const uninitializedFiber = createHostRootFiber(
    tag,
    isStrictMode,
    concurrentUpdatesByDefaultOverride,
  );

  // HostRoot의 FiberNode가 생성되어 FiberRootNode의 current에 할당된다.
  root.current = uninitializedFiber;

  uninitializedFiber.stateNode = root;
  ...
  initializeUpdateQueue(uninitializedFiber);
  return root;
}</code></pre>
<p><code>root.render()</code> 동작은 HostRoot에서 업데이트를 예약한다. 아래 코드는 render 함수의 동작이다.</p>
<pre><code class="language-typescript">function ReactDOMRoot(internalRoot: FiberRoot) {
  this._internalRoot = internalRoot;
}
ReactDOMHydrationRoot.prototype.render = ReactDOMRoot.prototype.render =
  function (children: ReactNodeList): void {
    const root = this._internalRoot;
    if (root === null) {
      throw new Error(&#39;Cannot update an unmounted root.&#39;);
    }
    updateContainer(children, root, null, null);
  };</code></pre>
<p>root 함수의 동작에서 updateContainer 함수를 호출하고 있는데 아래 코드는 updateContainer 함수의 동작이다. 자세히 살펴보면 update라는 변수에 createUpdate 함수의 반환값을 담고 있는데 해당 반환값에 있는 payload라는 프로퍼티에 element 파라미터 값을 저장하고있다. 이때 element파라미터에 전달되는 인자는 render함수를 호출할때 전달되는 첫번째 인자값이다.</p>
<pre><code class="language-typescript">export function updateContainer(
  element: ReactNodeList,
  container: OpaqueRoot,
  parentComponent: ?React$Component&lt;any, any&gt;,
  callback: ?Function,
): Lane {
  const current = container.current;
  const lane = requestUpdateLane(current);
  if (enableSchedulingProfiler) {
    markRenderScheduled(lane);
  }
  const context = getContextForSubtree(parentComponent);
  if (container.context === null) {
    container.context = context;
  } else {
    container.pendingContext = context;
  }
  const update = createUpdate(lane);
  // Caution: React DevTools currently depends on this property
  // being called &quot;element&quot;.

  // render() 함수를 호출할때 전달하는 첫번째 인자가 update객체의 payload 프로퍼티에 저장된다.
  update.payload = {element};

  // 이후 업데이트가 대기열에 추가되게 된다. 이게 어떻게 동작하는지는 해당 챕터에서 다루진 않는다. 다만 우선순위에 따라 업데이트가 처리되길 기다리고 있다는것만 알면된다.
  const root = enqueueUpdate(current, update, lane);

  if (root !== null) {
    scheduleUpdateOnFiber(root, current, lane);
    entangleTransitions(root, current, lane);
  }
  return lane;
}</code></pre>
<h1 id="3-render-단계의-initial-mount">3. Render 단계의 Initial mount</h1>
<h2 id="31-performconcurrentworkonroot">3.1 performConcurrentWorkOnRoot()</h2>
<p>이전 Overview에서 다뤘듯이 <code>PerformConcurrentWorkOnRoot()</code>는 Initial Mount 및 Re-Render에 대한 렌더링을 시작하는 진입점이다.</p>
<p>한가지 주의해야 할 점은 동시성으로 이름이 지정되더라도 필요한 경우 내부적으로 여전히 동기적 모드로 돌아간다는것이다. Initial Mount는 DefaultLane이 Lane을 차단하고 있기 때문에 발생하는 경우 중 하나이다.</p>
<p>다음 코드를 살펴보자. 일부 코드가 생략되어 있지만 sudo 코드라고 생각하고 함수명과 변수명을 글을 읽듯이 해석한다고만 생각해보자.</p>
<p>shouldTimeSlice 변수는 boolean type인데 어떨때 true값이 담기는지 살펴보면 root에 blockingLane이 포함되지 않으면서 ExpiredLane이 포함되지 않고 workLoop의 스케줄러 타임아웃이 비활성화 되거나 titmeOut이 되지 않았을때이다. 말로 풀어쓰는게 사실 더 복잡하니 코드 자체를 보는걸 추천한다.</p>
<p>이후 shouldTimeSlice값에 따라 동시성 렌더를 할것인지 동기적 렌더링을 할것인지 결정하게 된다.</p>
<pre><code class="language-typescript">function performConcurrentWorkOnRoot(root, didTimeout) {
  ...
  // 저장된 필드를 사용하여 작업 할 다음 Lane을 결정한다.
  let lanes = getNextLanes(
    root,
    root === workInProgressRoot ? workInProgressRootRenderLanes : NoLanes,
  );
  ...
  // 어떤 경우에는 time-slicing을 비활성화한다. 작업이 너무 오랫동안 CPU에 바인딩 되어 있거나 기본적으로 동기화 업데이트 모드에 있는 경우이다.
  const shouldTimeSlice =
    !includesBlockingLane(root, lanes) &amp;&amp;
    !includesExpiredLane(root, lanes) &amp;&amp;
    (disableSchedulerTimeoutInWorkLoop || !didTimeout);
  let exitStatus = shouldTimeSlice
    ? renderRootConcurrent(root, lanes)
    : renderRootSync(root, lanes);
  ...
}</code></pre>
<pre><code class="language-typescript">// Blocking means it is important, should not be interrupted.
export function includesBlockingLane(root: FiberRoot, lanes: Lanes) {
  //DefaultLane is blocking lane
  const SyncDefaultLanes =
    InputContinuousHydrationLane |
    InputContinuousLane |
    DefaultHydrationLane |
    DefaultLane;


  return (lanes &amp; SyncDefaultLanes) !== NoLanes;
}</code></pre>
<blockquote>
<p>지속적으로 언급되는 lane에 대해 자세히 알아보고 싶다면 jser.dev블로그의 <a href="https://jser.dev/react/2022/03/26/lanes-in-react/">lanes-in-react</a>포스트를 참고하세요.</p>
</blockquote>
<p>위 코드에서 보이듯 Initial Mount시에는 DefaultLane이 사용되고 있기 때문에 동시 모드가 실제로는 사용되지 않음을 알 수 있다. 이는 Initial Mount시에는 최대한 빨리 UI를 렌더링 해야하는데 동시 모드를 사용하며 렌더링을 지연시키는게 이에 맞지 않기 때문이다.</p>
<h2 id="32-renderrootsync">3.2 renderRootSync()</h2>
<p>renderRootSync는 내부적으로 그저 while loop를 돌고있는 함수이다.</p>
<pre><code class="language-typescript">function renderRootSync(root: FiberRoot, lanes: Lanes) {
  const prevExecutionContext = executionContext;
  executionContext |= RenderContext;
  const prevDispatcher = pushDispatcher();
  // If the root or lanes have changed, throw out the existing stack
  // and prepare a fresh one. Otherwise we&#39;ll continue where we left off.
  if (workInProgressRoot !== root || workInProgressRootRenderLanes !== lanes) {
    if (enableUpdaterTracking) {
      if (isDevToolsPresent) {
        const memoizedUpdaters = root.memoizedUpdaters;
        if (memoizedUpdaters.size &gt; 0) {
          restorePendingUpdaters(root, workInProgressRootRenderLanes);
          memoizedUpdaters.clear();
        }
        // At this point, move Fibers that scheduled the upcoming work from the Map to the Set.
        // If we bailout on this work, we&#39;ll move them back (like above).
        // It&#39;s important to move them now in case the work spawns more work at the same priority with different updaters.
        // That way we can keep the current update and future updates separate.
        movePendingFibersToMemoized(root, lanes);
      }
    }
    workInProgressTransitions = getTransitionsForLanes(root, lanes);
    // Root또는 Lane이 변경됐을 경우 새로운 Stack을 준비
    prepareFreshStack(root, lanes);
  }
  do {
    try {
      workLoopSync();
      break;
    } catch (thrownValue) {
      handleError(root, thrownValue);
    }
  } while (true);
  resetContextDependencies();
  executionContext = prevExecutionContext;
  popDispatcher(prevDispatcher);
  // Set this to null to indicate there&#39;s no in-progress render.
  workInProgressRoot = null;
  workInProgressRootRenderLanes = NoLanes;
  return workInProgressRootExitStatus;
}
// The work loop is an extremely hot path. Tell Closure not to inline it.
/** @noinline */
function workLoopSync() {
  // Already timed out, so perform work without checking if we need to yield.
  // 해당 while문의 조건은 workInProgress가 존재하는지에 대한 여부이다.
  while (workInProgress !== null) {
    // 함수명처럼 하나의 Fiber Node 단위로 작동한다. 해당 함수는 3.3에서 더 자세히 알아보자.
    performUnitOfWork(workInProgress);
  }
}</code></pre>
<p>3.2에서 중요한건 WorkInprogress가 무엇을 의미하는지 알아야한다. React코드를 살펴보다보면 <code>current</code>와 <code>workInprogress</code> prefix는 어디에나 있는데, React는 Fiber Tree를 사용하여 내부적으로 현재 상태를 나타내기 때문에 업데이트가 있을 때마다 React는 새로운 Tree를 구성하고 이전 Tree와 비교해야한다. 따라서 <code>current</code>는 UI에 그려지는 현재 버전을 의미하고 workInprogress는 빌드중인 다음 <code>current</code> 버전을 의미한다.</p>
<h2 id="33-performunitofwork">3.3 performUnitOfWork()</h2>
<p>해당 함수는 React가 단일 Fiber Node에서 작동하여 수행할 작업이 있는지 확인한다.</p>
<pre><code class="language-typescript">function performUnitOfWork(unitOfWork: Fiber): void {
  // The current, flushed, state of this fiber is the alternate. Ideally
  // nothing should rely on this, but relying on it here means that we don&#39;t
  // need an additional field on the work in progress.
  const current = unitOfWork.alternate;
  let next;
  if (enableProfilerTimer &amp;&amp; (unitOfWork.mode &amp; ProfileMode) !== NoMode) {
    startProfilerTimer(unitOfWork);
    next = beginWork(current, unitOfWork, subtreeRenderLanes);
    stopProfilerTimerIfRunningAndRecordDelta(unitOfWork, true);
  } else {
    next = beginWork(current, unitOfWork, subtreeRenderLanes);
  }
  resetCurrentDebugFiberInDEV();
  unitOfWork.memoizedProps = unitOfWork.pendingProps;
  if (next === null) {
    // If this doesn&#39;t spawn new work, complete the current work.
    completeUnitOfWork(unitOfWork);
  } else {
    workInProgress = next;
as mentioned, workLoopSync() is just a while loop

that keeps running completeUnitOfWork() on workInProgress

So assigning workInProgress here means setting next Fiber Node to work on

  }
  ReactCurrentOwner.current = null;
}</code></pre>
<p>beginWork() 는 실제 렌더링이 일어나는 곳이다. </p>
]]></description>
        </item>
        <item>
            <title><![CDATA[Overview of React internals - React Core Deep Dive]]></title>
            <link>https://velog.io/@hajun-ryu/Overview-of-React-internals-React-Core-Deep-Dive</link>
            <guid>https://velog.io/@hajun-ryu/Overview-of-React-internals-React-Core-Deep-Dive</guid>
            <pubDate>Tue, 02 Apr 2024 11:27:42 GMT</pubDate>
            <description><![CDATA[<h1 id="react-core-deep-dive-시리즈를-시작하며">React Core Deep Dive 시리즈를 시작하며</h1>
<p>리액트의 내부 동작을 알아보기 위해 JSer.dev님의 <a href="https://jser.dev/series/react-source-code-walkthrough">React Internals Deep Dive</a> 블로그를 기반으로 Deep Dive를 시작해보려고한다.</p>
<p>해당 시리즈에서는 블로그 원문을 단순 번역하는게 아니라 내가 학습한것들을 기반으로 배운것과 시도해본것들을 기록 할 것이다.
혼자만 알아보는 막글을 쓰고싶지는 않아서 공개된 장소에 글을 작성하지만 혹여나 이 글을 읽게된다면 글에서 제공되는 정보가 틀릴 수 있으니 검증은 따로 충분히 해주셨으면 좋겠다. (물론 나 자신도 정보에 대한 검증을 위해 자료도 같이 첨부하며 정리 할 예정이다)</p>
<h1 id="react-디버깅">React 디버깅</h1>
<p>리액트의 내부 동작을 알아보기 위해 실제 리액트의 사용자(개발자)가 break point를 설정하고 디버깅을 할 수 있는 방법에 대해 알아보자.</p>
<h2 id="break-point-지정하기">break point 지정하기</h2>
<p>break point를 지정하는 방법은 크게 2가지가 있다.</p>
<h3 id="코드단에서-지정하기">코드단에서 지정하기</h3>
<p>Javascript의 키워드(예약어)인 <a href="https://developer.mozilla.org/ko/docs/Web/JavaScript/Reference/Statements/debugger">debugger</a> 사용</p>
<pre><code class="language-typescript">function App() {
  debugger;

  const [count, setCount] = useState(0);

  useEffect(() =&gt; {
    debugger;

    setCount(prevCount =&gt; prevCount + 1);
  }, []);

  return &lt;div&gt;currentCount: {count}&lt;/div&gt;;
}</code></pre>
<h3 id="브라우저-개발자-도구로-지정하기">브라우저 개발자 도구로 지정하기</h3>
<p>브라우저의 개발자 도구(Chrome 기준)에서 특정 DOM 요소의 하위 트리가 변경되었을때 break point를 만들 수 있다.
아래 사진처럼 특정 요소의 하위 트리가 변경되었을때 브레이크 포인트를 만든다면 하위에 어떠한 요소 하나라도 업데이트 된다면 브레이크 포인트로 작동해야한다.
<img src="https://velog.velcdn.com/images/hajun-ryu/post/92d98aa9-971e-47dc-a6ec-4f4ba7a236c6/image.png" alt=""></p>
<h2 id="디버깅-해보기">디버깅 해보기</h2>
<p>일단 위에서 살펴본 방법으로 디버깅을 해보자. 개념적으로 먼저 살펴보기 위해 원문 블로그에서 제공하고 있는 <a href="https://jser.dev/demos/react/overview">데모 페이지</a>를 활용 할 것이다.</p>
<h3 id="1-break-point-설정">1. break point 설정</h3>
<p>a. 데모 페이지에 기본적으로 아래와 같은 break point가 debugger 키워드를 사용하여 지정되어 있다.</p>
<pre><code class="language-typescript">function App() {
  const [count, setCount] = useState(1);
  debugger;

  useEffect(() =&gt; {
    debugger;

    setCount((count) =&gt; count + 1);
  }, []);
  return &lt;button&gt;{count}&lt;/button&gt;;
}
ReactDOM.createRoot(document.getElementById(&quot;container&quot;)).render(&lt;App /&gt;);</code></pre>
<p>b. 개발자 도구를 연 후 id가 container인 노드에 break point를 걸어준다.
<img src="https://velog.velcdn.com/images/hajun-ryu/post/109aade3-9c09-427a-9a14-aa9c023807fd/image.png" alt=""></p>
<h3 id="컴포넌트-렌더링시-첫번째-break-point">컴포넌트 렌더링시 첫번째 break point</h3>
<p>break point를 설정한 후 개발자 도구를 열고 새로고침을 하게되면 break point마다 코드의 실행이 멈추며 디버깅을 할 수 있다.</p>
<p>이때 콜스택을 첫번째 break point인 App function의 debugger 구문에서 코드 실행이 중단된다. 이때 콜스택이 어떻게 쌓였는지 살펴보자. 콜 스택이기 때문에 아래에 있는 요소가 더 먼저 실행된 함수라고 생각하면 된다.</p>
<p><img src="https://velog.velcdn.com/images/hajun-ryu/post/8cadcaa0-3a28-490b-8852-df795ad6026b/image.png" alt=""></p>
<p>위 콜스택에서 React가 렌더링을 어떻게 수행하는지에 대해 중요 몇가지 기능을 위주로 살펴보자. 지금은 단순히 용어를 보는것만으로는 이해가 되지 않으니 이정도의 흐름을 가지고 있구나 정도로 읽고 넘어가보자.</p>
<ol>
<li><code>ReactDOMRoot.render</code>: 보통의 경우 React를 실행할때의 entry point인 index 파일에서 실행하는 render 함수이다. 여기까지는 개발자가 직접 코드단으로 수정할 수 있는 레벨이다.</li>
<li><code>scheduleUpdateOnFiber</code>: React에게 렌더링 할 위치를 알려주는 함수이다. 초기 렌더링시에는 루트에서 호출된다.</li>
<li><code>ensureRootIsScheduled</code>: 예약되어 있는 <code>performConcurrentWorkOnRoot</code>가 있는지 확인하는 함수이다. 해당 동작은 중요한 역할을 한다고 하는데 React Scheduler 섹션에서 좀 더 자세히 알아보자.</li>
<li><code>scheduleCallback</code>: React Scheduler의 일부이고 <code>postMessage</code>에 의해 호출되는 비동기 함수라고 한다. 지금으로썬 이해하기 쉽지 않아보이니 이 또한 React Scheduler에서 자세히 알아보자.</li>
<li><code>workLoop</code>: 브라우저의 <code>event loop</code>처럼 React의 반응성 요소들을 지속적으로 처리하기 위한 동작이다. 이 개념도 React Scheduler에서 자세히 알아보자.</li>
<li><code>performConcurrentWorkOnRoot</code>: 예약되어있는 작업이 실행되는 구분이며 이때 구성 요소가 실제로 렌더링 된다.</li>
</ol>
<h3 id="두번째-break-point">두번째 break point</h3>
]]></description>
        </item>
        <item>
            <title><![CDATA[EC2 + Github Actions로 Next.js 배포 및 자동화(CI/CD) - 4편: Github Actions workflow분석]]></title>
            <link>https://velog.io/@hajun-ryu/EC2-Github-Actions%EB%A1%9C-Next.js-%EB%B0%B0%ED%8F%AC-%EB%B0%8F-%EC%9E%90%EB%8F%99%ED%99%94CICD-4%ED%8E%B8-Github-Actions-workflow%EB%B6%84%EC%84%9D</link>
            <guid>https://velog.io/@hajun-ryu/EC2-Github-Actions%EB%A1%9C-Next.js-%EB%B0%B0%ED%8F%AC-%EB%B0%8F-%EC%9E%90%EB%8F%99%ED%99%94CICD-4%ED%8E%B8-Github-Actions-workflow%EB%B6%84%EC%84%9D</guid>
            <pubDate>Sun, 16 Oct 2022 20:21:06 GMT</pubDate>
            <description><![CDATA[<p>Github Actions는 레포지토리의 <code>.github/workflows</code> 하위의 yml파일을 기반으로 동작한다.
Github Actions -&gt; S3 -&gt; CodeDeploy -&gt; EC2로 배포되도록 작성된 workflow를 분석해보자.</p>
<h2 id="deployyml">Deploy.yml</h2>
<pre><code class="language-yml"># workflow의 이름을 설정해준다.
name: CI/CD

# workflow가 작동되는 이벤트를 정의한다.
# 해당 workflow같은 경우는 main branch에 push 될 경우 작동된다.
on:
  push:
    branches:
      - main

# 아래 S3와 CodeDeploy에서 사용 될 env값을 정의한다.
# 이 값은 2편과 3편에서 설정해준 S3의 버킷명과
# CodeDeploy의 applicationName, groupName과 일치해야한다.
env:
  S3_BUCKET_NAME: dev-recruit.mash-up.kr
  CODE_DEPLOY_APPLICATION_NAME: dev-recruit-code-deploy
  CODE_DEPLOY_DEPLOYMENT_GROUP_NAME: dev-recruit

jobs:
  build:
    runs-on: ubuntu-latest

    steps:
      - name: Checkout
        uses: actions/checkout@v3
      - uses: actions/setup-node@v3
        with:
          node-version: 16

      # pacakge.json에 명시된 의존성을 설치한다.
      - name: Install dependencies
        run: yarn install

      # 프로젝트를 빌드한다.
      - name: Build next app
        run: yarn build

      # S3에 올리기 전 빌드파일을 압축해준다.
      # 여기서 $GITHUB_SHA라는 값을 사용하는데 이 값은 Github에서 기본적으로 제공하는 환경변수다.
      # $GITHUB_SHA는 해당 워크플로우를 트리거 한 커밋의 고유값이 할당되어있다.
      - name: Make zip file
        run: zip -qq -r ./$GITHUB_SHA.zip . -x &quot;node_modules/*&quot;
        shell: bash

      # 2편에서 만든 IAM사용자를 이용해 AWS 서비스에 접근하는데 필요한 권한을 얻어오는 단계이다.
      - 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

      # Make zip file 단계에서 압축된 빌드 파일을 S3 버킷에 업로드하는 단계이다.
      - name: Upload to S3
        run: aws s3 cp --region ap-northeast-2 ./$GITHUB_SHA.zip s3://$S3_BUCKET_NAME/$GITHUB_SHA.zip

      # S3에 업로드 된 빌드 파일을 이용해 CodeDeploy가 정의된 동작을 하도록 트리거해주는 단계이다.
      # CodeDeploy의 동작에 대한 정의는 다음편에서 다뤄볼것이다.
      - name: Code Deploy
        run: |
          aws deploy create-deployment \
          --deployment-config-name CodeDeployDefault.AllAtOnce \
          --application-name ${{ env.CODE_DEPLOY_APPLICATION_NAME }} \
          --deployment-group-name ${{ env.CODE_DEPLOY_DEPLOYMENT_GROUP_NAME }} \
          --s3-location bucket=$S3_BUCKET_NAME,bundleType=zip,key=$GITHUB_SHA.zip</code></pre>
<hr>
<h2 id="주석이-제거된-deployyml">주석이 제거된 Deploy.yml</h2>
<pre><code class="language-yml">name: CI/CD

on:
  push:
    branches:
      - main

env:
  S3_BUCKET_NAME: dev-recruit.mash-up.kr
  CODE_DEPLOY_APPLICATION_NAME: dev-recruit-code-deploy
  CODE_DEPLOY_DEPLOYMENT_GROUP_NAME: dev-recruit

jobs:
  build:
    runs-on: ubuntu-latest

    steps:
      - name: Checkout
        uses: actions/checkout@v3
      - uses: actions/setup-node@v3
        with:
          node-version: 16

      - name: Install dependencies
        run: yarn install

      - name: Build next app
        run: yarn build

      - name: Make zip file
        run: zip -qq -r ./$GITHUB_SHA.zip . -x &quot;node_modules/*&quot;
        shell: bash

      - 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: Upload to S3
        run: aws s3 cp --region ap-northeast-2 ./$GITHUB_SHA.zip s3://$S3_BUCKET_NAME/$GITHUB_SHA.zip

      - name: Code Deploy
        run: |
          aws deploy create-deployment \
          --deployment-config-name CodeDeployDefault.AllAtOnce \
          --application-name ${{ env.CODE_DEPLOY_APPLICATION_NAME }} \
          --deployment-group-name ${{ env.CODE_DEPLOY_DEPLOYMENT_GROUP_NAME }} \
          --s3-location bucket=$S3_BUCKET_NAME,bundleType=zip,key=$GITHUB_SHA.zip</code></pre>
<p>이로써 Github Actions의 workflow를 모두 작성했다!
다음편에서 CodeDeploy의 동작에 대한 정의를 하는 appspec.yml파일을 작성해보자</p>
]]></description>
        </item>
        <item>
            <title><![CDATA[EC2 + Github Actions로 Next.js 배포 및 자동화(CI/CD) - 3편: AWS CodeDeploy 생성 및 설정]]></title>
            <link>https://velog.io/@hajun-ryu/EC2-Github-Actions%EB%A1%9C-Next.js-%EB%B0%B0%ED%8F%AC-%EB%B0%8F-%EC%9E%90%EB%8F%99%ED%99%94CICD-3%ED%8E%B8-AWS-CodeDeploy-%EC%83%9D%EC%84%B1-%EB%B0%8F-%EC%84%A4%EC%A0%95</link>
            <guid>https://velog.io/@hajun-ryu/EC2-Github-Actions%EB%A1%9C-Next.js-%EB%B0%B0%ED%8F%AC-%EB%B0%8F-%EC%9E%90%EB%8F%99%ED%99%94CICD-3%ED%8E%B8-AWS-CodeDeploy-%EC%83%9D%EC%84%B1-%EB%B0%8F-%EC%84%A4%EC%A0%95</guid>
            <pubDate>Sun, 16 Oct 2022 19:38:26 GMT</pubDate>
            <description><![CDATA[<p>이제 S3에 저장된 빌드 파일을 EC2로 서빙시켜줄 CodeDeploy에 대한 설정을 해보자.</p>
<h2 id="codedeploy-설정">CodeDeploy 설정</h2>
<h3 id="step1">Step1</h3>
<p><img src="https://velog.velcdn.com/images/hajun-ryu/post/e3e6e7d5-a0ed-4832-a91a-d22676543560/image.png" alt=""></p>
<p>일단 IAM 서비스에 접근하여 새로운 역할을 생성해주어야 한다.
IAM -&gt; 역할 -&gt; 역할 만들기</p>
<hr>
<h3 id="step2">Step2</h3>
<p><img src="https://velog.velcdn.com/images/hajun-ryu/post/15b9b12c-39e0-47e9-83cb-222ad0ce7441/image.png" alt=""></p>
<p>엔터티 유형은 AWS 서비스를 선택한다.
사용 사례는 CodeDeploy를 검색하여 선택한다.
그리고 다음 단계로 넘어간다.</p>
<hr>
<h3 id="step3">Step3</h3>
<p><img src="https://velog.velcdn.com/images/hajun-ryu/post/2054dc90-9140-40c8-ba50-dc568737fbb6/image.png" alt=""></p>
<p>Step2에서 CodeDeploy를 선택했기 때문에 자동으로 정책이 추가 되어있을것이다.
<code>AWSCodeDeployRole</code>이라는 권한이 정상적으로 추가되어있다면 다음 단계로 넘어간다.</p>
<hr>
<h3 id="step4">Step4</h3>
<p><img src="https://velog.velcdn.com/images/hajun-ryu/post/0e3b52c3-0b9e-48b5-936e-48c3869b0c6d/image.png" alt=""></p>
<p><code>역할 이름</code>에 원하는 값을 입력해준다.
그리고 페이지 최하단으로 가서 역할 생성 버튼을 눌러준다.
이제 CodeDeploy를 생성 할 준비가 끝났다.</p>
<hr>
<h3 id="step5">Step5</h3>
<p><img src="https://velog.velcdn.com/images/hajun-ryu/post/4782f682-5144-47bc-87a0-7a72b68a481f/image.png" alt=""></p>
<p>CodeDeploy 서비스에 접근한다.
사이드바의 애플리케이션 메뉴를 선택한 후 애플리케이션 생성 버튼을 눌러준다.</p>
<hr>
<h3 id="step6">Step6</h3>
<p><img src="https://velog.velcdn.com/images/hajun-ryu/post/524c1bfa-d437-4a5c-a579-a1acb3570355/image.png" alt=""></p>
<p><code>애플리케이션 이름</code>에는 원하는 값을 입력해준다.
컴퓨팅 플랫폼은 <code>EC2/온프레미스</code>를 선택해준다.
그리고 애플리케이션 생성 버튼을 눌러준다.</p>
<hr>
<h3 id="step7">Step7</h3>
<p><img src="https://velog.velcdn.com/images/hajun-ryu/post/90b4bb09-b7e0-467d-888f-cd3e8c64cb39/image.png" alt=""></p>
<p>애플리케이션 생성이 정상적으로 되었다면 배포 그룹 생성 페이지를 볼 수 있을것이다.
해당 페이지에서 <code>배포 그룹 생성</code> 버튼을 눌러준다.</p>
<hr>
<h3 id="step8">Step8</h3>
<p><img src="https://velog.velcdn.com/images/hajun-ryu/post/d9e905f9-97a2-47bf-8bb5-359e808d18ab/image.png" alt=""></p>
<p><code>배포 그룹 이름</code>에는 원하는 값을 입력해준다.
서비스 역할은 Step1 ~ Step4에서 만들어준 역할을 선택해준다.</p>
<hr>
<h3 id="step9">Step9</h3>
<p><img src="https://velog.velcdn.com/images/hajun-ryu/post/fb2fd427-6f26-421e-bd5a-666e5974757d/image.png" alt="">
배포 방법은 <code>현재 위치</code>를 선택해준다.
환경 구성은 <code>Amazon EC2 인스턴스</code>를 선택한다.
그러면 태그를 선택할 수 있는데 <code>CodeDeploy-Instance</code>를 선택해준다.
이 태그는 <a href="https://velog.io/@hajun-ryu/EC2-Github-Actions%EB%A1%9C-Next.js-%EB%B0%B0%ED%8F%AC-%EB%B0%8F-%EC%9E%90%EB%8F%99%ED%99%94CICD-1%ED%8E%B8-AWS-EC2-%EC%83%9D%EC%84%B1-%EB%B0%8F-%EC%84%A4%EC%A0%95#step4">1편 Step4</a>에서 EC2 인스턴스에 지정해준 태그이고 이 태그를 이용하여 EC2와 CodeDeploy를 맵핑 시켜준다.</p>
<hr>
<h3 id="step10">Step10</h3>
<p><img src="https://velog.velcdn.com/images/hajun-ryu/post/c9d0dfe7-ca7f-40da-8e45-a5b03406a82e/image.png" alt=""></p>
<p>다른 설정은 건들지 않고 스크롤을 맨 아래로 내리면 로드 밸런싱 활성화라는 체크박스가 기본적으로 체크되어있을것이다.
로드 밸런싱 활성화 체크박스를 체크해제 해준다.
만약 따로 로드 밸런싱 설정을 해주었다면 알아서 활성화 해주어도 당연 무방하다.
이제 배포 그룹 생성 버튼을 눌러주면 CodeDeploy 설정은 끝나게 된다!</p>
<p>다음으로는 Github Actions workflow를 작성하러 가보자.</p>
]]></description>
        </item>
        <item>
            <title><![CDATA[EC2 + Github Actions로 Next.js 배포 및 자동화(CI/CD) - 2편: AWS S3 생성 및 설정]]></title>
            <link>https://velog.io/@hajun-ryu/EC2-Github-Actions%EB%A1%9C-Next.js-%EB%B0%B0%ED%8F%AC-%EB%B0%8F-%EC%9E%90%EB%8F%99%ED%99%94CICD-2%ED%8E%B8-AWS-S3-%EC%83%9D%EC%84%B1-%EB%B0%8F-%EC%84%A4%EC%A0%95</link>
            <guid>https://velog.io/@hajun-ryu/EC2-Github-Actions%EB%A1%9C-Next.js-%EB%B0%B0%ED%8F%AC-%EB%B0%8F-%EC%9E%90%EB%8F%99%ED%99%94CICD-2%ED%8E%B8-AWS-S3-%EC%83%9D%EC%84%B1-%EB%B0%8F-%EC%84%A4%EC%A0%95</guid>
            <pubDate>Sun, 16 Oct 2022 13:23:46 GMT</pubDate>
            <description><![CDATA[<p>Github Actions로 빌드된 결과물을 S3를 통해 EC2 인스턴스에 배포할것이다.
Github Actions에서 바로 EC2로 배포할 수 있다면 좋지만 아쉽게도 현시점에서 그런것은 불가능하다.
S3에 배포 파일을 저장하고 3편에서 설명 할 CodeDeploy를 통해 EC2 인스턴스에 실제 배포를 할 것이다.</p>
<h2 id="s3-생성">S3 생성</h2>
<h3 id="step1">Step1</h3>
<p><img src="https://velog.velcdn.com/images/hajun-ryu/post/2b183cde-4409-4a38-8133-839bc3dc45ac/image.png" alt=""></p>
<p>S3서비스에 접근하여 버킷 만들기 버튼을 눌러준다.</p>
<hr>
<h3 id="step2">Step2</h3>
<p><img src="https://velog.velcdn.com/images/hajun-ryu/post/c3b83c28-6814-4c4c-88c5-a0578a41909a/image.png" alt=""></p>
<p>버킷 이름과 리전을 선택해준다.
그리고 본인이 필요한 설정이 특별히 있는 경우가 아니면 기본 설정값으로 버킷을 만들어준다.</p>
<hr>
<h3 id="step3">Step3</h3>
<p><img src="https://velog.velcdn.com/images/hajun-ryu/post/4b4e7c2f-06f8-46bd-a5c2-771a660db98c/image.png" alt=""></p>
<p>S3용 IAM 생성을 위해 IAM 서비스로 접근한다.
사이드바에서 사용자 메뉴로 접근하여 사용자 추가 버튼을 눌러준다.</p>
<hr>
<h3 id="step4">Step4</h3>
<p><img src="https://velog.velcdn.com/images/hajun-ryu/post/f8227bd9-72db-44e0-95e7-3c73f0509d6b/image.png" alt=""></p>
<p>원하는 사용자 이름을 입력한다.
자격 증명 유형은 <code>액세스 키 - 프로그래밍 방식 액세스</code>를 선택해준다.
그리고 다음 단계로 넘어간다.</p>
<hr>
<h3 id="step5">Step5</h3>
<p><img src="https://velog.velcdn.com/images/hajun-ryu/post/04c75b8f-b030-4fce-be03-ec1efea4e0aa/image.png" alt="">
<img src="https://velog.velcdn.com/images/hajun-ryu/post/826d44d4-2630-4570-a65e-fb7478662596/image.png" alt=""></p>
<p>기존 정책 직접 연결 메뉴를 클릭한다.
그리고 정책 필터에서 S3FullAccess와 CodeDeployFullAccess를 검색하여 선택해준다.
2개를 선택하였다면 다음 단계로 넘어간다.
다음 단계는 태그 추가 단계인데 태그는 건너뛰고 또 다음 단계로 넘어간다.</p>
<hr>
<h3 id="step6">Step6</h3>
<p><img src="https://velog.velcdn.com/images/hajun-ryu/post/28f5de99-07eb-4471-9dd2-831d16c695c0/image.png" alt=""></p>
<p>검토 단계이다. Step4, 5에서 설정했던 값과 정책을 확인한다.
이상이 없다면 다음 단계로 넘어간다.</p>
<hr>
<h3 id="step7">Step7</h3>
<p><img src="https://velog.velcdn.com/images/hajun-ryu/post/a05812ca-0ed2-4aef-b113-571c327279a1/image.png" alt=""></p>
<p>생성이 완료되었다면 보안 자격 증명에 대한 정보를 알려줄것이다.
설명에도 나와있듯이 지금이 아니면 이 자격 증명에 대한 값을 알 수가 없다.
.csv파일을 다운로드하여 보관하거나 따로 메모장에 기록해두자.</p>
<hr>
<h3 id="step8">Step8</h3>
<p><img src="https://velog.velcdn.com/images/hajun-ryu/post/be501d63-9176-4cbc-9271-89a231ea5848/image.png" alt=""></p>
<p>방금 만든 IAM User의 accessKeyId와 Key를 Gitgub Actions를 사용 할 Github 레포지토리에 등록한다.
레포지토리의 설정 -&gt; Secrets -&gt; Actions탭에서 우측 상당 <code>New repository secret</code> 버튼을 클릭하여 Key: Value 형식으로 추가해주면 된다.</p>
<p>이로써 EC2 인스턴스로 배포 파일을 서빙 하기 전 저장소로 쓰일 S3 생성 및 설정이 끝났다.</p>
<p>다음으로는 S3에서 EC2 인스턴스로 서빙해주는 역할을 하는 CodeDeploy 설정을 해볼것이다.</p>
]]></description>
        </item>
        <item>
            <title><![CDATA[EC2 + Github Actions로 Next.js 배포 및 자동화(CI/CD) - 1편: AWS EC2 생성 및 설정]]></title>
            <link>https://velog.io/@hajun-ryu/EC2-Github-Actions%EB%A1%9C-Next.js-%EB%B0%B0%ED%8F%AC-%EB%B0%8F-%EC%9E%90%EB%8F%99%ED%99%94CICD-1%ED%8E%B8-AWS-EC2-%EC%83%9D%EC%84%B1-%EB%B0%8F-%EC%84%A4%EC%A0%95</link>
            <guid>https://velog.io/@hajun-ryu/EC2-Github-Actions%EB%A1%9C-Next.js-%EB%B0%B0%ED%8F%AC-%EB%B0%8F-%EC%9E%90%EB%8F%99%ED%99%94CICD-1%ED%8E%B8-AWS-EC2-%EC%83%9D%EC%84%B1-%EB%B0%8F-%EC%84%A4%EC%A0%95</guid>
            <pubDate>Thu, 07 Apr 2022 08:54:13 GMT</pubDate>
            <description><![CDATA[<p>배포 과정에 대한 글이니 Next.js나 AWS에 대한 설명은 생략하겠다.<br>1편은 Next서버가 구동 될 EC2 인스턴스를 생성 및 설정하는 과정이다.</p>
<h2 id="ec2-인스턴스-생성">EC2 인스턴스 생성</h2>
<hr>
<h3 id="step1">Step1</h3>
<p><img src="https://velog.velcdn.com/cloudflare/hajun-ryu/f84e4507-c7d7-4707-90bc-fb0c941385d6/create-ec2-instance.png" alt="EC2인스턴스 대시보드"></p>
<p>인스턴스 생성 전 우측 상단에서 사용 할 지역을 설정해준다.<br>해당글은 서울 리전(ap-northeast-2)으로 설정 후 진행 할 것이다.</p>
<p>지역을 설정해주었다면 EC2 대시보드에 접근한 후 인스턴스 시작 버튼을 클릭한다.</p>
<hr>
<h3 id="step2">Step2</h3>
<p><img src="https://velog.velcdn.com/cloudflare/hajun-ryu/6611765c-cb47-4d90-84f6-99e3023b373c/select-ami.png" alt="EC2 AMI 선택 화면">
<img src="https://velog.velcdn.com/cloudflare/hajun-ryu/120dc648-61c1-4bfc-b477-a098860c3bbf/select-instance.png" alt="EC2 Instance 선택 화면"></p>
<p>원하는 OS에서 선택 버튼을 눌러 다음 단계로 넘어간다.</p>
<p>프리티어를 사용할것이기 떄문에 t2.micro 유형을 선택 후 검토 및 시작을 누른다.</p>
<blockquote>
<p>이 글에서는 프리티어 기준으로 설명하지만. 혹시 프리티어를 사용하는게 아니라면 가성비가 더 좋은 t3a나 t4g인스턴스를 선택해도 상관없다.</p>
</blockquote>
<hr>
<h3 id="step3">Step3</h3>
<p><img src="https://velog.velcdn.com/cloudflare/hajun-ryu/5f80059b-c6c6-48dd-8ee1-4e0def868eba/edit-inbound-button.png" alt="EC2 인스턴스 보안 그룹 편집 버튼">
<img src="https://velog.velcdn.com/cloudflare/hajun-ryu/e2b2e4a0-1d6d-4d15-bc52-43246c2283bd/edit-inbound.png" alt="EC2 인스턴스 보안 그룹 편집"></p>
<p>인스턴스에 Next App을 배포 후 Http 또는 Https로 접근해야하기 때문에 보안그룹 편집으로 접근해 80포트와 443포트에 대해 퍼블릭하게 접근 가능하게끔 설정해준다.</p>
<p>그 후 검토 및 시작 버튼을 눌러 다시 인스턴스 시작 검토 단계로 돌아간다.</p>
<blockquote>
<p>만약 도메인을 구매해 https까지 붙이지 않을거라면 80포트만 허용해줘도 상관없다.</p>
</blockquote>
<hr>
<h3 id="step4">Step4</h3>
<p><img src="https://velog.velcdn.com/cloudflare/hajun-ryu/e7ddc037-b3bb-4707-9130-ee30a1031abf/edit-tag-button.png" alt="EC2인스턴스 태그 편집 버튼">
<img src="https://velog.velcdn.com/cloudflare/hajun-ryu/c7ea888f-a81e-4575-b823-139927d89c80/edit-tag.png" alt="EC2인스턴스 태그 편집"></p>
<p>태그 편집 버튼을 누룬다.</p>
<p>키 항목에 원하는 값을 넣어준다. 이 글에서는 <code>CodeDeploy-Instance</code>로 사용하겠다.</p>
<p>이 키 항목은 추후 배포 자동화를 위해 CodeDeploy에서 tag group을 지정하는데 이때 방금 설정한 EC2인스턴스의 tag의 키 항목과 매칭시켜 줄 것이다.
CodeDeploy에 정의한 동작이 tag로 매칭된 EC2인스턴스에 실행되는것이다.</p>
<p>키 항목에 원하는 값을 채워넣었다면 검토 및 시작 버튼을 누른다.</p>
<blockquote>
<p>키 항목 우측의 값 항목은 따로 추가해주지 않아도 된다.</p>
</blockquote>
<hr>
<h3 id="step5">Step5</h3>
<p><img src="https://velog.velcdn.com/cloudflare/hajun-ryu/3d84c74b-5349-445c-9c64-80c04d61c075/launch-instance.png" alt="EC2인스턴스 시작"></p>
<p>시작하기 버튼을 누른다.</p>
<hr>
<h3 id="step6">Step6</h3>
<p><img src="https://velog.velcdn.com/cloudflare/hajun-ryu/9a50d290-baf4-4fdf-8407-509c2645508a/create-key-pair.png" alt="키 페어 생성"></p>
<p>해당 인스턴스에 SSH로 접근하기 위한 .pem 키를 발급받는다.</p>
<p>기존에 사용하던 키페어가 있다면 기존 키 페어를 사용해도 좋다.</p>
<blockquote>
<p>키 페어는 분실하게 되면 아주 곤란하니 다운로드 받아 잘 보관하도록 하자</p>
</blockquote>
<hr>
<h3 id="step7">Step7</h3>
<p><img src="https://velog.velcdn.com/cloudflare/hajun-ryu/0bd546ac-d177-4e28-af1e-9ec2e5c953d9/elastic-ip-button.png" alt="탄력적 IP 버튼 누르기">
<img src="https://velog.velcdn.com/cloudflare/hajun-ryu/b349bf42-0749-4408-8212-153d2d381d02/elstic-ip-allocate.png" alt="탄력적 IP 할당 버튼 클릭">
<img src="https://velog.velcdn.com/cloudflare/hajun-ryu/465d12ef-57c7-4050-b31c-7df6ed92fe73/allocate-button.png" alt="탄력적 IP 할당"></p>
<p>(선택사항) EC2인스턴스를 그냥 사용하게 되면 IP가 동적으로 변경되게 된다. 만약 도메인을 할당해 사용한다면 필수적으로 탄력적 IP를 할당받아 인스턴스에 적용 시켜줘야한다. 일단 네트워크 및 보안 &gt; 탄력적 IP 메뉴에 진입하여 탄력적 IP를 할당받자.</p>
<hr>
<h3 id="step8">Step8</h3>
<p><img src="https://velog.velcdn.com/cloudflare/hajun-ryu/4af1cd36-86e2-4b17-b208-97ba2a60ade1/elastic-ip-click.png" alt="생선된 탄력적 IP 클릭">
<img src="https://velog.velcdn.com/cloudflare/hajun-ryu/a7e93f51-a019-44b1-b913-6f59e8193463/elastic-ip-associate-button.png" alt="탄력적 IP 주소 연결 버튼 클릭">
<img src="https://velog.velcdn.com/cloudflare/hajun-ryu/bee58ca9-e6e8-4120-beaf-86f725412fb9/elastic-ip-associate.png" alt="연결 할 인스턴스 선택 후 연결 버튼 클릭"></p>
<p>위에서 할당받은 IP를 생성한 EC2 인스턴스에 연결시켜주자.</p>
<p>이렇게 되면 EC2 인스턴스에 고정 IP 할당이 완료된것이다.</p>
<p>주의할 점은 인스턴스를 중지 혹은 종료한 후에 탄력적 IP를 릴리즈해주지 않으면 비용이 청구되니 사용하지 않는다고 판단이 되면 꼭 릴리즈를 시켜주자.</p>
<hr>
<h3 id="step9">Step9</h3>
<p>인스턴스를 만들때 발급받은 .pem키를 이용해 SSH로 터미널을 통해 인스턴스에 접근해보자.</p>
<p>필자는 보통 pem키를 ~/.ssh폴더에 모아놓고 관리하기 때문에 일단 ssh폴더로 다운받은 pem키를 이동시키겠다.</p>
<pre><code class="language-shell">mv  ~/Downloads/ap2-key-pair.pem ~/.ssh</code></pre>
<p>그리고 pem키를 소유자만 Read 할 수 있게끔 권한설정을 해주자.</p>
<pre><code class="language-shell">chmod 400 ~/.ssh/ap2-key-pair.pem</code></pre>
<p>그 후 pem키를 이용하여 EC2 인스턴스에 접근해보자.</p>
<pre><code class="language-shell">ssh -i ~/.ssh/ap2-key-pair.pem ubuntu@3.39.127.148</code></pre>
<p><code>ubuntu@</code> 뒤의 IP 주소는 아까 할당받아 EC2 인스턴스에 연결한 탄력적 IP주소를 적어준다.</p>
<p><img src="https://velog.velcdn.com/cloudflare/hajun-ryu/7879c211-688b-40f5-bdaf-2de44158880f/ec2-ssh-success.png" alt="EC2 인스턴스 SSH접속 성공 화면"></p>
<p>잘 접속이 되었다면 위와 같은 화면을 만날 수 있을것이다.</p>
<hr>
<h3 id="step10">Step10</h3>
<p>아직 자세히 설명하진 않았지만 조금 이따 사용 될 CodeDeploy 에이전트를 설치 해야한다. 아래 명령어를 접속한 인스턴스의 터미널에 입력해보자.</p>
<blockquote>
<p>아래 명령어는 ubuntu 20.24버전 기준으로 작성되었으니 만약 버전이 다를 경우엔 <a href="https://docs.aws.amazon.com/ko_kr/codedeploy/latest/userguide/codedeploy-agent-operations-install-ubuntu.html">Ubuntu Server용 CodeDeploy 에이전트 설치</a> 공식문서를 참고하자.</p>
</blockquote>
<pre><code class="language-shell">sudo apt update &amp;&amp; sudo apt upgrade
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 &gt; /tmp/logfile</code></pre>
<p><img src="https://velog.velcdn.com/cloudflare/hajun-ryu/21a47a25-3fed-448a-89c0-1c845e8221b8/code-deploy-running.png" alt="CodeDeploy 실행중"></p>
<pre><code class="language-shell">sudo service codedeploy-agent status</code></pre>
<p>해당 명령어를 입력했을때 위 사진같이 active(running) 상태가 나온다면 EC2 인스턴스에 CodeDeploy 에이전트 설치는 끝난것이다.</p>
<hr>
<h3 id="step11">Step11</h3>
<p>이제 다시 AWS로 돌아가 EC2 인스턴스에 IAM 설정을 해야 한다.</p>
<p><img src="https://velog.velcdn.com/cloudflare/hajun-ryu/939ee9c6-0d2e-422c-852b-7af4d68e1087/service-iam.png" alt="IAM 서비스 접근">
<img src="https://velog.velcdn.com/cloudflare/hajun-ryu/74f867d5-6d33-41c7-ba47-98e6a1ad5151/role-button.png" alt="역할 메뉴에서 역할 만들기 클릭">
<img src="https://velog.velcdn.com/cloudflare/hajun-ryu/760a2666-6e56-4b05-a6fa-2d364485b7f7/choose-use-case.png" alt="사용 사례에서 EC2 선택"></p>
<p>상단 검색바를 이용해 IAM 서비스에 접근 후 역할 메뉴에서 역할 만들기 버튼을 누른다.
그리고 신뢰할 수 있는 엔터티 유형에서 AWS 서비스를 선택 후 사용 사례를 EC2를 선택해준다.
그리고 다음 버튼을 눌러준다.</p>
<p>그 후 권한 추가를 해줄것이다.</p>
<p><img src="https://velog.velcdn.com/cloudflare/hajun-ryu/1fa8d72b-d170-476f-9bb2-a3854e649202/code-deploy-full-access.png" alt="CodeDeploy Full Access 권한 선택">
<img src="https://velog.velcdn.com/cloudflare/hajun-ryu/e8bbffea-cbe9-4580-993c-732e2652464f/s3-full-access.png" alt="S3 Full Access 권한 선택"></p>
<p>검색을 이용하여 AWSCodeDeployFullAccess, AmazonS3FullAccess 권한을 추가해준 후 다음 버튼을 눌러준다.</p>
<p><img src="https://velog.velcdn.com/cloudflare/hajun-ryu/a94396db-5993-4f5c-9ac2-5c0178442d78/role-review.png" alt="역할 이름 지정, 검토 및 생성"></p>
<p>마지막으로 역할의 이름을 지정해주고 설정한 서비스와 권한을 확인해준 후 역할 생성 버튼을 눌러준다.</p>
<hr>
<h3 id="step12">Step12</h3>
<p>이제 생성된 IAM role을 EC2인스턴스에 적용시켜주자.</p>
<blockquote>
<p>혹시 EC2에 접근했는데 인스턴스가 보이지 않는다면 우측 상단의 지역 설정이 제대로 되어있는지 확인해보자.</p>
</blockquote>
<p><img src="https://velog.velcdn.com/cloudflare/hajun-ryu/9bece534-7eb4-4774-b38d-cc1326512d84/edit-ec2-iam-role.png" alt="EC2 IAM 역할 수정">
<img src="https://velog.velcdn.com/cloudflare/hajun-ryu/c9dcb935-4943-4978-9888-7582ffddb0f7/select-ec2-iam-role.png" alt="EC2 IAM 역할 저장"></p>
<p>생성한 인스턴스의 IAM역할 수정 메뉴에 접근하여 방금 만든 <code>ec2-iam</code> role을 선택해준 후 저장 버튼을 눌러준다.</p>
<p>이로써 Next App을 배포 할 EC2 생성 및 설정이 끝났다.</p>
<p>다음으로는 Github Actions에서 Build된 파일을 EC2로 보내주기 전 해당 파일을 저장해줄 AWS S3 설정을 해볼것이다.</p>
]]></description>
        </item>
    </channel>
</rss>