<?xml version="1.0" encoding="UTF-8"?>
<rss version="2.0">
  <channel>
    <title>나는 딱따구리 개발자</title>
    <link>https://ukj0ng.tistory.com/</link>
    <description>머리박기를 잘하는 나는 딱따구리</description>
    <language>ko</language>
    <pubDate>Thu, 7 May 2026 20:37:01 +0900</pubDate>
    <generator>TISTORY</generator>
    <ttl>100</ttl>
    <managingEditor>Ukjong</managingEditor>
    <image>
      <title>나는 딱따구리 개발자</title>
      <url>https://tistory1.daumcdn.net/tistory/7014655/attach/7ee175eabebf48f694bc9f0542060b0d</url>
      <link>https://ukj0ng.tistory.com</link>
    </image>
    <item>
      <title>[Java] long보다 더 큰 수를 다룰 수 있다고?(BigInteger)</title>
      <link>https://ukj0ng.tistory.com/69</link>
      <description>&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;a href=&quot;https://www.acmicpc.net/problem/2824&quot; target=&quot;_blank&quot; rel=&quot;noopener&amp;nbsp;noreferrer&quot;&gt;https://www.acmicpc.net/problem/2824 &lt;/a&gt;&lt;span&gt;&lt;span&gt;문제를 풀다가 &lt;/span&gt;&lt;span style=&quot;color: #8100c2;&quot;&gt;`BigInteger`&lt;/span&gt;&lt;span&gt;를 처음 만났다. 일반적으로 &lt;/span&gt;&lt;span style=&quot;color: #8100c2;&quot;&gt;`int`&lt;/span&gt;&lt;span&gt;, &lt;/span&gt;&lt;span style=&quot;color: #8100c2;&quot;&gt;`long`&lt;/span&gt;&lt;span&gt;으로 충분하지만, &lt;/span&gt;&lt;/span&gt;&lt;span&gt;매우 큰 수를 다뤄야 하는 경우가 있어 정리해보려고 한다.&lt;/span&gt;&lt;/p&gt;
&lt;hr contenteditable=&quot;false&quot; data-ke-type=&quot;horizontalRule&quot; data-ke-style=&quot;style6&quot; /&gt;
&lt;h2 data-ke-size=&quot;size26&quot;&gt;BigInteger란?&lt;/h2&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;`BigInteger`는 불변의 임의 정밀도 정수로 자바의 기본 정수형은 메모리 크기가 고정되어 있어 이보다 더 큰 값들을 다를 때 사용한다.&lt;/p&gt;
&lt;h4 data-ke-size=&quot;size20&quot;&gt;범위&lt;/h4&gt;
&lt;ul style=&quot;list-style-type: disc;&quot; data-ke-list-type=&quot;disc&quot;&gt;
&lt;li&gt;`int` (4 byte): -21억 ~ 21억&lt;/li&gt;
&lt;li&gt;`long` (8 byte): -922경 ~ 922경&lt;/li&gt;
&lt;li&gt;`BigInteger`: -2^(Integer.MAX_VALUE) ~ 2^(Integer.MAX_VALUE) (약 6억 자리 이상)&lt;/li&gt;
&lt;/ul&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;기본 정수형들은 JVM에서 Stack에 저장되지만 `BigInteger`은 참조값이 Stack에 저장되고 실제 객체는 Heap에 저장된다.&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;708&quot; data-origin-height=&quot;537&quot;&gt;&lt;span data-url=&quot;https://blog.kakaocdn.net/dn/drvMtR/dJMcad1U5l9/x379VCnwbyh6WBZK5Tz4G1/img.png&quot; data-phocus=&quot;https://blog.kakaocdn.net/dn/drvMtR/dJMcad1U5l9/x379VCnwbyh6WBZK5Tz4G1/img.png&quot;&gt;&lt;img src=&quot;https://blog.kakaocdn.net/dn/drvMtR/dJMcad1U5l9/x379VCnwbyh6WBZK5Tz4G1/img.png&quot; srcset=&quot;https://img1.daumcdn.net/thumb/R1280x0/?scode=mtistory2&amp;fname=https%3A%2F%2Fblog.kakaocdn.net%2Fdn%2FdrvMtR%2FdJMcad1U5l9%2Fx379VCnwbyh6WBZK5Tz4G1%2Fimg.png&quot; onerror=&quot;this.onerror=null; this.src='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png'; this.srcset='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png';&quot; loading=&quot;lazy&quot; width=&quot;649&quot; height=&quot;492&quot; data-origin-width=&quot;708&quot; data-origin-height=&quot;537&quot;/&gt;&lt;/span&gt;&lt;/figure&gt;
&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;다음은 `BigInteger`의 실제 내부 구조이다. 사실 더 많은 필드들이 있지만 다 캡쳐하기가 어려워 여기까지만 캡쳐했다. `BigInteger`는 최소 48바이트를 필요로 하고 수가 커질 수록 +&amp;alpha;이다.&amp;nbsp;&lt;/p&gt;
&lt;h4 data-ke-size=&quot;size20&quot;&gt;구성 요소&lt;/h4&gt;
&lt;ul style=&quot;list-style-type: disc;&quot; data-ke-list-type=&quot;disc&quot;&gt;
&lt;li&gt;BigInteger 객체 헤더: 12~16바이트 (64비트 JVM 기준)&lt;/li&gt;
&lt;li&gt;signum: 4바이트&lt;/li&gt;
&lt;li&gt;mag 배열 참조: 4바이트&lt;/li&gt;
&lt;li&gt;int[] 배열 객체:&amp;nbsp;
&lt;ul style=&quot;list-style-type: disc;&quot; data-ke-list-type=&quot;disc&quot;&gt;
&lt;li&gt;배열 헤더: 12~16바이트&lt;/li&gt;
&lt;li&gt;배열 길이 필드: 4바이트&lt;/li&gt;
&lt;li&gt;최소 1개의 `int` 데이터: 4바이트&lt;/li&gt;
&lt;/ul&gt;
&lt;/li&gt;
&lt;/ul&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;span&gt;크기가 커질 때 &lt;/span&gt;&lt;span&gt; &lt;/span&gt;&lt;span&gt;&lt;span&gt;숫자가 커지면 `&lt;/span&gt;&lt;span style=&quot;color: #8100c2;&quot;&gt;int`&lt;/span&gt;&lt;span&gt; 배열 크기&lt;/span&gt;&lt;span&gt;가 늘어난다. (int 하나 = 32비트 = 약 10자리(2^32 &amp;asymp; 42억), &lt;u&gt;&lt;b&gt;32진법이라고 생각하면 된다. 32비트 연산을 하는 이유는 CPU가 32비트 연산에 최적화되어 있기 때문이다.&lt;/b&gt;&lt;/u&gt; )&lt;/span&gt;&lt;/span&gt;&lt;/p&gt;
&lt;ul style=&quot;list-style-type: disc;&quot; data-ke-list-type=&quot;disc&quot;&gt;
&lt;li&gt;1자리 수 (예: 5): 약 48바이트&lt;/li&gt;
&lt;li&gt;10자리 수: 약 48바이트 (int 1개로 충분)&lt;/li&gt;
&lt;li&gt;20자리 수: 약 52바이트 (int 2개 필요)&lt;/li&gt;
&lt;li&gt;100자리 수: 약 68바이트 (int 4개 필요)&lt;/li&gt;
&lt;li&gt;1000자리 수: 약 188바이트 (int 34개 필요)&lt;/li&gt;
&lt;/ul&gt;
&lt;blockquote data-ke-style=&quot;style2&quot;&gt;&lt;u&gt;&lt;span style=&quot;color: #000000;&quot;&gt;메모리 &amp;asymp; 48 + (필요한 int 개수 &amp;times; 4) 필요한 int 개수 &amp;asymp; &amp;lceil;10진수 자릿수 / 9.6&amp;rceil;&lt;/span&gt;&lt;/u&gt;&lt;/blockquote&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;BigInteger의 특징&lt;/h2&gt;
&lt;h3 data-ke-size=&quot;size23&quot;&gt;무한한 크기&lt;/h3&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;`BigInteger`는 내부적으로 `int[]배열을 사용해 값을 저장한다. 따라서 컴퓨터의 RAM이 허용하는 한, 이론적으로 크기에 제한이 없는 정수를 저장하고 연산할 수 있다.&lt;/p&gt;
&lt;h3 data-ke-size=&quot;size23&quot;&gt;불변성&lt;/h3&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;`String` 클래스와 마찬가지로 `BigInteger`는 불변객체이다. 한 번 생성된 `BigInteger` 객체의 값은 절대 변하지 않는다. 덧셈이나 뺄셈 같은 연산을 수행하면, 기존 값이 바뀌는 것이 아니라 계산 결과가 담긴 새로운 `BigInteger` 객체가 반환된다.&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;BigInteger의 사용법&lt;/h2&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;`BigInteger`는 클래스이므로, 사칙연산 기호(+, -) 대신 메서드를 사용해야 한다.&lt;/p&gt;
&lt;h3 data-ke-size=&quot;size23&quot;&gt;선언 및 생성&lt;/h3&gt;
&lt;pre id=&quot;code_1770370267977&quot; class=&quot;java&quot; data-ke-language=&quot;java&quot; data-ke-type=&quot;codeblock&quot;&gt;&lt;code&gt;import java.math.BigInteger;

BigInteger bigNum1 = new BigInteger(&quot;123456789123456789123456789&quot;);
BigInteger bigNum2 = BigInteger.valueOf(100); // long 범위 내라면
BigInteger binaryNum = new BigInteger(&quot;1010&quot;, 2); // 10진수 외의 진수 표현 (예: 2진수)

// 자주 쓰는 0, 1, 10은 상수로 제공
BigInteger zero = BigInteger.ZERO;
BigInteger one = BigInteger.ONE;
BigInteger ten = BigInteger.TEN;&lt;/code&gt;&lt;/pre&gt;
&lt;h3 data-ke-size=&quot;size23&quot;&gt;연산&lt;/h3&gt;
&lt;pre id=&quot;code_1770370339490&quot; class=&quot;java&quot; data-ke-language=&quot;java&quot; data-ke-type=&quot;codeblock&quot;&gt;&lt;code&gt;BigInteger a = new BigInteger(&quot;100&quot;);
BigInteger b = new BigInteger(&quot;20&quot;);

// 사칙연산
BigInteger sum = a.add(b);           // 덧셈
BigInteger diff = a.subtract(b);     // 뺄셈
BigInteger product = a.multiply(b);  // 곱셈
BigInteger quotient = a.divide(b);   // 나눗셈
BigInteger remainder = a.remainder(b); // 나머지
BigInteger power = a.pow(3);         // 거듭제곱

// 비교
int compare = a.compareTo(b);  // a &amp;gt; b이면 1, a == b이면 0, a &amp;lt; b이면 -1
boolean equal = a.equals(b);

// 최댓값/최솟값
BigInteger max = a.max(b);  // 200
BigInteger min = a.min(b);  // 100

// 최대공약수
BigInteger gcd = a.gcd(b);  // 20

// 모듈러 연산
BigInteger base = new BigInteger(&quot;2&quot;);
BigInteger exp = new BigInteger(&quot;10&quot;);
BigInteger mod = new BigInteger(&quot;1000&quot;);
base.modPow(exp, mod);  // 2^10 mod 1000 = 24
                        // 큰 수 거듭제곱 계산에 유용!

a.modInverse(b);  // a의 모듈러 곱셈 역원&lt;/code&gt;&lt;/pre&gt;
&lt;h3 data-ke-size=&quot;size23&quot;&gt;자료형 변환&lt;/h3&gt;
&lt;pre id=&quot;code_1770370754600&quot; class=&quot;java&quot; data-ke-language=&quot;java&quot; data-ke-type=&quot;codeblock&quot;&gt;&lt;code&gt;BigInteger big = new BigInteger(&quot;123456789&quot;);

// 기본 자료형으로 변환 (범위 초과 시 잘림!)
big.intValue();     // int로 변환
big.longValue();    // long으로 변환
big.floatValue();   // float으로 변환
big.doubleValue();  // double으로 변환

// 안전한 변환 (범위 검사)
big.intValueExact();   // int 범위 벗어나면 ArithmeticException
big.longValueExact();  // long 범위 벗어나면 ArithmeticException

// 문자열 변환
big.toString();        // &quot;123456789&quot; (10진수)
big.toString(2);       // &quot;111010110111100110100010101&quot; (2진수)
big.toString(16);      // &quot;75bcd15&quot; (16진수)&lt;/code&gt;&lt;/pre&gt;
&lt;h3 data-ke-size=&quot;size23&quot;&gt;비트 연산&lt;/h3&gt;
&lt;pre id=&quot;code_1770370781167&quot; class=&quot;java&quot; data-ke-language=&quot;java&quot; data-ke-type=&quot;codeblock&quot;&gt;&lt;code&gt;BigInteger a = new BigInteger(&quot;15&quot;);  // 1111 (2진수)
BigInteger b = new BigInteger(&quot;10&quot;);  // 1010 (2진수)

// 비트 논리 연산
a.and(b);     // 1010 = 10 (AND)
a.or(b);      // 1111 = 15 (OR)
a.xor(b);     // 0101 = 5 (XOR)
a.not();      // -16 (NOT, 2의 보수)

// 비트 시프트
a.shiftLeft(2);   // 15 &amp;lt;&amp;lt; 2 = 60
a.shiftRight(2);  // 15 &amp;gt;&amp;gt; 2 = 3

// 비트 개수
a.bitCount();     // 1의 개수 = 4
a.bitLength();    // 비트 길이 = 4

// 특정 비트 확인/설정
a.testBit(0);     // 최하위 비트가 1인가? true
a.setBit(5);      // 5번 비트를 1로
a.clearBit(0);    // 0번 비트를 0으로
a.flipBit(1);     // 1번 비트 반전&lt;/code&gt;&lt;/pre&gt;
&lt;h3 data-ke-size=&quot;size23&quot;&gt;소수 관련&lt;/h3&gt;
&lt;pre id=&quot;code_1770370813090&quot; class=&quot;java&quot; data-ke-language=&quot;java&quot; data-ke-type=&quot;codeblock&quot;&gt;&lt;code&gt;// 소수 생성 (확률적 소수)
BigInteger prime = BigInteger.probablePrime(100, new Random());

// 소수 판별 (확률적)
BigInteger num = new BigInteger(&quot;17&quot;);
num.isProbablePrime(100);  // true&lt;/code&gt;&lt;/pre&gt;
&lt;hr contenteditable=&quot;false&quot; data-ke-type=&quot;horizontalRule&quot; data-ke-style=&quot;style6&quot; /&gt;
&lt;h2 data-ke-size=&quot;size26&quot;&gt;주의사항&lt;/h2&gt;
&lt;h3 data-ke-size=&quot;size23&quot;&gt;속도 저하&lt;/h3&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;`BigInteger`는 기본 타입보다 속도가 느리다. 단순히 큰 숫자가 필요하다고 해서 무조건 사용하는 것은 좋지 않다.&amp;nbsp;&lt;/p&gt;
&lt;h3 data-ke-size=&quot;size23&quot;&gt;메모리 사용&lt;/h3&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;객체 생성이 빈번하게 일어나고(불변성이므로), 내부 배열 크기도 유동적이므로 메모리 관리에 신경 써야 한다.&lt;/p&gt;
&lt;h3 data-ke-size=&quot;size23&quot;&gt;비교 연산&lt;/h3&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;객체이므로 `==` 연산자가 아닌 `.equals()`나 `.compareTo()`를 사용해야 값을 비교할 수 있다.&lt;/p&gt;</description>
      <category>Java</category>
      <author>Ukjong</author>
      <guid isPermaLink="true">https://ukj0ng.tistory.com/69</guid>
      <comments>https://ukj0ng.tistory.com/69#entry69comment</comments>
      <pubDate>Fri, 6 Feb 2026 18:59:48 +0900</pubDate>
    </item>
    <item>
      <title>[Java] JVM 2 - JVM이란?</title>
      <link>https://ukj0ng.tistory.com/68</link>
      <description>&lt;p data-ke-size=&quot;size16&quot;&gt;JVM이 왜 나왔는지를 알기 위해선 자바의 철학을 알아야 한다. 자바는 &quot;Write Once, Run Anywhere&quot;라는 슬로건으로 어느 운영체제에서도 실행 가능하다. 이를 실제로 가능하게 하는 주체는 JVM이다. 따라서, JVM의 구성요소와 Java 파일 실행 과정을 순차적을 공부할 예정이다.&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;JVM(Java Virtual Machine)이란?&lt;/h2&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;JVM은 자바 프로그램을 실행하기 위한 가상 머신으로 운영체제 위에서 동작하며, 자바의 '플랫폼 독립성'을 제공하는 요소이다.&amp;nbsp;&lt;/p&gt;
&lt;h4 data-ke-size=&quot;size20&quot;&gt;&lt;b&gt;플랫폼 독립성&lt;/b&gt;&lt;/h4&gt;
&lt;ul style=&quot;list-style-type: disc;&quot; data-ke-list-type=&quot;disc&quot;&gt;
&lt;li&gt;`.class`의 바이트코드는 특정 CPU의 기계어가 아니라, JVM의 명령어 집합&lt;/li&gt;
&lt;li&gt;OS마다 JVM 구현이 다르게 존재하며, 각각의 JVM이 바이트코드를 OS/CPU에 맞춰서 실행 &lt;span style=&quot;color: #000000; text-align: left;&quot;&gt;➡️ &lt;span style=&quot;color: #333333;&quot;&gt;같은 `.class`가 여러 OS에서 실행 가능&amp;nbsp;&lt;/span&gt;&lt;/span&gt;&lt;/li&gt;
&lt;/ul&gt;
&lt;h4 data-ke-size=&quot;size20&quot;&gt;&lt;b&gt;JVM의 역할&lt;/b&gt;&lt;/h4&gt;
&lt;ul style=&quot;list-style-type: disc;&quot; data-ke-list-type=&quot;disc&quot;&gt;
&lt;li&gt;`.class`를 읽어서 기계어로 변환하고 실행&lt;/li&gt;
&lt;li&gt;메모리 관리(Garbage Collection)&lt;/li&gt;
&lt;/ul&gt;
&lt;blockquote data-ke-style=&quot;style3&quot;&gt; &lt;span style=&quot;color: #000000;&quot;&gt;자바 컴파일러로 `.class`를 만들고 이를 각각 읽는 JVM을 만든다면, 처음부터 각 OS에 맞는 기계어로 변역하면 되지 않을까?&amp;nbsp;&lt;/span&gt;&lt;br /&gt;&lt;span style=&quot;color: #000000;&quot;&gt;컴파일러 방식(C/C++)은 소스코드를 특정 OS/CPU가 이해하는 기계어로 컴파일한다. 그래서 윈도우, 리눅스, 맥 등 환경이 바뀔 때마다 그에 맞는 컴파일러로 OS 종류만큼 n번 빌드해서 배포해야 한다.&lt;/span&gt;&lt;br /&gt;&lt;span style=&quot;color: #000000;&quot;&gt;반면 자바 컴파일러는 공용 언어인 `.class`를 만들고 각 환경마다 그에 맞는 JVM이 설치되어 있기 때문에, 개발자는 `.class`(또는 `.jar`) 파일 하나만 1번 빌드해서 배포하면 된다. n번 빌드해야 하는 수고를 1번으로 줄인 것이다.&amp;nbsp;&lt;/span&gt;&lt;/blockquote&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;JVM의 구성요소&lt;/h2&gt;
&lt;p&gt;&lt;figure class=&quot;imageblock alignCenter&quot; data-ke-mobileStyle=&quot;widthOrigin&quot; data-origin-width=&quot;857&quot; data-origin-height=&quot;658&quot;&gt;&lt;span data-url=&quot;https://blog.kakaocdn.net/dn/bGpo0V/dJMcacBYMbP/M5h91tZoTXXAkGsNzEWYn0/img.png&quot; data-phocus=&quot;https://blog.kakaocdn.net/dn/bGpo0V/dJMcacBYMbP/M5h91tZoTXXAkGsNzEWYn0/img.png&quot; data-alt=&quot;https://hongchangsub.com/java3/&quot;&gt;&lt;img src=&quot;https://blog.kakaocdn.net/dn/bGpo0V/dJMcacBYMbP/M5h91tZoTXXAkGsNzEWYn0/img.png&quot; srcset=&quot;https://img1.daumcdn.net/thumb/R1280x0/?scode=mtistory2&amp;fname=https%3A%2F%2Fblog.kakaocdn.net%2Fdn%2FbGpo0V%2FdJMcacBYMbP%2FM5h91tZoTXXAkGsNzEWYn0%2Fimg.png&quot; onerror=&quot;this.onerror=null; this.src='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png'; this.srcset='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png';&quot; loading=&quot;lazy&quot; width=&quot;666&quot; height=&quot;511&quot; data-origin-width=&quot;857&quot; data-origin-height=&quot;658&quot;/&gt;&lt;/span&gt;&lt;figcaption&gt;https://hongchangsub.com/java3/&lt;/figcaption&gt;
&lt;/figure&gt;
&lt;/p&gt;
&lt;h3 data-ke-size=&quot;size23&quot;&gt;클래스 로더&lt;/h3&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;자바는 코드를 한 번에 다 메모리에 올리지 않는다. `new User()`하거나 `User.staticMethod()`처럼 클래스 정보가 당장 필요하고 메모리에 없을 때, 클래스 로더가 동작한다. 필요한 시점에 클래스를 동적으로 로딩(lazy loading)한다. 클래스 정보를 로딩할 때, 3단계를 거친다.&lt;/p&gt;
&lt;h3 data-ke-size=&quot;size23&quot;&gt;클래스 로딩의 3단계&lt;/h3&gt;
&lt;h4 data-ke-size=&quot;size20&quot;&gt;1. 로딩(Loading)&lt;/h4&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;`.class`파일을 찾아서 메모리에 적재한다.&amp;nbsp;&lt;/p&gt;
&lt;h4 data-ke-size=&quot;size20&quot;&gt;2. 링킹(Linking)&lt;/h4&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;로딩된 클래스가 실행 가능한 상태가 되도록 준비하고 세 부분으로 나뉜다.&lt;/p&gt;
&lt;ul style=&quot;list-style-type: disc;&quot; data-ke-list-type=&quot;disc&quot;&gt;
&lt;li&gt;검증(Verify): 바이트코드가 올바른지 검증&lt;/li&gt;
&lt;li&gt;준비(Prepare): `static` 필드를 메모리에 할당&lt;/li&gt;
&lt;li&gt;해결(Resolve): 상수 풀에 있는 심볼릭 레퍼런스를 실제 참조로 변환&lt;/li&gt;
&lt;/ul&gt;
&lt;h4 data-ke-size=&quot;size20&quot;&gt;3. 초기화(Initialization)&lt;/h4&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;`static {...}` 블록을 실행하고 `static` 변수에 실제 값을 넣는다.&amp;nbsp;&lt;/p&gt;
&lt;blockquote data-ke-style=&quot;style3&quot;&gt; &lt;span style=&quot;color: #000000;&quot;&gt;`ClassNotFoundException` vs `NoClassDefFoundError`&lt;/span&gt;&lt;br /&gt;&lt;span style=&quot;color: #000000;&quot;&gt;클래스가 없으면 두 가지 에러가 발생할 수 있다.&amp;nbsp;&lt;br /&gt;&lt;/span&gt;&lt;br /&gt;&lt;span style=&quot;background-color: #fcfcfc; color: #000000; text-align: left;&quot;&gt;`ClassNotFoundException`&lt;/span&gt; &lt;br /&gt;&lt;span style=&quot;color: #000000;&quot;&gt;런타임에 동적으로 클래스를 찾으려 했더니 `.class` 파일 자체가 없어 발생한 예외로 개발자가 직접 찾으라고 시켰을 때 발생한다. 예상 가능한 상황으로 `build.gradle`에서 의존성이 빠졌거나 classpath가 올바르지 않을 때 발생한다.&lt;/span&gt;&lt;br /&gt;&lt;br /&gt;&lt;span style=&quot;color: #000000;&quot;&gt;`NoClassDefFoundError`&lt;/span&gt;&lt;br /&gt;&lt;span style=&quot;color: #000000;&quot;&gt; 컴파일 시점엔 있었는데, 런타임에 클래스 정의를 못 찾은 것으로 컴파일 땐 있었는데 실행하려고 하니 없을 때 발생한다. 예상 못한 상황으로 JAR 파일이 정상적이지 않거나, 서버에 배포된 파일 버전이 다를 때 발생한다.&amp;nbsp;&lt;/span&gt;&lt;/blockquote&gt;
&lt;hr contenteditable=&quot;false&quot; data-ke-type=&quot;horizontalRule&quot; data-ke-style=&quot;style8&quot; /&gt;
&lt;h3 data-ke-size=&quot;size23&quot;&gt;메모리&lt;/h3&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;JVM의 메모리는 &lt;b&gt;스레드마다 각각 생성되는 공간&lt;/b&gt;과 &lt;b&gt;모든 스레드가 공유하는 공간&lt;/b&gt;으로 나뉜다.&lt;/p&gt;
&lt;h4 data-ke-size=&quot;size20&quot;&gt;스레드 각각 생성되는 공간&lt;/h4&gt;
&lt;ol style=&quot;list-style-type: decimal;&quot; data-ke-list-type=&quot;decimal&quot;&gt;
&lt;li&gt;스택
&lt;ul style=&quot;list-style-type: disc;&quot; data-ke-list-type=&quot;disc&quot;&gt;
&lt;li&gt;메서드가 호출될 때마다 스택 프레임이 생성되고, 메서드가 종료되면 제거&lt;/li&gt;
&lt;li&gt;프레임 내부에는 메서드의 &lt;b&gt;지역 변수, 파라미터&lt;/b&gt;, 리턴 값 등이 저장&lt;/li&gt;
&lt;/ul&gt;
&lt;/li&gt;
&lt;li&gt;PC 레지스터
&lt;ul style=&quot;list-style-type: disc;&quot; data-ke-list-type=&quot;disc&quot;&gt;
&lt;li&gt;현재 실행 중인 JVM 명령어의 주소를 저장&lt;/li&gt;
&lt;li&gt;CPU가 스레드를 번갈아 가며 실행할 때, &quot;어디&quot;까지 실행했는지&quot;를 기억하여 다음 실행할 바이트코드의 위치를 가리킴&lt;/li&gt;
&lt;/ul&gt;
&lt;/li&gt;
&lt;li&gt;네이티브 메서드 스택
&lt;ul style=&quot;list-style-type: disc;&quot; data-ke-list-type=&quot;disc&quot;&gt;
&lt;li&gt;자바가 아닌 C/C++로 작성된 네이티브 메서드를 실행할 때 사용하는 별도의 스택 (JNI가 사용)&lt;/li&gt;
&lt;/ul&gt;
&lt;/li&gt;
&lt;/ol&gt;
&lt;h4 data-ke-size=&quot;size20&quot;&gt;모든 스레드가 공유하는 공간&lt;/h4&gt;
&lt;ol style=&quot;list-style-type: decimal;&quot; data-ke-list-type=&quot;decimal&quot;&gt;
&lt;li&gt;메서드 영역
&lt;ul style=&quot;list-style-type: disc;&quot; data-ke-list-type=&quot;disc&quot;&gt;
&lt;li&gt;클래스 로더가 읽어온 클래스 정보가 저장되는 곳&lt;/li&gt;
&lt;li&gt;클래스 메타데이터(필드, 메서드 정보), static 변수, 상수 풀, 메서드의 바이트코드가 저장&lt;/li&gt;
&lt;/ul&gt;
&lt;/li&gt;
&lt;li&gt;힙
&lt;ul style=&quot;list-style-type: disc;&quot; data-ke-list-type=&quot;disc&quot;&gt;
&lt;li&gt;`new` 연산자로 생성된 모든 객체와 배열이 저장되는 곳&lt;/li&gt;
&lt;li&gt;GC가 불필요한 객체를 청소하는 주 공간&lt;/li&gt;
&lt;li&gt;효율적인 관리를 위해 세대로 나뉜다.
&lt;ul style=&quot;list-style-type: disc;&quot; data-ke-list-type=&quot;disc&quot;&gt;
&lt;li&gt;Young Generation (젊은 세대)&lt;b data-index-in-node=&quot;0&quot; data-path-to-node=&quot;9,0,1,1,1,0,0&quot;&gt;:&lt;/b&gt; 생명 주기가 짧은 객체들이 사는 곳
&lt;ul style=&quot;list-style-type: disc;&quot; data-ke-list-type=&quot;disc&quot;&gt;
&lt;li&gt;Eden: 객체가 최초로 생성되는 구역&lt;/li&gt;
&lt;li&gt;Survivor 0/1: Eden에서 GC를 살아남은 객체가 잠시 머무는&amp;nbsp;&amp;nbsp;&lt;/li&gt;
&lt;/ul&gt;
&lt;/li&gt;
&lt;li&gt;Old Generation (늙은 세대): Young 영역에서 오랫동안 살아남은 객체들이 이동(Promotion)해 오는 곳&lt;/li&gt;
&lt;/ul&gt;
&lt;/li&gt;
&lt;/ul&gt;
&lt;/li&gt;
&lt;/ol&gt;
&lt;p&gt;&lt;figure class=&quot;imageblock alignCenter&quot; data-ke-mobileStyle=&quot;widthOrigin&quot; data-origin-width=&quot;400&quot; data-origin-height=&quot;126&quot;&gt;&lt;span data-url=&quot;https://blog.kakaocdn.net/dn/cmjRWk/dJMcab37EiA/PIkTBoROIIlbs6nmTCPCSk/img.png&quot; data-phocus=&quot;https://blog.kakaocdn.net/dn/cmjRWk/dJMcab37EiA/PIkTBoROIIlbs6nmTCPCSk/img.png&quot;&gt;&lt;img src=&quot;https://blog.kakaocdn.net/dn/cmjRWk/dJMcab37EiA/PIkTBoROIIlbs6nmTCPCSk/img.png&quot; srcset=&quot;https://img1.daumcdn.net/thumb/R1280x0/?scode=mtistory2&amp;fname=https%3A%2F%2Fblog.kakaocdn.net%2Fdn%2FcmjRWk%2FdJMcab37EiA%2FPIkTBoROIIlbs6nmTCPCSk%2Fimg.png&quot; onerror=&quot;this.onerror=null; this.src='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png'; this.srcset='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png';&quot; loading=&quot;lazy&quot; width=&quot;400&quot; height=&quot;126&quot; data-origin-width=&quot;400&quot; data-origin-height=&quot;126&quot;/&gt;&lt;/span&gt;&lt;/figure&gt;
&lt;/p&gt;
&lt;blockquote data-ke-style=&quot;style3&quot;&gt; &lt;span style=&quot;color: #000000;&quot;&gt;왜 힙 영역을 Young과 Old로 나눴을까?&lt;/span&gt;&lt;br /&gt;&lt;span style=&quot;color: #000000;&quot;&gt;대부분의 객체는 금방 죽기 때문에, 평소엔 Young만 GC하다가 가끔 Old를 GC하는게 평균 STW를 짧게 하기 때문이다.&lt;/span&gt;&lt;/blockquote&gt;
&lt;hr contenteditable=&quot;false&quot; data-ke-type=&quot;horizontalRule&quot; data-ke-style=&quot;style8&quot; /&gt;
&lt;h3 data-ke-size=&quot;size23&quot;&gt;실행 엔진&lt;/h3&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;메모리에 적재된 바이트코드를 기계어로 변경하여 명령어 단위로 실행하는 역할이다.&lt;/p&gt;
&lt;h4 data-ke-size=&quot;size20&quot;&gt;1. 인터프리터&lt;/h4&gt;
&lt;ul style=&quot;list-style-type: disc;&quot; data-ke-list-type=&quot;disc&quot;&gt;
&lt;li&gt;&lt;b data-index-in-node=&quot;0&quot; data-path-to-node=&quot;5,0,0&quot;&gt;역할:&lt;/b&gt; 프로그램 시작 즉시 바이트코드를 한 줄씩 읽어서 실행한다. 컴파일 시간을 기다릴 필요가 없어 초기 응답 속도가 빠르다.&lt;/li&gt;
&lt;li&gt;&lt;b data-index-in-node=&quot;0&quot; data-path-to-node=&quot;5,1,0&quot;&gt;카운팅(Profiling):&lt;/b&gt; 단순히 실행만 하는 것이 아니라, &lt;b data-index-in-node=&quot;35&quot; data-path-to-node=&quot;5,1,0&quot;&gt;어떤 메서드가 자주 쓰이는지&lt;/b&gt; 체크하기 위해 두 가지 카운터를 관리한다.
&lt;ol style=&quot;list-style-type: decimal;&quot; data-ke-list-type=&quot;decimal&quot;&gt;
&lt;li&gt;&lt;b data-index-in-node=&quot;0&quot; data-path-to-node=&quot;5,1,1,0,0&quot;&gt;Invocation Counter (호출 카운터):&lt;/b&gt; 메서드가 몇 번 호출되었는지 센다.&lt;/li&gt;
&lt;li&gt;&lt;b data-index-in-node=&quot;0&quot; data-path-to-node=&quot;5,1,1,1,0&quot;&gt;Backedge Counter (역방향 엣지 카운터):&lt;/b&gt; 메서드 내의 루프가 몇 번 돌았는지 센다.&lt;/li&gt;
&lt;/ol&gt;
&lt;/li&gt;
&lt;li&gt;&lt;b data-index-in-node=&quot;0&quot; data-path-to-node=&quot;5,2,0&quot;&gt;JIT 요청:&lt;/b&gt; 두 카운터의 합계가 임계치에 도달하면 JIT 컴파일러에게 &lt;b&gt;&quot;이거 핫(Hot)한 코드니까 컴파일 좀 해줘!&quot;&lt;/b&gt;라고 요청한다.&lt;/li&gt;
&lt;/ul&gt;
&lt;h4 style=&quot;color: #000000; text-align: start;&quot; data-ke-size=&quot;size20&quot;&gt;2. JIT 컴파일러(Just-In-Time Compiler)&lt;/h4&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;인터프리터의 요청을 받아 &lt;b data-index-in-node=&quot;14&quot; data-path-to-node=&quot;7&quot;&gt;자주 실행되는(Hot) 코드&lt;/b&gt;를 네이티브 코드로 컴파일하여 캐시(Code Cache)에 저장한다. 자바는 성능과 컴파일 속도 사이의 균형을 맞추기 위해 &lt;b&gt;계층적 컴파일(Tiered Compilation)&lt;/b&gt;을 수행한다.&lt;/p&gt;
&lt;h4 data-path-to-node=&quot;8&quot; data-ke-size=&quot;size20&quot;&gt;1. 계층적 컴파일 (Tiered Compilation) 흐름&lt;/h4&gt;
&lt;ul style=&quot;list-style-type: disc;&quot; data-path-to-node=&quot;9&quot; data-ke-list-type=&quot;disc&quot;&gt;
&lt;li&gt;&lt;b data-index-in-node=&quot;0&quot; data-path-to-node=&quot;9,0,0&quot;&gt;Level 0 (인터프리터):&lt;/b&gt; 초기 실행.&lt;/li&gt;
&lt;li&gt;&lt;b data-index-in-node=&quot;0&quot; data-path-to-node=&quot;9,1,0&quot;&gt;Level 1~3 (C1 컴파일러 - Client):&lt;/b&gt;
&lt;ul style=&quot;list-style-type: disc;&quot; data-path-to-node=&quot;9,1,1&quot; data-ke-list-type=&quot;disc&quot;&gt;
&lt;li&gt;&lt;b data-index-in-node=&quot;0&quot; data-path-to-node=&quot;9,1,1,0,0&quot;&gt;임계값:&lt;/b&gt; 약 1,500번 실행 시 동작.&lt;/li&gt;
&lt;li&gt;&lt;b data-index-in-node=&quot;0&quot; data-path-to-node=&quot;9,1,1,1,0&quot;&gt;특징:&lt;/b&gt; 복잡한 최적화는 안 하지만 &lt;b data-index-in-node=&quot;19&quot; data-path-to-node=&quot;9,1,1,1,0&quot;&gt;컴파일 속도가 빠르다.&lt;/b&gt;&amp;nbsp;&lt;/li&gt;
&lt;li&gt;&lt;b data-index-in-node=&quot;0&quot; data-path-to-node=&quot;9,1,1,2,0&quot;&gt;목적:&lt;/b&gt; 코드를 빠르게 네이티브로 바꿔서 일단 실행 속도를 높인다.&lt;/li&gt;
&lt;/ul&gt;
&lt;/li&gt;
&lt;li&gt;&lt;b data-index-in-node=&quot;0&quot; data-path-to-node=&quot;9,2,0&quot;&gt;Level 4 (C2 컴파일러 - Server):&lt;/b&gt;
&lt;ul style=&quot;list-style-type: disc;&quot; data-path-to-node=&quot;9,2,1&quot; data-ke-list-type=&quot;disc&quot;&gt;
&lt;li&gt;&lt;b data-index-in-node=&quot;0&quot; data-path-to-node=&quot;9,2,1,0,0&quot;&gt;임계값:&lt;/b&gt; 약 10,000번 실행 시 동작.&lt;/li&gt;
&lt;li&gt;&lt;b data-index-in-node=&quot;0&quot; data-path-to-node=&quot;9,2,1,1,0&quot;&gt;특징:&lt;/b&gt; &lt;b data-index-in-node=&quot;4&quot; data-path-to-node=&quot;9,2,1,1,0&quot;&gt;매우 강력하고 공격적인 최적화&lt;/b&gt;를 수행한다.&amp;nbsp;&lt;/li&gt;
&lt;li&gt;&lt;b data-index-in-node=&quot;0&quot; data-path-to-node=&quot;9,2,1,2,0&quot;&gt;최적화 기술:&lt;/b&gt; 인라이닝(메서드 호출 제거), 루프 언롤링, 탈출 분석, 불필요한 락 제거 등.&lt;/li&gt;
&lt;li&gt;&lt;b data-index-in-node=&quot;0&quot; data-path-to-node=&quot;9,2,1,3,0&quot;&gt;목적:&lt;/b&gt; 극한의 최고 성능을 뽑아낸다.&lt;/li&gt;
&lt;/ul&gt;
&lt;/li&gt;
&lt;/ul&gt;
&lt;h4 data-path-to-node=&quot;10&quot; data-ke-size=&quot;size20&quot;&gt;2. OSR (On-Stack Replacement)&lt;/h4&gt;
&lt;ul style=&quot;list-style-type: disc;&quot; data-path-to-node=&quot;11&quot; data-ke-list-type=&quot;disc&quot;&gt;
&lt;li&gt;&lt;b data-index-in-node=&quot;0&quot; data-path-to-node=&quot;11,0,0&quot;&gt;루프 도중 컴파일:&lt;/b&gt; 메서드가 끝나지 않고 루프(for, while)가 엄청나게 오래 도는 경우에도 최적화가 가능하다.&lt;/li&gt;
&lt;li&gt;&lt;b data-index-in-node=&quot;0&quot; data-path-to-node=&quot;11,1,0&quot;&gt;동작:&lt;/b&gt; 루프를 도는 도중 Backedge Counter가 임계치를 넘으면, 백그라운드에서 컴파일을 완료한 뒤 &lt;b data-index-in-node=&quot;61&quot; data-path-to-node=&quot;11,1,0&quot;&gt;실행 중인 루프를 즉시 네이티브 코드로 교체&lt;/b&gt;해서 나머지 반복을 빠르게 처리한다.&lt;/li&gt;
&lt;/ul&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;처음 코드를 실행하면 인터프리터나 C1 컴파일러로 동작하여 속도가 들쑥날쑥하다. 정확한 성능을 측정하려면 C2 컴파일러가 개입할 때까지 충분히 실행시켜야 한다.&lt;/p&gt;
&lt;pre id=&quot;code_1770107235030&quot; class=&quot;java&quot; data-ke-language=&quot;java&quot; data-ke-type=&quot;codeblock&quot;&gt;&lt;code&gt;// 1. Warm-up 단계: JIT(C2) 컴파일 유도
for(int i=0; i&amp;lt;10000; i++) {
    calculate(100); 
}

// 2. 실제 측정 단계: 이제 최적화된 네이티브 코드로 실행됨
long start = System.nanoTime();
calculate(100); 
long end = System.nanoTime();&lt;/code&gt;&lt;/pre&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;h3 data-ke-size=&quot;size23&quot;&gt;가비지 컬렉터&lt;/h3&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;힙(Heap) 영역을 주기적으로 모니터링하여 &lt;b data-index-in-node=&quot;25&quot; data-path-to-node=&quot;3&quot;&gt;더 이상 사용하지 않는(Unreachable) 객체를 메모리에서 제거&lt;/b&gt;하는 역할을 한다. GC는 아무거나 지우는 게 아니라, &lt;b&gt;'GC Root'&lt;/b&gt;라고 불리는 시작점으로부터 &lt;b data-index-in-node=&quot;48&quot; data-path-to-node=&quot;5&quot;&gt;참조가 이어져 있는지&lt;/b&gt;를 확인한다.&lt;/p&gt;
&lt;ul style=&quot;list-style-type: disc;&quot; data-ke-list-type=&quot;disc&quot;&gt;
&lt;li&gt;&lt;b data-index-in-node=&quot;0&quot; data-path-to-node=&quot;6,0,0&quot;&gt;Reachable:&lt;/b&gt; Root에서 연결선이 닿는 객체&lt;/li&gt;
&lt;li&gt;&lt;b data-index-in-node=&quot;0&quot; data-path-to-node=&quot;6,1,0&quot;&gt;Unreachable:&lt;/b&gt; 연결선이 끊어진 객체&lt;/li&gt;
&lt;li&gt;GC Root가 될 수 있는 것
&lt;ol style=&quot;list-style-type: decimal;&quot; data-ke-list-type=&quot;decimal&quot;&gt;
&lt;li&gt;Stack의 지역 변수: 현재 실행 중인 메서드 안에서 참조하는 객체&lt;/li&gt;
&lt;li&gt;Method Area의 static 변수: 전역적으로 참조되는 정적 객체&lt;/li&gt;
&lt;li&gt;JNI로 생성된 객체: 네이티브 코드에서 참조하는 객체&lt;/li&gt;
&lt;li&gt;실행 중인 Thread: 활성화된 스레드 자체&lt;/li&gt;
&lt;/ol&gt;
&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;JVM이 메모리 상태를 보고 &lt;b data-index-in-node=&quot;16&quot; data-path-to-node=&quot;10&quot;&gt;최적의 타이밍에 자동으로 실행&lt;/b&gt;한다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;ul style=&quot;list-style-type: disc;&quot; data-ke-list-type=&quot;disc&quot;&gt;
&lt;li&gt;&lt;b data-index-in-node=&quot;0&quot; data-path-to-node=&quot;11,0,0&quot;&gt;Minor GC:&lt;/b&gt; Young Generation의 &lt;b data-index-in-node=&quot;28&quot; data-path-to-node=&quot;11,0,0&quot;&gt;Eden 영역이 가득 찼을 때&lt;/b&gt; 발생 (빈번하고 빠름).&lt;/li&gt;
&lt;li&gt;&lt;b data-index-in-node=&quot;0&quot; data-path-to-node=&quot;11,1,0&quot;&gt;Major GC (Full GC):&lt;/b&gt; &lt;b data-index-in-node=&quot;20&quot; data-path-to-node=&quot;11,1,0&quot;&gt;Old 영역이 가득 찼을 때&lt;/b&gt; 발생 (느리고 무거움).&lt;/li&gt;
&lt;/ul&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-path-to-node=&quot;13&quot; data-ke-size=&quot;size16&quot;&gt;Stop-the-World(STW)는 GC가 실행되는 동안 &lt;b data-index-in-node=&quot;12&quot; data-path-to-node=&quot;13&quot;&gt;애플리케이션의 모든 스레드가 일시 정지&lt;/b&gt;하는 현상이다.&lt;/p&gt;
&lt;ul style=&quot;list-style-type: disc;&quot; data-path-to-node=&quot;14&quot; data-ke-list-type=&quot;disc&quot;&gt;
&lt;li&gt;&lt;b data-index-in-node=&quot;0&quot; data-path-to-node=&quot;14,0,0&quot;&gt;왜 멈추는가?&lt;/b&gt;
&lt;ul style=&quot;list-style-type: disc;&quot; data-ke-list-type=&quot;disc&quot;&gt;
&lt;li&gt;GC가 객체의 메모리 주소를 옮기거나 정리하는 도중에 애플리케이션이 접근하면 &lt;b data-index-in-node=&quot;51&quot; data-path-to-node=&quot;14,0,0&quot;&gt;데이터 충돌이나 오염&lt;/b&gt;이 발생할 수 있기 때문이다. 안전을 위해 세상(World)을 멈추고 청소한다.&lt;/li&gt;
&lt;/ul&gt;
&lt;/li&gt;
&lt;/ul&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;STW 시간이 길어지면&lt;span&gt;&amp;nbsp;&lt;/span&gt;&lt;b data-path-to-node=&quot;14,1,0&quot; data-index-in-node=&quot;21&quot;&gt;API 응답 지연&lt;/b&gt;이나&lt;span&gt;&amp;nbsp;&lt;/span&gt;&lt;b data-path-to-node=&quot;14,1,0&quot; data-index-in-node=&quot;33&quot;&gt;DB 타임아웃&lt;/b&gt;이 발생하여 서비스 장애로 이어질 수 있다. 따라서 GC 튜닝의 핵심은 이&lt;span&gt;&amp;nbsp;&lt;/span&gt;&lt;b data-path-to-node=&quot;14,1,0&quot; data-index-in-node=&quot;82&quot;&gt;STW 시간을 줄이는 것&lt;/b&gt;이다.&lt;/p&gt;
&lt;blockquote data-ke-style=&quot;style3&quot;&gt; &lt;span style=&quot;color: #000000;&quot;&gt;`System.gc()`를 왜 쓰면 안될까?&lt;/span&gt;&lt;br /&gt;&lt;span style=&quot;color: #000000;&quot;&gt; 개발자가 코드에서 `System.gc()`를 호출하여 강제로 GC를 실행하는 것을 권장하지 않는다. JVM에게 요청한다고 해서 즉시 실행된다는 보장이 없고, 실행된다 하더라도 예상치 못한 타이밍에 STW가 발생해 큰 문제를 유발할 수 있다.&lt;/span&gt;&lt;/blockquote&gt;
&lt;blockquote data-ke-style=&quot;style3&quot;&gt; &lt;span style=&quot;color: #000000;&quot;&gt;논리적 메모리 누수&lt;/span&gt;&lt;br /&gt;&lt;span style=&quot;color: #000000;&quot;&gt;자바에 GC가 있다고 해서 메모리 누수가 없는 것은 아니다. 예를 들어, `static List` 같은 전역 컬렉션에 객체를 계속 담아두고 삭제하지 않는다면, GC는 이를 &quot;사용 중인 객체(Reachable)로 판단하여 절대 지우지 못한다. 결국 Heap이 가득 차 `OutOfMemoryError`가 발생한다.&lt;/span&gt;&lt;/blockquote&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;Java 파일 실행 과정&lt;/h2&gt;
&lt;p&gt;&lt;figure class=&quot;imageblock alignCenter&quot; data-ke-mobileStyle=&quot;widthOrigin&quot; data-origin-width=&quot;860&quot; data-origin-height=&quot;578&quot;&gt;&lt;span data-url=&quot;https://blog.kakaocdn.net/dn/TizMV/dJMb99ZzX02/NCkoIfZ5u5OQVWorgXu1AK/img.jpg&quot; data-phocus=&quot;https://blog.kakaocdn.net/dn/TizMV/dJMb99ZzX02/NCkoIfZ5u5OQVWorgXu1AK/img.jpg&quot; data-alt=&quot;https://www.geeksforgeeks.org/java/compilation-execution-java-program/&quot;&gt;&lt;img src=&quot;https://blog.kakaocdn.net/dn/TizMV/dJMb99ZzX02/NCkoIfZ5u5OQVWorgXu1AK/img.jpg&quot; srcset=&quot;https://img1.daumcdn.net/thumb/R1280x0/?scode=mtistory2&amp;fname=https%3A%2F%2Fblog.kakaocdn.net%2Fdn%2FTizMV%2FdJMb99ZzX02%2FNCkoIfZ5u5OQVWorgXu1AK%2Fimg.jpg&quot; onerror=&quot;this.onerror=null; this.src='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png'; this.srcset='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png';&quot; loading=&quot;lazy&quot; width=&quot;687&quot; height=&quot;462&quot; data-origin-width=&quot;860&quot; data-origin-height=&quot;578&quot;/&gt;&lt;/span&gt;&lt;figcaption&gt;https://www.geeksforgeeks.org/java/compilation-execution-java-program/&lt;/figcaption&gt;
&lt;/figure&gt;
&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;자바 파일을 실행하게 되면 위와 같은 과정을 거치게 된다.&amp;nbsp;&lt;/p&gt;
&lt;h3 data-ke-size=&quot;size23&quot;&gt;1. 컴파일&lt;/h3&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;자바 컴파일러가 `A.java`파일을 읽어 `A.class`를 만든다. 여기서 `A.class`파일을 바이트코드라고 한다.&amp;nbsp;&lt;/p&gt;
&lt;h3 data-ke-size=&quot;size23&quot;&gt;2. 실행&lt;/h3&gt;
&lt;p data-path-to-node=&quot;2&quot; data-ke-size=&quot;size16&quot;&gt;JVM이 생성된 `A.class`파일을 읽어들여 실행한다. 이 과정은 다음과 같이 세부적으로 나뉜다.&lt;/p&gt;
&lt;ul style=&quot;list-style-type: disc;&quot; data-path-to-node=&quot;3&quot; data-ke-list-type=&quot;disc&quot;&gt;
&lt;li&gt;&lt;b data-index-in-node=&quot;0&quot; data-path-to-node=&quot;3,0,0&quot;&gt;클래스 로딩:&lt;/b&gt; JVM의 &lt;b&gt;클래스 로더&lt;/b&gt;가 `A.class`파일을 찾아 읽어 들인 후, &lt;b&gt;JVM 메모리&lt;/b&gt;의 메서드 영역에 올린다.&lt;/li&gt;
&lt;li&gt;&lt;b data-index-in-node=&quot;0&quot; data-path-to-node=&quot;3,1,0&quot;&gt;실행:&lt;/b&gt; 메모리에 로드된 바이트코드를 실행 엔진이 해석한다.
&lt;ul style=&quot;list-style-type: disc;&quot; data-path-to-node=&quot;3,1,1&quot; data-ke-list-type=&quot;disc&quot;&gt;
&lt;li&gt;이때 인터프리터나 &lt;b data-index-in-node=&quot;27&quot; data-path-to-node=&quot;3,1,1,0,0&quot;&gt;JIT 컴파일러&lt;/b&gt;가 바이트코드를 각 운영체제(OS)가 이해할 수 있는 기계어로 변환한다.&lt;/li&gt;
&lt;/ul&gt;
&lt;/li&gt;
&lt;li&gt;&lt;b data-index-in-node=&quot;0&quot; data-path-to-node=&quot;3,2,0&quot;&gt;하드웨어 처리:&lt;/b&gt; 변환된 기계어가 CPU에 전달되어 최종적으로 프로그램이 실행된다.&lt;/li&gt;
&lt;/ul&gt;</description>
      <category>Java</category>
      <author>Ukjong</author>
      <guid isPermaLink="true">https://ukj0ng.tistory.com/68</guid>
      <comments>https://ukj0ng.tistory.com/68#entry68comment</comments>
      <pubDate>Tue, 3 Feb 2026 17:41:29 +0900</pubDate>
    </item>
    <item>
      <title>[Java] JVM 1 - JDK와 JRE</title>
      <link>https://ukj0ng.tistory.com/67</link>
      <description>&lt;p data-ke-size=&quot;size16&quot;&gt;기능 구현보다 이 기능을 어떻게 구현해야 더 좋은 구현이 될까를 고민하는 지금 Java 프로그램을 실행하는 JVM을 알아야 좋은 구현을 진행할 것이라는 생각이 들었다. 따라서, JVM을 공부해보려고 한다.&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;JDK(Java Development Kit)와 JRE(Java Runtime Environment)&lt;/h2&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;JVM을 공부하는데 왜 JDK와 JRE가 나올까?&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;이는 JDK와 JRE에 JVM이 포함되어 있고, Java를 개발하기 위해선 단순히 JDK 안에 있는 `javac`(컴파일러)가 필요하기 때문이다. 또, JVM은 혼자서 돌아가지 못하며, `String`, `System`, `ArrayList` 같은 기본 클래스 파일들이 있어야 코드를 실행할 수 있는데, 이 파일들이 바로 JRE에 들어있기 때문이다. JVM이 클래스 로더를 통해 이 파일들을 어떻게 읽어오는지 알아야 한다.&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;JRE&lt;/h2&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;JRE는 &quot;자바 애플리케이션을 실행하기 위해 필요한 최소한의 환경&quot;이다. 개발 관련 도구는 없고, 오직 &lt;u&gt;&lt;b&gt;실행만을 목적&lt;/b&gt;&lt;/u&gt;으로 한다.&amp;nbsp;&lt;/p&gt;
&lt;h3 data-ke-size=&quot;size23&quot;&gt;JRE의 구성 요소&lt;/h3&gt;
&lt;ul style=&quot;list-style-type: disc;&quot; data-ke-list-type=&quot;disc&quot;&gt;
&lt;li&gt;JVM&lt;/li&gt;
&lt;li&gt;자바 표준 라이브러리(java.lang, java.util&amp;hellip;)&lt;/li&gt;
&lt;li&gt;런타임 지원 파일&lt;/li&gt;
&lt;li&gt;java 명령어 - JVM 런처&lt;/li&gt;
&lt;/ul&gt;
&lt;h4 data-ke-size=&quot;size20&quot;&gt;JVM&lt;/h4&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;자바 바이트코드(.class)를 운영체제(OS)가 이해할 수 있는 기계어로 통역하고 실행하는 엔진이다.&amp;nbsp;&lt;/p&gt;
&lt;h4 data-ke-size=&quot;size20&quot;&gt;자바 표준 라이브러리&lt;/h4&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;JVM이 코드를 실행하다가 `System.out.println()`이나 `ArrayList`, `Thread`등을 만나면, 이 기능들이 정의된 실제 파일이 필요하다. JRE는 이 표준 클래스 파일들(API)을 묶어서 가지고 있다.&amp;nbsp;&lt;/p&gt;
&lt;h4 data-ke-size=&quot;size20&quot;&gt;런타임 지원 파일&lt;/h4&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;JVM이 실행되는 동안 필요한 데이터 파일들이다.&amp;nbsp;&lt;/p&gt;
&lt;pre id=&quot;code_1770017467113&quot; class=&quot;javascript&quot; data-ke-language=&quot;javascript&quot; data-ke-type=&quot;codeblock&quot;&gt;&lt;code&gt;lib/
├── tzdb.dat              # 타임존 데이터
│   &amp;rarr; ZonedDateTime, ZoneId 등에서 사용
├── currency.data         # 통화 정보
│   &amp;rarr; Currency.getInstance() 등에서 사용
├── charsets.jar          # 문자 인코딩
│   &amp;rarr; UTF-8, EUC-KR 등 변환
└── security/
    └── java.policy       # 보안 정책
        &amp;rarr; SecurityManager에서 사용&lt;/code&gt;&lt;/pre&gt;
&lt;h4 data-ke-size=&quot;size20&quot;&gt;java 명령어 (JVM 런처)&lt;/h4&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;JVM을 시작하고 애플리케이션을 실행하는 프로그램이다. &lt;u&gt;JVM 프로세스 생성&lt;/u&gt;, &lt;u&gt;클래스패스 설정&lt;/u&gt;, &lt;u&gt;&lt;span&gt;JVM 옵션 전달 &lt;/span&gt;&lt;span style=&quot;color: #383a42;&quot;&gt;(`&lt;/span&gt;&lt;span&gt;-Xmx`, `-XX:+UseG1GC` 등&lt;/span&gt;&lt;/u&gt;&lt;span style=&quot;color: #383a42;&quot;&gt;&lt;u&gt;)&lt;/u&gt;, &lt;u&gt;`main()` 메서드 찾아서 실행&lt;/u&gt;하는 역할을 한다.&amp;nbsp;&lt;/span&gt;&lt;/p&gt;
&lt;blockquote data-ke-style=&quot;style3&quot;&gt; &lt;span style=&quot;color: #000000;&quot;&gt;JVM은 혼자서 작동할 수 있을까?&lt;/span&gt;&lt;br /&gt;&lt;span style=&quot;color: #000000;&quot;&gt;아니다. &lt;br /&gt;System.class가 없으면 `System.out.println()`을 실행할 수 없고, java 명령어가 없으면 JVM을 시작할 수 없다. 또, 런타임 지원 파일들(타임존 데이터, 문자 인코딩 등)이 없다면 실행할 수 없다. &lt;/span&gt;&lt;br /&gt;&lt;span style=&quot;color: #000000;&quot;&gt;따라서 &lt;b&gt;'JRE = JVM + 실행에 필요한 모든 것'&lt;/b&gt;으로 볼 수 있다.&lt;/span&gt;&lt;/blockquote&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;JDK&lt;/h2&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;JDK는 &quot;자바 애플리케이션을 개발하기 위한 도구 모음&quot;이다. JRE가 &quot;실행&quot;에 집중한다면, &lt;b&gt;&lt;u&gt;JDK는 &quot;개발&quot;에 필요한 모든 것을 포함&lt;/u&gt;&lt;/b&gt;한다. JDK를 설치하면 JRE가 포함되어 있으므로 실행도 당연히 가능하다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;즉, &lt;b&gt;'JDK = JRE + 개발 도구'&lt;/b&gt;이다.&lt;/p&gt;
&lt;h3 data-ke-size=&quot;size23&quot;&gt;JDK의 구성 요소&lt;/h3&gt;
&lt;ul style=&quot;list-style-type: disc;&quot; data-ke-list-type=&quot;disc&quot;&gt;
&lt;li&gt;JRE&lt;/li&gt;
&lt;li&gt;javac - 자바 컴파일러&lt;/li&gt;
&lt;li&gt;java - JVM 런처&lt;/li&gt;
&lt;li&gt;javadoc - API 문서 생성&lt;/li&gt;
&lt;li&gt;jar - JAR 파일 생성/관리&lt;/li&gt;
&lt;li&gt;jdb - 디버거&lt;/li&gt;
&lt;/ul&gt;
&lt;h4 data-ke-size=&quot;size20&quot;&gt;javac&lt;/h4&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;사람이 작성한 코드(.java)를 JVM이 이해할 수 있는 바이트코드(.class)로 변환한다.&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;b&gt;실제로 하는 일:&lt;/b&gt;&lt;/p&gt;
&lt;ol style=&quot;list-style-type: decimal;&quot; data-ke-list-type=&quot;decimal&quot;&gt;
&lt;li&gt;&lt;span&gt;&lt;b&gt;&lt;span&gt;문법 검사&lt;/span&gt;&lt;/b&gt;&lt;span&gt;(Syntax Check): 괄호, 세미콜론 등 문법 오류 검출&lt;/span&gt;&lt;/span&gt;&lt;/li&gt;
&lt;li&gt;&lt;span&gt;&lt;b&gt;&lt;span&gt;타입 검사&lt;/span&gt;&lt;/b&gt;&lt;span&gt;(Type Check): 타입 불일치, 제네릭 오류 검출&lt;/span&gt;&lt;/span&gt;&lt;/li&gt;
&lt;li&gt;&lt;span&gt;&lt;b&gt;&lt;span&gt;바이트코드 생성&lt;/span&gt;&lt;/b&gt;&lt;span&gt;: JVM이 실행 가능한 중간 언어로 변환&lt;/span&gt;&lt;/span&gt;&lt;/li&gt;
&lt;li&gt;&lt;span&gt;&lt;b&gt;&lt;span&gt;최적화&lt;/span&gt;&lt;/b&gt;&lt;span&gt;: 상수 폴딩, 데드 코드 제거 등&lt;/span&gt;&lt;/span&gt;&lt;/li&gt;
&lt;/ol&gt;
&lt;pre id=&quot;code_1770019405945&quot; class=&quot;bash&quot; data-ke-language=&quot;bash&quot; data-ke-type=&quot;codeblock&quot;&gt;&lt;code&gt;# 사용 예시
# Case 1: 단일 클래스 파일 실행 (학습/테스트)
javac HelloWorld.java  # 먼저 컴파일
java HelloWorld        # 그 다음 실행

# Case 2: JAR 파일 실행 (배포된 애플리케이션)
java -jar myapp.jar

# Case 3: 클래스패스 지정 (외부 라이브러리 사용)
java -cp target/classes:lib/* com.example.Main

# Case 4: JVM 옵션 + JAR 실행 (운영 서버)
java -Xms512m -Xmx2g -XX:+UseG1GC -jar app.jar&lt;/code&gt;&lt;/pre&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;IntelliJ나 Eclipse에서 코드를 저장하면 자동으로 컴파일되는데, &lt;span&gt;이때 IDE가 백그라운드에서 &lt;/span&gt;&lt;span style=&quot;color: #a626a4;&quot;&gt;`javac`&lt;/span&gt;&lt;span&gt;를 호출하고 있다. &quot;Auto Build&quot; 기능이 꺼져있으면 수동으로 컴파일해야 하는데, 이 경우 저장했는데도 변경사항이 반영 안 되는 것처럼 보일 수 있다. &lt;span style=&quot;color: #000000;&quot;&gt;(&lt;span style=&quot;background-color: #fcfcfc; text-align: left;&quot;&gt;IDE의 빨간 밑줄이 바로 javac의 검사 결과)&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;/p&gt;
&lt;blockquote data-ke-style=&quot;style3&quot;&gt; &lt;span style=&quot;color: #000000;&quot;&gt;javac가 중요한 이유&lt;/span&gt;&lt;br /&gt;&lt;span style=&quot;color: #000000;&quot;&gt;1. 컴파일 타임에 오류를 잡아줌 ➡️ 런타임 에러 방지&lt;/span&gt;&lt;br /&gt;&lt;span style=&quot;color: #000000;&quot;&gt;2. 바이트코드 최적화로 실행 성능 향상&lt;/span&gt;&lt;/blockquote&gt;
&lt;h4 data-ke-size=&quot;size20&quot;&gt;java&lt;/h4&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;JVM을 시작하고 컴파일된 `.class`파일을 로딩하여 애플리케이션을 시작한다. `main()`메서드를 찾아 호출하는 시작점이다.&lt;/p&gt;
&lt;blockquote data-ke-style=&quot;style3&quot;&gt; &lt;span style=&quot;color: #000000;&quot;&gt;JDK의 java vs JRE의 java&lt;/span&gt;&lt;br /&gt;&lt;span style=&quot;color: #000000;&quot;&gt;&lt;span style=&quot;color: #000000;&quot;&gt;JRE에도 JVM을 시작하는 java가 있다. 서로 무슨 차이가 있을까? 사실 없다. 마침 jdk1.8버전이 있어서 확인해봤다.&lt;/span&gt;&lt;/span&gt;&lt;figure class=&quot;imageblock alignCenter&quot; data-ke-mobileStyle=&quot;widthOrigin&quot; data-origin-width=&quot;651&quot; data-origin-height=&quot;290&quot;&gt;&lt;span data-url=&quot;https://blog.kakaocdn.net/dn/bpIAr0/dJMcagRUCNG/dxsT7codWgmcoUk9lZTr21/img.png&quot; data-phocus=&quot;https://blog.kakaocdn.net/dn/bpIAr0/dJMcagRUCNG/dxsT7codWgmcoUk9lZTr21/img.png&quot; data-alt=&quot;JDK의 java&quot;&gt;&lt;img src=&quot;https://blog.kakaocdn.net/dn/bpIAr0/dJMcagRUCNG/dxsT7codWgmcoUk9lZTr21/img.png&quot; srcset=&quot;https://img1.daumcdn.net/thumb/R1280x0/?scode=mtistory2&amp;fname=https%3A%2F%2Fblog.kakaocdn.net%2Fdn%2FbpIAr0%2FdJMcagRUCNG%2FdxsT7codWgmcoUk9lZTr21%2Fimg.png&quot; onerror=&quot;this.onerror=null; this.src='//t1.daumcdn.net/tistory_admin/static/images/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;290&quot; data-origin-width=&quot;651&quot; data-origin-height=&quot;290&quot;/&gt;&lt;/span&gt;&lt;figcaption&gt;JDK의 java&lt;/figcaption&gt;
&lt;/figure&gt;
&lt;figure class=&quot;imageblock alignCenter&quot; data-ke-mobileStyle=&quot;widthOrigin&quot; data-origin-width=&quot;641&quot; data-origin-height=&quot;344&quot;&gt;&lt;span data-url=&quot;https://blog.kakaocdn.net/dn/bbBLTN/dJMcaia79m1/gSKAeCzY2VmitrPKPu1c9k/img.png&quot; data-phocus=&quot;https://blog.kakaocdn.net/dn/bbBLTN/dJMcaia79m1/gSKAeCzY2VmitrPKPu1c9k/img.png&quot; data-alt=&quot;JRE의 java&quot;&gt;&lt;img src=&quot;https://blog.kakaocdn.net/dn/bbBLTN/dJMcaia79m1/gSKAeCzY2VmitrPKPu1c9k/img.png&quot; srcset=&quot;https://img1.daumcdn.net/thumb/R1280x0/?scode=mtistory2&amp;fname=https%3A%2F%2Fblog.kakaocdn.net%2Fdn%2FbbBLTN%2FdJMcaia79m1%2FgSKAeCzY2VmitrPKPu1c9k%2Fimg.png&quot; onerror=&quot;this.onerror=null; this.src='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png'; this.srcset='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png';&quot; loading=&quot;lazy&quot; width=&quot;641&quot; height=&quot;344&quot; data-origin-width=&quot;641&quot; data-origin-height=&quot;344&quot;/&gt;&lt;/span&gt;&lt;figcaption&gt;JRE의 java&lt;/figcaption&gt;
&lt;/figure&gt;
&lt;/blockquote&gt;
&lt;h4 style=&quot;color: #000000; text-align: start;&quot; data-ke-size=&quot;size20&quot;&gt;javadoc&lt;/h4&gt;
&lt;p style=&quot;color: #333333; text-align: start;&quot; data-ke-size=&quot;size16&quot;&gt;소스 코드의 주석을 읽어 HTML 형태의 API 문서를 자동으로 생성해 준다.&amp;nbsp;&lt;/p&gt;
&lt;h4 style=&quot;color: #000000; text-align: start;&quot; data-ke-size=&quot;size20&quot;&gt;jar&lt;/h4&gt;
&lt;p style=&quot;color: #333333; text-align: start;&quot; data-ke-size=&quot;size16&quot;&gt;여러 개의 클래스 파일과 리소스를 하나의 &lt;u&gt;&lt;b&gt;압축 파일(`.jar`)로 묶어주는 도구&lt;/b&gt;&lt;/u&gt;이다.&lt;/p&gt;
&lt;p style=&quot;color: #333333; text-align: start;&quot; data-ke-size=&quot;size16&quot;&gt;jar 파일 구조&lt;/p&gt;
&lt;pre id=&quot;code_1770020334680&quot; class=&quot;bash&quot; data-ke-language=&quot;bash&quot; data-ke-type=&quot;codeblock&quot;&gt;&lt;code&gt;myapp.jar
├── META-INF/
│   └── MANIFEST.MF         # 메타데이터 (Main-Class 등)
├── com/example/
│   ├── Main.class
│   ├── User.class
│   └── ...
└── application.properties  # 리소스 파일&lt;/code&gt;&lt;/pre&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;배포를 한다면, 개발 환경에서 jar 파일을 생성하고 AWS EC2에 jar 파일을 배포하면 된다.&amp;nbsp;&lt;/p&gt;
&lt;h4 style=&quot;color: #000000; text-align: start;&quot; data-ke-size=&quot;size20&quot;&gt;jdb&lt;/h4&gt;
&lt;p style=&quot;color: #333333; text-align: start;&quot; data-ke-size=&quot;size16&quot;&gt;커맨드 라인에서 사용하는 디버깅 도구이다. IntelliJ, Eclipse 디버거의 원조 격이다.&lt;/p&gt;
&lt;pre id=&quot;code_1770020389308&quot; class=&quot;bash&quot; data-ke-language=&quot;bash&quot; data-ke-type=&quot;codeblock&quot;&gt;&lt;code&gt;# 디버그 모드로 실행
java -agentlib:jdwp=transport=dt_socket,server=y,suspend=y,address=5005 MyApp

# 다른 터미널에서 jdb 연결
jdb -attach localhost:5005&lt;/code&gt;&lt;/pre&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;JDK/JRE 구분이 중요한 이유&lt;/h2&gt;
&lt;h3 data-ke-size=&quot;size23&quot;&gt;1. Docker 이미지 최적화&lt;/h3&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;단일 스테이지로 Dockerfile을 만들면&lt;/p&gt;
&lt;ul style=&quot;list-style-type: disc;&quot; data-ke-list-type=&quot;disc&quot;&gt;
&lt;li&gt;JDK 전체&lt;/li&gt;
&lt;li&gt;Gradle 캐시&lt;/li&gt;
&lt;li&gt;소스 코드 (`.java` 파일)&lt;/li&gt;
&lt;li&gt;빌드 중간 산출물&lt;/li&gt;
&lt;li&gt;app.jar&lt;/li&gt;
&lt;/ul&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;이 포함되어 있다. 조금 더 구체적으로 살펴보자.&lt;/p&gt;
&lt;pre id=&quot;code_1770020828785&quot; class=&quot;bash&quot; data-ke-language=&quot;bash&quot; data-ke-type=&quot;codeblock&quot;&gt;&lt;code&gt;docker run --rm myapp:single-stage ls -lh

# 1. JDK 전체
/usr/local/openjdk-17/
├── bin/
│   ├── java       (필요!) ✅
│   ├── javac      (불필요!) ❌ - 이미 컴파일 끝남
│   ├── jar        (불필요!) ❌ - 이미 JAR 만들어짐
│   ├── javadoc    (불필요!) ❌ - 문서 생성 안 함
│   ├── jdb        (불필요!) ❌ - 운영에서 디버깅 안 함
│   └── ...
└── lib/
    └── modules    (일부 필요, 대부분 불필요)

# 2. Gradle 캐시
/root/.gradle/
├── caches/        (불필요!) ❌ - 빌드 끝났으니 필요 없음
└── wrapper/       (불필요!) ❌ - Gradle 실행 안 함

# 3. 소스 코드
/app/src/
├── main/java/     (불필요!) ❌ - 이미 컴파일됨
├── test/          (불필요!) ❌ - 테스트 코드 필요 없음
└── resources/     (일부 필요) ⚠️ - app.jar에 이미 포함됨

# 4. 빌드 중간 산출물
/app/build/
├── classes/       (불필요!) ❌ - app.jar에 이미 포함
├── tmp/           (불필요!) ❌ - 임시 파일
└── libs/
    └── app.jar    (필요!) ✅ - 이것만 있으면 됨!

# 5. 기타
├── build.gradle
├── settings.gradle
└── ...           (불필요!) ❌&lt;/code&gt;&lt;/pre&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;하지만 실제로 필요한 것은&amp;nbsp;&lt;/p&gt;
&lt;pre id=&quot;code_1770020879357&quot; class=&quot;bash&quot; data-ke-language=&quot;bash&quot; data-ke-type=&quot;codeblock&quot;&gt;&lt;code&gt;JRE:
├── bin/
│   └── java           ✅ (실행 명령어)
├── lib/
│   ├── modules/       ✅ (표준 라이브러리)
│   │   ├── java.base.jmod
│   │   ├── java.sql.jmod
│   │   └── ...
│   ├── jvm.cfg        ✅ (JVM 설정)
│   └── ...
└── JVM 자체           ✅

app.jar:         ✅ (우리 애플리케이션)&lt;/code&gt;&lt;/pre&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;이만큼이다.&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;따라서, 멀티 스테이지 빌드를 통해 필요한 것들만 빌드해 &lt;u&gt;&lt;b&gt;Dockerfile의 크기를 줄일 수 있다.&lt;/b&gt;&lt;/u&gt;&amp;nbsp;&lt;/p&gt;
&lt;h3 data-ke-size=&quot;size23&quot;&gt;2. 운영 환경 메모리 최적화&lt;/h3&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;운영환경에서 컨테이너에 할당된 메모리가 부족할 수 있다. 1번의 사항을 고려하면 운영환경에서도 더 적은 리소스를 가지고 운영할 수 있다.&lt;/p&gt;
&lt;h3 data-ke-size=&quot;size23&quot;&gt;3. 보안 관점&lt;/h3&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;JDK를 그대로 Dockerfile에 포함시켰다면 javac, jar, jdb가 포함되어 있다.&lt;/p&gt;
&lt;h4 data-ke-size=&quot;size20&quot;&gt;javac&lt;/h4&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;해커가 이미 컨테이너 안에 들어왔다면, 컨테이너 쉘에 접근이 가능하다. 이 때, 쉘 스크립트로 공격 파일을 생성하고, javac로 컴파일 한 후 해당 파일을 실행해 공격을 할 수 있다.&lt;/p&gt;
&lt;h4 data-ke-size=&quot;size20&quot;&gt;jar&lt;/h4&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;`application.properties`에 중요한 정보를 다 넣었다고 가정해보자. jar파일엔 `application.properties`가 있고, 이를 통해 중요한 정보가 탈취할 수 있다. 또, 악성 코드를 추가해 재패킹할 수도 있다.&lt;/p&gt;
&lt;h4 data-ke-size=&quot;size20&quot;&gt;jdb&lt;/h4&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;실행 중인 프로세스에 접근해 실행 중인 모든 스레드를 확인하고 메모리에 있는 모든 민감 정보에 접근이 가능하다. 또, 특정 로직에 브레이크포인트를 걸어 로직을 바꿀 수도 있다.&lt;/p&gt;
&lt;hr contenteditable=&quot;false&quot; data-ke-type=&quot;horizontalRule&quot; data-ke-style=&quot;style6&quot; /&gt;
&lt;h2 data-ke-size=&quot;size26&quot;&gt;결론&lt;/h2&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;JDK의 구성요소를 알고 배포 과정에서 필요한 요소들만 넣고, 적절한 환경 구성하기 위해 JDK와 JRE를 알아야 한다. 다음엔 JVM에 대해 시작할 예정이다.&amp;nbsp;&lt;/p&gt;</description>
      <category>Java</category>
      <author>Ukjong</author>
      <guid isPermaLink="true">https://ukj0ng.tistory.com/67</guid>
      <comments>https://ukj0ng.tistory.com/67#entry67comment</comments>
      <pubDate>Mon, 2 Feb 2026 17:56:47 +0900</pubDate>
    </item>
    <item>
      <title>[Web] RESTful API란?</title>
      <link>https://ukj0ng.tistory.com/66</link>
      <description>&lt;p data-ke-size=&quot;size16&quot;&gt;최근 프로젝트들에서 API를 설계할 때, 항상 RESTful API로 설계를 했다. 사실 알고 있는 통신 방식이 RESTful 밖에 없었는데, GraphQL과 같은 통신 방식을 들으며 RESTful API를 내가 납득하고 쓸 수 있어야 겠다는 생각이 들어서 RESTful API란 무엇인지 공부해보고자 한다.&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;1. RESTful API란?&lt;/h2&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;RESTful API는 &lt;b&gt;REST(Representational State Transfer) 아키텍처 스타일&lt;/b&gt;을 따르는 웹 API라고 한다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;영어를 해석해보면:&lt;/p&gt;
&lt;ul style=&quot;list-style-type: disc;&quot; data-ke-list-type=&quot;disc&quot;&gt;
&lt;li&gt;&lt;b&gt;&lt;span style=&quot;color: #333333; text-align: start;&quot;&gt;Representational(표현의)&lt;/span&gt;&lt;/b&gt;
&lt;ul style=&quot;list-style-type: disc;&quot; data-ke-list-type=&quot;disc&quot;&gt;
&lt;li&gt;&lt;span style=&quot;color: #333333; text-align: start;&quot;&gt;서버의 실제 데이터나 리소스 자체를 주고받는 게 아니라 표현(Representationa)을 주고받는다는 의미&lt;/span&gt;&lt;/li&gt;
&lt;li&gt;&lt;span style=&quot;color: #333333; text-align: start;&quot;&gt;사용자 정보를 JSON, XML 등 다양한 형태로 &quot;표현&quot;해서 전달&lt;/span&gt;&lt;/li&gt;
&lt;/ul&gt;
&lt;/li&gt;
&lt;li&gt;&lt;b&gt;&lt;span style=&quot;color: #333333; text-align: start;&quot;&gt;State(상태)&lt;/span&gt;&lt;/b&gt;
&lt;ul style=&quot;list-style-type: disc;&quot; data-ke-list-type=&quot;disc&quot;&gt;
&lt;li&gt;&lt;span style=&quot;color: #333333; text-align: start;&quot;&gt;리소스의 현재 상태&lt;/span&gt;&lt;/li&gt;
&lt;/ul&gt;
&lt;/li&gt;
&lt;li&gt;&lt;b&gt;&lt;span style=&quot;color: #333333; text-align: start;&quot;&gt;Transfer(전송)&lt;/span&gt;&lt;/b&gt;
&lt;ul style=&quot;list-style-type: disc;&quot; data-ke-list-type=&quot;disc&quot;&gt;
&lt;li&gt;&lt;span style=&quot;color: #333333; text-align: start;&quot;&gt;클라이언트와 서버 간에 이 상태 표현을 주고받는 것&lt;/span&gt;&lt;span style=&quot;color: #333333; text-align: start;&quot;&gt;&lt;/span&gt;&lt;/li&gt;
&lt;/ul&gt;
&lt;/li&gt;
&lt;/ul&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;span style=&quot;color: #333333; text-align: start;&quot;&gt;이다.&amp;nbsp;&lt;/span&gt;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;span style=&quot;color: #333333; text-align: start;&quot;&gt;합쳐서 이해해보면 &quot;리소스의 현재 상태를 표현된 형태로 전송한다&quot;하는 웹 API라고 볼 수 있고, RESTful이란 REST 원칙을 (완벽하게는 아니더라도) 따르는 것이다. 여기까진 와닿지 않을 수 있지만, 앞으로 나올 예시들을 보면 이해가 될 것이다.&amp;nbsp;&lt;/span&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;2. RESTful API가 나온 이유&lt;/h2&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;2000년대 초반 당시 엔터프라이즈 시스템 통신의 표준이었던 &lt;b&gt;SOAP(Simple Object Access Protocol)&lt;/b&gt;는 너무 복잡했다. 크게 단점이 4가지 정도가 있는데, 가볍게만 보고 넘어가자.&lt;/p&gt;
&lt;hr contenteditable=&quot;false&quot; data-ke-type=&quot;horizontalRule&quot; data-ke-style=&quot;style8&quot; /&gt;
&lt;h3 data-ke-size=&quot;size23&quot;&gt;SOAP의 4가지 단점&lt;/h3&gt;
&lt;h4 data-ke-size=&quot;size20&quot;&gt;2.1. 전송해야할 패킷의 &lt;b&gt;엄청난 오버헤드&lt;/b&gt;&lt;/h4&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;SOAP는 XML 기반이라 불필요한 태그와 메타데이터가 너무 많았다.&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;REST에서 주로 사용하는 JSON과 SOAP의 XML을 직접 비교해보자.&amp;nbsp;&lt;/p&gt;
&lt;pre id=&quot;code_1769528309112&quot; class=&quot;bash&quot; data-ke-language=&quot;bash&quot; data-ke-type=&quot;codeblock&quot;&gt;&lt;code&gt;&amp;lt;!-- XML --&amp;gt;
&amp;lt;?xml version=&quot;1.0&quot;?&amp;gt;
&amp;lt;soap:Envelope xmlns:soap=&quot;http://schemas.xmlsoap.org/soap/envelope/&quot;&amp;gt;
  &amp;lt;soap:Body&amp;gt;
    &amp;lt;GetUserResponse&amp;gt;
      &amp;lt;User&amp;gt;
        &amp;lt;Id&amp;gt;123&amp;lt;/Id&amp;gt;
        &amp;lt;Name&amp;gt;홍길동&amp;lt;/Name&amp;gt;
        &amp;lt;Email&amp;gt;hong@example.com&amp;lt;/Email&amp;gt;
      &amp;lt;/User&amp;gt;
    &amp;lt;/GetUserResponse&amp;gt;
  &amp;lt;/soap:Body&amp;gt;
&amp;lt;/soap:Envelope&amp;gt;&lt;/code&gt;&lt;/pre&gt;
&lt;pre id=&quot;code_1769528327359&quot; class=&quot;bash&quot; data-ke-language=&quot;bash&quot; data-ke-type=&quot;codeblock&quot;&gt;&lt;code&gt;// JSON
{
  &quot;id&quot;: 123,
  &quot;name&quot;: &quot;홍길동&quot;,
  &quot;email&quot;: &quot;hong@example.com&quot;
}&lt;/code&gt;&lt;/pre&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;같은 데이터를 전송하는데 SOAP는 &lt;b&gt;8~10배 더 무겁고&lt;/b&gt; 그 때문에 &lt;b&gt;네트워크 대역폭과 전송 시간이 그만큼 낭비&lt;/b&gt;된다.&amp;nbsp;&lt;/p&gt;
&lt;hr contenteditable=&quot;false&quot; data-ke-type=&quot;horizontalRule&quot; data-ke-style=&quot;style8&quot; /&gt;
&lt;h4 data-ke-size=&quot;size20&quot;&gt;2.2. 도구 의존성&lt;/h4&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;SOAP를 사용하려면 복잡한 도구와 라이브러리가 필요했다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;b&gt;SOAP 개발 프로세스&lt;/b&gt;&lt;/p&gt;
&lt;ol style=&quot;list-style-type: decimal;&quot; data-ke-list-type=&quot;decimal&quot;&gt;
&lt;li&gt;WSDL(서비스 명세서) 작성&lt;/li&gt;
&lt;li&gt;코드 생성 도구 (wsdl2java 등) 실행&lt;/li&gt;
&lt;li&gt;생성된 클래스 파일들 확인&lt;/li&gt;
&lt;li&gt;SOAP 라이브러리 (Apache Axis, JAX-WS 등) 추가&lt;/li&gt;
&lt;li&gt;설정 파일 작성&lt;/li&gt;
&lt;/ol&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;b&gt;REST 개발 프로세스&lt;/b&gt;&lt;/p&gt;
&lt;pre id=&quot;code_1769528523028&quot; class=&quot;javascript&quot; data-ke-language=&quot;javascript&quot; data-ke-type=&quot;codeblock&quot;&gt;&lt;code&gt;fetch('https://api.example.com/users/123')
  .then(res =&amp;gt; res.json())
  .then(user =&amp;gt; console.log(user));&lt;/code&gt;&lt;/pre&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;SOAP는 도구 없이는 개발이 거의 불가능했고, 도구가 바뀌거나 버전이 올라가면 호환성 문제가 발생했다. 하지만 REST는 기본 HTTP 클라이언트만 있으면 충분하다.&amp;nbsp;&lt;/p&gt;
&lt;hr contenteditable=&quot;false&quot; data-ke-type=&quot;horizontalRule&quot; data-ke-style=&quot;style8&quot; /&gt;
&lt;h4 data-ke-size=&quot;size20&quot;&gt;2.3. 디버깅의 어려움&lt;/h4&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;SOAP의 에러 메세지는 중첩된 XML 구조 안에 깊이 숨겨져 있어 파악하기 어려웠다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;b&gt;SOAP 에러:&lt;/b&gt;&lt;/p&gt;
&lt;pre id=&quot;code_1769528603392&quot; class=&quot;bash&quot; data-ke-language=&quot;bash&quot; data-ke-type=&quot;codeblock&quot;&gt;&lt;code&gt;&amp;lt;soap:Envelope&amp;gt;
  &amp;lt;soap:Body&amp;gt;
    &amp;lt;soap:Fault&amp;gt;
      &amp;lt;faultcode&amp;gt;soap:Server&amp;lt;/faultcode&amp;gt;
      &amp;lt;faultstring&amp;gt;Error&amp;lt;/faultstring&amp;gt;
      &amp;lt;detail&amp;gt;
        &amp;lt;message&amp;gt;
          NullPointerException at line 45...
          (중첩된 스택 트레이스 50줄)
        &amp;lt;/message&amp;gt;
      &amp;lt;/detail&amp;gt;
    &amp;lt;/soap:Fault&amp;gt;
  &amp;lt;/soap:Body&amp;gt;
&amp;lt;/soap:Envelope&amp;gt;&lt;/code&gt;&lt;/pre&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;b&gt;REST 에러:&lt;/b&gt;&lt;/p&gt;
&lt;pre id=&quot;code_1769528621629&quot; class=&quot;bash&quot; data-ke-language=&quot;bash&quot; data-ke-type=&quot;codeblock&quot;&gt;&lt;code&gt;HTTP/1.1 404 Not Found

{
  &quot;error&quot;: &quot;USER_NOT_FOUND&quot;,
  &quot;message&quot;: &quot;User with id 123 not found&quot;
}&lt;/code&gt;&lt;/pre&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;SOAP는 무엇이 문제인지 파악하기 어렵지만, REST는 상태 코드와 명확한 메세지로 파악이 쉽다. 또, REST는 브라우저 개발자 도구나 Postman 같은 직관적인 도구로 쉽게 테스트하고 디버깅할 수 있다.&lt;/p&gt;
&lt;hr contenteditable=&quot;false&quot; data-ke-type=&quot;horizontalRule&quot; data-ke-style=&quot;style8&quot; /&gt;
&lt;h4 data-ke-size=&quot;size20&quot;&gt;2.4. 모바일에서 효율 안좋음&lt;/h4&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;2000년 후반기에 모바일 시대가 열렸는데, SOAP는 모바일 환경에 치명적이었다.&amp;nbsp;&lt;/p&gt;
&lt;div&gt;
&lt;table style=&quot;border-collapse: collapse; width: 100%;&quot; border=&quot;1&quot; data-ke-align=&quot;alignLeft&quot;&gt;
&lt;tbody&gt;
&lt;tr&gt;
&lt;td&gt;&lt;b&gt;문제&lt;/b&gt;&lt;/td&gt;
&lt;td&gt;&lt;b&gt;SOAP&lt;/b&gt;&lt;/td&gt;
&lt;td&gt;&lt;b&gt;REST&lt;/b&gt;&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;&lt;b&gt;대역폭&lt;/b&gt;&lt;/td&gt;
&lt;td&gt;무거운 XML &amp;rarr; 느린 로딩&lt;/td&gt;
&lt;td&gt;가벼운 JSON &amp;rarr; 빠른 로딩&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;&lt;b&gt;배터리&lt;/b&gt;&lt;/td&gt;
&lt;td&gt;복잡한 XML 파싱 &amp;rarr; 높은 CPU 사용&lt;/td&gt;
&lt;td&gt;간단한 JSON 파싱 &amp;rarr; 낮은 CPU 사용&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;&lt;b&gt;메모리&lt;/b&gt;&lt;/td&gt;
&lt;td&gt;복잡한 객체 생성 &amp;rarr; 높은 메모리&lt;/td&gt;
&lt;td&gt;단순한 객체 &amp;rarr; 낮은 메모리&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;&lt;b&gt;데이터 요금&lt;/b&gt;&lt;/td&gt;
&lt;td&gt;8-10배 많은 전송량&lt;/td&gt;
&lt;td&gt;최소한의 전송량&lt;/td&gt;
&lt;/tr&gt;
&lt;/tbody&gt;
&lt;/table&gt;
&lt;/div&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;모바일 기기의 제한된 자원(배터리, 메모리, 네트워크)을 고려하면 SOAP는 너무 비효율적이었다.&lt;/p&gt;
&lt;hr contenteditable=&quot;false&quot; data-ke-type=&quot;horizontalRule&quot; data-ke-style=&quot;style8&quot; /&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;Roy Fielding&lt;/b&gt;은 REST를 제안했다.&lt;/p&gt;
&lt;blockquote data-ke-style=&quot;style2&quot;&gt;웹은 이미 잘 작동하고 있다. 복잡하게 새로운 걸 만들지 말고, 웹의 강점을 그대로 활용하자.&lt;/blockquote&gt;
&lt;blockquote data-ke-style=&quot;style3&quot;&gt; &lt;span style=&quot;color: #000000;&quot;&gt;웹의 강점&lt;/span&gt;&lt;br /&gt;&lt;span style=&quot;color: #000000;&quot;&gt;1. HTTP 프로토콜 - 이미 완성된 통신 표준&lt;/span&gt;&lt;br /&gt;&lt;span style=&quot;color: #000000;&quot;&gt;2. URL - 모든 것을 식별하는 주소 체계&lt;/span&gt;&lt;br /&gt;&lt;span style=&quot;color: #000000;&quot;&gt;3. HTTP 캐싱&lt;/span&gt;&lt;br /&gt;&lt;span style=&quot;color: #000000;&quot;&gt;4. HTTP 상태 코드 - 명확한 의미 전달&lt;/span&gt;&lt;br /&gt;&lt;span style=&quot;color: #000000;&quot;&gt;5. HTTP 메서드 - 의미가 있는 동사&lt;/span&gt;&lt;br /&gt;&lt;span style=&quot;color: #000000;&quot;&gt;6. 무상태성&lt;/span&gt;&lt;br /&gt;&lt;span style=&quot;color: #000000;&quot;&gt;7. 계층화 시스템 - 중간 서버 활용&lt;/span&gt;&lt;br /&gt;&lt;span style=&quot;color: #000000;&quot;&gt;8. 하이퍼링크 - 리소스 연결&lt;/span&gt;&lt;/blockquote&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;3. RESTful API 예시&lt;/h2&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;RESTful API에 대한 예시를 먼저 살펴보자. (HTTP 메서드는 여기서 다루지 않겠다.)&lt;/p&gt;
&lt;pre id=&quot;code_1769529335466&quot; class=&quot;bash&quot; data-ke-language=&quot;bash&quot; data-ke-type=&quot;codeblock&quot;&gt;&lt;code&gt;# 게시글
GET    /api/v1/posts                          # 목록 (페이지네이션)
GET    /api/v1/posts/{id}                     # 조회
POST   /api/v1/posts                          # 작성
PUT    /api/v1/posts/{id}                     # 수정
DELETE /api/v1/posts/{id}                     # 삭제

# 중첩 리소스
GET    /api/v1/posts/{id}/comments            # 댓글 목록
POST   /api/v1/posts/{id}/comments            # 댓글 작성
PUT    /api/v1/posts/{id}/comments/{cid}      # 댓글 수정
DELETE /api/v1/posts/{id}/comments/{cid}      # 댓글 삭제

# 액션
POST   /api/v1/posts/{id}/like                # 좋아요
DELETE /api/v1/posts/{id}/like                # 좋아요 취소
POST   /api/v1/posts/{id}/publish             # 게시
POST   /api/v1/posts/{id}/archive             # 보관&lt;/code&gt;&lt;/pre&gt;
&lt;pre id=&quot;code_1769529397992&quot; class=&quot;bash&quot; data-ke-language=&quot;bash&quot; data-ke-type=&quot;codeblock&quot;&gt;&lt;code&gt;# 페이지네이션
GET /api/v1/posts?page=2&amp;amp;limit=20

# 필터링
GET /api/v1/posts?status=published&amp;amp;category=tech

# 정렬
GET /api/v1/posts?sort=-created_at,title

# 필드 선택 (Sparse Fieldsets)
GET /api/v1/posts?fields=id,title,created_at

# 관계 포함
GET /api/v1/posts?include=author,comments

# 복합 쿼리
GET /api/v1/posts?status=published&amp;amp;tag=javascript&amp;amp;sort=-views&amp;amp;page=1&amp;amp;limit=10&lt;/code&gt;&lt;/pre&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;4. REST의 원칙&lt;/h2&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;REST 아키텍처는 6가지 원칙을 가지고 있다.&lt;/p&gt;
&lt;hr contenteditable=&quot;false&quot; data-ke-type=&quot;horizontalRule&quot; data-ke-style=&quot;style8&quot; /&gt;
&lt;h3 style=&quot;color: #000000;&quot; data-ke-size=&quot;size23&quot;&gt;4.1. 클라이언트-서버 구조 (Client-Server)&lt;/h3&gt;
&lt;p&gt;&lt;figure class=&quot;imageblock alignCenter&quot; data-ke-mobileStyle=&quot;widthOrigin&quot; data-origin-width=&quot;1280&quot; data-origin-height=&quot;768&quot;&gt;&lt;span data-url=&quot;https://blog.kakaocdn.net/dn/trslU/dJMcaaREQzM/xGz1eEoqFGQd8UkLIuiKJ0/img.png&quot; data-phocus=&quot;https://blog.kakaocdn.net/dn/trslU/dJMcaaREQzM/xGz1eEoqFGQd8UkLIuiKJ0/img.png&quot; data-alt=&quot;https://en.wikipedia.org/wiki/Client%E2%80%93server_model&quot;&gt;&lt;img src=&quot;https://blog.kakaocdn.net/dn/trslU/dJMcaaREQzM/xGz1eEoqFGQd8UkLIuiKJ0/img.png&quot; srcset=&quot;https://img1.daumcdn.net/thumb/R1280x0/?scode=mtistory2&amp;fname=https%3A%2F%2Fblog.kakaocdn.net%2Fdn%2FtrslU%2FdJMcaaREQzM%2FxGz1eEoqFGQd8UkLIuiKJ0%2Fimg.png&quot; onerror=&quot;this.onerror=null; this.src='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png'; this.srcset='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png';&quot; loading=&quot;lazy&quot; width=&quot;592&quot; height=&quot;355&quot; data-origin-width=&quot;1280&quot; data-origin-height=&quot;768&quot;/&gt;&lt;/span&gt;&lt;figcaption&gt;https://en.wikipedia.org/wiki/Client%E2%80%93server_model&lt;/figcaption&gt;
&lt;/figure&gt;
&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;UI와 데이터 처리를 분리한다. &amp;rarr; &lt;u&gt;&lt;b&gt;각자 독립적으로 개발 및 배포가 가능하다.&lt;/b&gt;&lt;/u&gt;&amp;nbsp;&lt;/p&gt;
&lt;hr contenteditable=&quot;false&quot; data-ke-style=&quot;style8&quot; data-ke-type=&quot;horizontalRule&quot; /&gt;
&lt;h3 style=&quot;color: #000000;&quot; data-ke-size=&quot;size23&quot;&gt;4.2. &lt;u&gt;&lt;b&gt;무상태성 (Stateless)&lt;/b&gt;&lt;/u&gt;&lt;/h3&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;서버는 클라이언트의 상태를 저장하지 않는다.&amp;nbsp;&lt;/p&gt;
&lt;ol style=&quot;list-style-type: decimal;&quot; data-ke-list-type=&quot;decimal&quot;&gt;
&lt;li&gt;확장성: 어떤 서버든 요청 처리 가능&lt;/li&gt;
&lt;li&gt;신뢰성: 서버 재시작해도 문제 없음&lt;/li&gt;
&lt;li&gt;간단함: 세션 관리 불필요&lt;/li&gt;
&lt;/ol&gt;
&lt;hr contenteditable=&quot;false&quot; data-ke-style=&quot;style8&quot; data-ke-type=&quot;horizontalRule&quot; /&gt;
&lt;h3 style=&quot;color: #000000;&quot; data-ke-size=&quot;size23&quot;&gt;4.3. 캐시 가능 (Cacheable)&lt;/h3&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;응답을 캐시할 수 있어 성능 향상&lt;/p&gt;
&lt;ol style=&quot;list-style-type: decimal;&quot; data-ke-list-type=&quot;decimal&quot;&gt;
&lt;li&gt;속도 향상&lt;/li&gt;
&lt;li&gt;서버 부하 감사: 반복 요청을 캐시가 처리&lt;/li&gt;
&lt;li&gt;대역폭 절약: 네트워크 트래픽 감소&lt;/li&gt;
&lt;li&gt;자동 지원: 브라우저, CDN이 자동으로 캐싱 처&lt;/li&gt;
&lt;/ol&gt;
&lt;hr contenteditable=&quot;false&quot; data-ke-type=&quot;horizontalRule&quot; data-ke-style=&quot;style8&quot; /&gt;
&lt;h3 style=&quot;color: #000000;&quot; data-ke-size=&quot;size23&quot;&gt;4.4. 계층화 시스템 (Layered System)&lt;/h3&gt;
&lt;p&gt;&lt;figure class=&quot;imageblock alignCenter&quot; data-ke-mobileStyle=&quot;widthOrigin&quot; data-origin-width=&quot;747&quot; data-origin-height=&quot;1003&quot;&gt;&lt;span data-url=&quot;https://blog.kakaocdn.net/dn/cXjRQL/dJMcaivkWhN/lOITn41DiBhmfMwLbKHe51/img.png&quot; data-phocus=&quot;https://blog.kakaocdn.net/dn/cXjRQL/dJMcaivkWhN/lOITn41DiBhmfMwLbKHe51/img.png&quot;&gt;&lt;img src=&quot;https://blog.kakaocdn.net/dn/cXjRQL/dJMcaivkWhN/lOITn41DiBhmfMwLbKHe51/img.png&quot; srcset=&quot;https://img1.daumcdn.net/thumb/R1280x0/?scode=mtistory2&amp;fname=https%3A%2F%2Fblog.kakaocdn.net%2Fdn%2FcXjRQL%2FdJMcaivkWhN%2FlOITn41DiBhmfMwLbKHe51%2Fimg.png&quot; onerror=&quot;this.onerror=null; this.src='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png'; this.srcset='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png';&quot; loading=&quot;lazy&quot; width=&quot;507&quot; height=&quot;681&quot; data-origin-width=&quot;747&quot; data-origin-height=&quot;1003&quot;/&gt;&lt;/span&gt;&lt;/figure&gt;
&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;span style=&quot;color: #333333; text-align: start;&quot;&gt;클라이언트는 중간 서버를 알 필요 없다.&lt;span&gt;&amp;nbsp;&lt;/span&gt;&lt;/span&gt;&amp;rarr; &lt;u&gt;&lt;b&gt;클라이언트는 최종 API 주소만 알면 된다.&lt;/b&gt;&lt;/u&gt;&lt;/p&gt;
&lt;ol style=&quot;list-style-type: decimal;&quot; data-ke-list-type=&quot;decimal&quot;&gt;
&lt;li&gt;보안 강화: 중간에 방화벽, 게이트웨이 추가 가능&lt;/li&gt;
&lt;li&gt;유연성: 계층 추가/변경이 클라이언트에 영향 없음&lt;/li&gt;
&lt;li&gt;성능 최적화: 캐시 서버, CDN 투명하게 추가 가능&lt;/li&gt;
&lt;li&gt;부하 분산: 로드 밸런서로 트래픽 분리&lt;/li&gt;
&lt;/ol&gt;
&lt;hr contenteditable=&quot;false&quot; data-ke-type=&quot;horizontalRule&quot; data-ke-style=&quot;style8&quot; /&gt;
&lt;h3 style=&quot;color: #000000;&quot; data-ke-size=&quot;size23&quot;&gt;4.5. &lt;u&gt;&lt;b&gt;인터페이스 일관성 (Uniform Interface)&lt;/b&gt;&lt;/u&gt;&lt;/h3&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;REST엔 4가지 제약 조건이 있다.&lt;/p&gt;
&lt;h4 data-ke-size=&quot;size20&quot;&gt;1. 리소스 식별&lt;/h4&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;URI로 리소스를 식별&lt;/p&gt;
&lt;pre id=&quot;code_1769532850783&quot; class=&quot;bash&quot; data-ke-language=&quot;bash&quot; data-ke-type=&quot;codeblock&quot;&gt;&lt;code&gt;GET /users/123           # 사용자
GET /posts/456           # 게시글
GET /posts/456/comments  # 댓글&lt;/code&gt;&lt;/pre&gt;
&lt;h4 data-ke-size=&quot;size20&quot;&gt;2. 응답 데이터로 리소스를 조작&lt;/h4&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;span&gt;&lt;span&gt;응답 데이터 &lt;/span&gt;&lt;span style=&quot;color: #4078f2;&quot;&gt;=&lt;/span&gt;&lt;span&gt; 리소스의 현재 상태 &lt;/span&gt;&lt;/span&gt;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;span&gt;&lt;span&gt;이 상태를 보고&lt;/span&gt;&lt;span style=&quot;color: #4078f2;&quot;&gt;:&lt;/span&gt;&lt;span&gt; &lt;/span&gt;&lt;/span&gt;&lt;span&gt;&lt;/span&gt;&lt;/p&gt;
&lt;ul style=&quot;list-style-type: disc;&quot; data-ke-list-type=&quot;disc&quot;&gt;
&lt;li&gt;어떤 필드를 수정할 수 있는지 알 수 있다&lt;/li&gt;
&lt;li&gt;어떤 값으로 수정해야 하는지 알 수 있다&lt;/li&gt;
&lt;li&gt;어떤 ID로 삭제해야 하는지 알 수 있다&lt;/li&gt;
&lt;/ul&gt;
&lt;h4 data-ke-size=&quot;size20&quot;&gt;3. 요청, 응답에 메타데이터 포함(메세지 처리 방법 설명)&lt;/h4&gt;
&lt;ul style=&quot;list-style-type: disc;&quot; data-ke-list-type=&quot;disc&quot;&gt;
&lt;li&gt;&lt;span&gt;&lt;span&gt;Content&lt;/span&gt;&lt;span style=&quot;color: #4078f2;&quot;&gt;-&lt;/span&gt;&lt;span&gt;Type&lt;/span&gt;&lt;span style=&quot;color: #4078f2;&quot;&gt;:&lt;/span&gt;&lt;span&gt; 어떤 형식인지&lt;/span&gt;&lt;/span&gt;&lt;/li&gt;
&lt;li&gt;&lt;span&gt;&lt;span&gt;Cache&lt;/span&gt;&lt;span style=&quot;color: #4078f2;&quot;&gt;-&lt;/span&gt;&lt;span&gt;Control&lt;/span&gt;&lt;span style=&quot;color: #4078f2;&quot;&gt;:&lt;/span&gt;&lt;span&gt; 어떻게 캐시할지&lt;/span&gt;&lt;/span&gt;&lt;/li&gt;
&lt;li&gt;&lt;span&gt;&lt;span&gt;Authorization&lt;/span&gt;&lt;span style=&quot;color: #4078f2;&quot;&gt;:&lt;/span&gt;&lt;span&gt; 어떻게 인증할지&lt;/span&gt;&lt;/span&gt;&lt;/li&gt;
&lt;/ul&gt;
&lt;h4 data-ke-size=&quot;size20&quot;&gt;4. HATEOAS&lt;/h4&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;응답에 다음 가능한 액션의 링크 포함&lt;/p&gt;
&lt;pre id=&quot;code_1769533490399&quot; class=&quot;bash&quot; data-ke-language=&quot;bash&quot; data-ke-type=&quot;codeblock&quot;&gt;&lt;code&gt;{
  &quot;id&quot;: 123,
  &quot;status&quot;: &quot;pending&quot;,
  &quot;_links&quot;: {
    &quot;self&quot;: &quot;/orders/123&quot;,
    &quot;payment&quot;: &quot;/orders/123/payment&quot;,
    &quot;cancel&quot;: &quot;/orders/123&quot;
  }
}&lt;/code&gt;&lt;/pre&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;실무에서는 HATEOAS를 완벽하게 구현하는 경우가 드물다. 너무 이상적이고 복잡해서 비용 대비 효과가 낮기 때문이다.&lt;/p&gt;
&lt;hr contenteditable=&quot;false&quot; data-ke-style=&quot;style8&quot; data-ke-type=&quot;horizontalRule&quot; /&gt;
&lt;h3 style=&quot;color: #000000;&quot; data-ke-size=&quot;size23&quot;&gt;4.6. 온디맨드 코드&lt;/h3&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;서버가 실행 가능한 코드를 클라이언트에 전송할 수 있다.&lt;/p&gt;
&lt;pre id=&quot;code_1769533521028&quot; class=&quot;bash&quot; data-ke-language=&quot;bash&quot; data-ke-type=&quot;codeblock&quot;&gt;&lt;code&gt;// 서버가 검증 로직을 전송
{
  &quot;data&quot;: { /* ... */ },
  &quot;validation&quot;: &quot;function validate(email) { return /^[a-z0-9]+@/.test(email); }&quot;
}&lt;/code&gt;&lt;/pre&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;5. RESTful API 장점&lt;/h2&gt;
&lt;hr contenteditable=&quot;false&quot; data-ke-type=&quot;horizontalRule&quot; data-ke-style=&quot;style8&quot; /&gt;
&lt;h3 data-ke-size=&quot;size23&quot;&gt;5.1. &lt;u&gt;&lt;b&gt;확장성&lt;/b&gt;&lt;/u&gt;&lt;/h3&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;무상태 특성으로 서버를 쉽게 늘릴 수 있다.&amp;nbsp;&lt;/p&gt;
&lt;ul style=&quot;list-style-type: disc;&quot; data-ke-list-type=&quot;disc&quot;&gt;
&lt;li&gt;세션 공유가 불필요&lt;/li&gt;
&lt;li&gt;로드 밸런서가 자유롭게 분배&lt;/li&gt;
&lt;/ul&gt;
&lt;hr contenteditable=&quot;false&quot; data-ke-type=&quot;horizontalRule&quot; data-ke-style=&quot;style8&quot; /&gt;
&lt;h3 data-ke-size=&quot;size23&quot;&gt;5.2. 유연성&lt;/h3&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;프론트엔드와 백엔드가 독립적으로 개발 가능하다.&lt;/p&gt;
&lt;hr contenteditable=&quot;false&quot; data-ke-type=&quot;horizontalRule&quot; data-ke-style=&quot;style8&quot; /&gt;
&lt;h3 data-ke-size=&quot;size23&quot;&gt;5.3 플랫폼 독립성&lt;/h3&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;하나의 API로 모든 클라이언트를 지원한다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;동일한 RESTful API로:&lt;/p&gt;
&lt;ul style=&quot;list-style-type: disc;&quot; data-ke-list-type=&quot;disc&quot;&gt;
&lt;li&gt;웹 브라우저&lt;/li&gt;
&lt;li&gt;iOS 앱&lt;/li&gt;
&lt;li&gt;Android 앱&lt;/li&gt;
&lt;li&gt;데스크톱 앱&lt;/li&gt;
&lt;li&gt;IoT 기기&lt;/li&gt;
&lt;/ul&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;처리가 가능하다.&lt;/p&gt;
&lt;hr contenteditable=&quot;false&quot; data-ke-type=&quot;horizontalRule&quot; data-ke-style=&quot;style8&quot; /&gt;
&lt;h3 data-ke-size=&quot;size23&quot;&gt;5.4. 이해하기 쉬움&lt;/h3&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;표준 HTTP를 사용해 직관적이다. 따라서, 개발자라면 이해하기 쉽고 학습 곡선이 낮다.&amp;nbsp;&lt;/p&gt;
&lt;pre id=&quot;code_1769576673399&quot; class=&quot;bash&quot; data-ke-language=&quot;bash&quot; data-ke-type=&quot;codeblock&quot;&gt;&lt;code&gt;GET    /users      # 조회
POST   /users      # 생성
PUT    /users/123  # 수정
DELETE /users/123  # 삭제&lt;/code&gt;&lt;/pre&gt;
&lt;hr contenteditable=&quot;false&quot; data-ke-type=&quot;horizontalRule&quot; data-ke-style=&quot;style8&quot; /&gt;
&lt;h3 data-ke-size=&quot;size23&quot;&gt;5.5. 풍부한 생태계&lt;/h3&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;사용할 도구와 라이브러리들이 많다.&amp;nbsp;&lt;/p&gt;
&lt;ul style=&quot;list-style-type: disc;&quot; data-ke-list-type=&quot;disc&quot;&gt;
&lt;li&gt;테스트 도구: Postman, Insomnia, curl&lt;/li&gt;
&lt;li&gt;문서화: Swagger/OpenAPI&lt;/li&gt;
&lt;li&gt;프레임워크: Express, Django REST, Spring Boot&lt;/li&gt;
&lt;li&gt;클라우드: AWS API Gateway, Azure API Management&lt;/li&gt;
&lt;/ul&gt;
&lt;hr contenteditable=&quot;false&quot; data-ke-type=&quot;horizontalRule&quot; data-ke-style=&quot;style6&quot; /&gt;
&lt;h2 data-ke-size=&quot;size26&quot;&gt;6. RESTful API 단점&lt;/h2&gt;
&lt;hr contenteditable=&quot;false&quot; data-ke-type=&quot;horizontalRule&quot; data-ke-style=&quot;style8&quot; /&gt;
&lt;h3 style=&quot;color: #000000; text-align: start;&quot; data-ke-size=&quot;size23&quot;&gt;6.1. 오버페칭&lt;/h3&gt;
&lt;p style=&quot;color: #333333; text-align: start;&quot; data-ke-size=&quot;size16&quot;&gt;필요한 것보다 더 많은 데이터를 받는다.&lt;/p&gt;
&lt;pre id=&quot;code_1769576959729&quot; class=&quot;bash&quot; data-ke-language=&quot;bash&quot; data-ke-type=&quot;codeblock&quot;&gt;&lt;code&gt;// 이름만 필요한데...
GET /users/123

// 응답: 불필요한 데이터까지 전부
{
  &quot;id&quot;: 123,
  &quot;name&quot;: &quot;홍길동&quot;,           // ✅ 필요
  &quot;email&quot;: &quot;hong@ex.com&quot;,     // ❌ 불필요
  &quot;phone&quot;: &quot;010-1234-5678&quot;,   // ❌ 불필요
  &quot;address&quot;: { /* ... */ },   // ❌ 불필요
  &quot;preferences&quot;: { /* ... */ } // ❌ 불필요
}&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;대역폭 낭비&lt;/li&gt;
&lt;li&gt;파싱 시간 증가&lt;/li&gt;
&lt;/ul&gt;
&lt;hr contenteditable=&quot;false&quot; data-ke-style=&quot;style8&quot; data-ke-type=&quot;horizontalRule&quot; /&gt;
&lt;h3 style=&quot;color: #000000; text-align: start;&quot; data-ke-size=&quot;size23&quot;&gt;6.2. 언더페칭&lt;/h3&gt;
&lt;p style=&quot;color: #333333; text-align: start;&quot; data-ke-size=&quot;size16&quot;&gt;원하는 데이터를 얻기 위해 여러 번 요청해야 한다.&lt;/p&gt;
&lt;pre id=&quot;code_1769577005746&quot; class=&quot;bash&quot; data-ke-language=&quot;bash&quot; data-ke-type=&quot;codeblock&quot;&gt;&lt;code&gt;// 게시글 + 작성자 + 댓글 정보 필요

// 요청 1: 게시글
const post = await fetch('/posts/456').then(r =&amp;gt; r.json());

// 요청 2: 작성자
const author = await fetch(`/users/${post.author_id}`).then(r =&amp;gt; r.json());

// 요청 3: 댓글
const comments = await fetch('/posts/456/comments').then(r =&amp;gt; r.json());&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;3번의 네트워크 왕복(N+1 문제)&lt;/li&gt;
&lt;li&gt;총 응답 시간 증가&lt;/li&gt;
&lt;li&gt;모바일에서 배터리 소모&lt;/li&gt;
&lt;/ul&gt;
&lt;hr contenteditable=&quot;false&quot; data-ke-style=&quot;style8&quot; data-ke-type=&quot;horizontalRule&quot; /&gt;
&lt;h3 style=&quot;color: #000000; text-align: start;&quot; data-ke-size=&quot;size23&quot;&gt;6.3. 엔드포인트 폭발&lt;/h3&gt;
&lt;p style=&quot;color: #333333; text-align: start;&quot; data-ke-size=&quot;size16&quot;&gt;리소스 조합이 늘어날수록 엔드포인트가 기하급수적으로 증가한다.&amp;nbsp;&lt;/p&gt;
&lt;pre id=&quot;code_1769577079203&quot; class=&quot;bash&quot; data-ke-language=&quot;bash&quot; data-ke-type=&quot;codeblock&quot;&gt;&lt;code&gt;/users
/users/{id}
/users/{id}/posts
/users/{id}/posts/{postId}
/users/{id}/posts/{postId}/comments
/users/{id}/followers
/users/{id}/following
/users/{id}/likes
/posts
/posts/{id}
/posts/{id}/likes
/posts/{id}/shares
...&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;API 관리 부담&lt;/li&gt;
&lt;li&gt;문서화 어려움&lt;/li&gt;
&lt;li&gt;유지보수 복잡&lt;/li&gt;
&lt;/ul&gt;
&lt;hr contenteditable=&quot;false&quot; data-ke-style=&quot;style8&quot; data-ke-type=&quot;horizontalRule&quot; /&gt;
&lt;h3 style=&quot;color: #000000; text-align: start;&quot; data-ke-size=&quot;size23&quot;&gt;6.4. 표준 부재&lt;/h3&gt;
&lt;p style=&quot;color: #333333; text-align: start;&quot; data-ke-size=&quot;size16&quot;&gt;REST는 가이드일 뿐 엄격한 표준이 아니다.&amp;nbsp;&lt;/p&gt;
&lt;pre id=&quot;code_1769577262860&quot; class=&quot;bash&quot; data-ke-language=&quot;bash&quot; data-ke-type=&quot;codeblock&quot;&gt;&lt;code&gt;// 에러 응답이 API마다 제각각

// API A
{ &quot;error&quot;: &quot;Not found&quot; }

// API B
{ 
  &quot;message&quot;: &quot;User not found&quot;,
  &quot;code&quot;: 404 
}

// API C
{
  &quot;errors&quot;: [{
    &quot;status&quot;: &quot;404&quot;,
    &quot;title&quot;: &quot;Not Found&quot;,
    &quot;detail&quot;: &quot;User not found&quot;
  }]
}&lt;/code&gt;&lt;/pre&gt;
&lt;hr contenteditable=&quot;false&quot; data-ke-style=&quot;style8&quot; data-ke-type=&quot;horizontalRule&quot; /&gt;
&lt;h3 style=&quot;color: #000000; text-align: start;&quot; data-ke-size=&quot;size23&quot;&gt;6.5. 실시간 통신 부적합&lt;/h3&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;요청-응답 모델이라 실시간 기능에 비효율적이다.&lt;/p&gt;
&lt;pre id=&quot;code_1769577380512&quot; class=&quot;bash&quot; data-ke-language=&quot;bash&quot; data-ke-type=&quot;codeblock&quot;&gt;&lt;code&gt;// 채팅 기능을 REST로 구현하면...

// 폴링(Polling) 방식 - 비효율적
setInterval(() =&amp;gt; {
  fetch('/messages')  // 1초마다 서버에 요청
}, 1000);&lt;/code&gt;&lt;/pre&gt;
&lt;ol style=&quot;list-style-type: decimal;&quot; data-ke-list-type=&quot;decimal&quot;&gt;
&lt;li&gt;새 메시지 없어도 계속 요청&lt;/li&gt;
&lt;li&gt;서버 부하 증가&lt;/li&gt;
&lt;li&gt;배터리 소모&lt;/li&gt;
&lt;li&gt;실시간성 떨어짐 (1초 지연)&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;7.단점 개선 방안&lt;/h2&gt;
&lt;h3 data-ke-size=&quot;size23&quot;&gt;7.1. 오버페칭/언더페칭 해결&lt;/h3&gt;
&lt;ol style=&quot;list-style-type: decimal;&quot; data-ke-list-type=&quot;decimal&quot;&gt;
&lt;li&gt;필요한 필드만 요청할 수 있게 한다.&lt;/li&gt;
&lt;li&gt;한 번의 요청으로 관련 데이터까지 가져온다.&lt;/li&gt;
&lt;li&gt;아예 새로운 방식으로 전환한다.&amp;nbsp;&lt;/li&gt;
&lt;/ol&gt;
&lt;hr contenteditable=&quot;false&quot; data-ke-style=&quot;style8&quot; data-ke-type=&quot;horizontalRule&quot; /&gt;
&lt;h3 data-ke-size=&quot;size23&quot;&gt;7.2. 실시간 통신 해결&lt;/h3&gt;
&lt;ol style=&quot;list-style-type: decimal;&quot; data-ke-list-type=&quot;decimal&quot;&gt;
&lt;li&gt;REST와 WebSocket을 함께 사용한다.&lt;/li&gt;
&lt;li&gt;서버에서 클라이언트로 푸시한다.&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;8. 결론&lt;/h2&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;완벽한 REST를 추구하기 보다는, 상황에 맞는 실용적인 API를 만드는 것이 중요해보인다. 하지만, 대부분 실용적인 API는 REST의 성향을 띄고 있기 때문에 REST의 원칙을 이해하고, 필요에 따라 GraphQL, WebSocker 등 다른 기술과 조합해서 사용해야겠다.&amp;nbsp;&lt;/p&gt;</description>
      <category>Web</category>
      <author>Ukjong</author>
      <guid isPermaLink="true">https://ukj0ng.tistory.com/66</guid>
      <comments>https://ukj0ng.tistory.com/66#entry66comment</comments>
      <pubDate>Wed, 28 Jan 2026 14:56:52 +0900</pubDate>
    </item>
    <item>
      <title>[Infra] GitHub Actions와 Docker로 CI/CD 파이프라인 구축하기</title>
      <link>https://ukj0ng.tistory.com/65</link>
      <description>&lt;p data-ke-size=&quot;size16&quot;&gt;이전엔 내가 손으로 AWS에 코드를 배포했다. 하지만, 실제로 프로젝트를 진행하면 Infra 담당자가 전부 CI/CD 파이프라인을 구축해줬다. 내가 그 일을 언젠가 할 수도 있기에 코드를 `push`하면 자동으로 AWS에 반영되도록 CI/CD 파이프라인을 구축해보려고 한다.&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;1. 전체 아키텍처 및 데이터 흐름 (Architecture Overview)&lt;/h2&gt;
&lt;p&gt;&lt;figure class=&quot;imageblock alignCenter&quot; data-ke-mobileStyle=&quot;widthOrigin&quot; data-origin-width=&quot;619&quot; data-origin-height=&quot;684&quot;&gt;&lt;span data-url=&quot;https://blog.kakaocdn.net/dn/Mu1xC/dJMcadm1vtS/wQtKBbbV8J1HckoNrtAgk1/img.png&quot; data-phocus=&quot;https://blog.kakaocdn.net/dn/Mu1xC/dJMcadm1vtS/wQtKBbbV8J1HckoNrtAgk1/img.png&quot;&gt;&lt;img src=&quot;https://blog.kakaocdn.net/dn/Mu1xC/dJMcadm1vtS/wQtKBbbV8J1HckoNrtAgk1/img.png&quot; srcset=&quot;https://img1.daumcdn.net/thumb/R1280x0/?scode=mtistory2&amp;fname=https%3A%2F%2Fblog.kakaocdn.net%2Fdn%2FMu1xC%2FdJMcadm1vtS%2FwQtKBbbV8J1HckoNrtAgk1%2Fimg.png&quot; onerror=&quot;this.onerror=null; this.src='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png'; this.srcset='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png';&quot; loading=&quot;lazy&quot; width=&quot;619&quot; height=&quot;684&quot; data-origin-width=&quot;619&quot; data-origin-height=&quot;684&quot;/&gt;&lt;/span&gt;&lt;/figure&gt;
&lt;/p&gt;
&lt;h3 data-ke-size=&quot;size23&quot;&gt;1. git push&lt;/h3&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;개발자가 로컬 컴퓨터(Local)에서 코드를 작성하고 Github 원격 저장소로 `git push`를 수행한다. 이 `git push`가 Github Actions의 &lt;b&gt;Webhook&lt;/b&gt;을 통해 배포 파이프라인의 트리거 역할을 한다.&amp;nbsp;&lt;/p&gt;
&lt;h3 data-ke-size=&quot;size23&quot;&gt;2. build docker image&lt;/h3&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;Github Actions가 임시 가상 머신(Runner)을 할당받아 작업을 시작한다. Runner는 소스 코드를 다운로드하고, `Dockerfile`을 기반으로 애플리케이션을 실행 가능한 상태인 docker image로 빌드한다.&amp;nbsp;&lt;/p&gt;
&lt;blockquote data-ke-style=&quot;style3&quot;&gt; &lt;span style=&quot;color: #000000;&quot;&gt;Runner란?&lt;br /&gt;&lt;b&gt;Github가 빌려주는 일회성 가상 컴퓨터이다.&lt;/b&gt; &lt;br /&gt;우리가 `git push`를 하면 Github는 Microsoft Azure에서 &lt;b&gt;우분투(Linux)가 깔린 컴퓨터 하나를 할당&lt;/b&gt;해준다. 이게 바로 Runner이다.&amp;nbsp;&lt;br /&gt;Runner는 2가지 특징을 가지고 있다.&lt;br /&gt;&lt;br /&gt;1. 일회용&lt;br /&gt;Runner는 `deploy.yml` 작업이 끝나면 &lt;b&gt;즉시 삭제&lt;/b&gt;된다. 그래서 다음 배포 때는 또 다른 완전히 깨끗한 새 컴퓨터가 배정된다. 따라서, 항상 순수한 격리 환경에서 빌드가 수행됨을 보장한다 . (`deploy.yml`은 Runner가 실행해야할 명령어가 작성된 파일이다.)&lt;br /&gt;&lt;br /&gt;2. 미리 세팅된 환경&lt;br /&gt;이 컴퓨터에는 Docker, Java, Node.js, AWS CLI 같은 개발 필수 도구들이 미리 다 설치되어 있다. 그래서 우리가 따로 `install docker`를 하지 않아도 바로 빌드가 가능하다.&amp;nbsp;&lt;/span&gt;&lt;/blockquote&gt;
&lt;h3 data-ke-size=&quot;size23&quot;&gt;3. push image&lt;/h3&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;2단계에서 빌드한 이미지를 Docker Hub로 전송한다. 이미지를 EC2로 바로 보내지 않고 Docker Hub를 거치는 이유는 &lt;b&gt;버전 관리&lt;/b&gt;와 &lt;b&gt;안정성&lt;/b&gt; 때문이다. 언제든 문제가 생기면 이전 버전의 이미지를 다시 가져올 수 있다.&amp;nbsp;&lt;/p&gt;
&lt;h3 data-ke-size=&quot;size23&quot;&gt;4. SSH Connect &amp;amp; Command&lt;/h3&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;Github Actions Runner가 `SSH Key(.pem)`를 사용하여 AWS EC2 인스턴스에 원격 접속한다. 직접 파일을 전송하는 것이 아니라, &lt;b&gt;&quot;명령어(Script)&quot;&lt;/b&gt;만 전달한다.&amp;nbsp;&lt;/p&gt;
&lt;h3 data-ke-size=&quot;size23&quot;&gt;5. pull latest image&lt;/h3&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;명령을 받은 EC2는 Docker Hub에 접속하여 방금 업로드된 최신 버전(`latest`)의 이미지를 다운로드(Pull)한다. 필요한 레이어만 다운로드하므로 전송 속도가 빠르고 효율적이다.&lt;/p&gt;
&lt;h3 style=&quot;color: #000000; text-align: start;&quot; data-ke-size=&quot;size23&quot;&gt;6. stop old&lt;/h3&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;기존에 포트를 점유하고 있던 구버전 컨테이너를 중지하고 삭제한다. (포트 충돌 방지)&lt;/p&gt;
&lt;h3 style=&quot;color: #000000; text-align: start;&quot; data-ke-size=&quot;size23&quot;&gt;7. run new container&lt;/h3&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;다운로드한 새 이미지를 기반으로 컨테이너를 실행한다. 이때 `-p 80:80`옵션을 통해 외부 트래픽을 새 컨테이너로 연결한다.&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;2. Github Actions을 통한 CI/CD 파이프라인 만들기&lt;/h2&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;과정은 크게 필수 파일 작성, 서버(EC2) 환경 설정, 보안 키 등록, 배포 순으로 진행된다.&amp;nbsp;&lt;/p&gt;
&lt;h3 data-ke-size=&quot;size23&quot;&gt;1. 필수 파일 작성 (`Dockerfile` &amp;amp; `deploy.yml`)&lt;/h3&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;Github Actions가 작동하려면 정해진 규칙에 따라 파일 위치와 내용을 작성해야 한다.&lt;/p&gt;
&lt;p&gt;&lt;figure class=&quot;imageblock alignCenter&quot; data-ke-mobileStyle=&quot;widthOrigin&quot; data-origin-width=&quot;198&quot; data-origin-height=&quot;117&quot;&gt;&lt;span data-url=&quot;https://blog.kakaocdn.net/dn/F6dcu/dJMcaiBSk2Q/G7tgLc6nha1pYKnI0GXiFk/img.png&quot; data-phocus=&quot;https://blog.kakaocdn.net/dn/F6dcu/dJMcaiBSk2Q/G7tgLc6nha1pYKnI0GXiFk/img.png&quot;&gt;&lt;img src=&quot;https://blog.kakaocdn.net/dn/F6dcu/dJMcaiBSk2Q/G7tgLc6nha1pYKnI0GXiFk/img.png&quot; srcset=&quot;https://img1.daumcdn.net/thumb/R1280x0/?scode=mtistory2&amp;fname=https%3A%2F%2Fblog.kakaocdn.net%2Fdn%2FF6dcu%2FdJMcaiBSk2Q%2FG7tgLc6nha1pYKnI0GXiFk%2Fimg.png&quot; onerror=&quot;this.onerror=null; this.src='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png'; this.srcset='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png';&quot; loading=&quot;lazy&quot; width=&quot;198&quot; height=&quot;117&quot; data-origin-width=&quot;198&quot; data-origin-height=&quot;117&quot;/&gt;&lt;/span&gt;&lt;/figure&gt;
&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;`.github/workflows` 경로는 Github이 정해놓은 규칙으로 Github Actions는 오직 이 폴더 안에 있는 파일만 읽는다. `deploy.yml`은 Runner에게 시킬 작업 내용을 적은 지시서이다. 크게 CI(빌드 및 푸시)와 CD(EC2 접속 및 배포) 두 단계로 나뉜다.&amp;nbsp;&lt;/p&gt;
&lt;pre id=&quot;code_1766510189665&quot; class=&quot;bash&quot; data-ke-language=&quot;bash&quot; data-ke-type=&quot;codeblock&quot;&gt;&lt;code&gt;name: CI/CD Pipeline

on:
  push:
    branches: [ &quot;main&quot; ] # main 브랜치에 push가 발생하면 실행

jobs:
  # [CI 단계] 이미지를 빌드하고 Docker Hub에 올리는 작업
  build-and-push:
    runs-on: ubuntu-latest
    steps:
      - name: Checkout code
        uses: actions/checkout@v3

      - name: Log in to Docker Hub
        uses: docker/login-action@v2
        with:
          username: ${{ secrets.DOCKER_USERNAME }}
          password: ${{ secrets.DOCKER_PASSWORD }}

      - name: Build and push Docker image
        run: |
          docker build -t ${{ secrets.DOCKER_USERNAME }}/platform-study:latest .
          docker push ${{ secrets.DOCKER_USERNAME }}/platform-study:latest

  # [CD 단계] AWS EC2에 접속해서 새 버전을 배포하는 작업
  deploy:
    needs: build-and-push # 위의 빌드 작업이 성공해야만 실행됨
    runs-on: ubuntu-latest
    steps:
      - name: Deploy to EC2
        uses: appleboy/ssh-action@v1.0.0
        with:
          host: ${{ secrets.EC2_HOST }}
          username: ${{ secrets.EC2_USERNAME }}
          key: ${{ secrets.EC2_PRIVATE_KEY }}
          script: |
            # 1. 도커 허브에서 최신 이미지 가져오기
            sudo docker pull ${{ secrets.DOCKER_USERNAME }}/platform-study:latest
            
            # 2. 기존 컨테이너 중지 및 삭제 (에러 무시)
            sudo docker stop my-server || true
            sudo docker rm my-server || true
            
            # 3. 새 컨테이너 실행 (80번 포트 연결)
            sudo docker run -d --name my-server -p 80:80 ${{ secrets.DOCKER_USERNAME }}/platform-study:latest
            
            # 4. 미사용 이미지 정리
            sudo docker image prune -f&lt;/code&gt;&lt;/pre&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;`Dockerfile`과 `index.html`은 &lt;a href=&quot;https://ukj0ng.tistory.com/61&quot; target=&quot;_blank&quot; rel=&quot;noopener&amp;nbsp;noreferrer&quot;&gt;https://ukj0ng.tistory.com/61&lt;/a&gt;에서 사용한 것을 그대로 사용하겠다.&amp;nbsp;&lt;/p&gt;
&lt;figure id=&quot;og_1766510118497&quot; contenteditable=&quot;false&quot; data-ke-type=&quot;opengraph&quot; data-ke-align=&quot;alignCenter&quot; data-og-type=&quot;article&quot; data-og-title=&quot;[Infra] Docker란? (Docker에서 AWS EC2까지-1)&quot; data-og-description=&quot;시작하기 전에그동안 프로젝트를 진행하며 docker-compose.yml은 AI를 사용해서 작성하거나 인프라 팀에게 받아쓰기만 했다. 인프라쪽을 잘 몰랐기 때문에, 팀을 만들 때도 이런 부분을 채워줄 수 있&quot; data-og-host=&quot;ukj0ng.tistory.com&quot; data-og-source-url=&quot;https://ukj0ng.tistory.com/61&quot; data-og-url=&quot;https://ukj0ng.tistory.com/61&quot; data-og-image=&quot;https://scrap.kakaocdn.net/dn/yRJki/hyZPMmRMWa/gloWYYAguGAPgcCKiMX090/img.png?width=461&amp;amp;height=109&amp;amp;face=0_0_461_109,https://scrap.kakaocdn.net/dn/bsgMl6/hyZPWDGidQ/vX1DFc37iS5sklQuglbFik/img.png?width=461&amp;amp;height=109&amp;amp;face=0_0_461_109,https://scrap.kakaocdn.net/dn/5zh4j/hyZP0F6sme/4KpIrTD9FjagVT9GIqizk0/img.png?width=657&amp;amp;height=252&amp;amp;face=0_0_657_252&quot;&gt;&lt;a href=&quot;https://ukj0ng.tistory.com/61&quot; target=&quot;_blank&quot; rel=&quot;noopener&quot; data-source-url=&quot;https://ukj0ng.tistory.com/61&quot;&gt;
&lt;div class=&quot;og-image&quot; style=&quot;background-image: url('https://scrap.kakaocdn.net/dn/yRJki/hyZPMmRMWa/gloWYYAguGAPgcCKiMX090/img.png?width=461&amp;amp;height=109&amp;amp;face=0_0_461_109,https://scrap.kakaocdn.net/dn/bsgMl6/hyZPWDGidQ/vX1DFc37iS5sklQuglbFik/img.png?width=461&amp;amp;height=109&amp;amp;face=0_0_461_109,https://scrap.kakaocdn.net/dn/5zh4j/hyZP0F6sme/4KpIrTD9FjagVT9GIqizk0/img.png?width=657&amp;amp;height=252&amp;amp;face=0_0_657_252');&quot;&gt;&amp;nbsp;&lt;/div&gt;
&lt;div class=&quot;og-text&quot;&gt;
&lt;p class=&quot;og-title&quot; data-ke-size=&quot;size16&quot;&gt;[Infra] Docker란? (Docker에서 AWS EC2까지-1)&lt;/p&gt;
&lt;p class=&quot;og-desc&quot; data-ke-size=&quot;size16&quot;&gt;시작하기 전에그동안 프로젝트를 진행하며 docker-compose.yml은 AI를 사용해서 작성하거나 인프라 팀에게 받아쓰기만 했다. 인프라쪽을 잘 몰랐기 때문에, 팀을 만들 때도 이런 부분을 채워줄 수 있&lt;/p&gt;
&lt;p class=&quot;og-host&quot; data-ke-size=&quot;size16&quot;&gt;ukj0ng.tistory.com&lt;/p&gt;
&lt;/div&gt;
&lt;/a&gt;&lt;/figure&gt;
&lt;h3 style=&quot;color: #000000; text-align: start;&quot; data-ke-size=&quot;size23&quot;&gt;2. AWS EC2 서버 준비&lt;/h3&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;서버 준비 역시 &lt;a href=&quot;https://ukj0ng.tistory.com/62&quot; target=&quot;_blank&quot; rel=&quot;noopener&amp;nbsp;noreferrer&quot;&gt;https://ukj0ng.tistory.com/62&lt;/a&gt;을 따라서 진행하면 된다.&amp;nbsp;&lt;/p&gt;
&lt;figure id=&quot;og_1766510283557&quot; contenteditable=&quot;false&quot; data-ke-type=&quot;opengraph&quot; data-ke-align=&quot;alignCenter&quot; data-og-type=&quot;article&quot; data-og-title=&quot;[Infra] AWS 클라우드 배포 (Docker에서 AWS EC2까지-2)&quot; data-og-description=&quot;이전 글에서 만든 &amp;#96;docker-compose.yml&amp;#96;을 AWS 클라우드 환경에서 배포를 진행해보려 한다. 1. EC2 인스턴스 생성EC2를 검색해서 인스턴스 시작 버튼을 눌러보자.이름은 원하는 걸 적고, OS 이미지는 우분&quot; data-og-host=&quot;ukj0ng.tistory.com&quot; data-og-source-url=&quot;https://ukj0ng.tistory.com/62&quot; data-og-url=&quot;https://ukj0ng.tistory.com/62&quot; data-og-image=&quot;https://scrap.kakaocdn.net/dn/b9IiYn/hyZP2RsfXn/iBXcO1B3HEfDAqgHpwCwd1/img.png?width=800&amp;amp;height=479&amp;amp;face=0_0_800_479,https://scrap.kakaocdn.net/dn/bzyPFr/hyZPYaq0jW/P0AzsK4GLSMNjRxY96epk0/img.png?width=800&amp;amp;height=479&amp;amp;face=0_0_800_479,https://scrap.kakaocdn.net/dn/uFld6/hyZPO53nWC/pTXnyHI5OLCjm0bpYCRkZk/img.png?width=796&amp;amp;height=693&amp;amp;face=0_0_796_693&quot;&gt;&lt;a href=&quot;https://ukj0ng.tistory.com/62&quot; target=&quot;_blank&quot; rel=&quot;noopener&quot; data-source-url=&quot;https://ukj0ng.tistory.com/62&quot;&gt;
&lt;div class=&quot;og-image&quot; style=&quot;background-image: url('https://scrap.kakaocdn.net/dn/b9IiYn/hyZP2RsfXn/iBXcO1B3HEfDAqgHpwCwd1/img.png?width=800&amp;amp;height=479&amp;amp;face=0_0_800_479,https://scrap.kakaocdn.net/dn/bzyPFr/hyZPYaq0jW/P0AzsK4GLSMNjRxY96epk0/img.png?width=800&amp;amp;height=479&amp;amp;face=0_0_800_479,https://scrap.kakaocdn.net/dn/uFld6/hyZPO53nWC/pTXnyHI5OLCjm0bpYCRkZk/img.png?width=796&amp;amp;height=693&amp;amp;face=0_0_796_693');&quot;&gt;&amp;nbsp;&lt;/div&gt;
&lt;div class=&quot;og-text&quot;&gt;
&lt;p class=&quot;og-title&quot; data-ke-size=&quot;size16&quot;&gt;[Infra] AWS 클라우드 배포 (Docker에서 AWS EC2까지-2)&lt;/p&gt;
&lt;p class=&quot;og-desc&quot; data-ke-size=&quot;size16&quot;&gt;이전 글에서 만든 `docker-compose.yml`을 AWS 클라우드 환경에서 배포를 진행해보려 한다. 1. EC2 인스턴스 생성EC2를 검색해서 인스턴스 시작 버튼을 눌러보자.이름은 원하는 걸 적고, OS 이미지는 우분&lt;/p&gt;
&lt;p class=&quot;og-host&quot; data-ke-size=&quot;size16&quot;&gt;ukj0ng.tistory.com&lt;/p&gt;
&lt;/div&gt;
&lt;/a&gt;&lt;/figure&gt;
&lt;h3 data-ke-size=&quot;size23&quot;&gt;3. Github Secrets 등록&lt;/h3&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;`deploy.yml` 파일에 비밀번호나 비밀키를 그대로 적으면 해킹의 위험이 있다. 따라서 Github 저장소의 Secrets 기능을 이용해 변수로 처리한다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;해당 repository의 'Settings'에 들어간다.&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;887&quot; data-origin-height=&quot;151&quot;&gt;&lt;span data-url=&quot;https://blog.kakaocdn.net/dn/qQCGH/dJMcafLXUVw/0XYWKkCrwLO5BmFn22xGaK/img.png&quot; data-phocus=&quot;https://blog.kakaocdn.net/dn/qQCGH/dJMcafLXUVw/0XYWKkCrwLO5BmFn22xGaK/img.png&quot;&gt;&lt;img src=&quot;https://blog.kakaocdn.net/dn/qQCGH/dJMcafLXUVw/0XYWKkCrwLO5BmFn22xGaK/img.png&quot; srcset=&quot;https://img1.daumcdn.net/thumb/R1280x0/?scode=mtistory2&amp;fname=https%3A%2F%2Fblog.kakaocdn.net%2Fdn%2FqQCGH%2FdJMcafLXUVw%2F0XYWKkCrwLO5BmFn22xGaK%2Fimg.png&quot; onerror=&quot;this.onerror=null; this.src='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png'; this.srcset='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png';&quot; loading=&quot;lazy&quot; width=&quot;748&quot; height=&quot;127&quot; data-origin-width=&quot;887&quot; data-origin-height=&quot;151&quot;/&gt;&lt;/span&gt;&lt;/figure&gt;
&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;왼쪽 메뉴에서 'Secrets and variable'의 'Actions'를 누르자.&lt;/p&gt;
&lt;p&gt;&lt;figure class=&quot;imageblock alignCenter&quot; data-ke-mobileStyle=&quot;widthOrigin&quot; data-origin-width=&quot;321&quot; data-origin-height=&quot;773&quot;&gt;&lt;span data-url=&quot;https://blog.kakaocdn.net/dn/nsUEE/dJMcacaDqH2/D8tppgkwxw8ZbO1oTvdo00/img.png&quot; data-phocus=&quot;https://blog.kakaocdn.net/dn/nsUEE/dJMcacaDqH2/D8tppgkwxw8ZbO1oTvdo00/img.png&quot;&gt;&lt;img src=&quot;https://blog.kakaocdn.net/dn/nsUEE/dJMcacaDqH2/D8tppgkwxw8ZbO1oTvdo00/img.png&quot; srcset=&quot;https://img1.daumcdn.net/thumb/R1280x0/?scode=mtistory2&amp;fname=https%3A%2F%2Fblog.kakaocdn.net%2Fdn%2FnsUEE%2FdJMcacaDqH2%2FD8tppgkwxw8ZbO1oTvdo00%2Fimg.png&quot; onerror=&quot;this.onerror=null; this.src='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png'; this.srcset='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png';&quot; loading=&quot;lazy&quot; width=&quot;321&quot; height=&quot;773&quot; data-origin-width=&quot;321&quot; data-origin-height=&quot;773&quot;/&gt;&lt;/span&gt;&lt;/figure&gt;
&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;변수를 등록하려면 'New repository secret'에서 등록해야 한다.&lt;/p&gt;
&lt;p&gt;&lt;figure class=&quot;imageblock alignCenter&quot; data-ke-mobileStyle=&quot;widthOrigin&quot; data-origin-width=&quot;805&quot; data-origin-height=&quot;498&quot;&gt;&lt;span data-url=&quot;https://blog.kakaocdn.net/dn/cENbd2/dJMcagqzkR8/a8jdwaufQ9HsMdiravzD61/img.png&quot; data-phocus=&quot;https://blog.kakaocdn.net/dn/cENbd2/dJMcagqzkR8/a8jdwaufQ9HsMdiravzD61/img.png&quot;&gt;&lt;img src=&quot;https://blog.kakaocdn.net/dn/cENbd2/dJMcagqzkR8/a8jdwaufQ9HsMdiravzD61/img.png&quot; srcset=&quot;https://img1.daumcdn.net/thumb/R1280x0/?scode=mtistory2&amp;fname=https%3A%2F%2Fblog.kakaocdn.net%2Fdn%2FcENbd2%2FdJMcagqzkR8%2Fa8jdwaufQ9HsMdiravzD61%2Fimg.png&quot; onerror=&quot;this.onerror=null; this.src='//t1.daumcdn.net/tistory_admin/static/images/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;444&quot; data-origin-width=&quot;805&quot; data-origin-height=&quot;498&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;795&quot; data-origin-height=&quot;428&quot;&gt;&lt;span data-url=&quot;https://blog.kakaocdn.net/dn/bHfllP/dJMcaiaQ3Dd/p1t8mXKr0GLOaHWPBm4bW0/img.png&quot; data-phocus=&quot;https://blog.kakaocdn.net/dn/bHfllP/dJMcaiaQ3Dd/p1t8mXKr0GLOaHWPBm4bW0/img.png&quot;&gt;&lt;img src=&quot;https://blog.kakaocdn.net/dn/bHfllP/dJMcaiaQ3Dd/p1t8mXKr0GLOaHWPBm4bW0/img.png&quot; srcset=&quot;https://img1.daumcdn.net/thumb/R1280x0/?scode=mtistory2&amp;fname=https%3A%2F%2Fblog.kakaocdn.net%2Fdn%2FbHfllP%2FdJMcaiaQ3Dd%2Fp1t8mXKr0GLOaHWPBm4bW0%2Fimg.png&quot; onerror=&quot;this.onerror=null; this.src='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png'; this.srcset='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png';&quot; loading=&quot;lazy&quot; width=&quot;693&quot; height=&quot;373&quot; data-origin-width=&quot;795&quot; data-origin-height=&quot;428&quot;/&gt;&lt;/span&gt;&lt;/figure&gt;
&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;ul style=&quot;list-style-type: disc;&quot; data-ke-list-type=&quot;disc&quot;&gt;
&lt;li&gt;&lt;span style=&quot;color: #000000;&quot;&gt;`DOCKER_USERNAME`: 도커 허브 아이디&lt;/span&gt;&lt;/li&gt;
&lt;li&gt;&lt;span style=&quot;color: #000000;&quot;&gt;`DOCKER_PASSWORD`: 도커 허브 비밀번호 (또는 토큰)&lt;/span&gt;&lt;/li&gt;
&lt;li&gt;&lt;span style=&quot;color: #000000;&quot;&gt;`EC2_HOST`: EC2 인스턴스의 퍼블릭 IP 주소&lt;/span&gt;&lt;/li&gt;
&lt;li&gt;&lt;span style=&quot;color: #000000;&quot;&gt;`EC2_USERNAME`: ubuntu&lt;/span&gt;&lt;/li&gt;
&lt;li&gt;&lt;span style=&quot;color: #000000;&quot;&gt;`EC2_PRIVATE_KEY`: .pem 키 파일의 내용 전체 (`-----BEGIN...` 부터 `...END-----` 까지)&lt;/span&gt;&lt;/li&gt;
&lt;/ul&gt;
&lt;h3 data-ke-size=&quot;size23&quot;&gt;&lt;span style=&quot;color: #000000;&quot;&gt;4. 최종 테스트&lt;/span&gt;&lt;/h3&gt;
&lt;p&gt;&lt;figure class=&quot;imageblock alignCenter&quot; data-ke-mobileStyle=&quot;widthOrigin&quot; data-origin-width=&quot;488&quot; data-origin-height=&quot;126&quot;&gt;&lt;span data-url=&quot;https://blog.kakaocdn.net/dn/k0gTd/dJMcajt0N1Z/5UZvBVJ0kdKAWItyKfvNx1/img.png&quot; data-phocus=&quot;https://blog.kakaocdn.net/dn/k0gTd/dJMcajt0N1Z/5UZvBVJ0kdKAWItyKfvNx1/img.png&quot;&gt;&lt;img src=&quot;https://blog.kakaocdn.net/dn/k0gTd/dJMcajt0N1Z/5UZvBVJ0kdKAWItyKfvNx1/img.png&quot; srcset=&quot;https://img1.daumcdn.net/thumb/R1280x0/?scode=mtistory2&amp;fname=https%3A%2F%2Fblog.kakaocdn.net%2Fdn%2Fk0gTd%2FdJMcajt0N1Z%2F5UZvBVJ0kdKAWItyKfvNx1%2Fimg.png&quot; onerror=&quot;this.onerror=null; this.src='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png'; this.srcset='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png';&quot; loading=&quot;lazy&quot; width=&quot;488&quot; height=&quot;126&quot; data-origin-width=&quot;488&quot; data-origin-height=&quot;126&quot;/&gt;&lt;/span&gt;&lt;/figure&gt;
&lt;figure class=&quot;imageblock alignCenter&quot; data-ke-mobileStyle=&quot;widthOrigin&quot; data-origin-width=&quot;396&quot; data-origin-height=&quot;212&quot;&gt;&lt;span data-url=&quot;https://blog.kakaocdn.net/dn/lZ5TM/dJMb99ZhyT9/9517bRkJYv1rV4kBTaVSvk/img.png&quot; data-phocus=&quot;https://blog.kakaocdn.net/dn/lZ5TM/dJMb99ZhyT9/9517bRkJYv1rV4kBTaVSvk/img.png&quot;&gt;&lt;img src=&quot;https://blog.kakaocdn.net/dn/lZ5TM/dJMb99ZhyT9/9517bRkJYv1rV4kBTaVSvk/img.png&quot; srcset=&quot;https://img1.daumcdn.net/thumb/R1280x0/?scode=mtistory2&amp;fname=https%3A%2F%2Fblog.kakaocdn.net%2Fdn%2FlZ5TM%2FdJMb99ZhyT9%2F9517bRkJYv1rV4kBTaVSvk%2Fimg.png&quot; onerror=&quot;this.onerror=null; this.src='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png'; this.srcset='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png';&quot; loading=&quot;lazy&quot; width=&quot;396&quot; height=&quot;212&quot; data-origin-width=&quot;396&quot; data-origin-height=&quot;212&quot;/&gt;&lt;/span&gt;&lt;/figure&gt;
&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;기존 html은 다음과 같다. html의 내용을 변경하고 `commit`, `push`를 진행하니&lt;/p&gt;
&lt;p&gt;&lt;figure class=&quot;imageblock alignCenter&quot; data-ke-mobileStyle=&quot;widthOrigin&quot; data-origin-width=&quot;1003&quot; data-origin-height=&quot;456&quot;&gt;&lt;span data-url=&quot;https://blog.kakaocdn.net/dn/wfZ0y/dJMcaiIEiWy/Z3nF31Q6pbkKrGlXqmxdzk/img.png&quot; data-phocus=&quot;https://blog.kakaocdn.net/dn/wfZ0y/dJMcaiIEiWy/Z3nF31Q6pbkKrGlXqmxdzk/img.png&quot;&gt;&lt;img src=&quot;https://blog.kakaocdn.net/dn/wfZ0y/dJMcaiIEiWy/Z3nF31Q6pbkKrGlXqmxdzk/img.png&quot; srcset=&quot;https://img1.daumcdn.net/thumb/R1280x0/?scode=mtistory2&amp;fname=https%3A%2F%2Fblog.kakaocdn.net%2Fdn%2FwfZ0y%2FdJMcaiIEiWy%2FZ3nF31Q6pbkKrGlXqmxdzk%2Fimg.png&quot; onerror=&quot;this.onerror=null; this.src='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png'; this.srcset='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png';&quot; loading=&quot;lazy&quot; width=&quot;722&quot; height=&quot;328&quot; data-origin-width=&quot;1003&quot; data-origin-height=&quot;456&quot;/&gt;&lt;/span&gt;&lt;/figure&gt;
&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;Actions에서 `build-and-push`와 `deploy`가 잘 된것을 확인할 수 있고, 실시간으로 변경된 것도 확인했다.&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;494&quot; data-origin-height=&quot;137&quot;&gt;&lt;span data-url=&quot;https://blog.kakaocdn.net/dn/bc2rh3/dJMcaihCW1r/DiaAOvqSj0jyqLFN7trJq0/img.png&quot; data-phocus=&quot;https://blog.kakaocdn.net/dn/bc2rh3/dJMcaihCW1r/DiaAOvqSj0jyqLFN7trJq0/img.png&quot;&gt;&lt;img src=&quot;https://blog.kakaocdn.net/dn/bc2rh3/dJMcaihCW1r/DiaAOvqSj0jyqLFN7trJq0/img.png&quot; srcset=&quot;https://img1.daumcdn.net/thumb/R1280x0/?scode=mtistory2&amp;fname=https%3A%2F%2Fblog.kakaocdn.net%2Fdn%2Fbc2rh3%2FdJMcaihCW1r%2FDiaAOvqSj0jyqLFN7trJq0%2Fimg.png&quot; onerror=&quot;this.onerror=null; this.src='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png'; this.srcset='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png';&quot; loading=&quot;lazy&quot; width=&quot;494&quot; height=&quot;137&quot; data-origin-width=&quot;494&quot; data-origin-height=&quot;137&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;390&quot; data-origin-height=&quot;214&quot;&gt;&lt;span data-url=&quot;https://blog.kakaocdn.net/dn/brLHBd/dJMcagRDE0B/EZzbKMopJ9PInTkjAadURk/img.png&quot; data-phocus=&quot;https://blog.kakaocdn.net/dn/brLHBd/dJMcagRDE0B/EZzbKMopJ9PInTkjAadURk/img.png&quot;&gt;&lt;img src=&quot;https://blog.kakaocdn.net/dn/brLHBd/dJMcagRDE0B/EZzbKMopJ9PInTkjAadURk/img.png&quot; srcset=&quot;https://img1.daumcdn.net/thumb/R1280x0/?scode=mtistory2&amp;fname=https%3A%2F%2Fblog.kakaocdn.net%2Fdn%2FbrLHBd%2FdJMcagRDE0B%2FEZzbKMopJ9PInTkjAadURk%2Fimg.png&quot; onerror=&quot;this.onerror=null; this.src='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png'; this.srcset='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png';&quot; loading=&quot;lazy&quot; width=&quot;390&quot; height=&quot;214&quot; data-origin-width=&quot;390&quot; data-origin-height=&quot;214&quot;/&gt;&lt;/span&gt;&lt;/figure&gt;
&lt;/p&gt;
&lt;h4 data-ke-size=&quot;size20&quot;&gt;`build-and-push`의 과정&lt;/h4&gt;
&lt;p&gt;&lt;figure class=&quot;imageblock alignCenter&quot; data-ke-mobileStyle=&quot;widthOrigin&quot; data-origin-width=&quot;340&quot; data-origin-height=&quot;449&quot;&gt;&lt;span data-url=&quot;https://blog.kakaocdn.net/dn/cPEuj8/dJMcahbWsCI/iN3MQ3I0kZvjaEbxVkwXZ1/img.png&quot; data-phocus=&quot;https://blog.kakaocdn.net/dn/cPEuj8/dJMcahbWsCI/iN3MQ3I0kZvjaEbxVkwXZ1/img.png&quot;&gt;&lt;img src=&quot;https://blog.kakaocdn.net/dn/cPEuj8/dJMcahbWsCI/iN3MQ3I0kZvjaEbxVkwXZ1/img.png&quot; srcset=&quot;https://img1.daumcdn.net/thumb/R1280x0/?scode=mtistory2&amp;fname=https%3A%2F%2Fblog.kakaocdn.net%2Fdn%2FcPEuj8%2FdJMcahbWsCI%2FiN3MQ3I0kZvjaEbxVkwXZ1%2Fimg.png&quot; onerror=&quot;this.onerror=null; this.src='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png'; this.srcset='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png';&quot; loading=&quot;lazy&quot; width=&quot;340&quot; height=&quot;449&quot; data-origin-width=&quot;340&quot; data-origin-height=&quot;449&quot;/&gt;&lt;/span&gt;&lt;/figure&gt;
&lt;/p&gt;
&lt;ul style=&quot;list-style-type: disc;&quot; data-ke-list-type=&quot;disc&quot;&gt;
&lt;li&gt;Set up job: Runner가 준비됨&lt;/li&gt;
&lt;li&gt;Checkout code: 깃허브에서 코드를 다운로드함&lt;/li&gt;
&lt;li&gt;Login to Docker Hub: `Secrets`에 등록한 아이디/비밀번호로 로그인 성공&lt;/li&gt;
&lt;li&gt;Build and push: 도커 이미지를 만들고 Hub로 전송 완료&lt;/li&gt;
&lt;li&gt;Post Cleanup: 워크플로우 실행 중 사용했던 Docker Hub 로그인 정보나 Checkout 토큰 등 민감한 데이터를 러너에서 안전하게 삭제하고, 작업을 종료하는 뒷정리 단계&lt;/li&gt;
&lt;/ul&gt;
&lt;h4 data-ke-size=&quot;size20&quot;&gt;`deploy`의 과정&lt;/h4&gt;
&lt;p&gt;&lt;figure class=&quot;imageblock alignCenter&quot; data-ke-mobileStyle=&quot;widthOrigin&quot; data-origin-width=&quot;323&quot; data-origin-height=&quot;266&quot;&gt;&lt;span data-url=&quot;https://blog.kakaocdn.net/dn/7eSgc/dJMcagYo34y/O9XBQvlKKBcGeU2x4kuLA0/img.png&quot; data-phocus=&quot;https://blog.kakaocdn.net/dn/7eSgc/dJMcagYo34y/O9XBQvlKKBcGeU2x4kuLA0/img.png&quot;&gt;&lt;img src=&quot;https://blog.kakaocdn.net/dn/7eSgc/dJMcagYo34y/O9XBQvlKKBcGeU2x4kuLA0/img.png&quot; srcset=&quot;https://img1.daumcdn.net/thumb/R1280x0/?scode=mtistory2&amp;fname=https%3A%2F%2Fblog.kakaocdn.net%2Fdn%2F7eSgc%2FdJMcagYo34y%2FO9XBQvlKKBcGeU2x4kuLA0%2Fimg.png&quot; onerror=&quot;this.onerror=null; this.src='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png'; this.srcset='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png';&quot; loading=&quot;lazy&quot; width=&quot;323&quot; height=&quot;266&quot; data-origin-width=&quot;323&quot; data-origin-height=&quot;266&quot;/&gt;&lt;/span&gt;&lt;/figure&gt;
&lt;/p&gt;
&lt;ul style=&quot;list-style-type: disc;&quot; data-ke-list-type=&quot;disc&quot;&gt;
&lt;li&gt;Set up job: Runner가 준비됨&lt;/li&gt;
&lt;li&gt;Build appleboy/ssh-action@v1.0.0: EC2 접속에 필요한 외부 액션(Docker Container)을 다운로드하고 실행 준비를 마&lt;/li&gt;
&lt;li&gt;Deploy to EC2: Runner가 AWS EC2 서버에 SSH로 접속, 작성해둔 스크립트를 순서대로 실행&lt;/li&gt;
&lt;/ul&gt;
&lt;hr contenteditable=&quot;false&quot; data-ke-type=&quot;horizontalRule&quot; data-ke-style=&quot;style6&quot; /&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;이로써, Github Actions을 통해 CI/CD 파이프라인을 구축해보았다. 매번 수동으로 하던 배포 과정을 자동화하면서, 코드가 서버에 반영되는 전체적인 데이터 흐름을 명확하게 이해할 수 있었다.&lt;/p&gt;
&lt;p data-path-to-node=&quot;7&quot; data-ke-size=&quot;size16&quot;&gt;물론 아쉬운 점도 있다. 이번 실습에서는 편의상 latest 이미지만을 배포하도록 설정했지만, 실제 운영 환경이라면 &lt;b data-index-in-node=&quot;77&quot; data-path-to-node=&quot;7&quot;&gt;특정 버전의 이미지를 배포하거나 문제 발생 시 이전 버전으로 롤백(Rollback)하는 전략&lt;/b&gt;이 반드시 추가되어야 할 것이다.&lt;/p&gt;
&lt;p data-path-to-node=&quot;7&quot; data-ke-size=&quot;size16&quot;&gt;이제 배포의 자동화는 해결했다. 하지만 배포가 끝났다고 운영이 끝난 것은 아니다. 문득 &lt;b&gt;'지금 내 서버가 건강한 상태인가?'&lt;/b&gt;라는 의문이 들었다. 현재로서는 서버가 느려지거나 죽어도 내가 직접 들어가 보지 않는 이상 알 방법이 없다. 따라서 다음 단계로는 서버의 리소스 상태를 실시간으로 &lt;b data-index-in-node=&quot;164&quot; data-path-to-node=&quot;8&quot;&gt;모니터링&lt;/b&gt;하고, 임계치를 넘으면 알림을 보내주는 시스템을 구축해볼 예정이다. (근데 알림을 어디로 보내지?...)&lt;/p&gt;</description>
      <category>Infra</category>
      <author>Ukjong</author>
      <guid isPermaLink="true">https://ukj0ng.tistory.com/65</guid>
      <comments>https://ukj0ng.tistory.com/65#entry65comment</comments>
      <pubDate>Wed, 24 Dec 2025 11:26:16 +0900</pubDate>
    </item>
    <item>
      <title>[알고리즘] PCCP 정기 시험 후기(Java Lv.5)</title>
      <link>https://ukj0ng.tistory.com/64</link>
      <description>&lt;p data-ke-size=&quot;size16&quot;&gt;프로그래머스에서 진행하는 PCCP 정기 시험을 친구들과 같이 봤다. 싸피 과정이 끝나고 우리의 실력이 객관적으로 어느 정도인지 궁금했기 때문이다. 최근엔 기업 코테도 뜸했던 차에 PCCP 정기 시험으로 우리 수준을 점검해보기로 했다.&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;PCCP(Programmers Certified Coding Professional)란?&lt;/h2&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;그렙에서 만든 코딩전문역량인증 자격증이다. 그렙은 프로그래머스, 모니토를 운영중인 회사이다.&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;과거엔 채용사항에 우대가 좀 있던 것 같지만 최근엔 많이 사라졌다고 알고 있다.&amp;nbsp;&lt;/p&gt;
&lt;h3 data-ke-size=&quot;size23&quot;&gt;시험 안내&lt;/h3&gt;
&lt;ul style=&quot;list-style-type: disc;&quot; data-ke-list-type=&quot;disc&quot;&gt;
&lt;li&gt;시험시간: 120분&lt;/li&gt;
&lt;li&gt;문항수: 4문항&lt;/li&gt;
&lt;li&gt;시험과목: Python, JavaScript, Java, C, C++, C# (나는 Java를 선택했다.)&lt;/li&gt;
&lt;li&gt;방식: 비대면 (모니토 앱 사용)&lt;/li&gt;
&lt;/ul&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;기존 기업 코테에서 익숙하게 쓰던 모니토 환경이라 큰 불편함 없이 응시할 수 있었다.&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;h3 data-ke-size=&quot;size23&quot;&gt;출제범위&lt;/h3&gt;
&lt;p style=&quot;background-color: #ffffff; color: #212529; text-align: start;&quot; data-ke-size=&quot;size16&quot;&gt;출제 범위는 다음과 같다:&lt;/p&gt;
&lt;ul style=&quot;list-style-type: disc;&quot; data-ke-list-type=&quot;disc&quot;&gt;
&lt;li&gt;기본 프로그램 구현
&lt;ul style=&quot;list-style-type: disc;&quot; data-ke-list-type=&quot;disc&quot;&gt;
&lt;li&gt;&lt;span style=&quot;color: #000000; text-align: start;&quot;&gt;제시된 조건과 요구 사항을 만족하는 프로그램을 개발&lt;/span&gt;&lt;/li&gt;
&lt;/ul&gt;
&lt;/li&gt;
&lt;li&gt;초급 자료구조/알고리즘 활용
&lt;ul style=&quot;list-style-type: disc;&quot; data-ke-list-type=&quot;disc&quot;&gt;
&lt;li&gt;&lt;span style=&quot;color: #000000; text-align: start;&quot;&gt;문자열(String), 배열(Array), 그리디(Greedy), 정렬(Sort) 등을 활용한 프로그램 개발하는 능력&lt;/span&gt;&lt;/li&gt;
&lt;/ul&gt;
&lt;/li&gt;
&lt;li&gt;&lt;span style=&quot;color: #000000; text-align: start;&quot;&gt;중급 자료구조/알고리즘 활용&lt;/span&gt;
&lt;ul style=&quot;list-style-type: disc;&quot; data-ke-list-type=&quot;disc&quot;&gt;
&lt;li&gt;&lt;span style=&quot;color: #000000; text-align: start;&quot;&gt; &lt;span style=&quot;color: #000000; text-align: start;&quot;&gt;스택(Stack), 큐(Queue), 덱(Deque), 해시(Hash), 이진 탐색(Binary Search), 깊이 우선 탐색(DFS), 너비 우선 탐색(BFS) 등을 활용한 프로그램 개발하는 능력&lt;/span&gt; &lt;/span&gt;&lt;/li&gt;
&lt;/ul&gt;
&lt;/li&gt;
&lt;li&gt;&lt;span style=&quot;color: #000000; text-align: start;&quot;&gt;고급 자료구조/알고리즘 활용&lt;/span&gt;
&lt;ul style=&quot;list-style-type: disc;&quot; data-ke-list-type=&quot;disc&quot;&gt;
&lt;li&gt;&lt;span style=&quot;color: #000000; text-align: start;&quot;&gt; &lt;span style=&quot;color: #000000; text-align: start;&quot;&gt;그래프(Graph), 트리(Tree), 힙(Heap), 다이나믹 프로그래밍(Dynamic Programming) 등을 활용한 프로그램 개발하는 능력&lt;/span&gt; &lt;/span&gt;&lt;/li&gt;
&lt;/ul&gt;
&lt;/li&gt;
&lt;li&gt;&lt;span style=&quot;color: #000000; text-align: start;&quot;&gt;정확하고 효율적인 프로그램 작성&lt;/span&gt;
&lt;ul style=&quot;list-style-type: disc;&quot; data-ke-list-type=&quot;disc&quot;&gt;
&lt;li&gt;&lt;span style=&quot;color: #000000; text-align: start;&quot;&gt; &lt;span style=&quot;color: #000000; text-align: start;&quot;&gt;주어진 문제에 알맞은 자료구조와 알고리즘을 선택&lt;/span&gt; &lt;/span&gt;&lt;/li&gt;
&lt;li&gt;&lt;span style=&quot;color: #000000; text-align: start;&quot;&gt; &lt;span style=&quot;color: #000000; text-align: start;&quot;&gt;오류없이 프로그램을 개발&lt;/span&gt; &lt;/span&gt;&lt;/li&gt;
&lt;/ul&gt;
&lt;/li&gt;
&lt;/ul&gt;
&lt;h3 data-ke-size=&quot;size23&quot;&gt;&lt;span style=&quot;color: #000000; text-align: start;&quot;&gt;&lt;span style=&quot;color: #000000; text-align: start;&quot;&gt;합격 기준&lt;/span&gt;&lt;/span&gt;&lt;/h3&gt;
&lt;p style=&quot;background-color: #ffffff; color: #212529; text-align: start;&quot; data-ke-size=&quot;size16&quot;&gt;합격 기준은 다음과 같으며 400점 이상 획득해야 합격이라고 한다:&lt;/p&gt;
&lt;p&gt;&lt;figure class=&quot;imageblock alignCenter&quot; data-ke-mobileStyle=&quot;widthOrigin&quot; data-origin-width=&quot;1206&quot; data-origin-height=&quot;359&quot;&gt;&lt;span data-url=&quot;https://blog.kakaocdn.net/dn/w4HM3/dJMcachn7GJ/ysfW8FXN14E01HHdpSdmr1/img.png&quot; data-phocus=&quot;https://blog.kakaocdn.net/dn/w4HM3/dJMcachn7GJ/ysfW8FXN14E01HHdpSdmr1/img.png&quot;&gt;&lt;img src=&quot;https://blog.kakaocdn.net/dn/w4HM3/dJMcachn7GJ/ysfW8FXN14E01HHdpSdmr1/img.png&quot; srcset=&quot;https://img1.daumcdn.net/thumb/R1280x0/?scode=mtistory2&amp;fname=https%3A%2F%2Fblog.kakaocdn.net%2Fdn%2Fw4HM3%2FdJMcachn7GJ%2FysfW8FXN14E01HHdpSdmr1%2Fimg.png&quot; onerror=&quot;this.onerror=null; this.src='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png'; this.srcset='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png';&quot; loading=&quot;lazy&quot; width=&quot;716&quot; height=&quot;213&quot; data-origin-width=&quot;1206&quot; data-origin-height=&quot;359&quot;/&gt;&lt;/span&gt;&lt;/figure&gt;
&lt;/p&gt;
&lt;h2 style=&quot;background-color: #ffffff; color: #212529; text-align: start;&quot; data-ke-size=&quot;size26&quot;&gt;후기&lt;/h2&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;Java로 응시하였고 Lv5를 받았다!&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;559&quot; data-origin-height=&quot;780&quot;&gt;&lt;span data-url=&quot;https://blog.kakaocdn.net/dn/YgzNd/dJMcaiIDycd/Gv0mrURWTicRH3snrH9Jh0/img.png&quot; data-phocus=&quot;https://blog.kakaocdn.net/dn/YgzNd/dJMcaiIDycd/Gv0mrURWTicRH3snrH9Jh0/img.png&quot;&gt;&lt;img src=&quot;https://blog.kakaocdn.net/dn/YgzNd/dJMcaiIDycd/Gv0mrURWTicRH3snrH9Jh0/img.png&quot; srcset=&quot;https://img1.daumcdn.net/thumb/R1280x0/?scode=mtistory2&amp;fname=https%3A%2F%2Fblog.kakaocdn.net%2Fdn%2FYgzNd%2FdJMcaiIDycd%2FGv0mrURWTicRH3snrH9Jh0%2Fimg.png&quot; onerror=&quot;this.onerror=null; this.src='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png'; this.srcset='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png';&quot; loading=&quot;lazy&quot; width=&quot;507&quot; height=&quot;707&quot; data-origin-width=&quot;559&quot; data-origin-height=&quot;780&quot;/&gt;&lt;/span&gt;&lt;/figure&gt;
&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;1번, 2번 문제는 백준 기준으로 실버1~골드5 정도의 난이도였다. 구현문제와 BFS를 아는가?를 물어보는 문제였다.&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;3번문제가 제일 까다로웠다. 나는 그리디라고 생각한다. 그리디 문제를 오랜만에 만나다보니 처음엔 'DP인가?' 하며 상태를 정의하려고 했지만 실패했다. 1, 2, 4번을 모두 풀고 다시 3번으로 돌아와서 문제를 하나씩 뜯어보니 '그리디로 풀릴 것 같은데?' 라는 생각이 들어 그렇게 구현했다. 그리디는 시험장에선 증명하기가 어렵다보니 풀면서도 긴가 민가 했다.&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;4번은 BFS 응용문제였다. 일반적으로 BFS는 중복체크를 해야 메모리초과가 발생하지 않는데, 중복을 허용한다는 조건이 있어서 처음엔 당황했다. 하지만 문제를 잘 읽으면 한 가지 조건을 찾을 수 있어 메모리초과를 방지할 수 있다.&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;힘든 과정이 앞에 포함되서 더 성취감 있고 재밌는 것 같다.&lt;/b&gt;) '실무 개발엔 알고리즘이 필요없다'는 말을 많이 들었지만 코테를 공부하는 이유는 알고리즘을 공부하는 것보다도 '&lt;b&gt;문제를 마주했을 때 해결하는 힘을 배우는 게 아닐까'&lt;/b&gt;라는 생각이 든다.&amp;nbsp;&lt;/p&gt;</description>
      <category>Java</category>
      <author>Ukjong</author>
      <guid isPermaLink="true">https://ukj0ng.tistory.com/64</guid>
      <comments>https://ukj0ng.tistory.com/64#entry64comment</comments>
      <pubDate>Mon, 22 Dec 2025 01:27:10 +0900</pubDate>
    </item>
    <item>
      <title>[Web] POST에서도 멱등성을? (Redis로 멱등성 키 API 구현하기)</title>
      <link>https://ukj0ng.tistory.com/63</link>
      <description>&lt;p data-ke-size=&quot;size16&quot;&gt;HTTP 메서드 스펙상 `POST`는 멱등성을 보장하지 않는다. 하지만 결제나 포인트 충전 같은 민감한 로직에선 네트워크 지연이나 사용자의 실수로 인한 중복 처리를 반드시 막아야 한다. 이를 멱등키를 적용해 구현할 수 있다는 사실을 알게 되었다.&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;855&quot; data-origin-height=&quot;252&quot;&gt;&lt;span data-url=&quot;https://blog.kakaocdn.net/dn/bVMDHw/dJMcag47ciF/tg4d9FKS4z5kXdbk5Ji7tk/img.png&quot; data-phocus=&quot;https://blog.kakaocdn.net/dn/bVMDHw/dJMcag47ciF/tg4d9FKS4z5kXdbk5Ji7tk/img.png&quot; data-alt=&quot;https://docs.tosspayments.com/blog/what-is-idempotency&quot;&gt;&lt;img src=&quot;https://blog.kakaocdn.net/dn/bVMDHw/dJMcag47ciF/tg4d9FKS4z5kXdbk5Ji7tk/img.png&quot; srcset=&quot;https://img1.daumcdn.net/thumb/R1280x0/?scode=mtistory2&amp;fname=https%3A%2F%2Fblog.kakaocdn.net%2Fdn%2FbVMDHw%2FdJMcag47ciF%2Ftg4d9FKS4z5kXdbk5Ji7tk%2Fimg.png&quot; onerror=&quot;this.onerror=null; this.src='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png'; this.srcset='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png';&quot; loading=&quot;lazy&quot; width=&quot;855&quot; height=&quot;252&quot; data-origin-width=&quot;855&quot; data-origin-height=&quot;252&quot;/&gt;&lt;/span&gt;&lt;figcaption&gt;https://docs.tosspayments.com/blog/what-is-idempotency&lt;/figcaption&gt;
&lt;/figure&gt;
&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;그래서 한번 실험해보려고 한다. 사용자의 포인트를 충전하기 위한 API를 만들어야 하고, 멱등키를 지원해야 한다는 상황이다.&lt;/p&gt;
&lt;hr contenteditable=&quot;false&quot; data-ke-type=&quot;horizontalRule&quot; data-ke-style=&quot;style6&quot; /&gt;
&lt;h2 data-ke-size=&quot;size26&quot;&gt;요구사항&lt;/h2&gt;
&lt;ul style=&quot;list-style-type: disc;&quot; data-ke-list-type=&quot;disc&quot;&gt;
&lt;li&gt;URL: `POST /api/v1/points/charge`&lt;/li&gt;
&lt;li&gt;Header: ` Idempotency-Key`: `String`&lt;/li&gt;
&lt;li&gt;Request Body:&amp;nbsp;&lt;/li&gt;
&lt;/ul&gt;
&lt;pre id=&quot;code_1765529678432&quot; class=&quot;bash&quot; data-ke-language=&quot;bash&quot; data-ke-type=&quot;codeblock&quot;&gt;&lt;code&gt;{
  &quot;userId&quot;: 1,
  &quot;amount&quot;: 1000
}&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;Response Body (성공 시):&lt;/li&gt;
&lt;/ul&gt;
&lt;pre id=&quot;code_1765529705673&quot; class=&quot;bash&quot; data-ke-language=&quot;bash&quot; data-ke-type=&quot;codeblock&quot;&gt;&lt;code&gt;{
  &quot;status&quot;: &quot;SUCCESS&quot;,
  &quot;currentBalance&quot;: 1000,
  &quot;message&quot;: &quot;충전이 완료되었습니다.&quot;
}&lt;/code&gt;&lt;/pre&gt;
&lt;hr contenteditable=&quot;false&quot; data-ke-type=&quot;horizontalRule&quot; data-ke-style=&quot;style6&quot; /&gt;
&lt;h2 data-ke-size=&quot;size26&quot;&gt;설계&lt;/h2&gt;
&lt;p&gt;&lt;figure class=&quot;imageblock alignCenter&quot; data-ke-mobileStyle=&quot;widthOrigin&quot; data-origin-width=&quot;1192&quot; data-origin-height=&quot;970&quot;&gt;&lt;span data-url=&quot;https://blog.kakaocdn.net/dn/bPmwA6/dJMcadgcrDU/kzUwron0WPLLYgrdRH1jLk/img.png&quot; data-phocus=&quot;https://blog.kakaocdn.net/dn/bPmwA6/dJMcadgcrDU/kzUwron0WPLLYgrdRH1jLk/img.png&quot;&gt;&lt;img src=&quot;https://blog.kakaocdn.net/dn/bPmwA6/dJMcadgcrDU/kzUwron0WPLLYgrdRH1jLk/img.png&quot; srcset=&quot;https://img1.daumcdn.net/thumb/R1280x0/?scode=mtistory2&amp;fname=https%3A%2F%2Fblog.kakaocdn.net%2Fdn%2FbPmwA6%2FdJMcadgcrDU%2FkzUwron0WPLLYgrdRH1jLk%2Fimg.png&quot; onerror=&quot;this.onerror=null; this.src='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png'; this.srcset='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png';&quot; loading=&quot;lazy&quot; width=&quot;1192&quot; height=&quot;970&quot; data-origin-width=&quot;1192&quot; data-origin-height=&quot;970&quot;/&gt;&lt;/span&gt;&lt;/figure&gt;
&lt;/p&gt;
&lt;h3 data-ke-size=&quot;size23&quot;&gt;Redis에 멱등성 키를 저장한 이유&lt;/h3&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;Redis(캐시)는 보통&lt;/p&gt;
&lt;ol style=&quot;list-style-type: decimal;&quot; data-ke-list-type=&quot;decimal&quot;&gt;
&lt;li&gt;이 데이터가 얼마나 많이 접근/변경 되는지&lt;/li&gt;
&lt;li&gt;이 데이터를 생성하는 비용이 얼마나 비싼지&lt;/li&gt;
&lt;li&gt;다른 사용자들과 얼마나 많이 공유하는지&lt;/li&gt;
&lt;/ol&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;를 고려하여 적용한다. 위 3가지 상황에 멱등키는 고려될 대상이 아니다. 하지만, 데이터 캐싱이 아닌 분산 락의 역할로 Redis를 선택했다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;비교 항목높은 격리 수준 (가상머신, VM)낮은 격리 수준 (컨테이너)&lt;/p&gt;
&lt;table style=&quot;background-color: #ffffff; color: #353638; text-align: left; border-collapse: collapse; width: 100%;&quot; border=&quot;1&quot; data-ke-align=&quot;alignLeft&quot;&gt;
&lt;tbody&gt;
&lt;tr&gt;
&lt;td style=&quot;color: #353638; text-align: justify; width: 12.4419%;&quot;&gt;&lt;b&gt;비교&lt;/b&gt;&lt;/td&gt;
&lt;td style=&quot;color: #353638; text-align: justify; width: 37.6744%;&quot;&gt;RDB (MySQL)&lt;/td&gt;
&lt;td style=&quot;color: #353638; text-align: justify; width: 49.8837%;&quot;&gt;Redis (선택됨)&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td style=&quot;color: #353638; text-align: justify; width: 12.4419%;&quot;&gt;&lt;b&gt;속도&lt;/b&gt;&lt;/td&gt;
&lt;td style=&quot;color: #353638; text-align: justify; width: 37.6744%;&quot;&gt;느림 (Disk I/O)&lt;/td&gt;
&lt;td style=&quot;color: #353638; text-align: justify; width: 49.8837%;&quot;&gt;매우 빠름 (Memory)&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td style=&quot;color: #353638; text-align: justify; width: 12.4419%;&quot;&gt;&lt;b&gt;동시성 제어&lt;/b&gt;&lt;/td&gt;
&lt;td style=&quot;color: #353638; text-align: justify; width: 37.6744%;&quot;&gt;데드락 위험&lt;/td&gt;
&lt;td style=&quot;color: #353638; text-align: justify; width: 49.8837%;&quot;&gt;간단함 (SETNX)&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td style=&quot;color: #353638; text-align: justify; width: 12.4419%;&quot;&gt;&lt;b&gt;데이터 만료&lt;/b&gt;&lt;/td&gt;
&lt;td style=&quot;color: #353638; text-align: justify; width: 37.6744%;&quot;&gt;배치 작업 필요&lt;/td&gt;
&lt;td style=&quot;color: #353638; text-align: justify; width: 49.8837%;&quot;&gt;자동 (TTL)&lt;/td&gt;
&lt;/tr&gt;
&lt;/tbody&gt;
&lt;/table&gt;
&lt;h4 data-path-to-node=&quot;4&quot; data-ke-size=&quot;size20&quot;&gt;1. DB 보호&lt;/h4&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;트래픽이 몰릴 때 DB 트랜잭션으로만 중복을 막으려면, 서로 Row Lock을 잡으려는 Race Condition이 발생해 &lt;b&gt;데드락&lt;/b&gt;이 발생할 수 있다. Redis는 빠른 속도로 요청을 처리하므로, &lt;b&gt;DB 트랜잭션이 시작되기 전에 중복 요청을 사전에 차단하여 DB 자원을 보호&lt;/b&gt;할 수 있다.&amp;nbsp;&lt;/p&gt;
&lt;h4 data-path-to-node=&quot;7&quot; data-ke-size=&quot;size20&quot;&gt;2. 전역 메모리 역할&lt;/h4&gt;
&lt;p data-path-to-node=&quot;8&quot; data-ke-size=&quot;size16&quot;&gt;단일 서버라면 `ConcurrentHashMap`으로도 해결할 수 있지만, 서버가 여러 대로 스케일 아웃 되는 순간, 각 서버의 메모리는 공유되지 않는다. 따라서 중복 결제 사고가 발생할 수 있다. Redis는 모든 서버가 공통으로 사용하도록 해, &lt;b&gt;요청이 어떤 서버로 들어오든 일관된 멱등성을 보장&lt;/b&gt;할 수 있다.&lt;/p&gt;
&lt;h4 data-path-to-node=&quot;10&quot; data-ke-size=&quot;size20&quot;&gt;3. Atomic 연산&lt;/h4&gt;
&lt;p data-path-to-node=&quot;11&quot; data-ke-size=&quot;size16&quot;&gt;Redis는 검사와 저장을 원자적으로 동시에 수행할 수 있다.&amp;nbsp;&lt;/p&gt;
&lt;h4 data-path-to-node=&quot;13&quot; data-ke-size=&quot;size20&quot;&gt;4. TTL&lt;/h4&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;멱등성 키는 영구적으로 저장할 필요가 없는데, RDB에 저장한다면 별도의 배치 스케줄러를 돌려 삭제해야 한다. 하지만 Redis는 데이터 저장 시 &lt;b&gt;TTL&lt;/b&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;/h2&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;12개의 스레드에서 동시에 포인트 충전 요청을 보낸다.&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;예상 결과는&lt;/p&gt;
&lt;blockquote data-ke-style=&quot;style3&quot;&gt;최종 잔액: 1000&lt;br /&gt;히스토리 개수: 1&lt;/blockquote&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;/h2&gt;
&lt;p&gt;&lt;figure class=&quot;imageblock alignCenter&quot; data-ke-mobileStyle=&quot;widthOrigin&quot; data-origin-width=&quot;356&quot; data-origin-height=&quot;91&quot;&gt;&lt;span data-url=&quot;https://blog.kakaocdn.net/dn/bCCQiT/dJMcad1zDt7/DGcHdkUsT3A3mnrvLqDbs0/img.png&quot; data-phocus=&quot;https://blog.kakaocdn.net/dn/bCCQiT/dJMcad1zDt7/DGcHdkUsT3A3mnrvLqDbs0/img.png&quot; data-alt=&quot;결과&quot;&gt;&lt;img src=&quot;https://blog.kakaocdn.net/dn/bCCQiT/dJMcad1zDt7/DGcHdkUsT3A3mnrvLqDbs0/img.png&quot; srcset=&quot;https://img1.daumcdn.net/thumb/R1280x0/?scode=mtistory2&amp;fname=https%3A%2F%2Fblog.kakaocdn.net%2Fdn%2FbCCQiT%2FdJMcad1zDt7%2FDGcHdkUsT3A3mnrvLqDbs0%2Fimg.png&quot; onerror=&quot;this.onerror=null; this.src='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png'; this.srcset='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png';&quot; loading=&quot;lazy&quot; width=&quot;356&quot; height=&quot;91&quot; data-origin-width=&quot;356&quot; data-origin-height=&quot;91&quot;/&gt;&lt;/span&gt;&lt;figcaption&gt;결과&lt;/figcaption&gt;
&lt;/figure&gt;
&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;hr contenteditable=&quot;false&quot; data-ke-type=&quot;horizontalRule&quot; data-ke-style=&quot;style6&quot; /&gt;
&lt;h2 data-ke-size=&quot;size26&quot;&gt;배운 점&lt;/h2&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;사실 처음엔 그저, 멱등성 키가 존재하는지만 확인했더니 동작하지 않았다.&lt;/p&gt;
&lt;pre id=&quot;code_1765628320658&quot; class=&quot;java&quot; data-ke-language=&quot;java&quot; data-ke-type=&quot;codeblock&quot;&gt;&lt;code&gt;// [실패한 코드] 비원자적(Non-Atomic) 로직
if (!redisTemplate.hasKey(key)) {  // 1. 확인 (Check)
    // --- (이 찰나의 순간에 다른 스레드가 침투!) ---
    redisTemplate.opsForValue().set(key, &quot;value&quot;); // 2. 행동 (Act)
    return true;
}
return false;&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;358&quot; data-origin-height=&quot;158&quot;&gt;&lt;span data-url=&quot;https://blog.kakaocdn.net/dn/bIopII/dJMcafym8og/EEZDzZySOkbKVKlmsPTXv1/img.png&quot; data-phocus=&quot;https://blog.kakaocdn.net/dn/bIopII/dJMcafym8og/EEZDzZySOkbKVKlmsPTXv1/img.png&quot; data-alt=&quot;비원자적으로 멱등성 키를 조회했을 경우&quot;&gt;&lt;img src=&quot;https://blog.kakaocdn.net/dn/bIopII/dJMcafym8og/EEZDzZySOkbKVKlmsPTXv1/img.png&quot; srcset=&quot;https://img1.daumcdn.net/thumb/R1280x0/?scode=mtistory2&amp;fname=https%3A%2F%2Fblog.kakaocdn.net%2Fdn%2FbIopII%2FdJMcafym8og%2FEEZDzZySOkbKVKlmsPTXv1%2Fimg.png&quot; onerror=&quot;this.onerror=null; this.src='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png'; this.srcset='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png';&quot; loading=&quot;lazy&quot; width=&quot;358&quot; height=&quot;158&quot; data-origin-width=&quot;358&quot; data-origin-height=&quot;158&quot;/&gt;&lt;/span&gt;&lt;figcaption&gt;비원자적으로 멱등성 키를 조회했을 경우&lt;/figcaption&gt;
&lt;/figure&gt;
&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;그 이유를 확인해 보니 동시성 환경에서 1번과 2번 사이의 미세한 시간 차이 때문에 중복 체크가 뚫렸다. 이를 해결하기 위해 Redis의 `SETNX(Set If Not Exists) 명령어를 지원하는 `setIfAbsent()`메서드를 적용했다.&lt;/p&gt;
&lt;pre id=&quot;code_1765628463620&quot; class=&quot;java&quot; data-ke-language=&quot;java&quot; data-ke-type=&quot;codeblock&quot;&gt;&lt;code&gt;// [성공한 코드] 원자적(Atomic) 로직
Boolean result = redisTemplate.opsForValue()
        .setIfAbsent(key, &quot;value&quot;, Duration.ofMinutes(10));&lt;/code&gt;&lt;/pre&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;이를 통해 확인과 저장을 &lt;b&gt;분리할 수 없는 한 동작&lt;/b&gt;으로 수행해 처리할 수 있었다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;물론, 모든 요청이 Redis를 거쳐야 한다는 점에서 병목 지점이 될 수도 있다는 생각이 들었지만, 멱등성 키의 목적은 '비정상적인 중복 처리'를 방지하는 것이며 실제 서비스 환경에서 병목이 발생할 만큼의 대량 중복 요청은 현실적으로 드물다고 판단했다. 오히려 &lt;b&gt;Redis를 통해 DB의 트랜잭션 부하를 막아주는 이점이 훨씬 크다&lt;/b&gt;는 결론을 내렸다.&amp;nbsp;&lt;/p&gt;</description>
      <category>Web</category>
      <author>Ukjong</author>
      <guid isPermaLink="true">https://ukj0ng.tistory.com/63</guid>
      <comments>https://ukj0ng.tistory.com/63#entry63comment</comments>
      <pubDate>Sun, 14 Dec 2025 12:39:43 +0900</pubDate>
    </item>
    <item>
      <title>[Infra] AWS 클라우드 배포 (Docker에서 AWS EC2까지-2)</title>
      <link>https://ukj0ng.tistory.com/62</link>
      <description>&lt;p data-ke-size=&quot;size16&quot;&gt;이전 글에서 만든 `docker-compose.yml`을 AWS 클라우드 환경에서 배포를 진행해보려 한다.&amp;nbsp;&lt;/p&gt;
&lt;h2 data-ke-size=&quot;size26&quot;&gt;1. EC2 인스턴스 생성&lt;/h2&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;EC2를 검색해서 인스턴스 시작 버튼을 눌러보자.&lt;/p&gt;
&lt;p&gt;&lt;figure class=&quot;imageblock alignCenter&quot; data-ke-mobileStyle=&quot;widthOrigin&quot; data-origin-width=&quot;796&quot; data-origin-height=&quot;693&quot;&gt;&lt;span data-url=&quot;https://blog.kakaocdn.net/dn/bb6ktC/dJMcacO9wxo/hWkXXdKMT8q2yQ1S6EtZxk/img.png&quot; data-phocus=&quot;https://blog.kakaocdn.net/dn/bb6ktC/dJMcacO9wxo/hWkXXdKMT8q2yQ1S6EtZxk/img.png&quot;&gt;&lt;img src=&quot;https://blog.kakaocdn.net/dn/bb6ktC/dJMcacO9wxo/hWkXXdKMT8q2yQ1S6EtZxk/img.png&quot; srcset=&quot;https://img1.daumcdn.net/thumb/R1280x0/?scode=mtistory2&amp;fname=https%3A%2F%2Fblog.kakaocdn.net%2Fdn%2Fbb6ktC%2FdJMcacO9wxo%2FhWkXXdKMT8q2yQ1S6EtZxk%2Fimg.png&quot; onerror=&quot;this.onerror=null; this.src='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png'; this.srcset='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png';&quot; loading=&quot;lazy&quot; width=&quot;641&quot; height=&quot;558&quot; data-origin-width=&quot;796&quot; data-origin-height=&quot;693&quot;/&gt;&lt;/span&gt;&lt;/figure&gt;
&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;이름은 원하는 걸 적고, OS 이미지는 우분투를 선택했다.&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;1061&quot; data-origin-height=&quot;502&quot;&gt;&lt;span data-url=&quot;https://blog.kakaocdn.net/dn/cISg1K/dJMcacVU4H1/KS81Hd5LA8PCEtOJHX9tC1/img.png&quot; data-phocus=&quot;https://blog.kakaocdn.net/dn/cISg1K/dJMcacVU4H1/KS81Hd5LA8PCEtOJHX9tC1/img.png&quot;&gt;&lt;img src=&quot;https://blog.kakaocdn.net/dn/cISg1K/dJMcacVU4H1/KS81Hd5LA8PCEtOJHX9tC1/img.png&quot; srcset=&quot;https://img1.daumcdn.net/thumb/R1280x0/?scode=mtistory2&amp;fname=https%3A%2F%2Fblog.kakaocdn.net%2Fdn%2FcISg1K%2FdJMcacVU4H1%2FKS81Hd5LA8PCEtOJHX9tC1%2Fimg.png&quot; onerror=&quot;this.onerror=null; this.src='//t1.daumcdn.net/tistory_admin/static/images/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;330&quot; data-origin-width=&quot;1061&quot; data-origin-height=&quot;502&quot;/&gt;&lt;/span&gt;&lt;/figure&gt;
&lt;/p&gt;
&lt;blockquote data-ke-style=&quot;style3&quot;&gt; &lt;span style=&quot;color: #000000;&quot;&gt;우분투를 선택한 이유&lt;/span&gt;&lt;br /&gt;&lt;span style=&quot;color: #000000;&quot;&gt;우분투는 리눅스 커널(엔진) + APT(앱스토어)가 추가된 OS이다. 서버 개발자들이 가장 많이 사용하기 때문에 구글링 했을 때 자료가 압도적으로 많고, 초심자가 문제 해결을 하기 쉬워서 선택했다.&amp;nbsp;&lt;/span&gt;&lt;/blockquote&gt;
&lt;p&gt;&lt;figure class=&quot;imageblock alignCenter&quot; data-ke-mobileStyle=&quot;widthOrigin&quot; data-origin-width=&quot;1131&quot; data-origin-height=&quot;622&quot;&gt;&lt;span data-url=&quot;https://blog.kakaocdn.net/dn/et7JMm/dJMcaaqgRrs/ttoVnVigBnE0oRf7DBax20/img.png&quot; data-phocus=&quot;https://blog.kakaocdn.net/dn/et7JMm/dJMcaaqgRrs/ttoVnVigBnE0oRf7DBax20/img.png&quot;&gt;&lt;img src=&quot;https://blog.kakaocdn.net/dn/et7JMm/dJMcaaqgRrs/ttoVnVigBnE0oRf7DBax20/img.png&quot; srcset=&quot;https://img1.daumcdn.net/thumb/R1280x0/?scode=mtistory2&amp;fname=https%3A%2F%2Fblog.kakaocdn.net%2Fdn%2Fet7JMm%2FdJMcaaqgRrs%2FttoVnVigBnE0oRf7DBax20%2Fimg.png&quot; onerror=&quot;this.onerror=null; this.src='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png'; this.srcset='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png';&quot; loading=&quot;lazy&quot; width=&quot;702&quot; height=&quot;386&quot; data-origin-width=&quot;1131&quot; data-origin-height=&quot;622&quot;/&gt;&lt;/span&gt;&lt;/figure&gt;
&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;키 페어도 생성하자.&lt;/p&gt;
&lt;blockquote data-ke-style=&quot;style3&quot;&gt; &lt;span style=&quot;color: #000000;&quot;&gt;키 페어란?&lt;/span&gt;&lt;br /&gt;&lt;span style=&quot;color: #000000;&quot;&gt;서버의 보안을 위한 '디지털 자물쇠와 열쇠'이다.&amp;nbsp;&lt;/span&gt;&lt;br /&gt;&lt;span style=&quot;color: #000000;&quot;&gt;공개 키(자물쇠): AWS 서버에 저장된다.&lt;/span&gt;&lt;br /&gt;&lt;span style=&quot;color: #000000;&quot;&gt;개인 키(열쇠): 내 컴퓨터에 .pem 파일로 저장된다.&lt;/span&gt;&lt;br /&gt;&lt;span style=&quot;color: #000000;&quot;&gt;비밀번호 입력 방식보다 훨씬 안전하며, 이 `.pem`파일을 잃어버리면 서버에 접속할 수 없어 잘 보관해야 한다.&amp;nbsp;&lt;/span&gt;&lt;/blockquote&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;네트워크 설정 옆에 [편집]을 누르고 왼쪽 하단에 [보안 그룹 규칙 추가]를 누른다.&lt;/p&gt;
&lt;p&gt;&lt;figure class=&quot;imageblock alignCenter&quot; data-ke-mobileStyle=&quot;widthOrigin&quot; data-origin-width=&quot;1060&quot; data-origin-height=&quot;241&quot;&gt;&lt;span data-url=&quot;https://blog.kakaocdn.net/dn/u27bA/dJMcaiBM39W/Uexi5WgqgsmahMLUe3cU8K/img.png&quot; data-phocus=&quot;https://blog.kakaocdn.net/dn/u27bA/dJMcaiBM39W/Uexi5WgqgsmahMLUe3cU8K/img.png&quot;&gt;&lt;img src=&quot;https://blog.kakaocdn.net/dn/u27bA/dJMcaiBM39W/Uexi5WgqgsmahMLUe3cU8K/img.png&quot; srcset=&quot;https://img1.daumcdn.net/thumb/R1280x0/?scode=mtistory2&amp;fname=https%3A%2F%2Fblog.kakaocdn.net%2Fdn%2Fu27bA%2FdJMcaiBM39W%2FUexi5WgqgsmahMLUe3cU8K%2Fimg.png&quot; onerror=&quot;this.onerror=null; this.src='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png'; this.srcset='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png';&quot; loading=&quot;lazy&quot; width=&quot;694&quot; height=&quot;158&quot; data-origin-width=&quot;1060&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;열고 싶은 포트를 설정하고, CIDR 블록도 설정하자.&lt;/p&gt;
&lt;p&gt;&lt;figure class=&quot;imageblock alignCenter&quot; data-ke-mobileStyle=&quot;widthOrigin&quot; data-origin-width=&quot;1084&quot; data-origin-height=&quot;609&quot;&gt;&lt;span data-url=&quot;https://blog.kakaocdn.net/dn/lasLk/dJMcacVU5Fh/kfGYqZvy75QtACOp0MPky0/img.png&quot; data-phocus=&quot;https://blog.kakaocdn.net/dn/lasLk/dJMcacVU5Fh/kfGYqZvy75QtACOp0MPky0/img.png&quot;&gt;&lt;img src=&quot;https://blog.kakaocdn.net/dn/lasLk/dJMcacVU5Fh/kfGYqZvy75QtACOp0MPky0/img.png&quot; srcset=&quot;https://img1.daumcdn.net/thumb/R1280x0/?scode=mtistory2&amp;fname=https%3A%2F%2Fblog.kakaocdn.net%2Fdn%2FlasLk%2FdJMcacVU5Fh%2FkfGYqZvy75QtACOp0MPky0%2Fimg.png&quot; onerror=&quot;this.onerror=null; this.src='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png'; this.srcset='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png';&quot; loading=&quot;lazy&quot; width=&quot;707&quot; height=&quot;397&quot; data-origin-width=&quot;1084&quot; data-origin-height=&quot;609&quot;/&gt;&lt;/span&gt;&lt;/figure&gt;
&lt;/p&gt;
&lt;blockquote data-ke-style=&quot;style3&quot;&gt; &lt;span style=&quot;color: #000000;&quot;&gt;CIDR 블록이란?&lt;/span&gt;&lt;br /&gt;&lt;span style=&quot;color: #000000;&quot;&gt;'누가 접속할 수 있는가?'를 결정하는 IP 주소의 범위다.&amp;nbsp;&lt;br /&gt;`0.0.0.0/0`: 모든 IPv4 주소&lt;/span&gt;&lt;br /&gt;&lt;span style=&quot;color: #000000;&quot;&gt;`0.0.0.0/8`: 앞의 한 숫자만 고정하고 나머지 3자리는 아무거나&lt;/span&gt;&lt;br /&gt;&lt;span style=&quot;color: #000000;&quot;&gt;`0.0.0.0/16`: 앞의 두 숫자를 고정하고 나머지 2자리는 아무거나&lt;/span&gt;&lt;br /&gt;&lt;span style=&quot;color: #000000;&quot;&gt;`0.0.0.0/24`: 앞의 세 숫자를 고정하고 나머지 1자리는 아무거나&lt;/span&gt;&lt;br /&gt;&lt;span style=&quot;color: #000000;&quot;&gt;`0.0.0.0/32`: 1개의 IP 주소&lt;/span&gt;&lt;br /&gt;&lt;span style=&quot;color: #000000;&quot;&gt;`::/0`: 모든 IPv6 주소&lt;/span&gt;&lt;/blockquote&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;다 만들었으면 [인스턴스 시작]을 누른다.&lt;/p&gt;
&lt;hr contenteditable=&quot;false&quot; data-ke-type=&quot;horizontalRule&quot; data-ke-style=&quot;style6&quot; /&gt;
&lt;h2 data-ke-size=&quot;size26&quot;&gt;2. SSH 서버 접속하기&lt;/h2&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;EC2 목록에서 방금 만든 인스턴스를 클릭하고 왼쪽 상단에 있는 [연결] 버튼을 클릭한다. 그 다음 [EC2 인스턴스 연결]에서 [연결]&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;1790&quot; data-origin-height=&quot;743&quot;&gt;&lt;span data-url=&quot;https://blog.kakaocdn.net/dn/L2HyG/dJMcaaw2yoJ/km5KzcQd1KKxcJbsKxWBcK/img.png&quot; data-phocus=&quot;https://blog.kakaocdn.net/dn/L2HyG/dJMcaaw2yoJ/km5KzcQd1KKxcJbsKxWBcK/img.png&quot;&gt;&lt;img src=&quot;https://blog.kakaocdn.net/dn/L2HyG/dJMcaaw2yoJ/km5KzcQd1KKxcJbsKxWBcK/img.png&quot; srcset=&quot;https://img1.daumcdn.net/thumb/R1280x0/?scode=mtistory2&amp;fname=https%3A%2F%2Fblog.kakaocdn.net%2Fdn%2FL2HyG%2FdJMcaaw2yoJ%2Fkm5KzcQd1KKxcJbsKxWBcK%2Fimg.png&quot; onerror=&quot;this.onerror=null; this.src='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png'; this.srcset='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png';&quot; loading=&quot;lazy&quot; width=&quot;711&quot; height=&quot;295&quot; data-origin-width=&quot;1790&quot; data-origin-height=&quot;743&quot;/&gt;&lt;/span&gt;&lt;/figure&gt;
&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;해당 인스턴스의 터미널을 확인할 수 있다.&lt;/p&gt;
&lt;hr contenteditable=&quot;false&quot; data-ke-type=&quot;horizontalRule&quot; data-ke-style=&quot;style6&quot; /&gt;
&lt;h2 data-ke-size=&quot;size26&quot;&gt;3. 도커 설치하기&lt;/h2&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;이 인스턴스엔 아직 아무것도 설치 되어있지 않아서, APT를 업데이트하고 docker engine과 docker compose를 설치해야 한다.&lt;/p&gt;
&lt;pre id=&quot;code_1765272080565&quot; class=&quot;bash&quot; data-ke-language=&quot;bash&quot; data-ke-type=&quot;codeblock&quot;&gt;&lt;code&gt;sudo apt update // 설치 가능한 프로그램 목록을 최신화
sudo apt install docker.io -y // 도커 엔진 설치
sudo apt install docker-compose -y // 도커 컴포즈 설치
sudo usermod -aG docker $USER // 현재 사용자에게 도커 사용 권한 부여 (sudo를 매번 안 치기 위해)&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;793&quot; data-origin-height=&quot;135&quot;&gt;&lt;span data-url=&quot;https://blog.kakaocdn.net/dn/QfVwk/dJMcacuRhv0/z4FRjavvPoZ5qtrHjmrWe0/img.png&quot; data-phocus=&quot;https://blog.kakaocdn.net/dn/QfVwk/dJMcacuRhv0/z4FRjavvPoZ5qtrHjmrWe0/img.png&quot;&gt;&lt;img src=&quot;https://blog.kakaocdn.net/dn/QfVwk/dJMcacuRhv0/z4FRjavvPoZ5qtrHjmrWe0/img.png&quot; srcset=&quot;https://img1.daumcdn.net/thumb/R1280x0/?scode=mtistory2&amp;fname=https%3A%2F%2Fblog.kakaocdn.net%2Fdn%2FQfVwk%2FdJMcacuRhv0%2Fz4FRjavvPoZ5qtrHjmrWe0%2Fimg.png&quot; onerror=&quot;this.onerror=null; this.src='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png'; this.srcset='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png';&quot; loading=&quot;lazy&quot; width=&quot;711&quot; height=&quot;121&quot; data-origin-width=&quot;793&quot; data-origin-height=&quot;135&quot;/&gt;&lt;/span&gt;&lt;/figure&gt;
&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;그리고 나서 바로 `docker ps`를 하면 나오지 않는다. 리눅스에선 그룹 설정을 바꿔도, 지금 켜져 있는 &lt;b&gt;세션에 바로 반영이 되지 않아&lt;/b&gt;서 그렇다. 터미널을 껐다가 다시 SSH 연결을 하면 해결된다. 그렇지만 그건 귀찮으니까&amp;nbsp;&lt;/p&gt;
&lt;pre id=&quot;code_1765272478777&quot; class=&quot;bash&quot; data-ke-language=&quot;bash&quot; data-ke-type=&quot;codeblock&quot;&gt;&lt;code&gt;newgrp docker&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-origin-width=&quot;637&quot; data-origin-height=&quot;52&quot;&gt;&lt;span data-url=&quot;https://blog.kakaocdn.net/dn/eLxwhm/dJMcaaqgRSK/kZbyHjPdQREtZbhXI1Afgk/img.png&quot; data-phocus=&quot;https://blog.kakaocdn.net/dn/eLxwhm/dJMcaaqgRSK/kZbyHjPdQREtZbhXI1Afgk/img.png&quot;&gt;&lt;img src=&quot;https://blog.kakaocdn.net/dn/eLxwhm/dJMcaaqgRSK/kZbyHjPdQREtZbhXI1Afgk/img.png&quot; srcset=&quot;https://img1.daumcdn.net/thumb/R1280x0/?scode=mtistory2&amp;fname=https%3A%2F%2Fblog.kakaocdn.net%2Fdn%2FeLxwhm%2FdJMcaaqgRSK%2FkZbyHjPdQREtZbhXI1Afgk%2Fimg.png&quot; onerror=&quot;this.onerror=null; this.src='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png'; this.srcset='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png';&quot; loading=&quot;lazy&quot; width=&quot;649&quot; height=&quot;53&quot; data-origin-width=&quot;637&quot; data-origin-height=&quot;52&quot;/&gt;&lt;/span&gt;&lt;/figure&gt;
&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;`docker ps`로 다음과 같이 나오면 docker 설정이 끝난다.&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;4. 배포하기&lt;/h2&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;이제 저번에 만들었던 `docker-compose.yml` 내용을 이 서버로 옮겨서 실행해보자.&lt;/p&gt;
&lt;pre id=&quot;code_1765272969526&quot; class=&quot;bash&quot; data-ke-language=&quot;bash&quot; data-ke-type=&quot;codeblock&quot;&gt;&lt;code&gt;mkdir my-server
cd my-server
nano docker-compose.yml&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;574&quot; data-origin-height=&quot;50&quot;&gt;&lt;span data-url=&quot;https://blog.kakaocdn.net/dn/cYf90R/dJMcacBC2NJ/zzbFex0zI7FHasMQTELI10/img.png&quot; data-phocus=&quot;https://blog.kakaocdn.net/dn/cYf90R/dJMcacBC2NJ/zzbFex0zI7FHasMQTELI10/img.png&quot;&gt;&lt;img src=&quot;https://blog.kakaocdn.net/dn/cYf90R/dJMcacBC2NJ/zzbFex0zI7FHasMQTELI10/img.png&quot; srcset=&quot;https://img1.daumcdn.net/thumb/R1280x0/?scode=mtistory2&amp;fname=https%3A%2F%2Fblog.kakaocdn.net%2Fdn%2FcYf90R%2FdJMcacBC2NJ%2FzzbFex0zI7FHasMQTELI10%2Fimg.png&quot; onerror=&quot;this.onerror=null; this.src='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png'; this.srcset='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png';&quot; loading=&quot;lazy&quot; width=&quot;643&quot; height=&quot;56&quot; data-origin-width=&quot;574&quot; data-origin-height=&quot;50&quot;/&gt;&lt;/span&gt;&lt;/figure&gt;
&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;폴더를 만들어서 해당 파일 안에 &lt;span style=&quot;color: #333333; text-align: start;&quot;&gt;`docker-compose.yml`을 생성한다.&amp;nbsp;&lt;/span&gt;&lt;/p&gt;
&lt;p&gt;&lt;figure class=&quot;imageblock alignCenter&quot; data-ke-mobileStyle=&quot;widthOrigin&quot; data-origin-width=&quot;737&quot; data-origin-height=&quot;680&quot;&gt;&lt;span data-url=&quot;https://blog.kakaocdn.net/dn/R67Pg/dJMcafylrX6/oTpzi1ZxQs0nA0chhUSWrk/img.png&quot; data-phocus=&quot;https://blog.kakaocdn.net/dn/R67Pg/dJMcafylrX6/oTpzi1ZxQs0nA0chhUSWrk/img.png&quot;&gt;&lt;img src=&quot;https://blog.kakaocdn.net/dn/R67Pg/dJMcafylrX6/oTpzi1ZxQs0nA0chhUSWrk/img.png&quot; srcset=&quot;https://img1.daumcdn.net/thumb/R1280x0/?scode=mtistory2&amp;fname=https%3A%2F%2Fblog.kakaocdn.net%2Fdn%2FR67Pg%2FdJMcafylrX6%2FoTpzi1ZxQs0nA0chhUSWrk%2Fimg.png&quot; onerror=&quot;this.onerror=null; this.src='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png'; this.srcset='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png';&quot; loading=&quot;lazy&quot; width=&quot;713&quot; height=&quot;658&quot; data-origin-width=&quot;737&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;`Ctrl + X` -&amp;gt; `Y` -&amp;gt; `Enter`로 저장하자.&amp;nbsp;&lt;/p&gt;
&lt;pre id=&quot;code_1765273107922&quot; class=&quot;bash&quot; data-ke-language=&quot;bash&quot; data-ke-type=&quot;codeblock&quot;&gt;&lt;code&gt;docker compose up -d&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;&lt;figure class=&quot;imageblock alignCenter&quot; data-ke-mobileStyle=&quot;widthOrigin&quot; data-origin-width=&quot;1517&quot; data-origin-height=&quot;579&quot;&gt;&lt;span data-url=&quot;https://blog.kakaocdn.net/dn/cm9aXo/dJMcagqt8AH/vXPGXgNVDDewYfpTl27db0/img.png&quot; data-phocus=&quot;https://blog.kakaocdn.net/dn/cm9aXo/dJMcagqt8AH/vXPGXgNVDDewYfpTl27db0/img.png&quot;&gt;&lt;img src=&quot;https://blog.kakaocdn.net/dn/cm9aXo/dJMcagqt8AH/vXPGXgNVDDewYfpTl27db0/img.png&quot; srcset=&quot;https://img1.daumcdn.net/thumb/R1280x0/?scode=mtistory2&amp;fname=https%3A%2F%2Fblog.kakaocdn.net%2Fdn%2Fcm9aXo%2FdJMcagqt8AH%2FvXPGXgNVDDewYfpTl27db0%2Fimg.png&quot; onerror=&quot;this.onerror=null; this.src='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png'; this.srcset='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png';&quot; loading=&quot;lazy&quot; width=&quot;724&quot; height=&quot;276&quot; data-origin-width=&quot;1517&quot; data-origin-height=&quot;579&quot;/&gt;&lt;/span&gt;&lt;/figure&gt;
&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;근데 `docker ps`엔 nginx가 잘 뜨는데&lt;/p&gt;
&lt;p&gt;&lt;figure class=&quot;imageblock alignCenter&quot; data-ke-mobileStyle=&quot;widthOrigin&quot; data-origin-width=&quot;816&quot; data-origin-height=&quot;684&quot;&gt;&lt;span data-url=&quot;https://blog.kakaocdn.net/dn/bb5Lnv/dJMcadtIEmX/PmAShMeCyPbESeQos6KCK1/img.png&quot; data-phocus=&quot;https://blog.kakaocdn.net/dn/bb5Lnv/dJMcadtIEmX/PmAShMeCyPbESeQos6KCK1/img.png&quot;&gt;&lt;img src=&quot;https://blog.kakaocdn.net/dn/bb5Lnv/dJMcadtIEmX/PmAShMeCyPbESeQos6KCK1/img.png&quot; srcset=&quot;https://img1.daumcdn.net/thumb/R1280x0/?scode=mtistory2&amp;fname=https%3A%2F%2Fblog.kakaocdn.net%2Fdn%2Fbb5Lnv%2FdJMcadtIEmX%2FPmAShMeCyPbESeQos6KCK1%2Fimg.png&quot; onerror=&quot;this.onerror=null; this.src='//t1.daumcdn.net/tistory_admin/static/images/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;542&quot; data-origin-width=&quot;816&quot; data-origin-height=&quot;684&quot;/&gt;&lt;/span&gt;&lt;/figure&gt;
&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;nginx에 접속해보니 접속되지가 않았다.&lt;/p&gt;
&lt;h3 data-ke-size=&quot;size23&quot;&gt;보안 그룹 체크하기&lt;/h3&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;위에서 &lt;b&gt;보안 그룹&lt;/b&gt;을 체크할 때, &lt;b&gt;포트를 `8080`&lt;/b&gt;으로 열었는데 &lt;b&gt;`docker-compose.yml`&lt;/b&gt;에는 &lt;b&gt;`8081`&lt;/b&gt;로 포트를 열었다. 이럴 땐, 보안 그룹의 포트를 변경하거나 `docker-compose.yml`의 값을 변경하고 `docker-compose down`후 다시 `up -d`를 하면 된다. 난 보안 그룹의 포트를 변경하기로 했다. (Mysql 포트도 설정하지 않아서 추가로 설정해주었다.)&lt;/p&gt;
&lt;p&gt;&lt;figure class=&quot;imageblock alignCenter&quot; data-ke-mobileStyle=&quot;widthOrigin&quot; data-origin-width=&quot;1600&quot; data-origin-height=&quot;201&quot;&gt;&lt;span data-url=&quot;https://blog.kakaocdn.net/dn/blnHuv/dJMcadga045/GsETaqbFJnVP2b8SmZKtrK/img.png&quot; data-phocus=&quot;https://blog.kakaocdn.net/dn/blnHuv/dJMcadga045/GsETaqbFJnVP2b8SmZKtrK/img.png&quot;&gt;&lt;img src=&quot;https://blog.kakaocdn.net/dn/blnHuv/dJMcadga045/GsETaqbFJnVP2b8SmZKtrK/img.png&quot; srcset=&quot;https://img1.daumcdn.net/thumb/R1280x0/?scode=mtistory2&amp;fname=https%3A%2F%2Fblog.kakaocdn.net%2Fdn%2FblnHuv%2FdJMcadga045%2FGsETaqbFJnVP2b8SmZKtrK%2Fimg.png&quot; onerror=&quot;this.onerror=null; this.src='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png'; this.srcset='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png';&quot; loading=&quot;lazy&quot; width=&quot;709&quot; height=&quot;89&quot; data-origin-width=&quot;1600&quot; data-origin-height=&quot;201&quot;/&gt;&lt;/span&gt;&lt;/figure&gt;
&lt;/p&gt;
&lt;h3 data-ke-size=&quot;size23&quot;&gt;볼륨 설정하기&lt;/h3&gt;
&lt;p&gt;&lt;figure class=&quot;imageblock alignCenter&quot; data-ke-mobileStyle=&quot;widthOrigin&quot; data-origin-width=&quot;935&quot; data-origin-height=&quot;236&quot;&gt;&lt;span data-url=&quot;https://blog.kakaocdn.net/dn/cJbOjI/dJMcaf6bqdm/kwrvHijKPTilKWg50ZvaBK/img.png&quot; data-phocus=&quot;https://blog.kakaocdn.net/dn/cJbOjI/dJMcaf6bqdm/kwrvHijKPTilKWg50ZvaBK/img.png&quot;&gt;&lt;img src=&quot;https://blog.kakaocdn.net/dn/cJbOjI/dJMcaf6bqdm/kwrvHijKPTilKWg50ZvaBK/img.png&quot; srcset=&quot;https://img1.daumcdn.net/thumb/R1280x0/?scode=mtistory2&amp;fname=https%3A%2F%2Fblog.kakaocdn.net%2Fdn%2FcJbOjI%2FdJMcaf6bqdm%2FkwrvHijKPTilKWg50ZvaBK%2Fimg.png&quot; onerror=&quot;this.onerror=null; this.src='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png'; this.srcset='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png';&quot; loading=&quot;lazy&quot; width=&quot;705&quot; height=&quot;178&quot; data-origin-width=&quot;935&quot; data-origin-height=&quot;236&quot;/&gt;&lt;/span&gt;&lt;/figure&gt;
&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;이젠 403에러가 발생한다. 보안 그룹까지는 통과했는데 nginx에서 요청을 거부한 것이다.&lt;/p&gt;
&lt;p&gt;&lt;figure class=&quot;imageblock alignCenter&quot; data-ke-mobileStyle=&quot;widthOrigin&quot; data-origin-width=&quot;343&quot; data-origin-height=&quot;207&quot;&gt;&lt;span data-url=&quot;https://blog.kakaocdn.net/dn/bJNdY7/dJMcadga06I/hHZrjPiQ36DNxoke5p0qd0/img.png&quot; data-phocus=&quot;https://blog.kakaocdn.net/dn/bJNdY7/dJMcadga06I/hHZrjPiQ36DNxoke5p0qd0/img.png&quot;&gt;&lt;img src=&quot;https://blog.kakaocdn.net/dn/bJNdY7/dJMcadga06I/hHZrjPiQ36DNxoke5p0qd0/img.png&quot; srcset=&quot;https://img1.daumcdn.net/thumb/R1280x0/?scode=mtistory2&amp;fname=https%3A%2F%2Fblog.kakaocdn.net%2Fdn%2FbJNdY7%2FdJMcadga06I%2FhHZrjPiQ36DNxoke5p0qd0%2Fimg.png&quot; onerror=&quot;this.onerror=null; this.src='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png'; this.srcset='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png';&quot; loading=&quot;lazy&quot; width=&quot;343&quot; height=&quot;207&quot; data-origin-width=&quot;343&quot; data-origin-height=&quot;207&quot;/&gt;&lt;/span&gt;&lt;/figure&gt;
&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;어젠 volume을 설정했는데, 오늘 EC2 인스턴스엔 &lt;b&gt;볼륨을 설정한 파일이 없기 때문&lt;/b&gt;이다.&amp;nbsp;&lt;/p&gt;
&lt;pre id=&quot;code_1765274351597&quot; class=&quot;bash&quot; data-ke-language=&quot;bash&quot; data-ke-type=&quot;codeblock&quot;&gt;&lt;code&gt;nano index.html
&amp;lt;h1&amp;gt;Hello! This is AWS Server&amp;lt;/h1&amp;gt;&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;471&quot; data-origin-height=&quot;13&quot;&gt;&lt;span data-url=&quot;https://blog.kakaocdn.net/dn/EAKPc/dJMcajtVGf8/Wyj2jQEPCeEct1HE9LRQE1/img.png&quot; data-phocus=&quot;https://blog.kakaocdn.net/dn/EAKPc/dJMcajtVGf8/Wyj2jQEPCeEct1HE9LRQE1/img.png&quot;&gt;&lt;img src=&quot;https://blog.kakaocdn.net/dn/EAKPc/dJMcajtVGf8/Wyj2jQEPCeEct1HE9LRQE1/img.png&quot; srcset=&quot;https://img1.daumcdn.net/thumb/R1280x0/?scode=mtistory2&amp;fname=https%3A%2F%2Fblog.kakaocdn.net%2Fdn%2FEAKPc%2FdJMcajtVGf8%2FWyj2jQEPCeEct1HE9LRQE1%2Fimg.png&quot; onerror=&quot;this.onerror=null; this.src='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png'; this.srcset='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png';&quot; loading=&quot;lazy&quot; width=&quot;471&quot; height=&quot;13&quot; data-origin-width=&quot;471&quot; data-origin-height=&quot;13&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;458&quot; data-origin-height=&quot;180&quot;&gt;&lt;span data-url=&quot;https://blog.kakaocdn.net/dn/ujIdf/dJMcad1xW6J/bVy43N9BlB0xCd7ifgyMrK/img.png&quot; data-phocus=&quot;https://blog.kakaocdn.net/dn/ujIdf/dJMcad1xW6J/bVy43N9BlB0xCd7ifgyMrK/img.png&quot;&gt;&lt;img src=&quot;https://blog.kakaocdn.net/dn/ujIdf/dJMcad1xW6J/bVy43N9BlB0xCd7ifgyMrK/img.png&quot; srcset=&quot;https://img1.daumcdn.net/thumb/R1280x0/?scode=mtistory2&amp;fname=https%3A%2F%2Fblog.kakaocdn.net%2Fdn%2FujIdf%2FdJMcad1xW6J%2FbVy43N9BlB0xCd7ifgyMrK%2Fimg.png&quot; onerror=&quot;this.onerror=null; this.src='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png'; this.srcset='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png';&quot; loading=&quot;lazy&quot; width=&quot;458&quot; height=&quot;180&quot; data-origin-width=&quot;458&quot; data-origin-height=&quot;180&quot;/&gt;&lt;/span&gt;&lt;/figure&gt;
&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;볼륨에 해당하는 파일을 만들면, 다음과 같이 잘 접속 가능한 것을 확인할 수 있다.&amp;nbsp;&lt;/p&gt;
&lt;p&gt;&lt;figure class=&quot;imageblock alignCenter&quot; data-ke-mobileStyle=&quot;widthOrigin&quot; data-origin-width=&quot;493&quot; data-origin-height=&quot;375&quot;&gt;&lt;span data-url=&quot;https://blog.kakaocdn.net/dn/XtkjI/dJMcai2RirR/J1ndHgnuikEJZya4x8kaXk/img.png&quot; data-phocus=&quot;https://blog.kakaocdn.net/dn/XtkjI/dJMcai2RirR/J1ndHgnuikEJZya4x8kaXk/img.png&quot;&gt;&lt;img src=&quot;https://blog.kakaocdn.net/dn/XtkjI/dJMcai2RirR/J1ndHgnuikEJZya4x8kaXk/img.png&quot; srcset=&quot;https://img1.daumcdn.net/thumb/R1280x0/?scode=mtistory2&amp;fname=https%3A%2F%2Fblog.kakaocdn.net%2Fdn%2FXtkjI%2FdJMcai2RirR%2FJ1ndHgnuikEJZya4x8kaXk%2Fimg.png&quot; onerror=&quot;this.onerror=null; this.src='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png'; this.srcset='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png';&quot; loading=&quot;lazy&quot; width=&quot;493&quot; height=&quot;375&quot; data-origin-width=&quot;493&quot; data-origin-height=&quot;375&quot;/&gt;&lt;/span&gt;&lt;/figure&gt;
&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;Mysql도 잘 접속된 것을 확인할 수 있다.&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;/h2&gt;
&lt;p data-path-to-node=&quot;4&quot; data-ke-size=&quot;size16&quot;&gt;&lt;b&gt;1. 네트워크 계층의 이해 (Security Group vs Docker Port)&lt;/b&gt; Nginx 컨테이너가 정상 실행 중(Up)임에도 접속이 되지 않았던 원인은 포트 불일치였다. AWS의 &lt;b&gt;보안 그룹(Security Group)&lt;/b&gt;은 8080 포트만 허용했고, &lt;b&gt;Docker Compose&lt;/b&gt;는 8081 포트를 리스닝하고 있었다. 단순히 서버를 띄우는 것보다, &lt;b&gt;외부 요청이 방화벽을 통과해 컨테이너까지 도달하는 네트워크 경로&lt;/b&gt;를 일치시키는 것이 중요함을 배웠다.&lt;/p&gt;
&lt;p data-path-to-node=&quot;5&quot; data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-path-to-node=&quot;5&quot; data-ke-size=&quot;size16&quot;&gt;&lt;b&gt;2. 도커 볼륨(Volume)의 동작 원리 (403 Forbidden)&lt;/b&gt; 보안 그룹 해결 후 발생한 403 에러는 도커 볼륨의 특성을 간과해서 발생했다. 로컬 환경과 달리, 초기화된 EC2 인스턴스에는 마운트할 소스 파일(index.html)이 존재하지 않았다. `-v` 옵션은 파일의 '복사'가 아닌 호스트 경로의 '마운트'임을 명확히 이해했고, 호스트에 해당 파일을 생성하여 해결했다.&lt;/p&gt;
&lt;p data-path-to-node=&quot;6&quot; data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-path-to-node=&quot;6&quot; data-ke-size=&quot;size16&quot;&gt;&lt;b&gt;3. 리눅스 권한과 세션 관리&lt;/b&gt; docker 명령어를 `sudo` 없이 사용하기 위해 그룹 권한을 부여했으나 즉시 적용되지 않았다. 리눅스는 로그인 시점에 권한을 로드하기 때문이다. 재접속 대신 `newgrp` 명령어로 현재 세션의 그룹 정보를 갱신하여 해결할 수 있었다.&lt;/p&gt;
&lt;p data-path-to-node=&quot;6&quot; data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-path-to-node=&quot;6&quot; data-ke-size=&quot;size16&quot;&gt;&lt;span style=&quot;color: #1f1f1f; text-align: start;&quot;&gt;이번 배포 과정을 통해 OS(Ubuntu) -&amp;gt; Network(Aws Security Group) -&amp;gt; Middleware(Docker) -&amp;gt; Application(Nginx/Mysql)로 이어지는 인프라 계층 구조를 설계할 수 있게 되었다.&lt;/span&gt;&lt;/p&gt;</description>
      <category>Infra</category>
      <author>Ukjong</author>
      <guid isPermaLink="true">https://ukj0ng.tistory.com/62</guid>
      <comments>https://ukj0ng.tistory.com/62#entry62comment</comments>
      <pubDate>Tue, 9 Dec 2025 19:21:14 +0900</pubDate>
    </item>
    <item>
      <title>[Infra] Docker란? (Docker에서 AWS EC2까지-1)</title>
      <link>https://ukj0ng.tistory.com/61</link>
      <description>&lt;h2 data-ke-size=&quot;size26&quot;&gt;시작하기 전에&lt;/h2&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;그동안 프로젝트를 진행하며 docker-compose.yml은 AI를 사용해서 작성하거나 인프라 팀에게 받아쓰기만 했다. 인프라쪽을 잘 몰랐기 때문에, 팀을 만들 때도 이런 부분을 채워줄 수 있는 팀원을 구했는데 다시 생각해보면 &lt;b&gt;인프라를 하기가 무서웠다&lt;/b&gt;. 멋진 비즈니스 로직을 작성하는게 아닌 데이터베이스 백업, 스키마 변경, 서버 세팅 등은 잘하면 &lt;b&gt;안정적으로 서비스가 돌아가지만 실수하면 서비스 장애로 이어지는 부담스러운 작업&lt;/b&gt;이기 때문이다. (다시 생각해보면 그만큼 중요한 작업이다.)&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;하지만 개인이 하고 싶은 일 보다 팀에 도움이 되는 개발을 해야 한다고 생각한다. 따라서, SSAFY의 공식 일정이 끝난 지금부터 DevOps에 대한 공부와 팀을 위한 플랫폼 엔지니어링을 하기 위한 공부를 시작하려고 한다.&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;1. Docker&lt;/h2&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;Docker(도커)는 애플리케이션을 구축, 테스트 및 배포할 수 있는 컨테이너 기반의 오픈 소스 가상화 플랫폼이다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;쉽게 말해, 소프트웨어가 동작하는 데 필요한 모든 실행 환경(코드, 런타임, 시스템 도구, 라이브러리 등)을 '컨테이너'라는 표준화된 단위로 패키징하여, 어떤 환경에서든 동일하게 실행되도록 보장해 주는 도구이다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;b&gt;&quot;제 컴퓨터에선 돌아갔는데, 서버에선 안돼요...&quot;라는 문제를 해결하기 위한 도구라고 생각하자.&lt;/b&gt;&lt;/p&gt;
&lt;h3 data-ke-size=&quot;size23&quot;&gt;컨테이너(Docker) vs 가상머신&lt;/h3&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;흔한 질문이다. &quot;컨테이너와 가상머신의 차이가 뭔가요?&quot;&lt;/p&gt;
&lt;p&gt;&lt;figure class=&quot;imageblock alignCenter&quot; data-ke-mobileStyle=&quot;widthOrigin&quot; data-origin-width=&quot;796&quot; data-origin-height=&quot;342&quot;&gt;&lt;span data-url=&quot;https://blog.kakaocdn.net/dn/v8d87/dJMcajm8sSl/kzwAhtDPkWmvGcoHlJRqk0/img.webp&quot; data-phocus=&quot;https://blog.kakaocdn.net/dn/v8d87/dJMcajm8sSl/kzwAhtDPkWmvGcoHlJRqk0/img.webp&quot;&gt;&lt;img src=&quot;https://blog.kakaocdn.net/dn/v8d87/dJMcajm8sSl/kzwAhtDPkWmvGcoHlJRqk0/img.webp&quot; srcset=&quot;https://img1.daumcdn.net/thumb/R1280x0/?scode=mtistory2&amp;fname=https%3A%2F%2Fblog.kakaocdn.net%2Fdn%2Fv8d87%2FdJMcajm8sSl%2FkzwAhtDPkWmvGcoHlJRqk0%2Fimg.webp&quot; onerror=&quot;this.onerror=null; this.src='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png'; this.srcset='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png';&quot; loading=&quot;lazy&quot; width=&quot;805&quot; height=&quot;346&quot; data-origin-width=&quot;796&quot; data-origin-height=&quot;342&quot;/&gt;&lt;/span&gt;&lt;/figure&gt;
&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;왼쪽이 가상머신이고 오른쪽은 도커의 컨테이너이다. 가상머신은 OS를 포함하고 있고, 컨테이너에는 OS가 없다는 사실을 알 수 있다. 이 둘은 가상화 방식에 가장 큰 차이가 있다. 가상머신은 하드웨어를 가상화하고, 컨테이너는 운영체제 수준에서 가상화한다.&lt;/p&gt;
&lt;blockquote data-ke-style=&quot;style3&quot;&gt;&lt;span style=&quot;color: #000000;&quot;&gt; 가상화란?&lt;/span&gt;&lt;br /&gt;&lt;span style=&quot;color: #000000;&quot;&gt;가상화는 물리적인 하드웨어 자원을 논리적인 객체로 추상화는 기술이다. 말이 너무 어렵다. 조금 더 쉽게 설명하면 물리적으로 하나인 컴퓨터 자원(CPU, Memory, Disk 등)을 소프트웨어를 통해 논리적으로 쪼개거나 합쳐서, 마치 &lt;b&gt;여러 개의 자원이 있는 것&lt;/b&gt; 처럼 혹은 &lt;b&gt;하나의 큰 자원이 있는 것&lt;/b&gt; 처럼 보이게 만드는 기술이다.&lt;/span&gt;&lt;br /&gt;&lt;br /&gt;&lt;span style=&quot;color: #000000;&quot;&gt; 가상화는 왜 필요할까?&lt;/span&gt;&lt;br /&gt;&lt;span style=&quot;color: #000000;&quot;&gt;1. 자원의 효율적 사용가상화가 없던 시절에는 서버 한 대에 OS를 설치해서 사용했다. 성능이 좋은 서버를 샀다면, 해당 자원을 모두 사용하지 않는&amp;nbsp;&lt;b&gt;'유휴 자원'이 발생한다.&lt;/b&gt;&amp;nbsp;이를 해결하기 위해 필요하다.&lt;/span&gt;&lt;br /&gt;&lt;span style=&quot;color: #000000;&quot;&gt;2. 환경 격리 및 보안한 컴퓨터에서 여러 프로그램들 돌리고 있다고 가정해보자. 그 중 하나의 프로그램이 잘못돼어 OS가 다운된다면 다른 중요한 프로그램도 마비된다.&amp;nbsp;&lt;b&gt;위험한 작업이나 서로 충돌할 수 있는 프로그램은 아예 '가상머신/컨테이너'를 따로 써서 실행하면 안전하다.&lt;/b&gt;&lt;/span&gt;&lt;/blockquote&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;가상머신은 각각 완전한 OS를 설치해야 해서 무겁고 시작 속도가 느리다. 컴퓨터 안에 또 다른 컴퓨터를 만드는 것과 비슷하다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;반면 컨테이너는 호스트 OS의 커널을 공유하여 훨씬 가볍고 빠르다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;격리 수준에서도 차이가 있다. 가상머신은 완전히 독립적이어서 보안이 강력하지만, 컨테이너는 커널을 공유하므로 상대적으로 격리 수준이 낮다. 대신 컨테이너는 이미지 기반으로 어디서나 동일하게 실행되어 이식성이 뛰어나다.&lt;/p&gt;
&lt;table style=&quot;height: 381px;&quot; data-ke-align=&quot;alignLeft&quot;&gt;
&lt;thead&gt;
&lt;tr style=&quot;height: 24px;&quot;&gt;
&lt;th style=&quot;height: 24px; width: 110px;&quot; align=&quot;left&quot;&gt;비교 항목&lt;/th&gt;
&lt;th style=&quot;height: 24px; width: 357px;&quot; align=&quot;left&quot;&gt;높은 격리 수준 (가상머신, VM)&lt;/th&gt;
&lt;th style=&quot;height: 24px; width: 386px;&quot; align=&quot;left&quot;&gt;낮은 격리 수준 (컨테이너)&lt;/th&gt;
&lt;/tr&gt;
&lt;/thead&gt;
&lt;tbody&gt;
&lt;tr style=&quot;height: 21px;&quot;&gt;
&lt;td style=&quot;height: 21px; width: 110px; text-align: justify;&quot; align=&quot;left&quot;&gt;&lt;b&gt;핵심 특징&lt;/b&gt;&lt;/td&gt;
&lt;td style=&quot;height: 21px; width: 357px; text-align: justify;&quot; align=&quot;left&quot;&gt;하드웨어 전체를 가상화 (Guest OS 포함)&lt;/td&gt;
&lt;td style=&quot;height: 21px; width: 386px; text-align: justify;&quot; align=&quot;left&quot;&gt;운영체제 커널을 공유 (프로세스 격리)&lt;/td&gt;
&lt;/tr&gt;
&lt;tr style=&quot;height: 63px;&quot;&gt;
&lt;td style=&quot;height: 63px; width: 110px; text-align: justify;&quot; align=&quot;left&quot;&gt;&lt;b&gt;보안성 (장점)&lt;/b&gt;&lt;/td&gt;
&lt;td style=&quot;height: 63px; width: 357px; text-align: justify;&quot; align=&quot;left&quot;&gt;&lt;b&gt;매우 강력함.&lt;/b&gt;&lt;br /&gt;커널이 분리되어 있어 해킹 시에도 옆방(다른 VM)이나 집주인(Host)에게 피해가 거의 안 감.&lt;/td&gt;
&lt;td style=&quot;height: 63px; width: 386px; text-align: justify;&quot; align=&quot;left&quot;&gt;&lt;b&gt;기본적인 격리 제공.&lt;/b&gt;&lt;br /&gt;cgroups, namespaces로 분리되나, 커널 공유로 인해 취약점 발생 시 전체 시스템 위협 가능.&lt;/td&gt;
&lt;/tr&gt;
&lt;tr style=&quot;height: 63px;&quot;&gt;
&lt;td style=&quot;height: 63px; width: 110px; text-align: justify;&quot; align=&quot;left&quot;&gt;&lt;b&gt;성능/속도 (장점)&lt;/b&gt;&lt;/td&gt;
&lt;td style=&quot;height: 63px; width: 357px; text-align: justify;&quot; align=&quot;left&quot;&gt;&lt;b&gt;상대적 느림.&lt;/b&gt;&lt;br /&gt;OS 부팅 과정이 필요해 시작에 수 분 소요. 오버헤드(Overhead)가 큼.&lt;/td&gt;
&lt;td style=&quot;height: 63px; width: 386px; text-align: justify;&quot; align=&quot;left&quot;&gt;&lt;b&gt;매우 빠름.&lt;/b&gt;&lt;br /&gt;OS 부팅 없이 프로세스만 실행하면 되므로 수 초 내 실행. 네이티브에 가까운 성능.&lt;/td&gt;
&lt;/tr&gt;
&lt;tr style=&quot;height: 63px;&quot;&gt;
&lt;td style=&quot;height: 63px; width: 110px; text-align: justify;&quot; align=&quot;left&quot;&gt;&lt;b&gt;자원 효율성&lt;/b&gt;&lt;/td&gt;
&lt;td style=&quot;height: 63px; width: 357px; text-align: justify;&quot; align=&quot;left&quot;&gt;&lt;b&gt;낮음.&lt;/b&gt;&lt;br /&gt;사용하지 않아도 미리 할당된 자원(RAM 등)을 점유함.&lt;/td&gt;
&lt;td style=&quot;height: 63px; width: 386px; text-align: justify;&quot; align=&quot;left&quot;&gt;&lt;b&gt;높음.&lt;/b&gt;&lt;br /&gt;필요한 만큼만 자원을 쓰고, 여러 컨테이너를 고밀도로 실행 가능.&lt;/td&gt;
&lt;/tr&gt;
&lt;tr style=&quot;height: 63px;&quot;&gt;
&lt;td style=&quot;height: 63px; width: 110px; text-align: justify;&quot; align=&quot;left&quot;&gt;&lt;b&gt;OS 호환성&lt;/b&gt;&lt;/td&gt;
&lt;td style=&quot;height: 63px; width: 357px; text-align: justify;&quot; align=&quot;left&quot;&gt;&lt;b&gt;자유로움.&lt;/b&gt;&lt;br /&gt;Linux 위에 Windows, Windows 위에 Linux 등 서로 다른 OS 실행 가능.&lt;/td&gt;
&lt;td style=&quot;height: 63px; width: 386px; text-align: justify;&quot; align=&quot;left&quot;&gt;&lt;b&gt;제한적.&lt;/b&gt;&lt;br /&gt;Host OS의 커널을 공유하므로, Linux Host에서는 Linux 컨테이너만 구동 가능 (기본적으로).&lt;/td&gt;
&lt;/tr&gt;
&lt;tr style=&quot;height: 42px;&quot;&gt;
&lt;td style=&quot;height: 42px; width: 110px; text-align: justify;&quot; align=&quot;left&quot;&gt;&lt;b&gt;파일 크기&lt;/b&gt;&lt;/td&gt;
&lt;td style=&quot;height: 42px; width: 357px; text-align: justify;&quot; align=&quot;left&quot;&gt;&lt;b&gt;무거움 (GB 단위).&lt;/b&gt;&lt;br /&gt;OS 전체 파일이 포함됨.&lt;/td&gt;
&lt;td style=&quot;height: 42px; width: 386px; text-align: justify;&quot; align=&quot;left&quot;&gt;&lt;b&gt;가벼움 (MB 단위).&lt;/b&gt;&lt;br /&gt;애플리케이션과 라이브러리만 포함됨.&lt;/td&gt;
&lt;/tr&gt;
&lt;tr style=&quot;height: 42px;&quot;&gt;
&lt;td style=&quot;height: 42px; width: 110px; text-align: justify;&quot; align=&quot;left&quot;&gt;&lt;b&gt;주요 용도&lt;/b&gt;&lt;/td&gt;
&lt;td style=&quot;height: 42px; width: 357px; text-align: justify;&quot; align=&quot;left&quot;&gt;서로 완전히 다른 환경이 필요하거나, 강력한 보안이 요구되는 레거시 시스템.&lt;/td&gt;
&lt;td style=&quot;height: 42px; width: 386px; text-align: justify;&quot; align=&quot;left&quot;&gt;MSA(마이크로서비스), CI/CD, 빠른 배포와 확장이 필요한 웹 서버 개발.&lt;/td&gt;
&lt;/tr&gt;
&lt;/tbody&gt;
&lt;/table&gt;
&lt;hr contenteditable=&quot;false&quot; data-ke-type=&quot;horizontalRule&quot; data-ke-style=&quot;style6&quot; /&gt;
&lt;h2 data-ke-size=&quot;size26&quot;&gt;2. Docker의 4대 구성 요소&lt;/h2&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;Docker는 Dockerfile, Docker Image, Docker Container, Docker Volume로 구성되어 있다.&amp;nbsp;&lt;/p&gt;
&lt;h4 data-ke-size=&quot;size20&quot;&gt;Dockerfile&lt;/h4&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;애플리케이션을 어떻게 패키징할지 정의한 설정 파일이다. &quot;어떤 OS를 베이스로 쓸지&quot;, &quot;어떤 언어를 설치할지&quot;, &quot;어떤 포트를 열지&quot; 등의 과정을 스크립트로 작성한다.&amp;nbsp;&lt;/p&gt;
&lt;h4 data-ke-size=&quot;size20&quot;&gt;Docker Image&lt;/h4&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;Dockerfile을 빌드하여 만들어진 &lt;b&gt;불변(Read-only)의 파일&lt;/b&gt;이다. 컨테이너를 실행하기 위한 모든 정보를 포함하고 있으며, 이 이미지만 있으면 어디서든 똑같은 환경을 만들 수 있다.&lt;/p&gt;
&lt;h4 data-ke-size=&quot;size20&quot;&gt;Docker Container&amp;nbsp;&lt;/h4&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;Docker Image를 실행한 상태이다. 이미지에 쓰기 가능한 레이어를 얹어 메모리에 올린 인스턴스로, 하나의 이미지로 여러 개의 독립된 컨테이너를 동시에 실행할 수 있다.&amp;nbsp;&lt;/p&gt;
&lt;h4 data-ke-size=&quot;size20&quot;&gt;Docker Volume&lt;/h4&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;Docker Volume은 컨테이너 내부의 특정 폴더를 호스트 컴퓨터의 저장 공간과 연결(마운트)하는 기술이다. 기본적으로 Docker Container는 '쓰고 버리는' 개념으로 설계되었다. 따라서, Container를 삭제하는 순간 Container 내부에서 생성된 데이터는 함께 삭제된다. 데이터베이스를 Container로 띄웠는데, 컨테이너를 재시작했더니 모든 데이터가 날아간다면 끔찍할 것이다. Volume은 이 문제를 해결해준다.&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;3. 직접 해보기&lt;/h2&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;Dockerfile을 직접 생성해서 html을 띄워보고, docker-compose로 DB와 웹 서버를 한 번에 실행해보려고 한다.&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;목표&lt;/p&gt;
&lt;ol style=&quot;list-style-type: decimal;&quot; data-ke-list-type=&quot;decimal&quot;&gt;
&lt;li&gt;Nginx를 실행하고 'Welcome to nginx!' 확인하기&lt;/li&gt;
&lt;li&gt;1번의 내용 바꿔보기&lt;/li&gt;
&lt;li&gt;docker compose로 Nginx와 Mysql을 동시에 실행해보기&lt;/li&gt;
&lt;/ol&gt;
&lt;h3 data-ke-size=&quot;size23&quot;&gt;Nginx를 실행하기&lt;/h3&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;도커에서 Nginx를 실행하는 것은 매우 쉬웠다. 누군가 만들어 놓은 이미지를 다음과 같은 명령어로 간단하게 실행할 수 있었다.&amp;nbsp;&lt;/p&gt;
&lt;pre id=&quot;code_1764922690117&quot; class=&quot;bash&quot; data-ke-language=&quot;bash&quot; data-ke-type=&quot;codeblock&quot;&gt;&lt;code&gt;docker run -d -p 8080:80 nginx&lt;/code&gt;&lt;/pre&gt;
&lt;ul style=&quot;list-style-type: disc;&quot; data-ke-list-type=&quot;disc&quot;&gt;
&lt;li&gt;`docker run`: 이미지를 기반으로 컨테이너를 생성하고 시작하라는 명령어&lt;/li&gt;
&lt;li&gt;`-d`: Detached Mode(분리 모드), 컨테이너를 백그라운드에서 실행&lt;/li&gt;
&lt;li&gt;`-p 8080:80`: 포트 포워딩 설정, `[호스트 포트]:[컨테이너 포트]`
&lt;ul style=&quot;list-style-type: disc;&quot; data-ke-list-type=&quot;disc&quot;&gt;
&lt;li&gt;호스트의 해당 포트로 들어오는 모든 요청을 컨테이너 내부의 해당 포트로 연결하라는 명령어&lt;/li&gt;
&lt;/ul&gt;
&lt;/li&gt;
&lt;li&gt;`nginx`: 실행할 이미지 이름&amp;nbsp;&lt;/li&gt;
&lt;/ul&gt;
&lt;p&gt;&lt;figure class=&quot;imageblock alignCenter&quot; data-ke-mobileStyle=&quot;widthOrigin&quot; data-origin-width=&quot;1289&quot; data-origin-height=&quot;97&quot;&gt;&lt;span data-url=&quot;https://blog.kakaocdn.net/dn/bPLXFf/dJMcabWZn9H/E97vEkvtxGBoT3rR2b7Vjk/img.png&quot; data-phocus=&quot;https://blog.kakaocdn.net/dn/bPLXFf/dJMcabWZn9H/E97vEkvtxGBoT3rR2b7Vjk/img.png&quot;&gt;&lt;img src=&quot;https://blog.kakaocdn.net/dn/bPLXFf/dJMcabWZn9H/E97vEkvtxGBoT3rR2b7Vjk/img.png&quot; srcset=&quot;https://img1.daumcdn.net/thumb/R1280x0/?scode=mtistory2&amp;fname=https%3A%2F%2Fblog.kakaocdn.net%2Fdn%2FbPLXFf%2FdJMcabWZn9H%2FE97vEkvtxGBoT3rR2b7Vjk%2Fimg.png&quot; onerror=&quot;this.onerror=null; this.src='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png'; this.srcset='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png';&quot; loading=&quot;lazy&quot; width=&quot;727&quot; height=&quot;55&quot; data-origin-width=&quot;1289&quot; data-origin-height=&quot;97&quot;/&gt;&lt;/span&gt;&lt;/figure&gt;
&lt;figure class=&quot;imageblock alignCenter&quot; data-ke-mobileStyle=&quot;widthOrigin&quot; data-origin-width=&quot;1914&quot; data-origin-height=&quot;335&quot;&gt;&lt;span data-url=&quot;https://blog.kakaocdn.net/dn/cMLDse/dJMcag44zLY/URT2Kiaj2pNPTJzI2l3BuK/img.png&quot; data-phocus=&quot;https://blog.kakaocdn.net/dn/cMLDse/dJMcag44zLY/URT2Kiaj2pNPTJzI2l3BuK/img.png&quot;&gt;&lt;img src=&quot;https://blog.kakaocdn.net/dn/cMLDse/dJMcag44zLY/URT2Kiaj2pNPTJzI2l3BuK/img.png&quot; srcset=&quot;https://img1.daumcdn.net/thumb/R1280x0/?scode=mtistory2&amp;fname=https%3A%2F%2Fblog.kakaocdn.net%2Fdn%2FcMLDse%2FdJMcag44zLY%2FURT2Kiaj2pNPTJzI2l3BuK%2Fimg.png&quot; onerror=&quot;this.onerror=null; this.src='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png'; this.srcset='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png';&quot; loading=&quot;lazy&quot; width=&quot;701&quot; height=&quot;123&quot; data-origin-width=&quot;1914&quot; data-origin-height=&quot;335&quot;/&gt;&lt;/span&gt;&lt;/figure&gt;
&lt;/p&gt;
&lt;h3 data-ke-size=&quot;size23&quot;&gt;Nginx의 내용 바꾸기&lt;/h3&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;'Welcome to nginx!'말고 다른 내용으로 바꾸는 법을 알고 싶었다. 바꿀 내용을 `index.html`파일로 만들었다. (`index.html` 파일명이 중요했다.)&lt;/p&gt;
&lt;p&gt;&lt;figure class=&quot;imageblock alignCenter&quot; data-ke-mobileStyle=&quot;widthOrigin&quot; data-origin-width=&quot;621&quot; data-origin-height=&quot;220&quot;&gt;&lt;span data-url=&quot;https://blog.kakaocdn.net/dn/sPWHR/dJMcacn35Vh/0E0wrzCB1jPjA04ZEovZs0/img.png&quot; data-phocus=&quot;https://blog.kakaocdn.net/dn/sPWHR/dJMcacn35Vh/0E0wrzCB1jPjA04ZEovZs0/img.png&quot; data-alt=&quot;index.html&quot;&gt;&lt;img src=&quot;https://blog.kakaocdn.net/dn/sPWHR/dJMcacn35Vh/0E0wrzCB1jPjA04ZEovZs0/img.png&quot; srcset=&quot;https://img1.daumcdn.net/thumb/R1280x0/?scode=mtistory2&amp;fname=https%3A%2F%2Fblog.kakaocdn.net%2Fdn%2FsPWHR%2FdJMcacn35Vh%2F0E0wrzCB1jPjA04ZEovZs0%2Fimg.png&quot; onerror=&quot;this.onerror=null; this.src='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png'; this.srcset='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png';&quot; loading=&quot;lazy&quot; width=&quot;621&quot; height=&quot;220&quot; data-origin-width=&quot;621&quot; data-origin-height=&quot;220&quot;/&gt;&lt;/span&gt;&lt;figcaption&gt;index.html&lt;/figcaption&gt;
&lt;/figure&gt;
&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;그 다음 같은 폴더에 `Dockerfile`이라는 이름으로 파일을 만들었다.&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;677&quot; data-origin-height=&quot;207&quot;&gt;&lt;span data-url=&quot;https://blog.kakaocdn.net/dn/QORRy/dJMcaacH2zg/mwZPgrly097m2gha2tlOEK/img.png&quot; data-phocus=&quot;https://blog.kakaocdn.net/dn/QORRy/dJMcaacH2zg/mwZPgrly097m2gha2tlOEK/img.png&quot; data-alt=&quot;Dockerfile&quot;&gt;&lt;img src=&quot;https://blog.kakaocdn.net/dn/QORRy/dJMcaacH2zg/mwZPgrly097m2gha2tlOEK/img.png&quot; srcset=&quot;https://img1.daumcdn.net/thumb/R1280x0/?scode=mtistory2&amp;fname=https%3A%2F%2Fblog.kakaocdn.net%2Fdn%2FQORRy%2FdJMcaacH2zg%2FmwZPgrly097m2gha2tlOEK%2Fimg.png&quot; onerror=&quot;this.onerror=null; this.src='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png'; this.srcset='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png';&quot; loading=&quot;lazy&quot; width=&quot;677&quot; height=&quot;207&quot; data-origin-width=&quot;677&quot; data-origin-height=&quot;207&quot;/&gt;&lt;/span&gt;&lt;figcaption&gt;Dockerfile&lt;/figcaption&gt;
&lt;/figure&gt;
&lt;/p&gt;
&lt;ul style=&quot;list-style-type: disc;&quot; data-ke-list-type=&quot;disc&quot;&gt;
&lt;li&gt;`&lt;span&gt;FROM&lt;/span&gt; nginx:latest`: 베이스 이미지는 nginx를 쓰겠다&lt;/li&gt;
&lt;li&gt;`&lt;span&gt;COPY&lt;/span&gt;&lt;span&gt; index.html /usr/share/nginx/html/index.html`: 내가 만든 html 파일을 컨테이너 안의 해당 경로 파일 덮어씌워라&lt;/span&gt;&lt;/li&gt;
&lt;/ul&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;이 바꾼 `index.html`을 또 다른 이미지로 만들어보자.&lt;/p&gt;
&lt;pre id=&quot;code_1764923639206&quot; class=&quot;bash&quot; data-ke-language=&quot;bash&quot; data-ke-type=&quot;codeblock&quot;&gt;&lt;code&gt;docker build -t my-custom-nginx .&lt;/code&gt;&lt;/pre&gt;
&lt;ul style=&quot;list-style-type: disc;&quot; data-ke-list-type=&quot;disc&quot;&gt;
&lt;li&gt;`-t my-custom-nginx`: 이미지 이름을 `my-custom-nginx`라고 짓겠다.&lt;/li&gt;
&lt;/ul&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;내가 만든 Dockerfile을 Docker image로 만든 것이다.&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;610&quot; data-origin-height=&quot;135&quot;&gt;&lt;span data-url=&quot;https://blog.kakaocdn.net/dn/bCPbUo/dJMcagKLy14/KcBYgog2r8T858qgT0kV1K/img.png&quot; data-phocus=&quot;https://blog.kakaocdn.net/dn/bCPbUo/dJMcagKLy14/KcBYgog2r8T858qgT0kV1K/img.png&quot;&gt;&lt;img src=&quot;https://blog.kakaocdn.net/dn/bCPbUo/dJMcagKLy14/KcBYgog2r8T858qgT0kV1K/img.png&quot; srcset=&quot;https://img1.daumcdn.net/thumb/R1280x0/?scode=mtistory2&amp;fname=https%3A%2F%2Fblog.kakaocdn.net%2Fdn%2FbCPbUo%2FdJMcagKLy14%2FKcBYgog2r8T858qgT0kV1K%2Fimg.png&quot; onerror=&quot;this.onerror=null; this.src='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png'; this.srcset='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png';&quot; loading=&quot;lazy&quot; width=&quot;610&quot; height=&quot;135&quot; data-origin-width=&quot;610&quot; data-origin-height=&quot;135&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;657&quot; data-origin-height=&quot;252&quot;&gt;&lt;span data-url=&quot;https://blog.kakaocdn.net/dn/tgCJR/dJMcagxeaPn/6Kno037dipJFGWvMyQMkL1/img.png&quot; data-phocus=&quot;https://blog.kakaocdn.net/dn/tgCJR/dJMcagxeaPn/6Kno037dipJFGWvMyQMkL1/img.png&quot;&gt;&lt;img src=&quot;https://blog.kakaocdn.net/dn/tgCJR/dJMcagxeaPn/6Kno037dipJFGWvMyQMkL1/img.png&quot; srcset=&quot;https://img1.daumcdn.net/thumb/R1280x0/?scode=mtistory2&amp;fname=https%3A%2F%2Fblog.kakaocdn.net%2Fdn%2FtgCJR%2FdJMcagxeaPn%2F6Kno037dipJFGWvMyQMkL1%2Fimg.png&quot; onerror=&quot;this.onerror=null; this.src='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png'; this.srcset='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png';&quot; loading=&quot;lazy&quot; width=&quot;657&quot; height=&quot;252&quot; data-origin-width=&quot;657&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;8080포트는 이미 사용하고 있어서 8081포트를 사용했다.&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;이번엔 기존 컨테이너를 지우고, 그리고 다음과 같은 명령어를 실행했다.&lt;/p&gt;
&lt;pre id=&quot;code_1764924745360&quot; class=&quot;bash&quot; data-ke-language=&quot;bash&quot; data-ke-type=&quot;codeblock&quot;&gt;&lt;code&gt;docker run -d -p 8081:80 -v .:/usr/share/nginx/html nginx&lt;/code&gt;&lt;/pre&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;br /&gt;그리고 &lt;span style=&quot;color: #333333; text-align: start;&quot;&gt;`index.html`의 값을 바꾸고 저장했다.&lt;span&gt;&amp;nbsp;&lt;/span&gt;&lt;/span&gt;&lt;/p&gt;
&lt;p&gt;&lt;figure class=&quot;imageblock alignCenter&quot; data-ke-mobileStyle=&quot;widthOrigin&quot; data-origin-width=&quot;648&quot; data-origin-height=&quot;228&quot;&gt;&lt;span data-url=&quot;https://blog.kakaocdn.net/dn/qbRzk/dJMcabo9mF4/pnzNpqzg2rsbpjjbPKPhWk/img.png&quot; data-phocus=&quot;https://blog.kakaocdn.net/dn/qbRzk/dJMcabo9mF4/pnzNpqzg2rsbpjjbPKPhWk/img.png&quot; data-alt=&quot;index.html&quot;&gt;&lt;img src=&quot;https://blog.kakaocdn.net/dn/qbRzk/dJMcabo9mF4/pnzNpqzg2rsbpjjbPKPhWk/img.png&quot; srcset=&quot;https://img1.daumcdn.net/thumb/R1280x0/?scode=mtistory2&amp;fname=https%3A%2F%2Fblog.kakaocdn.net%2Fdn%2FqbRzk%2FdJMcabo9mF4%2FpnzNpqzg2rsbpjjbPKPhWk%2Fimg.png&quot; onerror=&quot;this.onerror=null; this.src='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png'; this.srcset='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png';&quot; loading=&quot;lazy&quot; width=&quot;648&quot; height=&quot;228&quot; data-origin-width=&quot;648&quot; data-origin-height=&quot;228&quot;/&gt;&lt;/span&gt;&lt;figcaption&gt;index.html&lt;/figcaption&gt;
&lt;/figure&gt;
&lt;figure class=&quot;imagegridblock&quot;&gt;
  &lt;div class=&quot;image-container&quot;&gt;&lt;span data-url=&quot;https://blog.kakaocdn.net/dn/tgCJR/dJMcagxeaPn/6Kno037dipJFGWvMyQMkL1/img.png&quot; data-phocus=&quot;https://blog.kakaocdn.net/dn/tgCJR/dJMcagxeaPn/6Kno037dipJFGWvMyQMkL1/img.png&quot; data-is-animation=&quot;false&quot; data-origin-height=&quot;252&quot; data-origin-width=&quot;657&quot; width=&quot;561&quot; height=&quot;215&quot; style=&quot;width: 53.9855%; margin-right: 10px;&quot; data-widthpercent=&quot;54.62&quot;&gt;&lt;img src=&quot;https://blog.kakaocdn.net/dn/tgCJR/dJMcagxeaPn/6Kno037dipJFGWvMyQMkL1/img.png&quot; srcset=&quot;https://img1.daumcdn.net/thumb/R1280x0/?scode=mtistory2&amp;amp;fname=https%3A%2F%2Fblog.kakaocdn.net%2Fdn%2FtgCJR%2FdJMcagxeaPn%2F6Kno037dipJFGWvMyQMkL1%2Fimg.png&quot; onerror=&quot;this.onerror=null; this.src='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png'; this.srcset='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png';&quot; loading=&quot;lazy&quot; width=&quot;657&quot; height=&quot;252&quot;/&gt;&lt;/span&gt;&lt;span data-url=&quot;https://blog.kakaocdn.net/dn/cF3XyD/dJMcahpnMMj/4yTJU640ccvvbQJD7ghHj1/img.png&quot; data-phocus=&quot;https://blog.kakaocdn.net/dn/cF3XyD/dJMcahpnMMj/4yTJU640ccvvbQJD7ghHj1/img.png&quot; data-origin-width=&quot;574&quot; data-origin-height=&quot;265&quot; data-is-animation=&quot;false&quot; width=&quot;561&quot; height=&quot;259&quot; style=&quot;width: 44.8517%;&quot; data-widthpercent=&quot;45.38&quot;&gt;&lt;img src=&quot;https://blog.kakaocdn.net/dn/cF3XyD/dJMcahpnMMj/4yTJU640ccvvbQJD7ghHj1/img.png&quot; srcset=&quot;https://img1.daumcdn.net/thumb/R1280x0/?scode=mtistory2&amp;amp;fname=https%3A%2F%2Fblog.kakaocdn.net%2Fdn%2FcF3XyD%2FdJMcahpnMMj%2F4yTJU640ccvvbQJD7ghHj1%2Fimg.png&quot; onerror=&quot;this.onerror=null; this.src='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png'; this.srcset='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png';&quot; loading=&quot;lazy&quot; width=&quot;574&quot; height=&quot;265&quot;/&gt;&lt;/span&gt;&lt;/div&gt;
&lt;/figure&gt;
&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;실시간으로 내가 변경한 값으로 바뀐 것을 확인할 수 있었다. 왜 바뀔 수 있는걸까? 내가 앞서 설정했던 `COPY&lt;span style=&quot;color: #333333; text-align: start;&quot;&gt;&lt;span&gt;&amp;nbsp;&lt;/span&gt;index.html /usr/share/nginx/html/index.html`는 `my-custom-nginx` 이미지에만 설정되어 있고 nginx에는 설정되지 않았을 건데 말이다.&amp;nbsp;&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: #333333; text-align: start;&quot;&gt;그 이유는 `-v`에 있다. ` -v .:/usr/share/nginx/html`를 해석해보자.&lt;/span&gt;&lt;/p&gt;
&lt;ul style=&quot;list-style-type: disc;&quot; data-ke-list-type=&quot;disc&quot;&gt;
&lt;li&gt;`-v`(Volume): 저장소에 연결&lt;/li&gt;
&lt;li&gt;`.`(Host 경로):&amp;nbsp;
&lt;ul style=&quot;list-style-type: disc;&quot; data-ke-list-type=&quot;disc&quot;&gt;
&lt;li&gt;&quot;내 컴퓨터의 현재 터미널이 위치한 폴더&quot;를 의미&lt;/li&gt;
&lt;/ul&gt;
&lt;/li&gt;
&lt;li&gt;`:`(구분자): 연결할 경로&lt;/li&gt;
&lt;li&gt;`/usr/share/nginx/html`(Container 경로):
&lt;ul style=&quot;list-style-type: disc;&quot; data-ke-list-type=&quot;disc&quot;&gt;
&lt;li&gt;Nginx가 기본적으로 `index.html` 파일을 찾아서 보여주는 약속된 내부 폴더&lt;/li&gt;
&lt;/ul&gt;
&lt;/li&gt;
&lt;/ul&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;원래 Nginx 이미지 안에는 'Welcome to nginx!'라고 적힌 기본 index.html 파일이 들어있다. 하지만 위 명령어를 치는 순간, &lt;b&gt;컨테이너 내부의 그 폴더가 내 컴퓨터의 현재 폴더로 덮어씌워진다.&amp;nbsp;&lt;/b&gt;Docker는 컨테이너를 재시작할 필요 없이, 브라우저에서 새로고침만 하면 바로 반영이 된다.&amp;nbsp;&lt;/p&gt;
&lt;blockquote data-ke-style=&quot;style3&quot;&gt; &lt;span style=&quot;color: #000000;&quot;&gt; 사실 `my-custom-nginx`를 실행하고 `index.html`을 바꾸어도 반영되지 않는다. 이는 `COPY`와 `Volume(-v)`의 결정적인 차이다. &lt;b&gt;`COPY`&lt;/b&gt;는 이미지를 빌드하는 순간에 파일을 복사해서 이미지에 넣는 것이기 때문에 &lt;u&gt;&lt;b&gt;스냅샷의 개념&lt;/b&gt;&lt;/u&gt;이고, &lt;b&gt;`Volume(-v)`&lt;/b&gt;은 내 폴더를 직접 컨테이너에 연결하기 때문에 &lt;u&gt;&lt;b&gt;실시간으로 변경이 가능&lt;/b&gt;&lt;/u&gt;하다.&amp;nbsp;&lt;/span&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;h3 data-ke-size=&quot;size23&quot;&gt;docker compose로 Nginx와 Mysql을 동시에 실행해보기&lt;/h3&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;docker compose는 여러 개의 컨테이너를 정의하고 실행하기 위한 도구이다. docker compose가 없었다면, 앞에서 했던 `docker run`을 매번 하나씩 해야한다. docker compose는 이런 과정을 `docker-compose.yml`이라는 파일에 적어두고, 한 번의 명령어로 실행한다.&amp;nbsp;&lt;/p&gt;
&lt;pre id=&quot;code_1764926738098&quot; class=&quot;bash&quot; data-ke-language=&quot;bash&quot; data-ke-type=&quot;codeblock&quot;&gt;&lt;code&gt;docker-compose up -d&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;&lt;figure class=&quot;imageblock alignCenter&quot; data-ke-mobileStyle=&quot;widthOrigin&quot; data-origin-width=&quot;670&quot; data-origin-height=&quot;499&quot;&gt;&lt;span data-url=&quot;https://blog.kakaocdn.net/dn/bdzgVT/dJMcahCTWLP/39q3lvJJepWyw21BvT70CK/img.png&quot; data-phocus=&quot;https://blog.kakaocdn.net/dn/bdzgVT/dJMcahCTWLP/39q3lvJJepWyw21BvT70CK/img.png&quot; data-alt=&quot;docker-compose.yml&quot;&gt;&lt;img src=&quot;https://blog.kakaocdn.net/dn/bdzgVT/dJMcahCTWLP/39q3lvJJepWyw21BvT70CK/img.png&quot; srcset=&quot;https://img1.daumcdn.net/thumb/R1280x0/?scode=mtistory2&amp;fname=https%3A%2F%2Fblog.kakaocdn.net%2Fdn%2FbdzgVT%2FdJMcahCTWLP%2F39q3lvJJepWyw21BvT70CK%2Fimg.png&quot; onerror=&quot;this.onerror=null; this.src='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png'; this.srcset='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png';&quot; loading=&quot;lazy&quot; width=&quot;682&quot; height=&quot;508&quot; data-origin-width=&quot;670&quot; data-origin-height=&quot;499&quot;/&gt;&lt;/span&gt;&lt;figcaption&gt;docker-compose.yml&lt;/figcaption&gt;
&lt;/figure&gt;
&lt;figure class=&quot;imageblock alignCenter&quot; data-ke-mobileStyle=&quot;widthOrigin&quot; data-origin-width=&quot;1399&quot; data-origin-height=&quot;196&quot;&gt;&lt;span data-url=&quot;https://blog.kakaocdn.net/dn/c0MAdM/dJMcaajtLDU/Iz9j12U94r0lrqdIJE8nc1/img.png&quot; data-phocus=&quot;https://blog.kakaocdn.net/dn/c0MAdM/dJMcaajtLDU/Iz9j12U94r0lrqdIJE8nc1/img.png&quot;&gt;&lt;img src=&quot;https://blog.kakaocdn.net/dn/c0MAdM/dJMcaajtLDU/Iz9j12U94r0lrqdIJE8nc1/img.png&quot; srcset=&quot;https://img1.daumcdn.net/thumb/R1280x0/?scode=mtistory2&amp;fname=https%3A%2F%2Fblog.kakaocdn.net%2Fdn%2Fc0MAdM%2FdJMcaajtLDU%2FIz9j12U94r0lrqdIJE8nc1%2Fimg.png&quot; onerror=&quot;this.onerror=null; this.src='//t1.daumcdn.net/tistory_admin/static/images/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;108&quot; data-origin-width=&quot;1399&quot; data-origin-height=&quot;196&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;680&quot; data-origin-height=&quot;522&quot;&gt;&lt;span data-url=&quot;https://blog.kakaocdn.net/dn/t963f/dJMcafd1y7R/Yrs1Tl5kENAeHFQDYM2smK/img.png&quot; data-phocus=&quot;https://blog.kakaocdn.net/dn/t963f/dJMcafd1y7R/Yrs1Tl5kENAeHFQDYM2smK/img.png&quot; data-alt=&quot;Docker mysql 컨테이너와 mysql workbench 연결&quot;&gt;&lt;img src=&quot;https://blog.kakaocdn.net/dn/t963f/dJMcafd1y7R/Yrs1Tl5kENAeHFQDYM2smK/img.png&quot; srcset=&quot;https://img1.daumcdn.net/thumb/R1280x0/?scode=mtistory2&amp;fname=https%3A%2F%2Fblog.kakaocdn.net%2Fdn%2Ft963f%2FdJMcafd1y7R%2FYrs1Tl5kENAeHFQDYM2smK%2Fimg.png&quot; onerror=&quot;this.onerror=null; this.src='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png'; this.srcset='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png';&quot; loading=&quot;lazy&quot; width=&quot;575&quot; height=&quot;441&quot; data-origin-width=&quot;680&quot; data-origin-height=&quot;522&quot;/&gt;&lt;/span&gt;&lt;figcaption&gt;Docker mysql 컨테이너와 mysql workbench 연결&lt;/figcaption&gt;
&lt;/figure&gt;
&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;docker compose로 nginx와 mysql이 잘 올라가 있는걸 확인할 수 있었고, mysql workbench와도 연결한 걸 확인할 수 있었다.&amp;nbsp;&lt;/p&gt;
&lt;blockquote data-ke-style=&quot;style3&quot;&gt; &lt;span style=&quot;color: #000000;&quot;&gt;`-1`은 왜 붙을까?&lt;br /&gt;docker compose는 기본적으로 컨테이너가 여러 개 늘어날 수도 있다고 가정하고 이름을 짓기 때문에, docker가 알아서 index를 붙힌다.&amp;nbsp;&lt;/span&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;'직접 해보기'를 통해서 docker의 기본적인 구성 요소를 이해할 수 있었다. 특히 `COPY`와 `Volume(-v)`의 차이가 인상 깊었고, 앞으로 소스 코드를 수정하면 바로 반영되는 `Volume(-v)`을 잘 사용해 개발 생산성을 올려야겠다. 다음엔 이 Image들을 AWS EC2에 올려서 public하게 접근이 가능하도록 해볼 예정이다.&amp;nbsp;&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;h4 data-ke-size=&quot;size20&quot;&gt;References&lt;/h4&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;a href=&quot;https://geekflare.com/devops/docker-vs-virtual-machine/&quot;&gt;https://geekflare.com/devops/docker-vs-virtual-machine/&lt;/a&gt;&lt;/p&gt;
&lt;figure id=&quot;og_1764921494184&quot; contenteditable=&quot;false&quot; data-ke-type=&quot;opengraph&quot; data-ke-align=&quot;alignCenter&quot; data-og-type=&quot;article&quot; data-og-title=&quot;Docker vs Virtual Machine (VM) - Understanding the Differences&quot; data-og-description=&quot;Learn the differences between the key features of Docker containers and virtual machines to make informed decisions for your infrastructure.&quot; data-og-host=&quot;geekflare.com&quot; data-og-source-url=&quot;https://geekflare.com/devops/docker-vs-virtual-machine/&quot; data-og-url=&quot;https://geekflare.com/devops/docker-vs-virtual-machine/&quot; data-og-image=&quot;https://scrap.kakaocdn.net/dn/potCm/hyZOFBj6xg/ovLsuqSj3BO3qF3un3nEA0/img.jpg?width=1200&amp;amp;height=630&amp;amp;face=0_0_1200_630&quot;&gt;&lt;a href=&quot;https://geekflare.com/devops/docker-vs-virtual-machine/&quot; target=&quot;_blank&quot; rel=&quot;noopener&quot; data-source-url=&quot;https://geekflare.com/devops/docker-vs-virtual-machine/&quot;&gt;
&lt;div class=&quot;og-image&quot; style=&quot;background-image: url('https://scrap.kakaocdn.net/dn/potCm/hyZOFBj6xg/ovLsuqSj3BO3qF3un3nEA0/img.jpg?width=1200&amp;amp;height=630&amp;amp;face=0_0_1200_630');&quot;&gt;&amp;nbsp;&lt;/div&gt;
&lt;div class=&quot;og-text&quot;&gt;
&lt;p class=&quot;og-title&quot; data-ke-size=&quot;size16&quot;&gt;Docker vs Virtual Machine (VM) - Understanding the Differences&lt;/p&gt;
&lt;p class=&quot;og-desc&quot; data-ke-size=&quot;size16&quot;&gt;Learn the differences between the key features of Docker containers and virtual machines to make informed decisions for your infrastructure.&lt;/p&gt;
&lt;p class=&quot;og-host&quot; data-ke-size=&quot;size16&quot;&gt;geekflare.com&lt;/p&gt;
&lt;/div&gt;
&lt;/a&gt;&lt;/figure&gt;</description>
      <category>Infra</category>
      <author>Ukjong</author>
      <guid isPermaLink="true">https://ukj0ng.tistory.com/61</guid>
      <comments>https://ukj0ng.tistory.com/61#entry61comment</comments>
      <pubDate>Fri, 5 Dec 2025 18:34:58 +0900</pubDate>
    </item>
    <item>
      <title>[Network] HTTPS는 정말 안전할까? : TLS Handshake의 원리와 PFS</title>
      <link>https://ukj0ng.tistory.com/60</link>
      <description>&lt;p data-ke-size=&quot;size16&quot;&gt;백엔드 개발자로서 HTTPS가 안전하다는 것은 누구나 안다. 하지만 &lt;b&gt;&quot;HTTPS 통신 중 서버의 개인키(Private Key)가 탈취되면 과거의 데이터는 안전한가?&quot;&lt;/b&gt;라는 질문을 받는다면 자신 있게 대답할 수 있을까?&amp;nbsp;&lt;br /&gt;&lt;br /&gt;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;오늘은 단순히 HTTPS의 흐름을 넘어, &lt;b&gt;RSA&lt;/b&gt;와 &lt;b&gt;Diffie-Hellman(ECDHE)&lt;/b&gt;의 결정적 차이, 완전 순방향 비밀성(PFS)의 원리에 대해 정리해 본다.&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;1. 키 교환의 두 가지 경우: RSA vs Diffie-Hellman&lt;/h2&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;HTTPS 핸드쉐이크의 핵심은 &lt;span style=&quot;color: #ee2323;&quot;&gt;&lt;b&gt;&quot;데이터를 암호화할 대칭키를 어떻게 안전하게 나눠가질 것인가?&quot;&lt;/b&gt;&lt;/span&gt;이다. 여기엔 크게 두 가지 방식이 있다.&amp;nbsp;&lt;/p&gt;
&lt;h3 data-ke-size=&quot;size23&quot;&gt;RSA&lt;/h3&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;RSA는 클라이언트가 직접 키를 만들어 교환한다. 클라이언트가 PMS(Pre-Master Secret)를 생성한 뒤, 서버의 공개키로 암호화해서 전송한다. (참고: PMS는 48바이트로, TLS 버전정보 2바이트와 난수 46바이트로 구성된다.)&lt;/p&gt;
&lt;p&gt;&lt;figure class=&quot;imageblock alignCenter&quot; data-ke-mobileStyle=&quot;widthOrigin&quot; data-origin-width=&quot;689&quot; data-origin-height=&quot;883&quot;&gt;&lt;span data-url=&quot;https://blog.kakaocdn.net/dn/TOwI4/dJMb99SpxAG/Em3JSktsgCZeAJR34LREfK/img.png&quot; data-phocus=&quot;https://blog.kakaocdn.net/dn/TOwI4/dJMb99SpxAG/Em3JSktsgCZeAJR34LREfK/img.png&quot; data-alt=&quot;그림1-RSA의 동작과정&quot;&gt;&lt;img src=&quot;https://blog.kakaocdn.net/dn/TOwI4/dJMb99SpxAG/Em3JSktsgCZeAJR34LREfK/img.png&quot; srcset=&quot;https://img1.daumcdn.net/thumb/R1280x0/?scode=mtistory2&amp;fname=https%3A%2F%2Fblog.kakaocdn.net%2Fdn%2FTOwI4%2FdJMb99SpxAG%2FEm3JSktsgCZeAJR34LREfK%2Fimg.png&quot; onerror=&quot;this.onerror=null; this.src='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png'; this.srcset='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png';&quot; loading=&quot;lazy&quot; width=&quot;549&quot; height=&quot;704&quot; data-origin-width=&quot;689&quot; data-origin-height=&quot;883&quot;/&gt;&lt;/span&gt;&lt;figcaption&gt;그림1-RSA의 동작과정&lt;/figcaption&gt;
&lt;/figure&gt;
&lt;/p&gt;
&lt;h3 data-ke-size=&quot;size23&quot;&gt;Diffie-Hellman(ECDHE)&lt;/h3&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;span style=&quot;color: #333333; text-align: start;&quot;&gt;Diffie-Hellman(특히 Ephemeral 버전인 ECDHE)은 키를 배달하지 않고,&lt;span&gt;&amp;nbsp;&lt;/span&gt;&lt;/span&gt;&lt;b&gt;서로 계산하여 유도&lt;/b&gt;해낸다. 서로 공개된 파라미터(&lt;span data-math=&quot;g, p&quot;&gt;g, p&lt;/span&gt;)를 교환한 뒤, 각자 비밀리에 만든 난수(&lt;span data-math=&quot;a, b&quot;&gt;a, b&lt;/span&gt;)를 활용해 결과적으로 동일한 PMS를 생성해낸다.&lt;/p&gt;
&lt;p&gt;&lt;figure class=&quot;imageblock alignCenter&quot; data-ke-mobileStyle=&quot;widthOrigin&quot; data-origin-width=&quot;1144&quot; data-origin-height=&quot;2378&quot;&gt;&lt;span data-url=&quot;https://blog.kakaocdn.net/dn/bx7K7v/dJMcacBBmuq/Yyd1ynv3P1qsS1TYKso0OK/img.png&quot; data-phocus=&quot;https://blog.kakaocdn.net/dn/bx7K7v/dJMcacBBmuq/Yyd1ynv3P1qsS1TYKso0OK/img.png&quot; data-alt=&quot;그림2-Diffie-Hellman의 동작과정&quot;&gt;&lt;img src=&quot;https://blog.kakaocdn.net/dn/bx7K7v/dJMcacBBmuq/Yyd1ynv3P1qsS1TYKso0OK/img.png&quot; srcset=&quot;https://img1.daumcdn.net/thumb/R1280x0/?scode=mtistory2&amp;fname=https%3A%2F%2Fblog.kakaocdn.net%2Fdn%2Fbx7K7v%2FdJMcacBBmuq%2FYyd1ynv3P1qsS1TYKso0OK%2Fimg.png&quot; onerror=&quot;this.onerror=null; this.src='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png'; this.srcset='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png';&quot; loading=&quot;lazy&quot; width=&quot;700&quot; height=&quot;1455&quot; data-origin-width=&quot;1144&quot; data-origin-height=&quot;2378&quot;/&gt;&lt;/span&gt;&lt;figcaption&gt;그림2-Diffie-Hellman의 동작과정&lt;/figcaption&gt;
&lt;/figure&gt;
&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;두 방식 모두 결과적으로 PMS를 만들어내고, 이를 바탕으로 실제 통신에 사용할 PM(Master Secret)을 만들고 이를 해싱해서 사용한다는 점은 같다.&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;2. 서버 개인키가 털리면 과거 데이터도 털릴까?&lt;/h2&gt;
&lt;h3 data-ke-size=&quot;size23&quot;&gt;RSA&lt;/h3&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;RSA는 &lt;b&gt;과거 데이터까지 전부 털린다.&lt;/b&gt; RSA 방식은 치명적인 약점이 있다. 패킷 안에 들어있는 PMS가 &lt;b&gt;서버의 공개키로 암호화&lt;/b&gt;되어 전송되기 때문이다. 만약 해커가 패킷을 전부 캡해 둔 상태에서, 나중에라도 서버의 &lt;b&gt;개인키&lt;/b&gt;를 탈취하는 데 성공한다면 해커는 개인키로 과거의 패킷을 복호화하여 PMS를 알아낼 수 있고, 이를 통해 모든 대화 내용을 복구할 수 있다.&lt;/p&gt;
&lt;h3 data-ke-size=&quot;size23&quot;&gt;Diffie-Hellman(ECDHE)&lt;/h3&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;Diffie-Hellman의 경우, &lt;b&gt;과거 데이터는 안전하다&lt;/b&gt;. 이를 &lt;b&gt;완전 순방향 비밀성(PFS)&lt;/b&gt;이라고도 한다.&amp;nbsp;이유는 단순하다. 서버의 개인키는 오직 &quot;이 파라미터 내가 보낸 거 맞아&quot;라고 인증하는 데만 쓰인다. 실제로 암호화 키(PMS)를 만든 파라미터인 난수는 통신이 끝나자마자 메모리에서 삭제된다. 따라서 해커가 나중에 서버 개인키를 훔쳐와도, 이미 사라진 난수를 복구할 수 없어 과거 데이터를 풀 수 없다.&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;이렇게만 표현하면, &quot;중간에 해커가 파라미터를 다 전달하는데, 이걸 못 복원한다고?&quot;라고 생각할 수도 있고 나 역시 그랬다. 조금 더 자세히 알아보자.&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;'그림2'의 3번과정에서 b는 매번 다르다. 랜덤한 b를 만들어서 연결하고 &lt;b&gt;연결이 끝나면 즉시 삭제&lt;/b&gt;한다. (OTP와 비슷한 개념이다.) 9번과정에서 만든 a도 동일하다. 사전 hello에서 공유한 g에 각자의 랜덤 수를 제곱한 다음 각각 A, B라는 파라미터를 서로에게 전달한다. 그럼 해커는 {g, A, B}라는 값을 갖게 된다.&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;Diffie-Hellman에선 해커는&lt;/p&gt;
&lt;p&gt;&lt;figure class=&quot;imageblock alignCenter&quot; data-ke-mobileStyle=&quot;widthOrigin&quot; data-origin-width=&quot;151&quot; data-origin-height=&quot;27&quot;&gt;&lt;span data-url=&quot;https://blog.kakaocdn.net/dn/o5Hvm/dJMcadUKOsI/zXIkXFq04Rn658nKV3Dg6K/img.png&quot; data-phocus=&quot;https://blog.kakaocdn.net/dn/o5Hvm/dJMcadUKOsI/zXIkXFq04Rn658nKV3Dg6K/img.png&quot;&gt;&lt;img src=&quot;https://blog.kakaocdn.net/dn/o5Hvm/dJMcadUKOsI/zXIkXFq04Rn658nKV3Dg6K/img.png&quot; srcset=&quot;https://img1.daumcdn.net/thumb/R1280x0/?scode=mtistory2&amp;fname=https%3A%2F%2Fblog.kakaocdn.net%2Fdn%2Fo5Hvm%2FdJMcadUKOsI%2FzXIkXFq04Rn658nKV3Dg6K%2Fimg.png&quot; onerror=&quot;this.onerror=null; this.src='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png'; this.srcset='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png';&quot; loading=&quot;lazy&quot; width=&quot;151&quot; height=&quot;27&quot; data-origin-width=&quot;151&quot; data-origin-height=&quot;27&quot;/&gt;&lt;/span&gt;&lt;/figure&gt;
&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;다음과 같은 수식을 풀어야 한다. (p 역시 사전 hello에서 서로 공유한다.)&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;클라이언트와 서버는 받은 파라미터에 a, b를 곱해서 PMS를 만들면 되지만, &lt;u&gt;&lt;b&gt;해커는 A에서 a를 추출한 다음 g^{ab}를 계산해야 한다. 이 계산이 현대 컴퓨터론 연산이 많아 불가능하다.&lt;/b&gt;&lt;/u&gt; (양자 컴퓨터가 상용화될 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;여기서 서버의 개인키를 탈취당해도 서명은 5단계, 10단계에서 암호화에 사용되기 때문에 해커가 이를 복호화해서 만들 수 있는 데이터는 A와 B 뿐이다. 따라서 해커는 PM을 만들 수 없기 때문에 기존에 탈취당했던 암호화된 패킷은 안전하다.&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;/h2&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;TLS 1.3부턴 RSA를 사용하지 않고 Diffie-Hellman 방식을 사용한다고 한다. 하지만 양자 컴퓨터가 상용화된다면 아마 PQC와 같은 기술이 적용된 TLS 1.4가 나오지 않을까 싶다.&amp;nbsp;&lt;/p&gt;</description>
      <category>Network</category>
      <category>HTTP</category>
      <category>https</category>
      <category>TLS</category>
      <author>Ukjong</author>
      <guid isPermaLink="true">https://ukj0ng.tistory.com/60</guid>
      <comments>https://ukj0ng.tistory.com/60#entry60comment</comments>
      <pubDate>Fri, 5 Dec 2025 01:19:36 +0900</pubDate>
    </item>
  </channel>
</rss>