<?xml version="1.0" encoding="UTF-8"?>
<rss version="2.0">
  <channel>
    <title>Winter Breeze</title>
    <link>https://breeze-winter.tistory.com/</link>
    <description>백엔드와 인프라에 관심이 많은 IT 꿈나무입니다.
Email: dev_in_wonderland@naver.com</description>
    <language>ko</language>
    <pubDate>Fri, 8 May 2026 18:03:44 +0900</pubDate>
    <generator>TISTORY</generator>
    <ttl>100</ttl>
    <managingEditor>겨울바람_</managingEditor>
    <image>
      <title>Winter Breeze</title>
      <url>https://tistory1.daumcdn.net/tistory/6303885/attach/72fc1b1b34ec40fa909a4f7defbf80cd</url>
      <link>https://breeze-winter.tistory.com</link>
    </image>
    <item>
      <title>[Go] 컨텍스트(Context)</title>
      <link>https://breeze-winter.tistory.com/69</link>
      <description>&lt;p&gt;&lt;figure class=&quot;imageblock alignCenter&quot; data-ke-mobileStyle=&quot;widthOrigin&quot; data-origin-width=&quot;1000&quot; data-origin-height=&quot;1000&quot;&gt;&lt;span data-url=&quot;https://blog.kakaocdn.net/dn/6cIAy/btsKBh98d0b/8ZVYWLwDBbjQXfkgHDAYHK/img.png&quot; data-phocus=&quot;https://blog.kakaocdn.net/dn/6cIAy/btsKBh98d0b/8ZVYWLwDBbjQXfkgHDAYHK/img.png&quot;&gt;&lt;img src=&quot;https://blog.kakaocdn.net/dn/6cIAy/btsKBh98d0b/8ZVYWLwDBbjQXfkgHDAYHK/img.png&quot; srcset=&quot;https://img1.daumcdn.net/thumb/R1280x0/?scode=mtistory2&amp;fname=https%3A%2F%2Fblog.kakaocdn.net%2Fdn%2F6cIAy%2FbtsKBh98d0b%2F8ZVYWLwDBbjQXfkgHDAYHK%2Fimg.png&quot; onerror=&quot;this.onerror=null; this.src='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png'; this.srcset='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png';&quot; loading=&quot;lazy&quot; width=&quot;392&quot; height=&quot;392&quot; data-origin-width=&quot;1000&quot; data-origin-height=&quot;1000&quot;/&gt;&lt;/span&gt;&lt;/figure&gt;
&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;고루틴은 한 번 시작하면 외부에서 직접 멈출 방법이 없다. 채널로 &lt;code&gt;quit&lt;/code&gt; 신호를 보내는 방법이 있지만, 고루틴이 다른 고루틴을 생성하는 계층 구조에서 &lt;code&gt;quit&lt;/code&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;code&gt;context&lt;/code&gt;는 취소 신호를 트리 구조로 자동 전파하기 때문에 복잡한 계층 구조에서 효과적으로 고루틴을 정지시킬 수 있다.&lt;/p&gt;
&lt;p&gt;&lt;figure class=&quot;imageblock alignCenter&quot; data-ke-mobileStyle=&quot;widthOrigin&quot; data-origin-width=&quot;932&quot; data-origin-height=&quot;454&quot;&gt;&lt;span data-url=&quot;https://blog.kakaocdn.net/dn/cbYvwr/dJMcacQbPyw/TP4LQ1S5AqWkc5wwxYhdmK/img.png&quot; data-phocus=&quot;https://blog.kakaocdn.net/dn/cbYvwr/dJMcacQbPyw/TP4LQ1S5AqWkc5wwxYhdmK/img.png&quot;&gt;&lt;img src=&quot;https://blog.kakaocdn.net/dn/cbYvwr/dJMcacQbPyw/TP4LQ1S5AqWkc5wwxYhdmK/img.png&quot; srcset=&quot;https://img1.daumcdn.net/thumb/R1280x0/?scode=mtistory2&amp;fname=https%3A%2F%2Fblog.kakaocdn.net%2Fdn%2FcbYvwr%2FdJMcacQbPyw%2FTP4LQ1S5AqWkc5wwxYhdmK%2Fimg.png&quot; onerror=&quot;this.onerror=null; this.src='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png'; this.srcset='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png';&quot; loading=&quot;lazy&quot; width=&quot;569&quot; height=&quot;277&quot; data-origin-width=&quot;932&quot; data-origin-height=&quot;454&quot;/&gt;&lt;/span&gt;&lt;/figure&gt;
&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;위의 그림처럼 고루틴이 부모 자식 관계로 트리 구조를 형성해 있을 때, 문제가 발생하면 &lt;code&gt;context&lt;/code&gt;를 통해 취소 신호가 전파된다. 이를 실제로 구현하면 아래의 코드와 같다.&lt;/p&gt;
&lt;p&gt;&lt;figure class=&quot;imageblock alignCenter&quot; data-ke-mobileStyle=&quot;widthOrigin&quot; data-origin-width=&quot;3060&quot; data-origin-height=&quot;2704&quot;&gt;&lt;span data-url=&quot;https://blog.kakaocdn.net/dn/HP323/dJMcajaFEyF/Kkt3MkyWiM24Mijm0jhKEK/img.png&quot; data-phocus=&quot;https://blog.kakaocdn.net/dn/HP323/dJMcajaFEyF/Kkt3MkyWiM24Mijm0jhKEK/img.png&quot;&gt;&lt;img src=&quot;https://blog.kakaocdn.net/dn/HP323/dJMcajaFEyF/Kkt3MkyWiM24Mijm0jhKEK/img.png&quot; srcset=&quot;https://img1.daumcdn.net/thumb/R1280x0/?scode=mtistory2&amp;fname=https%3A%2F%2Fblog.kakaocdn.net%2Fdn%2FHP323%2FdJMcajaFEyF%2FKkt3MkyWiM24Mijm0jhKEK%2Fimg.png&quot; onerror=&quot;this.onerror=null; this.src='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png'; this.srcset='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png';&quot; loading=&quot;lazy&quot; width=&quot;638&quot; height=&quot;564&quot; data-origin-width=&quot;3060&quot; data-origin-height=&quot;2704&quot;/&gt;&lt;/span&gt;&lt;/figure&gt;
&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;HTTP Handler에서 파생되는 queryDB 함수와 queryCache 함수, callAPI() 함수의 파라미터로 컨텍스트를 전달하여 취소 신호가 전파될 수 있도록 한다.&lt;/p&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;code&gt;context&lt;/code&gt;에는 자주 사용되는 4가지 생성 함수가 존재한다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;code&gt;context.Background()&lt;/code&gt;는 루트 컨텍스트로 &lt;code&gt;main&lt;/code&gt;함수, 테스트, 최상위 고루틴에서 사용되는 절대 취소되지 않는 빈 컨텍스트다. 모든 컨텍스트 트리의 시작점 역할을 한다.&lt;/p&gt;
&lt;p&gt;&lt;figure class=&quot;imageblock alignCenter&quot; data-ke-mobileStyle=&quot;widthOrigin&quot; data-origin-width=&quot;1808&quot; data-origin-height=&quot;1192&quot;&gt;&lt;span data-url=&quot;https://blog.kakaocdn.net/dn/bVFSbd/dJMcacQbPX3/eWaIKB3pkzv3vbnTjDwcp0/img.png&quot; data-phocus=&quot;https://blog.kakaocdn.net/dn/bVFSbd/dJMcacQbPX3/eWaIKB3pkzv3vbnTjDwcp0/img.png&quot;&gt;&lt;img src=&quot;https://blog.kakaocdn.net/dn/bVFSbd/dJMcacQbPX3/eWaIKB3pkzv3vbnTjDwcp0/img.png&quot; srcset=&quot;https://img1.daumcdn.net/thumb/R1280x0/?scode=mtistory2&amp;fname=https%3A%2F%2Fblog.kakaocdn.net%2Fdn%2FbVFSbd%2FdJMcacQbPX3%2FeWaIKB3pkzv3vbnTjDwcp0%2Fimg.png&quot; onerror=&quot;this.onerror=null; this.src='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png'; this.srcset='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png';&quot; loading=&quot;lazy&quot; width=&quot;534&quot; height=&quot;352&quot; data-origin-width=&quot;1808&quot; data-origin-height=&quot;1192&quot;/&gt;&lt;/span&gt;&lt;/figure&gt;
&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;빈 컨텍스트를 하위 함수에 전달하면서 필요에 따라 &lt;code&gt;WithCancel()&lt;/code&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;code&gt;context.WithCancel()&lt;/code&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;code&gt;cancel()&lt;/code&gt;을 호출하지 않으면 부모 고루틴이 취소될 때까지 자식 컨텍스트의 리소스가 해제되지 않기 때문에 &lt;code&gt;defer cancel()&lt;/code&gt;을 필수적으로 사용해야 한다.&lt;/p&gt;
&lt;p&gt;&lt;figure class=&quot;imageblock alignCenter&quot; data-ke-mobileStyle=&quot;widthOrigin&quot; data-origin-width=&quot;2524&quot; data-origin-height=&quot;1864&quot;&gt;&lt;span data-url=&quot;https://blog.kakaocdn.net/dn/ABRK7/dJMcagdZ1XP/FSFNnkFdku2qDe7qmoZ1dk/img.png&quot; data-phocus=&quot;https://blog.kakaocdn.net/dn/ABRK7/dJMcagdZ1XP/FSFNnkFdku2qDe7qmoZ1dk/img.png&quot;&gt;&lt;img src=&quot;https://blog.kakaocdn.net/dn/ABRK7/dJMcagdZ1XP/FSFNnkFdku2qDe7qmoZ1dk/img.png&quot; srcset=&quot;https://img1.daumcdn.net/thumb/R1280x0/?scode=mtistory2&amp;fname=https%3A%2F%2Fblog.kakaocdn.net%2Fdn%2FABRK7%2FdJMcagdZ1XP%2FFSFNnkFdku2qDe7qmoZ1dk%2Fimg.png&quot; onerror=&quot;this.onerror=null; this.src='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png'; this.srcset='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png';&quot; loading=&quot;lazy&quot; width=&quot;562&quot; height=&quot;415&quot; data-origin-width=&quot;2524&quot; data-origin-height=&quot;1864&quot;/&gt;&lt;/span&gt;&lt;/figure&gt;
&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;code&gt;context.WithTimeout()&lt;/code&gt;은 N초 안에 응답을 받지 못하면 동작을 취소하고 싶을 때 사용한다. HTTP 요청 혹은 DB 쿼리를 호출할 때 자주 사용된다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;code&gt;WithTimeout(ctx, d)&lt;/code&gt;은 내부적으로 &lt;code&gt;WithDeadline(ctx, time.Now().Add(d))&lt;/code&gt;와 동일하기 때문에 지정한 시간이 지나면 &lt;code&gt;cancel()&lt;/code&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;code&gt;defer cancel()&lt;/code&gt;을 명시하는 편이 좋다. 시간이 지나면 컨텍스트는 취소되지만, 고루틴에 지정된 타임아웃의 지정 시간과 리소스는 자동으로 해제되지 않기 때문에 호출하지 않으면 내부 타이머와 타이머가 참조하는 메모리가 지정된 시간이 지날 때까지 계속 할당되어 있기 때문에 리소스가 낭비된다.&lt;/p&gt;
&lt;p&gt;&lt;figure class=&quot;imageblock alignCenter&quot; data-ke-mobileStyle=&quot;widthOrigin&quot; data-origin-width=&quot;2848&quot; data-origin-height=&quot;2284&quot;&gt;&lt;span data-url=&quot;https://blog.kakaocdn.net/dn/bO2LFB/dJMb99TsmSD/f4gADnaGK9MjMq4YDu51vK/img.png&quot; data-phocus=&quot;https://blog.kakaocdn.net/dn/bO2LFB/dJMb99TsmSD/f4gADnaGK9MjMq4YDu51vK/img.png&quot;&gt;&lt;img src=&quot;https://blog.kakaocdn.net/dn/bO2LFB/dJMb99TsmSD/f4gADnaGK9MjMq4YDu51vK/img.png&quot; srcset=&quot;https://img1.daumcdn.net/thumb/R1280x0/?scode=mtistory2&amp;fname=https%3A%2F%2Fblog.kakaocdn.net%2Fdn%2FbO2LFB%2FdJMb99TsmSD%2Ff4gADnaGK9MjMq4YDu51vK%2Fimg.png&quot; onerror=&quot;this.onerror=null; this.src='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png'; this.srcset='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png';&quot; loading=&quot;lazy&quot; width=&quot;612&quot; height=&quot;491&quot; data-origin-width=&quot;2848&quot; data-origin-height=&quot;2284&quot;/&gt;&lt;/span&gt;&lt;/figure&gt;
&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;code&gt;context.WithValue()&lt;/code&gt;는 요청 ID, 인증 토큰처럼 요청 범위의 데이터를 파라미터 추가 없이 전달하려고 할 때 주로 사용된다.&lt;/p&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;code&gt;WithValue()&lt;/code&gt;를 통해 전달되는 키로 &lt;code&gt;string&lt;/code&gt;, &lt;code&gt;int&lt;/code&gt; 같은 내장 타입을 쓰게 되면 다른 패키지의 키와 충돌이 발생할 수 있기 때문에 반드시 패키지 전용 &lt;code&gt;struct&lt;/code&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;code&gt;&quot;userID&quot;&lt;/code&gt;를 전달하게 되면 다른 패키지에서도 같은 키로 접근이 가능하기 때문에 충돌 가능성이 생긴다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;그래서 패키지마다 전용 구조체 타입을 만들어 외부에서 접근이 불가능하게끔 만들거나 구분될 수 있도록 만들 필요가 있다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;Go는 타입을 비교할 때 패키지 경로까지 포함해서 판단하기 때문에, 이름이 같아도 다른 패키지에 위치해 있다면 서로 다른 타입으로 구분된다.&lt;/p&gt;
&lt;p&gt;&lt;figure class=&quot;imageblock alignCenter&quot; data-ke-mobileStyle=&quot;widthOrigin&quot; data-origin-width=&quot;3096&quot; data-origin-height=&quot;2452&quot;&gt;&lt;span data-url=&quot;https://blog.kakaocdn.net/dn/cFqUe3/dJMcacimjkI/pDOMOI5m8ulFeYzQCQBCik/img.png&quot; data-phocus=&quot;https://blog.kakaocdn.net/dn/cFqUe3/dJMcacimjkI/pDOMOI5m8ulFeYzQCQBCik/img.png&quot;&gt;&lt;img src=&quot;https://blog.kakaocdn.net/dn/cFqUe3/dJMcacimjkI/pDOMOI5m8ulFeYzQCQBCik/img.png&quot; srcset=&quot;https://img1.daumcdn.net/thumb/R1280x0/?scode=mtistory2&amp;fname=https%3A%2F%2Fblog.kakaocdn.net%2Fdn%2FcFqUe3%2FdJMcacimjkI%2FpDOMOI5m8ulFeYzQCQBCik%2Fimg.png&quot; onerror=&quot;this.onerror=null; this.src='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png'; this.srcset='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png';&quot; loading=&quot;lazy&quot; width=&quot;631&quot; height=&quot;500&quot; data-origin-width=&quot;3096&quot; data-origin-height=&quot;2452&quot;/&gt;&lt;/span&gt;&lt;/figure&gt;
&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;구현한 구조체를 unexported 하게끔 하여 외부에서 직접 건드리지 못하도록 하는 대신 Set/Get 함수를 필요에 따라 구현하여 전달하는 것이 좋다. 이는 Java의 캡슐화와 굉장히 유사하기 때문에 이전에 Java를 했던 사람이라면 무슨 말인지 쉽게 이해할 수 있을 것이다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;컨텍스트는 사용함에 있어 몇 가지 사용 규칙을 잘 준수해야 버그가 발생하지 않기 때문에 올바른 사용 방법을 숙지하는 것이 중요하다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;컨텍스트를 전달할 때는 첫 번째 파라미터로 전달하는 것이 올바른 사용법이며, 컨텍스트를 구조체에 저장하거나 nil 컨텍스트를 전달하지 않아야 한다. 만약 빈 컨텍스트를 전달하고 싶다면 &lt;code&gt;context.TODO()&lt;/code&gt;를 전달하는 것이 좋다. 그리고 한 번 더 언급하자면 &lt;code&gt;defer cancel()&lt;/code&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;code&gt;select&lt;/code&gt;문과 &lt;code&gt;context&lt;/code&gt;를 연계하여 사용되기 때문에 이전 포스팅에 작성했던 내용을 참고하여 두 기능 모두 잘 공부해서 사용하도록 하자.&lt;/p&gt;
&lt;p&gt;&lt;figure class=&quot;imageblock alignCenter&quot; data-ke-mobileStyle=&quot;widthOrigin&quot; data-origin-width=&quot;3132&quot; data-origin-height=&quot;3208&quot;&gt;&lt;span data-url=&quot;https://blog.kakaocdn.net/dn/bt6cAp/dJMcahRuMUn/klgbiki8nbHFYjOTcBGVEk/img.png&quot; data-phocus=&quot;https://blog.kakaocdn.net/dn/bt6cAp/dJMcahRuMUn/klgbiki8nbHFYjOTcBGVEk/img.png&quot;&gt;&lt;img src=&quot;https://blog.kakaocdn.net/dn/bt6cAp/dJMcahRuMUn/klgbiki8nbHFYjOTcBGVEk/img.png&quot; srcset=&quot;https://img1.daumcdn.net/thumb/R1280x0/?scode=mtistory2&amp;fname=https%3A%2F%2Fblog.kakaocdn.net%2Fdn%2Fbt6cAp%2FdJMcahRuMUn%2Fklgbiki8nbHFYjOTcBGVEk%2Fimg.png&quot; onerror=&quot;this.onerror=null; this.src='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png'; this.srcset='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png';&quot; loading=&quot;lazy&quot; width=&quot;662&quot; height=&quot;678&quot; data-origin-width=&quot;3132&quot; data-origin-height=&quot;3208&quot;/&gt;&lt;/span&gt;&lt;/figure&gt;
&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;Go를 시작한 계기는 쿠버네티스가 Go로 짜여져 있다는 말을 듣기도 했고 OpenTelemetry 사용 시 OCB를 사용하려면 Go로 작성해야 한다는 말을 들어서 였는데, 하다보니 Go라는 언어 자체에 매력이 있는 것 같다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;Go를 사용해서 메트릭 수집기를 만드는 프로젝트를 진행하고 있는데 관련된 내용도 기회가 된다면 포스팅 해보려고 한다.&lt;/p&gt;</description>
      <category>Dev</category>
      <category>context</category>
      <category>go</category>
      <category>Golang</category>
      <category>goroutine</category>
      <category>Go언어</category>
      <category>고루틴</category>
      <category>채널</category>
      <author>겨울바람_</author>
      <guid isPermaLink="true">https://breeze-winter.tistory.com/69</guid>
      <comments>https://breeze-winter.tistory.com/69#entry69comment</comments>
      <pubDate>Tue, 7 Apr 2026 14:45:59 +0900</pubDate>
    </item>
    <item>
      <title>[Go] 고루틴(Goroutine) &amp;amp; 채널(Channel)</title>
      <link>https://breeze-winter.tistory.com/66</link>
      <description>&lt;p&gt;&lt;figure class=&quot;imageblock alignCenter&quot; data-ke-mobileStyle=&quot;widthOrigin&quot; data-origin-width=&quot;1000&quot; data-origin-height=&quot;1000&quot;&gt;&lt;span data-url=&quot;https://blog.kakaocdn.net/dn/6cIAy/btsKBh98d0b/8ZVYWLwDBbjQXfkgHDAYHK/img.png&quot; data-phocus=&quot;https://blog.kakaocdn.net/dn/6cIAy/btsKBh98d0b/8ZVYWLwDBbjQXfkgHDAYHK/img.png&quot;&gt;&lt;img src=&quot;https://blog.kakaocdn.net/dn/6cIAy/btsKBh98d0b/8ZVYWLwDBbjQXfkgHDAYHK/img.png&quot; srcset=&quot;https://img1.daumcdn.net/thumb/R1280x0/?scode=mtistory2&amp;fname=https%3A%2F%2Fblog.kakaocdn.net%2Fdn%2F6cIAy%2FbtsKBh98d0b%2F8ZVYWLwDBbjQXfkgHDAYHK%2Fimg.png&quot; onerror=&quot;this.onerror=null; this.src='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png'; this.srcset='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png';&quot; loading=&quot;lazy&quot; width=&quot;392&quot; height=&quot;392&quot; data-origin-width=&quot;1000&quot; data-origin-height=&quot;1000&quot;/&gt;&lt;/span&gt;&lt;/figure&gt;
&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;Go의 동시성이 OS 스레드 기반의 다른 프로그래밍 언어들보다 훨씬 가벼운 이유는 런타임이 자체 스케줄러를 갖고 있기 때문이다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;OS 스레드를 Go가 직접 사용하는게 아니라, G(goroutine) -&amp;gt; P(processor) -&amp;gt; M(Machine-OS Thread)의 3계층 구조로 고루틴을 관리한다.&lt;/p&gt;
&lt;p&gt;&lt;figure class=&quot;imageblock alignCenter&quot; data-ke-mobileStyle=&quot;widthOrigin&quot; data-origin-width=&quot;326&quot; data-origin-height=&quot;368&quot;&gt;&lt;span data-url=&quot;https://blog.kakaocdn.net/dn/E6OMZ/dJMcahKBQxl/s5c6OlURw9giK3nyw1tvn1/img.png&quot; data-phocus=&quot;https://blog.kakaocdn.net/dn/E6OMZ/dJMcahKBQxl/s5c6OlURw9giK3nyw1tvn1/img.png&quot;&gt;&lt;img src=&quot;https://blog.kakaocdn.net/dn/E6OMZ/dJMcahKBQxl/s5c6OlURw9giK3nyw1tvn1/img.png&quot; srcset=&quot;https://img1.daumcdn.net/thumb/R1280x0/?scode=mtistory2&amp;fname=https%3A%2F%2Fblog.kakaocdn.net%2Fdn%2FE6OMZ%2FdJMcahKBQxl%2Fs5c6OlURw9giK3nyw1tvn1%2Fimg.png&quot; onerror=&quot;this.onerror=null; this.src='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png'; this.srcset='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png';&quot; loading=&quot;lazy&quot; width=&quot;326&quot; height=&quot;368&quot; data-origin-width=&quot;326&quot; data-origin-height=&quot;368&quot;/&gt;&lt;/span&gt;&lt;/figure&gt;
&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;고루틴은 Go의 경량 실행 단위(Thread)로 go 키워드를 통해 사용할 수 있다. OS 스레드가 환경에 따라 1~8MB의 스택 크기를 갖는 반면, 고루틴은 그에 비해 최대 500배 작은 2KB의 초기 스택을 갖는다. 항상 2KB로 고정되어 있는건 아니고 64bit 환경 기준 필요에 따라 최대 1GB까지 확장이 가능하다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;또 OS 스레드가 일반적으로 컨텍스트 스위치에 ~1&amp;mu;s가 걸리는 반면 고루틴은 일반적으로 컨텍스트 스위치에 ~100ns의 시간이 걸리는데, OS 스레드와 비교했을 때 10배 빠른 속도로 전환이 가능하다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;프로세서는 고루틴을 실제 OS 스레드에 올려 실행시키는 역할을 맡는다. 프로세서가 없으면 OS 스레드는 고루틴을 실행시킬 수 없기 때문에 Go 런타임의 핵심 자원이라고 할 수 있다. 프로세서는 기본적으로 &lt;code&gt;GOMAXPROCS&lt;/code&gt; 의 설정 값만큼 생성되며 기본적으로 CPU 코어 수만큼 생성된다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;각각의 프로세서는 Local Run Queue(LRQ)를 가지고 있는데, 프로그램이 실행되면 프로세서(P)가 할당되고 각 프로세서의 LRQ에는 실행할 고루틴(G)이 배치된다. 이때 OS 스레드(M)은 직접 고루틴을 실행시키는게 아니라 프로세서의 LRQ로부터 고루틴을 가져온 후 실행한다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;LRQ에는 최대 256개의 고루틴이 배치될 수 있으며, 그 숫자를 초과할 시 Global Queue로 이동된다. 만약 OS 스레드가 syscall로 인해 블로킹 상태가 되면 Handoff 메커니즘에 의해 프로세서는 동작 가능한 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;OS 스레드(M)는 OS가 실제로 스케줄링 하는 커널 스레드로 프로세서 없이 고루틴을 직접 실행할 수 없다. 블로킹 상태가 되어 프로세서와 분리된 후 다시 동작 가능한 상태가 되면 비어있는 프로세서를 찾거나 Global Queue에서 대기한다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;GPM 모델에서 스케줄러의 효율을 높이는 가장 중요한 메커니즘이 바로 Work-Stealing인데, 특정 프로세서의 LRQ가 비어버리는&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;경우, 해당 프로세서와 OS 스레드는 유휴 상태가 된다.&lt;/p&gt;
&lt;p&gt;&lt;figure class=&quot;imageblock alignCenter&quot; data-ke-mobileStyle=&quot;widthOrigin&quot; data-origin-width=&quot;867&quot; data-origin-height=&quot;368&quot;&gt;&lt;span data-url=&quot;https://blog.kakaocdn.net/dn/biANLG/dJMcaiiqiYo/e8QzpGnk0xSI9UfFiIkNik/img.png&quot; data-phocus=&quot;https://blog.kakaocdn.net/dn/biANLG/dJMcaiiqiYo/e8QzpGnk0xSI9UfFiIkNik/img.png&quot;&gt;&lt;img src=&quot;https://blog.kakaocdn.net/dn/biANLG/dJMcaiiqiYo/e8QzpGnk0xSI9UfFiIkNik/img.png&quot; srcset=&quot;https://img1.daumcdn.net/thumb/R1280x0/?scode=mtistory2&amp;fname=https%3A%2F%2Fblog.kakaocdn.net%2Fdn%2FbiANLG%2FdJMcaiiqiYo%2Fe8QzpGnk0xSI9UfFiIkNik%2Fimg.png&quot; onerror=&quot;this.onerror=null; this.src='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png'; this.srcset='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png';&quot; loading=&quot;lazy&quot; width=&quot;867&quot; height=&quot;368&quot; data-origin-width=&quot;867&quot; data-origin-height=&quot;368&quot;/&gt;&lt;/span&gt;&lt;/figure&gt;
&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;다른 프로세서에 고루틴이 쌓여있음에도 놀고 있는 자원이 생기게 되기 때문에 이를 방지하기 위해서 LRQ가 비어버린 프로세서는 다른 프로세서의 LRQ에서 고루틴을 절반 가져와 자신의 비어버린 LRQ에 채운 후 실행을 이어간다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;만약, 다른 프로세서의 LRQ에도 훔쳐올 고루틴이 없다면 Global Queue를 확인하여 대기 중인 고루틴을 가져온다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;고루틴을 사용하다보면 결과를 돌려받아야 할 때가 있는데, 이를 위해 채널을 사용한다. 채널은 송신 대기열(send Queue), 링 버퍼(Ring Buffer), 수신 대기열(Recv Queue)로 구성되어 있다.&lt;/p&gt;
&lt;p&gt;&lt;figure class=&quot;imageblock alignCenter&quot; data-ke-mobileStyle=&quot;widthOrigin&quot; data-origin-width=&quot;1001&quot; data-origin-height=&quot;544&quot;&gt;&lt;span data-url=&quot;https://blog.kakaocdn.net/dn/riLDG/dJMb996RGy6/ZswGfMDiBP1BVttSiVCOO0/img.png&quot; data-phocus=&quot;https://blog.kakaocdn.net/dn/riLDG/dJMb996RGy6/ZswGfMDiBP1BVttSiVCOO0/img.png&quot;&gt;&lt;img src=&quot;https://blog.kakaocdn.net/dn/riLDG/dJMb996RGy6/ZswGfMDiBP1BVttSiVCOO0/img.png&quot; srcset=&quot;https://img1.daumcdn.net/thumb/R1280x0/?scode=mtistory2&amp;fname=https%3A%2F%2Fblog.kakaocdn.net%2Fdn%2FriLDG%2FdJMb996RGy6%2FZswGfMDiBP1BVttSiVCOO0%2Fimg.png&quot; onerror=&quot;this.onerror=null; this.src='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png'; this.srcset='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png';&quot; loading=&quot;lazy&quot; width=&quot;1001&quot; height=&quot;544&quot; data-origin-width=&quot;1001&quot; data-origin-height=&quot;544&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;채널은 고루틴이 데이터를 주고받는 파이프라고 생각하면 된다. 채널은 Buffered 채널과 Unbuffered 라는 두 개의 특성으로 구분할 수 있는데, Unbuffered 채널의 경우 하나의 수신자가 데이터를 받을 때까지 송신자가 데이터를 보내는 채널에 묶여 있게 된다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;하지만, Buffered 채널을 사용하면 수신자가 받을 준비가 되어 있지 않더라도 지정된 버퍼의 크기만큼 데이터를 보내고 계속 다른 일을 수행할 수 있다.&lt;/p&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;code&gt;close()&lt;/code&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;code&gt;select&lt;/code&gt;문을 통해 여러 채널을 동시에 감시할 수 있다. &lt;code&gt;select&lt;/code&gt;문은 여러 개의 &lt;code&gt;case&lt;/code&gt;에서 각각 다른 채널을 기다리다가 준비가 된 채널의 &lt;code&gt;case&lt;/code&gt;를 실행한다. 만약 복수 채널에서 신호가 오면 무작위로 그 중 하나를 선택한다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;대표적으로 자주 사용되는 타임아웃 패턴은 실행이 오래 걸리는 프로세스를 호출하고 정해진 시간까지 응답이 반환되지 않으면, 실패로 간주하고 강제적으로 에러를 반환한다.&lt;/p&gt;
&lt;p&gt;&lt;figure class=&quot;imageblock alignCenter&quot; data-ke-mobileStyle=&quot;widthOrigin&quot; data-origin-width=&quot;3152&quot; data-origin-height=&quot;2116&quot;&gt;&lt;span data-url=&quot;https://blog.kakaocdn.net/dn/bTf9rT/dJMcadBl5kx/gwz50Lx7uuSZC5XDs2rIy0/img.png&quot; data-phocus=&quot;https://blog.kakaocdn.net/dn/bTf9rT/dJMcadBl5kx/gwz50Lx7uuSZC5XDs2rIy0/img.png&quot;&gt;&lt;img src=&quot;https://blog.kakaocdn.net/dn/bTf9rT/dJMcadBl5kx/gwz50Lx7uuSZC5XDs2rIy0/img.png&quot; srcset=&quot;https://img1.daumcdn.net/thumb/R1280x0/?scode=mtistory2&amp;fname=https%3A%2F%2Fblog.kakaocdn.net%2Fdn%2FbTf9rT%2FdJMcadBl5kx%2Fgwz50Lx7uuSZC5XDs2rIy0%2Fimg.png&quot; onerror=&quot;this.onerror=null; this.src='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png'; this.srcset='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png';&quot; loading=&quot;lazy&quot; width=&quot;3152&quot; height=&quot;2116&quot; data-origin-width=&quot;3152&quot; data-origin-height=&quot;2116&quot;/&gt;&lt;/span&gt;&lt;/figure&gt;
&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;무한 루프를 활용한 이벤트 루프 패턴은 &lt;code&gt;close(quit)&lt;/code&gt; 명령이 올 때까지 루프를 돌며 작업이 오면 해당 작업을 처리한다. &lt;code&gt;close(quit)&lt;/code&gt; 명령을 채널을 통해 수신하면 모든 worker가 루프를 종료한다.&lt;/p&gt;
&lt;p&gt;&lt;figure class=&quot;imageblock alignCenter&quot; data-ke-mobileStyle=&quot;widthOrigin&quot; data-origin-width=&quot;2596&quot; data-origin-height=&quot;2704&quot;&gt;&lt;span data-url=&quot;https://blog.kakaocdn.net/dn/r58NM/dJMcacvMBk3/R1KVBLakLSP74Vr8PSVKq1/img.png&quot; data-phocus=&quot;https://blog.kakaocdn.net/dn/r58NM/dJMcacvMBk3/R1KVBLakLSP74Vr8PSVKq1/img.png&quot;&gt;&lt;img src=&quot;https://blog.kakaocdn.net/dn/r58NM/dJMcacvMBk3/R1KVBLakLSP74Vr8PSVKq1/img.png&quot; srcset=&quot;https://img1.daumcdn.net/thumb/R1280x0/?scode=mtistory2&amp;fname=https%3A%2F%2Fblog.kakaocdn.net%2Fdn%2Fr58NM%2FdJMcacvMBk3%2FR1KVBLakLSP74Vr8PSVKq1%2Fimg.png&quot; onerror=&quot;this.onerror=null; this.src='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png'; this.srcset='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png';&quot; loading=&quot;lazy&quot; width=&quot;2596&quot; height=&quot;2704&quot; data-origin-width=&quot;2596&quot; data-origin-height=&quot;2704&quot;/&gt;&lt;/span&gt;&lt;/figure&gt;
&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;만약 &lt;code&gt;default&lt;/code&gt;문이 있으면, &lt;code&gt;case&lt;/code&gt;문 채널이 준비되지 않더라도 계속 대기하지 않고 바로 &lt;code&gt;default&lt;/code&gt;문을 실행한다. &lt;code&gt;for ch range&lt;/code&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;code&gt;default&lt;/code&gt;문을 활용하는 대표적인 예제가 바로 아래의 로그 처리다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;로그의 경우 일부가 드랍되어도 서비스의 성능에 크게 영향을 미치지 않는다.&lt;/p&gt;
&lt;p&gt;&lt;figure class=&quot;imageblock alignCenter&quot; data-ke-mobileStyle=&quot;widthOrigin&quot; data-origin-width=&quot;3460&quot; data-origin-height=&quot;4720&quot;&gt;&lt;span data-url=&quot;https://blog.kakaocdn.net/dn/mVmuf/dJMcadOUoMD/VkXHV19Cp0DrhwmRDOFjTK/img.png&quot; data-phocus=&quot;https://blog.kakaocdn.net/dn/mVmuf/dJMcadOUoMD/VkXHV19Cp0DrhwmRDOFjTK/img.png&quot;&gt;&lt;img src=&quot;https://blog.kakaocdn.net/dn/mVmuf/dJMcadOUoMD/VkXHV19Cp0DrhwmRDOFjTK/img.png&quot; srcset=&quot;https://img1.daumcdn.net/thumb/R1280x0/?scode=mtistory2&amp;fname=https%3A%2F%2Fblog.kakaocdn.net%2Fdn%2FmVmuf%2FdJMcadOUoMD%2FVkXHV19Cp0DrhwmRDOFjTK%2Fimg.png&quot; onerror=&quot;this.onerror=null; this.src='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png'; this.srcset='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png';&quot; loading=&quot;lazy&quot; width=&quot;3460&quot; height=&quot;4720&quot; data-origin-width=&quot;3460&quot; data-origin-height=&quot;4720&quot;/&gt;&lt;/span&gt;&lt;/figure&gt;
&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;로그를 전송하는 &lt;code&gt;Send()&lt;/code&gt; 메소드는 &lt;code&gt;default&lt;/code&gt;를 사용한 논블로킹 방식으로 값을 보낼 수 없는 상황 즉, 채널의 버퍼가 가득 차면 로그를 드랍한다. 로그를 수집하는 &lt;code&gt;Cousume()&lt;/code&gt; 메소드는 &lt;code&gt;default&lt;/code&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;정리해보자면 select문을 사용했을 때 준비된 케이스가 하나면 그것을 실행, 준비된 케이스가 여러 개면 특정 채널이 굶지 않도록 무작위로 하나 선택. 만약 준비된 케이스가 없다면 default를 실행하는데, default문이 없으면 블로킹 된다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;이번 주제는 난이도가 있어 며칠 동안 나눠쓰는 바람에 주기가 좀 길어졌다. 다음 주제는 context인데 이것도 어려워서 좀 시간이 걸릴 것 같다.&lt;/p&gt;</description>
      <category>Dev</category>
      <category>channel</category>
      <category>go</category>
      <category>goroutine</category>
      <category>select패턴</category>
      <category>고 언어</category>
      <category>고루틴</category>
      <category>채널</category>
      <author>겨울바람_</author>
      <guid isPermaLink="true">https://breeze-winter.tistory.com/66</guid>
      <comments>https://breeze-winter.tistory.com/66#entry66comment</comments>
      <pubDate>Fri, 27 Mar 2026 10:50:13 +0900</pubDate>
    </item>
    <item>
      <title>[Go] 메모리 관리에 대해서 알아보자</title>
      <link>https://breeze-winter.tistory.com/65</link>
      <description>&lt;p&gt;&lt;figure class=&quot;imageblock alignCenter&quot; data-ke-mobileStyle=&quot;widthOrigin&quot; data-origin-width=&quot;1000&quot; data-origin-height=&quot;1000&quot;&gt;&lt;span data-url=&quot;https://blog.kakaocdn.net/dn/6cIAy/btsKBh98d0b/8ZVYWLwDBbjQXfkgHDAYHK/img.png&quot; data-phocus=&quot;https://blog.kakaocdn.net/dn/6cIAy/btsKBh98d0b/8ZVYWLwDBbjQXfkgHDAYHK/img.png&quot;&gt;&lt;img src=&quot;https://blog.kakaocdn.net/dn/6cIAy/btsKBh98d0b/8ZVYWLwDBbjQXfkgHDAYHK/img.png&quot; srcset=&quot;https://img1.daumcdn.net/thumb/R1280x0/?scode=mtistory2&amp;fname=https%3A%2F%2Fblog.kakaocdn.net%2Fdn%2F6cIAy%2FbtsKBh98d0b%2F8ZVYWLwDBbjQXfkgHDAYHK%2Fimg.png&quot; onerror=&quot;this.onerror=null; this.src='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png'; this.srcset='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png';&quot; loading=&quot;lazy&quot; width=&quot;392&quot; height=&quot;392&quot; data-origin-width=&quot;1000&quot; data-origin-height=&quot;1000&quot;/&gt;&lt;/span&gt;&lt;/figure&gt;
&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;Go에서 메모리는 크게 두 곳에 할당된다. 스택은 함수가 호출될 때 자동으로 생성되고 반환 시 즉시 사라지는 임시 공간으로 사용되고, 힙은 장기적으로 보관되어야 하는 데이터가 머무는 공간으로 사용된다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;장기적인 보관이라 함은, &quot;변수가 함수의 범위를 벗어나도 살아남아야 한다.&quot; 정도로 생각하면 쉽게 이해할 수 있다. 함수의 범위에서 벗어나 힙에 할당되는 것을 escape 라고 부른다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;스택 메모리는 자동으로 해제되고 속도가 빠르며, 크기 제한이 존재한다. 주로 함수 내부에 정의된 지역 변수, 함수 파라미터가 스택 메모리에 저장된다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;힙 메모리는 GC를 통해 관리되어 속도가 느리지만, 크기가 스택 메모리에 비해 유연하다. 주로 포인터 타입으로 반환된 변수, 클로저 캡처 등이 저장된다.&lt;/p&gt;
&lt;p&gt;&lt;figure class=&quot;imageblock alignCenter&quot; data-ke-mobileStyle=&quot;widthOrigin&quot; data-origin-width=&quot;2452&quot; data-origin-height=&quot;1612&quot;&gt;&lt;span data-url=&quot;https://blog.kakaocdn.net/dn/SkZGm/dJMcadutHIO/BKHJWQY94ksENIXPYVTid0/img.png&quot; data-phocus=&quot;https://blog.kakaocdn.net/dn/SkZGm/dJMcadutHIO/BKHJWQY94ksENIXPYVTid0/img.png&quot;&gt;&lt;img src=&quot;https://blog.kakaocdn.net/dn/SkZGm/dJMcadutHIO/BKHJWQY94ksENIXPYVTid0/img.png&quot; srcset=&quot;https://img1.daumcdn.net/thumb/R1280x0/?scode=mtistory2&amp;fname=https%3A%2F%2Fblog.kakaocdn.net%2Fdn%2FSkZGm%2FdJMcadutHIO%2FBKHJWQY94ksENIXPYVTid0%2Fimg.png&quot; onerror=&quot;this.onerror=null; this.src='//t1.daumcdn.net/tistory_admin/static/images/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;377&quot; data-origin-width=&quot;2452&quot; data-origin-height=&quot;1612&quot;/&gt;&lt;/span&gt;&lt;/figure&gt;
&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;Go는 선언된 변수가 힙 메모리에 저장되어야 하는지 스택에 남아있어야 하는지를 컴파일 타임에 결정하는데, 이를 Escape Analysis라고 한다.&lt;/p&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;code&gt;malloc&lt;/code&gt;을 선언하는 C와 다르게, Go는 이 결정을 전적으로 컴파일러에게 맡긴다.&lt;/p&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;code&gt;go build -gcflags=&quot;-m&quot;&lt;/code&gt;&amp;nbsp;명령어를 사용하면 어떤 변수 혹은 객체 등이 escape 되는지 콘솔을 통해 확인할 수 있다.&lt;/p&gt;
&lt;p&gt;&lt;figure class=&quot;imageblock alignCenter&quot; data-ke-mobileStyle=&quot;widthOrigin&quot; data-origin-width=&quot;1880&quot; data-origin-height=&quot;1948&quot;&gt;&lt;span data-url=&quot;https://blog.kakaocdn.net/dn/eopOmx/dJMcahwXlNb/TIEKawZko5DkYLJUTINnXk/img.png&quot; data-phocus=&quot;https://blog.kakaocdn.net/dn/eopOmx/dJMcahwXlNb/TIEKawZko5DkYLJUTINnXk/img.png&quot;&gt;&lt;img src=&quot;https://blog.kakaocdn.net/dn/eopOmx/dJMcahwXlNb/TIEKawZko5DkYLJUTINnXk/img.png&quot; srcset=&quot;https://img1.daumcdn.net/thumb/R1280x0/?scode=mtistory2&amp;fname=https%3A%2F%2Fblog.kakaocdn.net%2Fdn%2FeopOmx%2FdJMcahwXlNb%2FTIEKawZko5DkYLJUTINnXk%2Fimg.png&quot; onerror=&quot;this.onerror=null; this.src='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png'; this.srcset='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png';&quot; loading=&quot;lazy&quot; width=&quot;482&quot; height=&quot;499&quot; data-origin-width=&quot;1880&quot; data-origin-height=&quot;1948&quot;/&gt;&lt;/span&gt;&lt;/figure&gt;
&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;위의 코드를 기반으로 &lt;code&gt;go build -gcflags=&quot;-m&quot;&lt;/code&gt; 명령어를 실행시키면 아래처럼 Escape Analysis가 발생하는 것을 확인할 수 있다.&lt;/p&gt;
&lt;p&gt;&lt;figure class=&quot;imageblock alignCenter&quot; data-ke-mobileStyle=&quot;widthOrigin&quot; data-origin-width=&quot;908&quot; data-origin-height=&quot;202&quot;&gt;&lt;span data-url=&quot;https://blog.kakaocdn.net/dn/b1rRbc/dJMcajapI8e/lBW4KitkOmGtTfs2sxQT3K/img.png&quot; data-phocus=&quot;https://blog.kakaocdn.net/dn/b1rRbc/dJMcajapI8e/lBW4KitkOmGtTfs2sxQT3K/img.png&quot;&gt;&lt;img src=&quot;https://blog.kakaocdn.net/dn/b1rRbc/dJMcajapI8e/lBW4KitkOmGtTfs2sxQT3K/img.png&quot; srcset=&quot;https://img1.daumcdn.net/thumb/R1280x0/?scode=mtistory2&amp;fname=https%3A%2F%2Fblog.kakaocdn.net%2Fdn%2Fb1rRbc%2FdJMcajapI8e%2FlBW4KitkOmGtTfs2sxQT3K%2Fimg.png&quot; onerror=&quot;this.onerror=null; this.src='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png'; this.srcset='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png';&quot; loading=&quot;lazy&quot; width=&quot;908&quot; height=&quot;202&quot; data-origin-width=&quot;908&quot; data-origin-height=&quot;202&quot;/&gt;&lt;/span&gt;&lt;/figure&gt;
&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;콘솔에 출력된 내용을 천천히 살펴보자.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;우선, &lt;code&gt;newUser&lt;/code&gt;가 인라인된 것을 확인할 수 있다. 인라인은 함수 호출을 없애는 최적화로 &lt;code&gt;newUser&lt;/code&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;인라인을 통해 함수 호출을 없애면 스택 프레임 생성/해제 오버헤드 비용이 줄어든다. 즉, Go 컴파일러가 &lt;code&gt;newUser&lt;/code&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;code&gt;fmt.Println()&lt;/code&gt; 함수도 인라인이 됐는데, &lt;code&gt;fmt.Println()&lt;/code&gt; 함수는 사실 &lt;code&gt;Fprintln(os.Stdout, a...)&lt;/code&gt; 함수를 감싸고 있는 래퍼(Wrapper)이기 때문에 실질적으로 동작을 수행하는 &lt;code&gt;Fprintln(os.Stdout, a...)&lt;/code&gt; 함수가 제거된 것이 아니라 래퍼인 &lt;code&gt;fmt.Println()&lt;/code&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;code&gt;name&lt;/code&gt; 파라미터에는 leak이라는 언급이 붙는데, 이는 해당 파라미터가 &lt;code&gt;u&lt;/code&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;code&gt;newUser&lt;/code&gt; 함수의 지역변수인 &lt;code&gt;u&lt;/code&gt;가 힙에 할당되었다는 메세지다. 해당 함수가 &lt;code&gt;&amp;amp;u&lt;/code&gt;로 변수가 저장된 메모리의 주소를 반환하기 때문에, 함수가 끝나도 &lt;code&gt;u&lt;/code&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;code&gt;...&lt;/code&gt;은 &lt;code&gt;fmt.Println()&lt;/code&gt;에서 사용되는 가변인자 슬라이스(&lt;code&gt;...interface{}&lt;/code&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;code&gt;p.Name&lt;/code&gt;은 &lt;code&gt;fmt.Println()&lt;/code&gt;의 &lt;code&gt;interface{}&lt;/code&gt;로 전달되어 boxing 되었기 때문에 힙으로 할당되었다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;Go의 컴파일러는 CPU의 월드 사이즈에 맞춰 구조체 필드 사이에 패딩을 자동으로 삽입하는데, 필드 순서를 잘 배치하는 것으로 구조체가 할당되는 메모리의 크기를 줄일 수 있다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;보통 64bit 컴퓨터의 월드 사이즈는 8 바이트이기 때문에 구조체의 필드를 8 바이트 기준으로 정렬하면 메모리 효율을 최적화 할 수 있다.&lt;/p&gt;
&lt;p&gt;&lt;figure class=&quot;imageblock alignCenter&quot; data-ke-mobileStyle=&quot;widthOrigin&quot; data-origin-width=&quot;1300&quot; data-origin-height=&quot;1860&quot;&gt;&lt;span data-url=&quot;https://blog.kakaocdn.net/dn/cLP331/dJMcabXNA7h/cPXIXLmiJidD2jU4B0O8E0/img.png&quot; data-phocus=&quot;https://blog.kakaocdn.net/dn/cLP331/dJMcabXNA7h/cPXIXLmiJidD2jU4B0O8E0/img.png&quot;&gt;&lt;img src=&quot;https://blog.kakaocdn.net/dn/cLP331/dJMcabXNA7h/cPXIXLmiJidD2jU4B0O8E0/img.png&quot; srcset=&quot;https://img1.daumcdn.net/thumb/R1280x0/?scode=mtistory2&amp;fname=https%3A%2F%2Fblog.kakaocdn.net%2Fdn%2FcLP331%2FdJMcabXNA7h%2FcPXIXLmiJidD2jU4B0O8E0%2Fimg.png&quot; onerror=&quot;this.onerror=null; this.src='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png'; this.srcset='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png';&quot; loading=&quot;lazy&quot; width=&quot;345&quot; height=&quot;494&quot; data-origin-width=&quot;1300&quot; data-origin-height=&quot;1860&quot;/&gt;&lt;/span&gt;&lt;/figure&gt;
&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;BadStruct 구조체가 할당된 메모리의 경우&lt;/p&gt;
&lt;p&gt;&lt;figure class=&quot;imageblock alignCenter&quot; data-ke-mobileStyle=&quot;widthOrigin&quot; data-origin-width=&quot;537&quot; data-origin-height=&quot;328&quot;&gt;&lt;span data-url=&quot;https://blog.kakaocdn.net/dn/bq5E5j/dJMcabwIchG/G575B7qVKLFkFcJ0FjYYAK/img.png&quot; data-phocus=&quot;https://blog.kakaocdn.net/dn/bq5E5j/dJMcabwIchG/G575B7qVKLFkFcJ0FjYYAK/img.png&quot;&gt;&lt;img src=&quot;https://blog.kakaocdn.net/dn/bq5E5j/dJMcabwIchG/G575B7qVKLFkFcJ0FjYYAK/img.png&quot; srcset=&quot;https://img1.daumcdn.net/thumb/R1280x0/?scode=mtistory2&amp;fname=https%3A%2F%2Fblog.kakaocdn.net%2Fdn%2Fbq5E5j%2FdJMcabwIchG%2FG575B7qVKLFkFcJ0FjYYAK%2Fimg.png&quot; onerror=&quot;this.onerror=null; this.src='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png'; this.srcset='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png';&quot; loading=&quot;lazy&quot; width=&quot;386&quot; height=&quot;236&quot; data-origin-width=&quot;537&quot; data-origin-height=&quot;328&quot;/&gt;&lt;/span&gt;&lt;/figure&gt;
&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;무려 17bytes의 패딩이 들어가 실제 데이터의 크기인 15bytes보다 더 큰 패딩이 추가된 것을 볼 수 있다. 이렇게 되면 32bytes의 메모리를 사용하는 반면,&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;GoodStruct 구조체가 할당된 메모리의 경우&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;669&quot; data-origin-height=&quot;195&quot;&gt;&lt;span data-url=&quot;https://blog.kakaocdn.net/dn/caDZUq/dJMcai3C6NE/GGzck7kJ1ViirIxpKrf9iK/img.png&quot; data-phocus=&quot;https://blog.kakaocdn.net/dn/caDZUq/dJMcai3C6NE/GGzck7kJ1ViirIxpKrf9iK/img.png&quot;&gt;&lt;img src=&quot;https://blog.kakaocdn.net/dn/caDZUq/dJMcai3C6NE/GGzck7kJ1ViirIxpKrf9iK/img.png&quot; srcset=&quot;https://img1.daumcdn.net/thumb/R1280x0/?scode=mtistory2&amp;fname=https%3A%2F%2Fblog.kakaocdn.net%2Fdn%2FcaDZUq%2FdJMcai3C6NE%2FGGzck7kJ1ViirIxpKrf9iK%2Fimg.png&quot; onerror=&quot;this.onerror=null; this.src='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png'; this.srcset='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png';&quot; loading=&quot;lazy&quot; width=&quot;473&quot; height=&quot;138&quot; data-origin-width=&quot;669&quot; data-origin-height=&quot;195&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;필드의 순서를 다르게 한 것만으로도 32bytes였던 구조체의 크기를 16bytes로 50%나 줄일 수 있다.&amp;nbsp;&amp;nbsp;&lt;/p&gt;</description>
      <category>Dev</category>
      <category>Gc</category>
      <category>go</category>
      <category>Golang</category>
      <category>구조체</category>
      <category>메모리</category>
      <category>메모리 최적화</category>
      <category>메모리 할당</category>
      <category>최적화</category>
      <author>겨울바람_</author>
      <guid isPermaLink="true">https://breeze-winter.tistory.com/65</guid>
      <comments>https://breeze-winter.tistory.com/65#entry65comment</comments>
      <pubDate>Wed, 18 Mar 2026 17:37:09 +0900</pubDate>
    </item>
    <item>
      <title>[Prometheus] Thanos &amp;amp; Sharding</title>
      <link>https://breeze-winter.tistory.com/63</link>
      <description>&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;1266&quot;&gt;&lt;span data-url=&quot;https://blog.kakaocdn.net/dn/qRehT/dJMcagEBZ12/ZUYqbAQ4K4WdlJDa44hSY1/img.png&quot; data-phocus=&quot;https://blog.kakaocdn.net/dn/qRehT/dJMcagEBZ12/ZUYqbAQ4K4WdlJDa44hSY1/img.png&quot;&gt;&lt;img src=&quot;https://blog.kakaocdn.net/dn/qRehT/dJMcagEBZ12/ZUYqbAQ4K4WdlJDa44hSY1/img.png&quot; srcset=&quot;https://img1.daumcdn.net/thumb/R1280x0/?scode=mtistory2&amp;fname=https%3A%2F%2Fblog.kakaocdn.net%2Fdn%2FqRehT%2FdJMcagEBZ12%2FZUYqbAQ4K4WdlJDa44hSY1%2Fimg.png&quot; onerror=&quot;this.onerror=null; this.src='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png'; this.srcset='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png';&quot; loading=&quot;lazy&quot; width=&quot;220&quot; height=&quot;218&quot; data-origin-width=&quot;1280&quot; data-origin-height=&quot;1266&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;Prometheus는 메트릭 수집 기능 및 시각화, 쿼리 기능을 제공하며 모니터링에서 사실상 표준 처럼 사용되고 있다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;하지만, 프로메테우스는 단일 노드 시스템으로 설계되어 있기에 확장성과 고가용성 측면에서 부족함이 존재한다. 거기에 수집한 시계열 데이터를 로컬 디스크에 저장하기에 데이터의 영구 보존이 어렵다는 단점을 안고 있다.&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;이런 단점을 보완하기 위해 등장한 것이 바로 Thanos 다.&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;1280&quot; data-origin-height=&quot;687&quot;&gt;&lt;span data-url=&quot;https://blog.kakaocdn.net/dn/lA9sQ/dJMcajnOx3u/kR6McGK7mZu4buphvQrb9k/img.png&quot; data-phocus=&quot;https://blog.kakaocdn.net/dn/lA9sQ/dJMcajnOx3u/kR6McGK7mZu4buphvQrb9k/img.png&quot;&gt;&lt;img src=&quot;https://blog.kakaocdn.net/dn/lA9sQ/dJMcajnOx3u/kR6McGK7mZu4buphvQrb9k/img.png&quot; srcset=&quot;https://img1.daumcdn.net/thumb/R1280x0/?scode=mtistory2&amp;fname=https%3A%2F%2Fblog.kakaocdn.net%2Fdn%2FlA9sQ%2FdJMcajnOx3u%2FkR6McGK7mZu4buphvQrb9k%2Fimg.png&quot; onerror=&quot;this.onerror=null; this.src='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png'; this.srcset='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png';&quot; loading=&quot;lazy&quot; width=&quot;354&quot; height=&quot;190&quot; data-origin-width=&quot;1280&quot; data-origin-height=&quot;687&quot;/&gt;&lt;/span&gt;&lt;/figure&gt;
&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;Thanos는 위에서 언급됐던 Prometheus의 단점을 모두 보완할 수 있다.&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;Prometheus 서버에 Thanos Sidecar가 함께 배포된다. Prometheus는 수집된 데이터를 기본적으로 2시간 간격으로 TSDB Block으로 압축하여 로컬 디스크에 저장하는데, Thanos Sidecar는 TSDB Block이 완성되면 이를 감지하여 원격 저장소에 자동으로 업로드한다.&lt;/p&gt;
&lt;p&gt;&lt;figure class=&quot;imageblock alignCenter&quot; data-ke-mobileStyle=&quot;widthOrigin&quot; data-origin-width=&quot;1398&quot; data-origin-height=&quot;488&quot;&gt;&lt;span data-url=&quot;https://blog.kakaocdn.net/dn/46R4n/dJMcafy3WyX/Yee33y7YMG6JeJB9AzZQY0/img.png&quot; data-phocus=&quot;https://blog.kakaocdn.net/dn/46R4n/dJMcafy3WyX/Yee33y7YMG6JeJB9AzZQY0/img.png&quot;&gt;&lt;img src=&quot;https://blog.kakaocdn.net/dn/46R4n/dJMcafy3WyX/Yee33y7YMG6JeJB9AzZQY0/img.png&quot; srcset=&quot;https://img1.daumcdn.net/thumb/R1280x0/?scode=mtistory2&amp;fname=https%3A%2F%2Fblog.kakaocdn.net%2Fdn%2F46R4n%2FdJMcafy3WyX%2FYee33y7YMG6JeJB9AzZQY0%2Fimg.png&quot; onerror=&quot;this.onerror=null; this.src='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png'; this.srcset='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png';&quot; loading=&quot;lazy&quot; width=&quot;1398&quot; height=&quot;488&quot; data-origin-width=&quot;1398&quot; data-origin-height=&quot;488&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;추가적으로 Prometheus는 쌓여있는 작은 TSDB Block을 로컬에서 Compaction(압축)을 통해 하나의 큰 블록 단위로 만드는 작업을 진행한다. 하지만 Thanos를 함께 사용하는 경우 병합된 새 블록의 ID와 구조가 이전의 것과 달라지기 때문에 중복 업로드나 데이터 불일치가 발생할 수 있다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;따라서 Prometheus의 Compaction 기능을 비활성화 하고 Thanos Compactor를 사용하는 것이 좋다. Thanos Compactor를 사용하게 되면, 로컬이 아닌 원격 저장소 내에서 Compaction을 시행하게 된다.&lt;/p&gt;
&lt;p&gt;&lt;figure class=&quot;imageblock alignCenter&quot; data-ke-mobileStyle=&quot;widthOrigin&quot; data-origin-width=&quot;878&quot; data-origin-height=&quot;209&quot;&gt;&lt;span data-url=&quot;https://blog.kakaocdn.net/dn/b4VXsP/dJMcagLvtf3/tokLvhagy3gxclvwlE0LL1/img.png&quot; data-phocus=&quot;https://blog.kakaocdn.net/dn/b4VXsP/dJMcagLvtf3/tokLvhagy3gxclvwlE0LL1/img.png&quot; data-alt=&quot;각각 prometheus-0, prometheus-shard-1-0의 설정값이다.&quot;&gt;&lt;img src=&quot;https://blog.kakaocdn.net/dn/b4VXsP/dJMcagLvtf3/tokLvhagy3gxclvwlE0LL1/img.png&quot; srcset=&quot;https://img1.daumcdn.net/thumb/R1280x0/?scode=mtistory2&amp;fname=https%3A%2F%2Fblog.kakaocdn.net%2Fdn%2Fb4VXsP%2FdJMcagLvtf3%2FtokLvhagy3gxclvwlE0LL1%2Fimg.png&quot; onerror=&quot;this.onerror=null; this.src='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png'; this.srcset='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png';&quot; loading=&quot;lazy&quot; width=&quot;878&quot; height=&quot;209&quot; data-origin-width=&quot;878&quot; data-origin-height=&quot;209&quot;/&gt;&lt;/span&gt;&lt;figcaption&gt;각각 prometheus-0, prometheus-shard-1-0의 설정값이다.&lt;/figcaption&gt;
&lt;/figure&gt;
&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;Prometheus는 Sharding을 통해 데이터를 분산 저장할 수 있다. 메트릭 주소값(__address__)을 md5로 해싱하여 'tmp_hash' 라벨로 매핑한 뒤 'modules' 값으로 나눈 나머지 값에 따라 각 프로메테우스 인스턴스에 대해&amp;nbsp; 타겟으로 설정된다. 만약 나눈 나머지의 값이 0이라면 'regex'가 0인 prometheus-0으로 1일 경우 prometheus-shard-1-0으로 메트릭이 스크랩된다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;데이터의 영구 저장 이외에도 Prometheus는 인스턴스를 늘리는 경우, 동일 대상으로 중복된 데이터를 수집하게 되는데 이러한 문제도 Thanos를 통해 해결이 가능하다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;Thanos Query는 replicaLabel을 통해 조회 시점에 중복된 데이터를 하나로 통일하여 반환한다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;예를 들어 Prometheus에서 수집한 데이터의 라벨이&lt;/p&gt;
&lt;ul style=&quot;list-style-type: disc; background-color: #ffffff; color: #212529; text-align: left;&quot; data-ke-list-type=&quot;disc&quot;&gt;
&lt;li&gt;Prometheus + sidecar &amp;ldquo;A&amp;rdquo;:&lt;span&gt;&amp;nbsp;&lt;/span&gt;cluster=1,env=2,replica=A&lt;/li&gt;
&lt;li&gt;Prometheus + sidecar &amp;ldquo;B&amp;rdquo;:&lt;span&gt;&amp;nbsp;&lt;/span&gt;cluster=1,env=2,replica=B&lt;/li&gt;
&lt;/ul&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;위의 예제와 같을 때, replica를 제외한 라벨의 값이 모두 동일하기 때문에 Thanos Query는 이를 다른 프로메테우스에서 수집된 동일한 데이터로 판단하고 쿼리 결과에서 하나의 값만을 반환한다.&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p&gt;&lt;figure class=&quot;imageblock alignCenter&quot; data-ke-mobileStyle=&quot;widthOrigin&quot; data-origin-width=&quot;1831&quot; data-origin-height=&quot;1016&quot;&gt;&lt;span data-url=&quot;https://blog.kakaocdn.net/dn/chiSKu/dJMcabXIyc4/u4K8KQQjXeQzxXJ6wJ9CkK/img.png&quot; data-phocus=&quot;https://blog.kakaocdn.net/dn/chiSKu/dJMcabXIyc4/u4K8KQQjXeQzxXJ6wJ9CkK/img.png&quot;&gt;&lt;img src=&quot;https://blog.kakaocdn.net/dn/chiSKu/dJMcabXIyc4/u4K8KQQjXeQzxXJ6wJ9CkK/img.png&quot; srcset=&quot;https://img1.daumcdn.net/thumb/R1280x0/?scode=mtistory2&amp;fname=https%3A%2F%2Fblog.kakaocdn.net%2Fdn%2FchiSKu%2FdJMcabXIyc4%2Fu4K8KQQjXeQzxXJ6wJ9CkK%2Fimg.png&quot; onerror=&quot;this.onerror=null; this.src='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png'; this.srcset='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png';&quot; loading=&quot;lazy&quot; width=&quot;1831&quot; height=&quot;1016&quot; data-origin-width=&quot;1831&quot; data-origin-height=&quot;1016&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;만약 Sharding을 진행했다면, 분리된 데이터를 Thanos Query가 하나로 통합하여 보내주기 때문에 Grafana와 같은 대시보드 사용 시 매우 유용하다.&lt;/p&gt;</description>
      <category>DevOps</category>
      <category>MinIO</category>
      <category>Prometheus</category>
      <category>sharding</category>
      <category>THANOS</category>
      <category>고가용성</category>
      <category>샤딩</category>
      <category>타노스</category>
      <category>프로메테우스</category>
      <author>겨울바람_</author>
      <guid isPermaLink="true">https://breeze-winter.tistory.com/63</guid>
      <comments>https://breeze-winter.tistory.com/63#entry63comment</comments>
      <pubDate>Thu, 12 Mar 2026 10:42:09 +0900</pubDate>
    </item>
    <item>
      <title>[Prometheus] Re-Labeling</title>
      <link>https://breeze-winter.tistory.com/62</link>
      <description>&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;1266&quot;&gt;&lt;span data-url=&quot;https://blog.kakaocdn.net/dn/qRehT/dJMcagEBZ12/ZUYqbAQ4K4WdlJDa44hSY1/img.png&quot; data-phocus=&quot;https://blog.kakaocdn.net/dn/qRehT/dJMcagEBZ12/ZUYqbAQ4K4WdlJDa44hSY1/img.png&quot;&gt;&lt;img src=&quot;https://blog.kakaocdn.net/dn/qRehT/dJMcagEBZ12/ZUYqbAQ4K4WdlJDa44hSY1/img.png&quot; srcset=&quot;https://img1.daumcdn.net/thumb/R1280x0/?scode=mtistory2&amp;fname=https%3A%2F%2Fblog.kakaocdn.net%2Fdn%2FqRehT%2FdJMcagEBZ12%2FZUYqbAQ4K4WdlJDa44hSY1%2Fimg.png&quot; onerror=&quot;this.onerror=null; this.src='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png'; this.srcset='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png';&quot; loading=&quot;lazy&quot; width=&quot;220&quot; height=&quot;218&quot; data-origin-width=&quot;1280&quot; data-origin-height=&quot;1266&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;Re-Labeling은 라벨을 재설정하는 것으로 Prometheus의 타겟과 메트릭을 분류하고 필터링할 수 있게 한다.&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;prometheus.yaml 파일 내부의 scrape_configs 아래에 relabel_configs 속성을 지정할 수 있다. Re-Labeling은 스크랩이 발생하기 전에 이루어다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;다른 방법은 metric_relabel_configs 인데, 이 속성에 지정된 라벨링은 스크랩 이후에 발생한다. 따라서 스크랩한 후에 받은 모든 메트릭과 라벨에 접근할 수 있다.&lt;/p&gt;
&lt;h3 data-ke-size=&quot;size23&quot;&gt;relabel_configs&lt;/h3&gt;
&lt;p&gt;&lt;figure class=&quot;imageblock alignLeft&quot; data-ke-mobileStyle=&quot;widthOrigin&quot; data-origin-width=&quot;447&quot; data-origin-height=&quot;143&quot;&gt;&lt;span data-url=&quot;https://blog.kakaocdn.net/dn/cFtamg/dJMcadOBN5m/Zgopj3Tuc9L6obK8y6JUx0/img.png&quot; data-phocus=&quot;https://blog.kakaocdn.net/dn/cFtamg/dJMcadOBN5m/Zgopj3Tuc9L6obK8y6JUx0/img.png&quot;&gt;&lt;img src=&quot;https://blog.kakaocdn.net/dn/cFtamg/dJMcadOBN5m/Zgopj3Tuc9L6obK8y6JUx0/img.png&quot; srcset=&quot;https://img1.daumcdn.net/thumb/R1280x0/?scode=mtistory2&amp;fname=https%3A%2F%2Fblog.kakaocdn.net%2Fdn%2FcFtamg%2FdJMcadOBN5m%2FZgopj3Tuc9L6obK8y6JUx0%2Fimg.png&quot; onerror=&quot;this.onerror=null; this.src='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png'; this.srcset='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png';&quot; loading=&quot;lazy&quot; width=&quot;447&quot; height=&quot;143&quot; data-origin-width=&quot;447&quot; data-origin-height=&quot;143&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;relabel_configs의 'source_labels'는 발생한 메트릭의 labels 배열 중 Re-Labeling을 통해 접근하고자 하는 라벨의 배열을 의미한다. 위의 예제에서는 '__meta_ec2_tag_env' 라는 이름의 라벨의 값에 접근하고자 사용됐다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;'regex'는 source_labels를 통해 추출한 라벨의 특정 값과 일치되는 값을 걸러내기 위해 사용한다. 즉, ' __meta_ec2_tag_env'의 값이 'prod'일 경우 'action' 속성에 정의된 행동을 취하게 된다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;'keep'은 필터링된 타겟을 계속 스크랩하겠다는 의미이며, `drop'은 필터링된 타겟을 스크랩하지 않겠다는 것이다. 주의해야 할 점은 'keep'을 선택할 경우 라벨의 값이 'prod'가 아닌 메트릭은 수집되지 않는다는 것이다.&lt;/p&gt;
&lt;p&gt;&lt;figure class=&quot;imageblock alignLeft&quot; data-ke-mobileStyle=&quot;widthOrigin&quot; data-origin-width=&quot;346&quot; data-origin-height=&quot;142&quot;&gt;&lt;span data-url=&quot;https://blog.kakaocdn.net/dn/pTaJA/dJMcaioU85R/j0uEdS6CVNpTFqQZbWcRdK/img.png&quot; data-phocus=&quot;https://blog.kakaocdn.net/dn/pTaJA/dJMcaioU85R/j0uEdS6CVNpTFqQZbWcRdK/img.png&quot;&gt;&lt;img src=&quot;https://blog.kakaocdn.net/dn/pTaJA/dJMcaioU85R/j0uEdS6CVNpTFqQZbWcRdK/img.png&quot; srcset=&quot;https://img1.daumcdn.net/thumb/R1280x0/?scode=mtistory2&amp;fname=https%3A%2F%2Fblog.kakaocdn.net%2Fdn%2FpTaJA%2FdJMcaioU85R%2Fj0uEdS6CVNpTFqQZbWcRdK%2Fimg.png&quot; onerror=&quot;this.onerror=null; this.src='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png'; this.srcset='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png';&quot; loading=&quot;lazy&quot; width=&quot;346&quot; height=&quot;142&quot; data-origin-width=&quot;346&quot; data-origin-height=&quot;142&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;만약 source_labels의 값이 하나 이상인 경우, Prometheus는 각 라벨의 값을 가져와서 ';'을 통해 결합한다. 위의 예제는 라벨의 값이 '{env=&quot;dev&quot;}, {team=&quot;marketing&quot;}' 일 경우 해당 메트릭을 계속 스크랩하겠다는 것을 의미한다.&lt;/p&gt;
&lt;p&gt;&lt;figure class=&quot;imageblock alignLeft&quot; data-ke-mobileStyle=&quot;widthOrigin&quot; data-origin-width=&quot;355&quot; data-origin-height=&quot;160&quot;&gt;&lt;span data-url=&quot;https://blog.kakaocdn.net/dn/cq4EKF/dJMcafyX93P/H7DKrKaj5kGO8LaB4qKTs0/img.png&quot; data-phocus=&quot;https://blog.kakaocdn.net/dn/cq4EKF/dJMcafyX93P/H7DKrKaj5kGO8LaB4qKTs0/img.png&quot;&gt;&lt;img src=&quot;https://blog.kakaocdn.net/dn/cq4EKF/dJMcafyX93P/H7DKrKaj5kGO8LaB4qKTs0/img.png&quot; srcset=&quot;https://img1.daumcdn.net/thumb/R1280x0/?scode=mtistory2&amp;fname=https%3A%2F%2Fblog.kakaocdn.net%2Fdn%2Fcq4EKF%2FdJMcafyX93P%2FH7DKrKaj5kGO8LaB4qKTs0%2Fimg.png&quot; onerror=&quot;this.onerror=null; this.src='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png'; this.srcset='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png';&quot; loading=&quot;lazy&quot; width=&quot;355&quot; height=&quot;160&quot; data-origin-width=&quot;355&quot; data-origin-height=&quot;160&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;세미콜론(;) 대신 대시(-)를 사용하고 싶다면, `seperator' 속성을 사용하면 된다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;Target Labels는 모든 Timeseries의 라벨에 추가되는 라벨이다. 이는 스크랩에서 반환되는 라벨이기에 어떤 메트릭을 추가해도 항상 Target Labels를 가지게 된다.&amp;nbsp;&lt;/p&gt;
&lt;p&gt;&lt;figure class=&quot;imageblock alignLeft&quot; data-ke-mobileStyle=&quot;widthOrigin&quot; data-origin-width=&quot;378&quot; data-origin-height=&quot;192&quot;&gt;&lt;span data-url=&quot;https://blog.kakaocdn.net/dn/JZbFs/dJMcadA3Wc5/c3BGkMS1xfkHqnXY4bSUxk/img.png&quot; data-phocus=&quot;https://blog.kakaocdn.net/dn/JZbFs/dJMcadA3Wc5/c3BGkMS1xfkHqnXY4bSUxk/img.png&quot;&gt;&lt;img src=&quot;https://blog.kakaocdn.net/dn/JZbFs/dJMcadA3Wc5/c3BGkMS1xfkHqnXY4bSUxk/img.png&quot; srcset=&quot;https://img1.daumcdn.net/thumb/R1280x0/?scode=mtistory2&amp;fname=https%3A%2F%2Fblog.kakaocdn.net%2Fdn%2FJZbFs%2FdJMcadA3Wc5%2Fc3BGkMS1xfkHqnXY4bSUxk%2Fimg.png&quot; onerror=&quot;this.onerror=null; this.src='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png'; this.srcset='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png';&quot; loading=&quot;lazy&quot; width=&quot;378&quot; height=&quot;192&quot; data-origin-width=&quot;378&quot; data-origin-height=&quot;192&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;'__address__' 라벨의 값이 192.168.1.1:80 이라고 가정해보자. 이 라벨의 IP 부분을 Target Labels로 저장하고 싶다면, 우선 포트 부분을 제외시키고 IP만 노출되도록 변환해야 한다.&amp;nbsp;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;'action'을 replace로 지정하고 'target_label'의 값을 통해 라벨에 새롭게 이름을 부여한다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;regex는 콜론(:)을 기준으로 콜론 앞의 값 전부 그리고 뒤의 값 전부를 의미하며, 소괄호로 감싼 부분은 시작부터 콜론(:) 전까지의 모든 부분을 의미한다. 이를 $1으로 추출하여 'replacement'에 지정하는 것으로 '__address__' 라벨에서 IP 값만을 추출하여 Target Labels의 값으로 지정할 수 있다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;결론적으로 Target Labels는 {ip=&quot;&lt;span style=&quot;color: #333333; text-align: start;&quot;&gt;&lt;span&gt;&amp;nbsp;&lt;/span&gt;192.168.1.1&quot;}&amp;nbsp; 이렇게 표기될 것이다.&lt;/span&gt;&lt;/p&gt;
&lt;p&gt;&lt;figure class=&quot;imageblock alignLeft&quot; data-ke-mobileStyle=&quot;widthOrigin&quot; data-origin-width=&quot;348&quot; data-origin-height=&quot;120&quot;&gt;&lt;span data-url=&quot;https://blog.kakaocdn.net/dn/CQjuk/dJMcaaj7avg/ROOEnpzbVpsU46pTZdkknK/img.png&quot; data-phocus=&quot;https://blog.kakaocdn.net/dn/CQjuk/dJMcaaj7avg/ROOEnpzbVpsU46pTZdkknK/img.png&quot;&gt;&lt;img src=&quot;https://blog.kakaocdn.net/dn/CQjuk/dJMcaaj7avg/ROOEnpzbVpsU46pTZdkknK/img.png&quot; srcset=&quot;https://img1.daumcdn.net/thumb/R1280x0/?scode=mtistory2&amp;fname=https%3A%2F%2Fblog.kakaocdn.net%2Fdn%2FCQjuk%2FdJMcaaj7avg%2FROOEnpzbVpsU46pTZdkknK%2Fimg.png&quot; onerror=&quot;this.onerror=null; this.src='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png'; this.srcset='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png';&quot; loading=&quot;lazy&quot; width=&quot;348&quot; height=&quot;120&quot; data-origin-width=&quot;348&quot; data-origin-height=&quot;120&quot;/&gt;&lt;/span&gt;&lt;/figure&gt;
&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;span style=&quot;color: #333333; text-align: start;&quot;&gt;만약 라벨을 삭제하고 싶다면 'regex' 필드에 삭제하고자 하는 라벨의 이름과 일치하는 정규표현식을 작성한다. 그리고 action을 labeldrop으로 설정하면 정규표현식과 일치하는 라벨이 삭제된다.&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;labeldrop과 반대의 역할을 수행하는 action은 labelkeep이다. 동일하게 'regex'에 스크랩을 유지하고자 하는 라벨의 이름을 지정하고 action을 labelkeep으로 설정하면 된다. 주의할 점은 keep과 동일하게 지정한 라벨을 제외한 모든 라벨이 삭제된다.&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;참고로 '__'로 시작하는 라벨은 별도로 labeldrop을 사용하지 않더라도 Re-Labeling 이 끝난 후 삭제된다. 만약 '__'로 시작하는 라벨을 유지하고 싶다면 labelmap을 통해 쉽게 유지 가능하다.&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;labelmap은 라벨의 값을 수정하지 않고 라벨의 이름을 수정한다.&amp;nbsp;&lt;/span&gt;&lt;span style=&quot;color: #333333; text-align: start;&quot;&gt;&lt;/span&gt;&lt;/p&gt;
&lt;p&gt;&lt;figure class=&quot;imageblock alignLeft&quot; data-ke-mobileStyle=&quot;widthOrigin&quot; data-origin-width=&quot;310&quot; data-origin-height=&quot;137&quot;&gt;&lt;span data-url=&quot;https://blog.kakaocdn.net/dn/UafaQ/dJMcabpOjM9/nwu3VNoP7b37zQqv7P1zS1/img.png&quot; data-phocus=&quot;https://blog.kakaocdn.net/dn/UafaQ/dJMcabpOjM9/nwu3VNoP7b37zQqv7P1zS1/img.png&quot;&gt;&lt;img src=&quot;https://blog.kakaocdn.net/dn/UafaQ/dJMcabpOjM9/nwu3VNoP7b37zQqv7P1zS1/img.png&quot; srcset=&quot;https://img1.daumcdn.net/thumb/R1280x0/?scode=mtistory2&amp;fname=https%3A%2F%2Fblog.kakaocdn.net%2Fdn%2FUafaQ%2FdJMcabpOjM9%2Fnwu3VNoP7b37zQqv7P1zS1%2Fimg.png&quot; onerror=&quot;this.onerror=null; this.src='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png'; this.srcset='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png';&quot; loading=&quot;lazy&quot; width=&quot;310&quot; height=&quot;137&quot; data-origin-width=&quot;310&quot; data-origin-height=&quot;137&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;'__meta_ec2_'로 시작하는 모든 라벨의 이름을 'ec2_' 로 시작하도록 변경한다.&lt;span style=&quot;color: #333333; text-align: start;&quot;&gt;&lt;/span&gt;&lt;/p&gt;
&lt;h3 data-ke-size=&quot;size23&quot;&gt;metric_relabel_configs&lt;/h3&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;metric_relabel_configs는 위에서 언급했듯이 스크랩 이후에 이루어지기 때문에, 스크랩한 모든 메트릭에 접근할 수 있다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;metric_relabel_configs에서 사용되는 설정들은 relabel_configs에서 사용했던 설정들과 동일하다.&lt;/p&gt;
&lt;p&gt;&lt;figure class=&quot;imageblock alignLeft&quot; data-ke-mobileStyle=&quot;widthOrigin&quot; data-origin-width=&quot;345&quot; data-origin-height=&quot;131&quot;&gt;&lt;span data-url=&quot;https://blog.kakaocdn.net/dn/4rgqr/dJMcagq5jWV/m5YjJTxKGzi0BQd4O64tKK/img.png&quot; data-phocus=&quot;https://blog.kakaocdn.net/dn/4rgqr/dJMcagq5jWV/m5YjJTxKGzi0BQd4O64tKK/img.png&quot;&gt;&lt;img src=&quot;https://blog.kakaocdn.net/dn/4rgqr/dJMcagq5jWV/m5YjJTxKGzi0BQd4O64tKK/img.png&quot; srcset=&quot;https://img1.daumcdn.net/thumb/R1280x0/?scode=mtistory2&amp;fname=https%3A%2F%2Fblog.kakaocdn.net%2Fdn%2F4rgqr%2FdJMcagq5jWV%2Fm5YjJTxKGzi0BQd4O64tKK%2Fimg.png&quot; onerror=&quot;this.onerror=null; this.src='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png'; this.srcset='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png';&quot; loading=&quot;lazy&quot; width=&quot;345&quot; height=&quot;131&quot; data-origin-width=&quot;345&quot; data-origin-height=&quot;131&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;수집된 메트릭의 이름은 '__name__' 라벨에 저장된다. 즉 위의 예제는 'http_errors_total' 이라는 이름의 메트릭을 드랍하기 위해 사용된다.&amp;nbsp;&lt;/p&gt;
&lt;p&gt;&lt;figure class=&quot;imageblock alignLeft&quot; data-ke-mobileStyle=&quot;widthOrigin&quot; data-origin-width=&quot;413&quot; data-origin-height=&quot;202&quot;&gt;&lt;span data-url=&quot;https://blog.kakaocdn.net/dn/ZEcqI/dJMcahQ32Kn/5UsrcRiCS0ETaxAh16O89k/img.png&quot; data-phocus=&quot;https://blog.kakaocdn.net/dn/ZEcqI/dJMcahQ32Kn/5UsrcRiCS0ETaxAh16O89k/img.png&quot;&gt;&lt;img src=&quot;https://blog.kakaocdn.net/dn/ZEcqI/dJMcahQ32Kn/5UsrcRiCS0ETaxAh16O89k/img.png&quot; srcset=&quot;https://img1.daumcdn.net/thumb/R1280x0/?scode=mtistory2&amp;fname=https%3A%2F%2Fblog.kakaocdn.net%2Fdn%2FZEcqI%2FdJMcahQ32Kn%2F5UsrcRiCS0ETaxAh16O89k%2Fimg.png&quot; onerror=&quot;this.onerror=null; this.src='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png'; this.srcset='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png';&quot; loading=&quot;lazy&quot; width=&quot;413&quot; height=&quot;202&quot; data-origin-width=&quot;413&quot; data-origin-height=&quot;202&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;수집된 메트릭의 이름을 변경하고자 한다면 위의 예제처럼 작성하면 된다. 'http_errors_total' 이라는 메트릭의 이름을 'http_failures_total'로 바꿀 때 사용되는 예제다.&lt;/p&gt;
&lt;p&gt;&lt;figure class=&quot;imageblock alignLeft&quot; data-ke-mobileStyle=&quot;widthOrigin&quot; data-origin-width=&quot;315&quot; data-origin-height=&quot;181&quot;&gt;&lt;span data-url=&quot;https://blog.kakaocdn.net/dn/pc1IJ/dJMcabi2DXj/ZTBq4LtSbNqVirYSmbuKy0/img.png&quot; data-phocus=&quot;https://blog.kakaocdn.net/dn/pc1IJ/dJMcabi2DXj/ZTBq4LtSbNqVirYSmbuKy0/img.png&quot;&gt;&lt;img src=&quot;https://blog.kakaocdn.net/dn/pc1IJ/dJMcabi2DXj/ZTBq4LtSbNqVirYSmbuKy0/img.png&quot; srcset=&quot;https://img1.daumcdn.net/thumb/R1280x0/?scode=mtistory2&amp;fname=https%3A%2F%2Fblog.kakaocdn.net%2Fdn%2Fpc1IJ%2FdJMcabi2DXj%2FZTBq4LtSbNqVirYSmbuKy0%2Fimg.png&quot; onerror=&quot;this.onerror=null; this.src='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png'; this.srcset='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png';&quot; loading=&quot;lazy&quot; width=&quot;315&quot; height=&quot;181&quot; data-origin-width=&quot;315&quot; data-origin-height=&quot;181&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;만약 path라벨의 값이 HTTP 요청의 경로인 '/cars'이라면, 라벨의 모습은 {path=&quot;/cars&quot;}일 것이다. 이 라벨의 값을 이용해 {endpoint=&quot;cars&quot;} 라벨을 새롭게 만들어내고자 할 때 사용하는 것이 위의 예제다.&amp;nbsp;&lt;/p&gt;</description>
      <category>DevOps</category>
      <category>Prometheus</category>
      <category>Relabeling</category>
      <category>라벨 재설정</category>
      <category>리라벨링</category>
      <category>프로메테우스</category>
      <author>겨울바람_</author>
      <guid isPermaLink="true">https://breeze-winter.tistory.com/62</guid>
      <comments>https://breeze-winter.tistory.com/62#entry62comment</comments>
      <pubDate>Tue, 3 Mar 2026 14:34:05 +0900</pubDate>
    </item>
    <item>
      <title>[Prometheus] Recording Rules</title>
      <link>https://breeze-winter.tistory.com/61</link>
      <description>&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;1266&quot;&gt;&lt;span data-url=&quot;https://blog.kakaocdn.net/dn/qRehT/dJMcagEBZ12/ZUYqbAQ4K4WdlJDa44hSY1/img.png&quot; data-phocus=&quot;https://blog.kakaocdn.net/dn/qRehT/dJMcagEBZ12/ZUYqbAQ4K4WdlJDa44hSY1/img.png&quot;&gt;&lt;img src=&quot;https://blog.kakaocdn.net/dn/qRehT/dJMcagEBZ12/ZUYqbAQ4K4WdlJDa44hSY1/img.png&quot; srcset=&quot;https://img1.daumcdn.net/thumb/R1280x0/?scode=mtistory2&amp;fname=https%3A%2F%2Fblog.kakaocdn.net%2Fdn%2FqRehT%2FdJMcagEBZ12%2FZUYqbAQ4K4WdlJDa44hSY1%2Fimg.png&quot; onerror=&quot;this.onerror=null; this.src='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png'; this.srcset='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png';&quot; loading=&quot;lazy&quot; width=&quot;220&quot; height=&quot;218&quot; data-origin-width=&quot;1280&quot; data-origin-height=&quot;1266&quot;/&gt;&lt;/span&gt;&lt;/figure&gt;
&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;Recording Rules는 Prometheus가 주기적으로 PromQL 표현식을 평가하고 그 결과를 데이터베이스에 저장할 수 있도록 한다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;Grafana 같은 대시보드에서 요청을 보냈을 때 Prometheus는 데이터베이스에서 Recording Rules의 결과를 직접 가져와 대시보드로 전달할 수 있다.&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;Recording Rule을 설정하려면 별도의 rules.yaml 파일에 규칙을 저장해야 한다.&lt;/p&gt;
&lt;p&gt;&lt;figure class=&quot;imageblock alignCenter&quot; data-ke-mobileStyle=&quot;widthOrigin&quot; data-origin-width=&quot;381&quot; data-origin-height=&quot;307&quot;&gt;&lt;span data-url=&quot;https://blog.kakaocdn.net/dn/Zcljj/dJMcagq2Z6k/nFngalRuw09ztMM9oITVK0/img.png&quot; data-phocus=&quot;https://blog.kakaocdn.net/dn/Zcljj/dJMcagq2Z6k/nFngalRuw09ztMM9oITVK0/img.png&quot;&gt;&lt;img src=&quot;https://blog.kakaocdn.net/dn/Zcljj/dJMcagq2Z6k/nFngalRuw09ztMM9oITVK0/img.png&quot; srcset=&quot;https://img1.daumcdn.net/thumb/R1280x0/?scode=mtistory2&amp;fname=https%3A%2F%2Fblog.kakaocdn.net%2Fdn%2FZcljj%2FdJMcagq2Z6k%2FnFngalRuw09ztMM9oITVK0%2Fimg.png&quot; onerror=&quot;this.onerror=null; this.src='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png'; this.srcset='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png';&quot; loading=&quot;lazy&quot; width=&quot;381&quot; height=&quot;307&quot; data-origin-width=&quot;381&quot; data-origin-height=&quot;307&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;rule.yaml 파일의 예제를 살펴보자.&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;groups 아래에 하나 이상의 규칙 그룹을 정의한다. 규칙 그룹의 이름과 함께 규칙 평가 간격을 지정해야 한다. 기본적으로 전역 평가 간격을 사용하지만, 그룹 내에 원하는 값을 지정하여 덮어쓰는 것이 가능하다.&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;이는 Recording Rule을 얼마나 자주 실행하고 수집할지를 결정하고, 특정 표현식을 실행한다. rules 섹션 아래의 각 규칙은 Prometheus에게 expr 필드에 정의된 PromQL의 표현식을 평가하라는 지시를 내린다.&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;label 필드는 결과를 저장하기 전에 선택적으로 라벨을 추가하거나 제거할 수 있다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;그리고 그룹 내의 규칙들은 선언된 순서대로 순차적으로 평가된다. 즉, 첫 번째로 선언된 규칙이 종료되면 두 번째로 선언된 규칙이 실행되는 것이다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;만약 그룹이 여러 개 존재한다면, 그룹은 병렬적으로 수행되지만 그룹 내의 규칙들은 순차적으로 수행된다.&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;Recording Rule의 간단한 사용 예시를 들자면, 노드에서 사용 가능한 메모리 공간의 비율과 파일 시스템에서 사용 가능한 여유 공간의 비율을 추적하고 싶다고 가정해보자.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;100 - (100 * node_memory_MemFree_bytes / node_memory_MemTotal_bytes)&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;100 * node_filesystem_free_bytes / node_filesystem_avail_bytes&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;각각 위와 같은 두 개의 표현식을 필요로 한다. 매번 이 정보를 수집하기 위해 표현식을 실행하고 싶지 않다고 한다면 주기적으로 정보를 수집하기 위해 Recording Rule을 설정하는 것이 좋다.&lt;/p&gt;
&lt;p&gt;&lt;figure class=&quot;imageblock alignCenter&quot; data-ke-mobileStyle=&quot;widthOrigin&quot; data-origin-width=&quot;828&quot; data-origin-height=&quot;183&quot;&gt;&lt;span data-url=&quot;https://blog.kakaocdn.net/dn/C76zD/dJMcadOzuLR/VaJDZ8aAfU4179Yz2Okvwk/img.png&quot; data-phocus=&quot;https://blog.kakaocdn.net/dn/C76zD/dJMcadOzuLR/VaJDZ8aAfU4179Yz2Okvwk/img.png&quot;&gt;&lt;img src=&quot;https://blog.kakaocdn.net/dn/C76zD/dJMcadOzuLR/VaJDZ8aAfU4179Yz2Okvwk/img.png&quot; srcset=&quot;https://img1.daumcdn.net/thumb/R1280x0/?scode=mtistory2&amp;fname=https%3A%2F%2Fblog.kakaocdn.net%2Fdn%2FC76zD%2FdJMcadOzuLR%2FVaJDZ8aAfU4179Yz2Okvwk%2Fimg.png&quot; onerror=&quot;this.onerror=null; this.src='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png'; this.srcset='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png';&quot; loading=&quot;lazy&quot; width=&quot;828&quot; height=&quot;183&quot; data-origin-width=&quot;828&quot; data-origin-height=&quot;183&quot;/&gt;&lt;/span&gt;&lt;/figure&gt;
&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;record 필드에 설정한 규칙의 명칭을 Prometheus의 Expression Browser에 넣으면, expr 필드에 선언한 표현식을 반환한다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;앞서 언급했듯 rule.yaml 파일 내의 규칙들은 그룹 내에서 순차적으로 실행될 것이기에 node_memory_memFree_percent 규칙이 실행이 완료된 후 node_filesystem_free_percent 규칙이 실행될 것이다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;순차적으로 규칙이 실행되기에 현재 진행 중인 규칙이 앞서 실행된 규칙을 참조하는 것도 가능하다.&lt;/p&gt;
&lt;p&gt;&lt;figure class=&quot;imageblock alignCenter&quot; data-ke-mobileStyle=&quot;widthOrigin&quot; data-origin-width=&quot;997&quot; data-origin-height=&quot;185&quot;&gt;&lt;span data-url=&quot;https://blog.kakaocdn.net/dn/bCvJLp/dJMcabJ4Rqs/QfJk2vbqCzBfKpPwErqMgK/img.png&quot; data-phocus=&quot;https://blog.kakaocdn.net/dn/bCvJLp/dJMcabJ4Rqs/QfJk2vbqCzBfKpPwErqMgK/img.png&quot;&gt;&lt;img src=&quot;https://blog.kakaocdn.net/dn/bCvJLp/dJMcabJ4Rqs/QfJk2vbqCzBfKpPwErqMgK/img.png&quot; srcset=&quot;https://img1.daumcdn.net/thumb/R1280x0/?scode=mtistory2&amp;fname=https%3A%2F%2Fblog.kakaocdn.net%2Fdn%2FbCvJLp%2FdJMcabJ4Rqs%2FQfJk2vbqCzBfKpPwErqMgK%2Fimg.png&quot; onerror=&quot;this.onerror=null; this.src='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png'; this.srcset='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png';&quot; loading=&quot;lazy&quot; width=&quot;997&quot; height=&quot;185&quot; data-origin-width=&quot;997&quot; data-origin-height=&quot;185&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;첫 번째 규칙을 살펴보면, Node Exporter 파일 시스템의 여유 공간의 비율을 가져오는 것이고 두 번째 규칙은 그 평균을 구하는 것이다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;rules.yaml 파일이 수정될 때마다 Prometheus가 이를 자동적으로 감지하지는 않는다. 변경사항을 반영하기 위해서는 Prometheus를 재시작해야 한다. rules.yaml 파일의 작성이 완료됐다면 저장하고 prometheus.yaml 파일에 어떤 파일을 사용할지 지정하면 끝이다.&lt;/p&gt;
&lt;p&gt;&lt;figure class=&quot;imageblock alignLeft&quot; data-ke-mobileStyle=&quot;widthOrigin&quot; data-origin-width=&quot;187&quot; data-origin-height=&quot;52&quot;&gt;&lt;span data-url=&quot;https://blog.kakaocdn.net/dn/nLgAS/dJMcadgNlnw/qfNGKxS1O5xsIeEWBUk7L1/img.png&quot; data-phocus=&quot;https://blog.kakaocdn.net/dn/nLgAS/dJMcadgNlnw/qfNGKxS1O5xsIeEWBUk7L1/img.png&quot;&gt;&lt;img src=&quot;https://blog.kakaocdn.net/dn/nLgAS/dJMcadgNlnw/qfNGKxS1O5xsIeEWBUk7L1/img.png&quot; srcset=&quot;https://img1.daumcdn.net/thumb/R1280x0/?scode=mtistory2&amp;fname=https%3A%2F%2Fblog.kakaocdn.net%2Fdn%2FnLgAS%2FdJMcadgNlnw%2FqfNGKxS1O5xsIeEWBUk7L1%2Fimg.png&quot; onerror=&quot;this.onerror=null; this.src='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png'; this.srcset='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png';&quot; loading=&quot;lazy&quot; width=&quot;187&quot; height=&quot;52&quot; data-origin-width=&quot;187&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;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;Prometheus를 재시작 한 후, 설정한 규칙이 정상적으로 동작가능 한지 확인하고 싶다면 Prometheus의 Expression Browser의 Rule health 탭에서 State가 OK로 표시되어 있는지 확인해보면 된다.&lt;/p&gt;
&lt;p&gt;&lt;figure class=&quot;imageblock alignCenter&quot; data-ke-mobileStyle=&quot;widthOrigin&quot; data-origin-width=&quot;1917&quot; data-origin-height=&quot;405&quot;&gt;&lt;span data-url=&quot;https://blog.kakaocdn.net/dn/JVnym/dJMcaiCnMOI/BrtQjsdMCPQF9bwiY2ocLK/img.png&quot; data-phocus=&quot;https://blog.kakaocdn.net/dn/JVnym/dJMcaiCnMOI/BrtQjsdMCPQF9bwiY2ocLK/img.png&quot;&gt;&lt;img src=&quot;https://blog.kakaocdn.net/dn/JVnym/dJMcaiCnMOI/BrtQjsdMCPQF9bwiY2ocLK/img.png&quot; srcset=&quot;https://img1.daumcdn.net/thumb/R1280x0/?scode=mtistory2&amp;fname=https%3A%2F%2Fblog.kakaocdn.net%2Fdn%2FJVnym%2FdJMcaiCnMOI%2FBrtQjsdMCPQF9bwiY2ocLK%2Fimg.png&quot; onerror=&quot;this.onerror=null; this.src='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png'; this.srcset='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png';&quot; loading=&quot;lazy&quot; width=&quot;1917&quot; height=&quot;405&quot; data-origin-width=&quot;1917&quot; data-origin-height=&quot;405&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;이제 node_memory_memFree_percent 를 사용하면 별도의 PromQL 표현식 입력 없이 결과값을 반환한다.&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;1907&quot; data-origin-height=&quot;342&quot;&gt;&lt;span data-url=&quot;https://blog.kakaocdn.net/dn/bmVQLT/dJMcagR8yvd/nKrRB7plFCGJyhwKHqVhEk/img.png&quot; data-phocus=&quot;https://blog.kakaocdn.net/dn/bmVQLT/dJMcagR8yvd/nKrRB7plFCGJyhwKHqVhEk/img.png&quot;&gt;&lt;img src=&quot;https://blog.kakaocdn.net/dn/bmVQLT/dJMcagR8yvd/nKrRB7plFCGJyhwKHqVhEk/img.png&quot; srcset=&quot;https://img1.daumcdn.net/thumb/R1280x0/?scode=mtistory2&amp;fname=https%3A%2F%2Fblog.kakaocdn.net%2Fdn%2FbmVQLT%2FdJMcagR8yvd%2FnKrRB7plFCGJyhwKHqVhEk%2Fimg.png&quot; onerror=&quot;this.onerror=null; this.src='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png'; this.srcset='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png';&quot; loading=&quot;lazy&quot; width=&quot;1907&quot; height=&quot;342&quot; data-origin-width=&quot;1907&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;&amp;nbsp;&lt;/p&gt;</description>
      <category>DevOps</category>
      <author>겨울바람_</author>
      <guid isPermaLink="true">https://breeze-winter.tistory.com/61</guid>
      <comments>https://breeze-winter.tistory.com/61#entry61comment</comments>
      <pubDate>Fri, 27 Feb 2026 00:00:39 +0900</pubDate>
    </item>
    <item>
      <title>[Prometheus] Histogram &amp;amp; Summary</title>
      <link>https://breeze-winter.tistory.com/60</link>
      <description>&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;1266&quot;&gt;&lt;span data-url=&quot;https://blog.kakaocdn.net/dn/qRehT/dJMcagEBZ12/ZUYqbAQ4K4WdlJDa44hSY1/img.png&quot; data-phocus=&quot;https://blog.kakaocdn.net/dn/qRehT/dJMcagEBZ12/ZUYqbAQ4K4WdlJDa44hSY1/img.png&quot;&gt;&lt;img src=&quot;https://blog.kakaocdn.net/dn/qRehT/dJMcagEBZ12/ZUYqbAQ4K4WdlJDa44hSY1/img.png&quot; srcset=&quot;https://img1.daumcdn.net/thumb/R1280x0/?scode=mtistory2&amp;fname=https%3A%2F%2Fblog.kakaocdn.net%2Fdn%2FqRehT%2FdJMcagEBZ12%2FZUYqbAQ4K4WdlJDa44hSY1%2Fimg.png&quot; onerror=&quot;this.onerror=null; this.src='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png'; this.srcset='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png';&quot; loading=&quot;lazy&quot; width=&quot;220&quot; height=&quot;218&quot; data-origin-width=&quot;1280&quot; data-origin-height=&quot;1266&quot;/&gt;&lt;/span&gt;&lt;/figure&gt;
&lt;/p&gt;
&lt;h3 data-ke-size=&quot;size23&quot;&gt;Histogram&lt;/h3&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;Histogram 타입은 관측된 데이터의 분포를 측정하는 메트릭이다.&lt;/p&gt;
&lt;p&gt;&lt;figure class=&quot;imageblock alignCenter&quot; data-ke-mobileStyle=&quot;widthOrigin&quot; data-origin-width=&quot;1887&quot; data-origin-height=&quot;177&quot;&gt;&lt;span data-url=&quot;https://blog.kakaocdn.net/dn/N3kFb/dJMcadgM8jL/SYuyrufd5aZJN7mG7CNQrk/img.png&quot; data-phocus=&quot;https://blog.kakaocdn.net/dn/N3kFb/dJMcadgM8jL/SYuyrufd5aZJN7mG7CNQrk/img.png&quot;&gt;&lt;img src=&quot;https://blog.kakaocdn.net/dn/N3kFb/dJMcadgM8jL/SYuyrufd5aZJN7mG7CNQrk/img.png&quot; srcset=&quot;https://img1.daumcdn.net/thumb/R1280x0/?scode=mtistory2&amp;fname=https%3A%2F%2Fblog.kakaocdn.net%2Fdn%2FN3kFb%2FdJMcadgM8jL%2FSYuyrufd5aZJN7mG7CNQrk%2Fimg.png&quot; onerror=&quot;this.onerror=null; this.src='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png'; this.srcset='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png';&quot; loading=&quot;lazy&quot; width=&quot;1887&quot; height=&quot;177&quot; data-origin-width=&quot;1887&quot; data-origin-height=&quot;177&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;Histogram 메트릭 타입 내부에는 세 가지의 서브 타입 메트릭이 존재한다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;첫 번째는 '_count'다. 이 메트릭은 이름 그대로 샘플 데이터의 수를 의미한다. 샘플 데이터 값이 아닌 몇 개의 샘플 데이터가 존재하는지를 나타낸다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;위의 예시에서 사용된 'http_request_total_count'는 받은 요청의 수를 나타낸다.&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p&gt;&lt;figure class=&quot;imageblock alignCenter&quot; data-ke-mobileStyle=&quot;widthOrigin&quot; data-origin-width=&quot;1888&quot; data-origin-height=&quot;178&quot;&gt;&lt;span data-url=&quot;https://blog.kakaocdn.net/dn/53Qqs/dJMcahcpRqR/b6kncZX6u7sxE1MTsSWB4k/img.png&quot; data-phocus=&quot;https://blog.kakaocdn.net/dn/53Qqs/dJMcahcpRqR/b6kncZX6u7sxE1MTsSWB4k/img.png&quot; data-alt=&quot;round 함수로 소수점을 정리했기에 메트릭 이름이 보이지 않지만, http_request_total_sum으로 측정된 값이다.&quot;&gt;&lt;img src=&quot;https://blog.kakaocdn.net/dn/53Qqs/dJMcahcpRqR/b6kncZX6u7sxE1MTsSWB4k/img.png&quot; srcset=&quot;https://img1.daumcdn.net/thumb/R1280x0/?scode=mtistory2&amp;fname=https%3A%2F%2Fblog.kakaocdn.net%2Fdn%2F53Qqs%2FdJMcahcpRqR%2Fb6kncZX6u7sxE1MTsSWB4k%2Fimg.png&quot; onerror=&quot;this.onerror=null; this.src='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png'; this.srcset='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png';&quot; loading=&quot;lazy&quot; width=&quot;1888&quot; height=&quot;178&quot; data-origin-width=&quot;1888&quot; data-origin-height=&quot;178&quot;/&gt;&lt;/span&gt;&lt;figcaption&gt;round 함수로 소수점을 정리했기에 메트릭 이름이 보이지 않지만, http_request_total_sum으로 측정된 값이다.&lt;/figcaption&gt;
&lt;/figure&gt;
&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;두 번째는 '_sum'이다. 이 메트릭은 수집된 모든 데이터의 합계를 의미한다. 'http_request_total_count'에서 측정된 각 샘플 데이터의 값의 합이 표시되는 것을 확인할 수 있다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;예를 들어, {code=&quot;200&quot;, instance=&quot;node01:3000&quot;, job=&quot;api&quot;, method=&quot;get&quot;, route=&quot;/events&quot;} 인 메트릭의 총 수가 155이고 155개의 샘플 데이터의 모든 값을 더한 값이 175라는 것이다.&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p&gt;&lt;figure class=&quot;imageblock alignCenter&quot; data-ke-mobileStyle=&quot;widthOrigin&quot; data-origin-width=&quot;1852&quot; data-origin-height=&quot;195&quot;&gt;&lt;span data-url=&quot;https://blog.kakaocdn.net/dn/v5ZH5/dJMcacvrG2K/W3zKrgmk4MrltAuuX3kS2K/img.png&quot; data-phocus=&quot;https://blog.kakaocdn.net/dn/v5ZH5/dJMcacvrG2K/W3zKrgmk4MrltAuuX3kS2K/img.png&quot; data-alt=&quot;해당 캡처본에서 보이는 것 이외에도 더 많은 샘플 데이터가 존재한다.&quot;&gt;&lt;img src=&quot;https://blog.kakaocdn.net/dn/v5ZH5/dJMcacvrG2K/W3zKrgmk4MrltAuuX3kS2K/img.png&quot; srcset=&quot;https://img1.daumcdn.net/thumb/R1280x0/?scode=mtistory2&amp;fname=https%3A%2F%2Fblog.kakaocdn.net%2Fdn%2Fv5ZH5%2FdJMcacvrG2K%2FW3zKrgmk4MrltAuuX3kS2K%2Fimg.png&quot; onerror=&quot;this.onerror=null; this.src='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png'; this.srcset='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png';&quot; loading=&quot;lazy&quot; width=&quot;1852&quot; height=&quot;195&quot; data-origin-width=&quot;1852&quot; data-origin-height=&quot;195&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;p data-ke-size=&quot;size16&quot;&gt;세 번째는 가장 어려운 '_bucket'이다. 다른 서브 메트릭들과는 다르게 'le' 라벨이 추가된 것을 확인할 수 있다. 해당 라벨이 존재하는 버킷 데이터는 le 라벨에 설정된 값보다 작거나 같은 모든 샘플 데이터들을 포함한다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;http_request_total_bucket{code=&quot;200&quot;, instance=&quot;node01:3000&quot;, job=&quot;api&quot;, le=&quot;0.08&quot;, method=&quot;get&quot;, route=&quot;/events&quot;}&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;이 메트릭을 예시로 들자면, le 값이 0.08로 설정되어 있고 반환 값이 362인 것을 확인할 수 있다. 해석해보자면, /events 경로로 들어온 GET 요청 중 0.08초 이내(작거나 같음)로 처리된 요청이 362개 있다는 의미다.&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;'_bucket'은 le에 지정된 값 이하에서 관측된 값들을 나타낸다.&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;만약 le 값이 0.02이고 반환된 값이 90이라면, 0.02초 이내로 처리된 요청이 90개가 있다는 것이다.&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;여기서 알고 넘어가야 하는 건 le가 0.08인 반환값에는 le가 0.05, 0.02인 값들이 포함하고 있다는 것이다. 즉, 값이 누적되기 때문에 le값이 커질 수록 값 또한 증가한다.&lt;/p&gt;
&lt;p&gt;&lt;figure class=&quot;imageblock alignCenter&quot; data-ke-mobileStyle=&quot;widthOrigin&quot; data-origin-width=&quot;1862&quot; data-origin-height=&quot;38&quot;&gt;&lt;span data-url=&quot;https://blog.kakaocdn.net/dn/7vUwI/dJMcadA1GLy/TK7dIsDocgZKcwXfsyrGt1/img.png&quot; data-phocus=&quot;https://blog.kakaocdn.net/dn/7vUwI/dJMcadA1GLy/TK7dIsDocgZKcwXfsyrGt1/img.png&quot;&gt;&lt;img src=&quot;https://blog.kakaocdn.net/dn/7vUwI/dJMcadA1GLy/TK7dIsDocgZKcwXfsyrGt1/img.png&quot; srcset=&quot;https://img1.daumcdn.net/thumb/R1280x0/?scode=mtistory2&amp;fname=https%3A%2F%2Fblog.kakaocdn.net%2Fdn%2F7vUwI%2FdJMcadA1GLy%2FTK7dIsDocgZKcwXfsyrGt1%2Fimg.png&quot; onerror=&quot;this.onerror=null; this.src='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png'; this.srcset='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png';&quot; loading=&quot;lazy&quot; width=&quot;1862&quot; height=&quot;38&quot; data-origin-width=&quot;1862&quot; data-origin-height=&quot;38&quot;/&gt;&lt;/span&gt;&lt;/figure&gt;
&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;최종적으로는 le 값이 '+Inf' 인 메트릭이 존재한다. 이는 양수 무한대보다 작은 모든 샘플 데이터의 수를 의미한다. 이 메트릭은 어떤 경우에는 '_count' 메트릭의 값과 동일할 수도 있지만, 샘플 데이터가 음수 값을 포함할 수도 있기 때문에 다른 경우도 존재한다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;Histogram 타입을 활용하면 Quantiles 값을 매우 쉽게 계산할 수 있다. Quantiles란 분포 상에서 특정 한계값보다 위나 아래에 있는 값의 개수를 결정한다.&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;'_bucket' 메트릭의 le 라벨과 동일한 역할을 하는 것 같지만, Quantiles는 백분위수를 나타낸다. Histogram의 백분위수를 알고 싶다면, 'histogram_quantile()' 함수를 사용하면 된다.&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;1881&quot; data-origin-height=&quot;163&quot;&gt;&lt;span data-url=&quot;https://blog.kakaocdn.net/dn/47JzV/dJMcadHORk9/Jo6lcs4TbL00xRUD00bv3k/img.png&quot; data-phocus=&quot;https://blog.kakaocdn.net/dn/47JzV/dJMcadHORk9/Jo6lcs4TbL00xRUD00bv3k/img.png&quot;&gt;&lt;img src=&quot;https://blog.kakaocdn.net/dn/47JzV/dJMcadHORk9/Jo6lcs4TbL00xRUD00bv3k/img.png&quot; srcset=&quot;https://img1.daumcdn.net/thumb/R1280x0/?scode=mtistory2&amp;fname=https%3A%2F%2Fblog.kakaocdn.net%2Fdn%2F47JzV%2FdJMcadHORk9%2FJo6lcs4TbL00xRUD00bv3k%2Fimg.png&quot; onerror=&quot;this.onerror=null; this.src='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png'; this.srcset='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png';&quot; loading=&quot;lazy&quot; width=&quot;1881&quot; height=&quot;163&quot; data-origin-width=&quot;1881&quot; data-origin-height=&quot;163&quot;/&gt;&lt;/span&gt;&lt;/figure&gt;
&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;75%의 백분위수를 알고 싶다면, 다음과 같이 입력하면 된다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;histogram_quantile(0.75, http_request_total_bucket)&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;첫 번째 인자로 구하고자 하는 백분위수를 입력하고, 다음으로는 관심있는 메트릭을 전달한다.&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;{code=&quot;200&quot;, instance=&quot;node01:3000&quot;, job=&quot;api&quot;, method=&quot;get&quot;, route=&quot;/events&quot;} 0.28431055900621116 라는 값이 나왔다면, /events 경로로 들어온 GET 요청 중 75%의 요청이 284ms 이하로 처리되었다는 것을 의미한다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;Histogram 메트릭의 백분위수는 SLO가 정확하게 충족되고 있는지를 측정하는데 사용할 수 있다.&amp;nbsp;&lt;/p&gt;
&lt;h3 style=&quot;color: #000000; text-align: start;&quot; data-ke-size=&quot;size23&quot;&gt;Summary&lt;/h3&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;Summary 타입은 Histogram과 비슷하게 동작하지만 기본적으로 퍼센트로 동작하기 때문에 약간 차이가 있다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;Histogram 메트릭과 동일하게 '_count', '_sum' 와 quantiles를 포함한 세 개의 서브 메트릭을 가진다. 가장 큰 차이는 'quantiles'를 포함한 메트릭에서 발생한다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;quantiles가 포함된 메트릭은 기본적으로 Histogram에 histogram_quantile 함수를 사용한 것과 동일하게 동작한다.&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;Histogram과 Summary의 가장 차이를 비교해보면, Histogram은 버킷의 크기를 설정할 수 있다. 클라이언트에 부담도 덜하고 어떤 Quantile도 선택할 수 있지만 Prometheus 서버는 백분위수에 대한 계산을 수행해야 하기 때문에 서버 측 비용이 Summary에 비해 높다.&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;Summary 타입은 백분위수를 미리 정의해야 하기 때문에 클라이언트 측 부담이 더욱 크고 미리 정의한 Quantile 값만 사용할 수 있다.&lt;/p&gt;</description>
      <category>DevOps</category>
      <category>histogram</category>
      <category>Metric</category>
      <category>Monitoring</category>
      <category>o11y</category>
      <category>Prometheus</category>
      <category>quantiles</category>
      <category>SUMMARY</category>
      <category>메트릭</category>
      <category>모니터링</category>
      <category>프로메테우스</category>
      <author>겨울바람_</author>
      <guid isPermaLink="true">https://breeze-winter.tistory.com/60</guid>
      <comments>https://breeze-winter.tistory.com/60#entry60comment</comments>
      <pubDate>Thu, 26 Feb 2026 10:43:52 +0900</pubDate>
    </item>
    <item>
      <title>CKA, CKAD, CKS 자격증 취득 후기, 앞으로의 방향성</title>
      <link>https://breeze-winter.tistory.com/59</link>
      <description>&lt;p data-ke-size=&quot;size16&quot;&gt;작년부터 진행해오던 클라우드 네이티브 전환 프로젝트를 마무리하며, 오랜만에 글을 남겨보려 한다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;해당 프로젝트를 진행하며 신입이었던 나는 쿠버네티스 플랫폼을 직접 구축했고, 이전에 스터디로만 접해보았던 Observability를 실제 프로젝트에 적용해보는 경험도 할 수 있었다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;사수가 없어 나 혼자 여러 일들을 해야했고, 시행착오도 많았으며&amp;nbsp; 부담이 많이 되기도 했지만 덕분에 적극적으로 공부하고 스터디에 참여할 수 있는 계기가 되었다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;여러 시행착오와 어려움을 겪으며 좀 더 체계적으로 지식을 정리해보고 싶다는 생각이 들었고, 이를 계기로 쿠버네티스 자격증 취득을 결심하게 되었다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;대부분의 IT 자격증은 늘 실효성에 대한 의문이 따라붙는다. 리눅스 자격증이 있다고 해서 그 사람이 실무에서 반드시 뛰어난 역량을 발휘한다고 단정할 수는 없듯, 쿠버네티스 자격증인 CKA, CKAD, CKS 역시 이런 평가에서 자유로울 수는 없다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;이 자격증들은 가상 환경에 접속해 문제를 해결하는 실습형 시험이기에 단순히 문제를 암기하는 것만으로는 통과하기 어렵다. 그럼에도 자격증 취득 사실만으로 해당 분야의 전문가라고 말하기는 힘들다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;나 역시 자격증 취득을 준비하는 과정을 통해 쿠버네티스에 익숙해졌다고 생각하지만, 스스로를 전문가라고 부를 만큼 깊이 있는 이해에 도달했다고 느끼지는 못했다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;그럼에도 누군가 쿠버네티스 자격증 취득을 고민하고 있다면, 나는 적극적으로 취득하기를 추천하고 싶다. 준비 과정 자체가 좋은 공부가 되기 때문이다. 특히, 시험 특성상 공식 문서를 적극적으로 활용해야 하기 때문에, 자연스럽게 공식 문서에 익숙해지고 필요한 정보를 빠르게 찾아 적용하는 능력을 기를 수 있다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;여러 우여곡절 끝에 대표적인 세 가지 자격증을 취득할 수 있었지만, 여기서 만족하지 않고 좀 더 쿠버네티스에 대한 공부를 이어가고자 한다. 또한 쿠버네티스 위에서 주로 사용되는 CNCF 프로젝트들에 대한 이해도 더 깊이 가져가야겠다는 생각이 들었다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;이번 프로젝트에서도 여러 CNCF 프로젝트를 도입해보았지만 내 역량이 충분하지 않아 그 기능과 철학을 온전히 활용하지 못한 것 같아 아쉬움이 남는다.&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;앞으로는 이번 프로젝트에서 사용했던 Istio, Cilium, OpenTelemetry, Prometheus를 보다 체계적으로 공부해보고자 한다. 단순히 설치하고 사용한느 수준을 넘어 왜 필요한지, 어떤 문제를 해결하기 위해 등장했는지까지 이해하는 것이 목표다. 아울러「쿠버네티스 창시자에게 배우는 모범 사례」라는 책을 읽으며, 쿠버네티스의 아키텍처와 운영 원칙을 다시 한 번 정리해보고자 한다.&lt;/p&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;letter-spacing: 0px;&quot;&gt;아울러 쿠버네티스뿐 아니라, 그 기반이 되는 리눅스에 대해서도 보다 깊이 있기 정리해보고 싶다는 생각이 들었다. 실무에서 쿠버네티스를 다루다 보면 결국 운영체제에 대한 이해가 중요하다는 것을 체감하게 된다.&lt;/span&gt;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;span style=&quot;letter-spacing: 0px;&quot;&gt;이에 따라 리눅스 자격증 취득도 다음 목표로 삼고자 한다. 쿠버네티스 자격증과 마찬가지로 리눅스 재단에서 발행하는 LFCS와 Red Hat에서 발행하는 RHCSA 중 하나를 준비해 볼 계획이다.&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;letter-spacing: 0px;&quot;&gt;마침 회사에서 RHCSA 취득 비용을 지원해주고 있어, 우선은 RHCSA를 목표로 준비해볼 생각이다.&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;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;오랜만에 적는 글이라 여러모로 두서가 없지만, 그래도 목표로 삼았던 자격증 취득을 이뤄 굉장히 기분이 좋다. 부끄럽지만 마지막은 자격증 자랑으로 마무리 하고자 한다. 이 글을 읽는 사람들도 목표로 삼았던 것들을 모두 이룰 수 있기를 바란다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p&gt;&lt;figure class=&quot;imageblock alignCenter&quot; data-ke-mobileStyle=&quot;widthOrigin&quot; data-origin-width=&quot;976&quot; data-origin-height=&quot;752&quot;&gt;&lt;span data-url=&quot;https://blog.kakaocdn.net/dn/wQtYx/dJMcah4pmTM/08rIkmR1IjEW0Kk91Q8hPK/img.png&quot; data-phocus=&quot;https://blog.kakaocdn.net/dn/wQtYx/dJMcah4pmTM/08rIkmR1IjEW0Kk91Q8hPK/img.png&quot;&gt;&lt;img src=&quot;https://blog.kakaocdn.net/dn/wQtYx/dJMcah4pmTM/08rIkmR1IjEW0Kk91Q8hPK/img.png&quot; srcset=&quot;https://img1.daumcdn.net/thumb/R1280x0/?scode=mtistory2&amp;fname=https%3A%2F%2Fblog.kakaocdn.net%2Fdn%2FwQtYx%2FdJMcah4pmTM%2F08rIkmR1IjEW0Kk91Q8hPK%2Fimg.png&quot; onerror=&quot;this.onerror=null; this.src='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png'; this.srcset='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png';&quot; loading=&quot;lazy&quot; width=&quot;976&quot; height=&quot;752&quot; data-origin-width=&quot;976&quot; data-origin-height=&quot;752&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;977&quot; data-origin-height=&quot;752&quot;&gt;&lt;span data-url=&quot;https://blog.kakaocdn.net/dn/Q1HrD/dJMcaac9riR/9bTTkjw4TEdHmHlmdK7K91/img.png&quot; data-phocus=&quot;https://blog.kakaocdn.net/dn/Q1HrD/dJMcaac9riR/9bTTkjw4TEdHmHlmdK7K91/img.png&quot;&gt;&lt;img src=&quot;https://blog.kakaocdn.net/dn/Q1HrD/dJMcaac9riR/9bTTkjw4TEdHmHlmdK7K91/img.png&quot; srcset=&quot;https://img1.daumcdn.net/thumb/R1280x0/?scode=mtistory2&amp;fname=https%3A%2F%2Fblog.kakaocdn.net%2Fdn%2FQ1HrD%2FdJMcaac9riR%2F9bTTkjw4TEdHmHlmdK7K91%2Fimg.png&quot; onerror=&quot;this.onerror=null; this.src='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png'; this.srcset='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png';&quot; loading=&quot;lazy&quot; width=&quot;977&quot; height=&quot;752&quot; data-origin-width=&quot;977&quot; data-origin-height=&quot;752&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;980&quot; data-origin-height=&quot;758&quot;&gt;&lt;span data-url=&quot;https://blog.kakaocdn.net/dn/c0buEH/dJMcaibbSf5/rFaj3zkyBOU7B45fJMXK91/img.png&quot; data-phocus=&quot;https://blog.kakaocdn.net/dn/c0buEH/dJMcaibbSf5/rFaj3zkyBOU7B45fJMXK91/img.png&quot;&gt;&lt;img src=&quot;https://blog.kakaocdn.net/dn/c0buEH/dJMcaibbSf5/rFaj3zkyBOU7B45fJMXK91/img.png&quot; srcset=&quot;https://img1.daumcdn.net/thumb/R1280x0/?scode=mtistory2&amp;fname=https%3A%2F%2Fblog.kakaocdn.net%2Fdn%2Fc0buEH%2FdJMcaibbSf5%2FrFaj3zkyBOU7B45fJMXK91%2Fimg.png&quot; onerror=&quot;this.onerror=null; this.src='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png'; this.srcset='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png';&quot; loading=&quot;lazy&quot; width=&quot;980&quot; height=&quot;758&quot; data-origin-width=&quot;980&quot; data-origin-height=&quot;758&quot;/&gt;&lt;/span&gt;&lt;/figure&gt;
&lt;/p&gt;</description>
      <category>Certification</category>
      <category>CKA</category>
      <category>ckad</category>
      <category>CKS</category>
      <category>k8s</category>
      <category>자격증</category>
      <category>쿠버네티스</category>
      <author>겨울바람_</author>
      <guid isPermaLink="true">https://breeze-winter.tistory.com/59</guid>
      <comments>https://breeze-winter.tistory.com/59#entry59comment</comments>
      <pubDate>Wed, 11 Feb 2026 16:18:34 +0900</pubDate>
    </item>
    <item>
      <title>Book Study - SRE (Site Reliability Engineering) Chapter08, 09</title>
      <link>https://breeze-winter.tistory.com/58</link>
      <description>&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;521&quot;&gt;&lt;span data-url=&quot;https://blog.kakaocdn.net/dn/67rA0/btsM92xhhKq/jIHA4qw8sjmMYZMXeUlkhK/tfile.dat&quot; data-phocus=&quot;https://blog.kakaocdn.net/dn/67rA0/btsM92xhhKq/jIHA4qw8sjmMYZMXeUlkhK/tfile.dat&quot;&gt;&lt;img src=&quot;https://blog.kakaocdn.net/dn/67rA0/btsM92xhhKq/jIHA4qw8sjmMYZMXeUlkhK/tfile.dat&quot; srcset=&quot;https://img1.daumcdn.net/thumb/R1280x0/?scode=mtistory2&amp;fname=https%3A%2F%2Fblog.kakaocdn.net%2Fdn%2F67rA0%2FbtsM92xhhKq%2FjIHA4qw8sjmMYZMXeUlkhK%2Ftfile.dat&quot; onerror=&quot;this.onerror=null; this.src='//t1.daumcdn.net/tistory_admin/static/images/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;521&quot; data-origin-width=&quot;400&quot; data-origin-height=&quot;521&quot;/&gt;&lt;/span&gt;&lt;/figure&gt;
&lt;/p&gt;
&lt;h1&gt;Chapter 8 - Release Engineering&lt;/h1&gt;
&lt;h2 data-ke-size=&quot;size26&quot;&gt;Summary&lt;/h2&gt;
&lt;h3 data-ke-size=&quot;size23&quot;&gt;릴리즈 엔지니어의 역할&lt;/h3&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;릴리즈 엔지니어는 소프트웨어 엔지니어 및 사이트 SRE와 협력하여 소프트웨어의 빌드, 테스트, 패키징, 배포 등 릴리즈에 필요한 모든 단계를 정의한다. 이를 통해 개발 팀이 기능 개발에 집중할 수 있도록 지원하며, 일관되고 반복 가능한 릴리즈 프로세스를 보장한다.&lt;/p&gt;
&lt;h3 data-ke-size=&quot;size23&quot;&gt;릴리즈 엔지니어링의 철학&lt;/h3&gt;
&lt;ul style=&quot;list-style-type: disc;&quot; data-ke-list-type=&quot;disc&quot;&gt;
&lt;li&gt;&lt;b&gt;자기 주도 서비스 모델&lt;/b&gt; : 릴리즈 프로세스는 자동화를 통해 엔지니어의 개입을 최소화하여 문제 발생하지 않는 이상 엔지니어가 개입할 필요가 없다&lt;/li&gt;
&lt;li&gt;&lt;b&gt;빠른 릴리즈 주기&lt;/b&gt;&lt;/li&gt;
&lt;li&gt;&lt;b&gt;밀폐된 빌드&lt;/b&gt; : 빌드 프로세스의 결과물은 항상 동일해야 하기에 버전 관리를 통해 일관성을 유지한다&lt;/li&gt;
&lt;li&gt;&lt;b&gt;원리와 절차의 강제&lt;/b&gt; : 프로젝트 릴리즈 시 엄격한 관리 작업을 통해 문제가 발생했을 때, 새로운 빌드에 어떤 변경 사항이 포함되어 있는지 쉽게 확인할 수 있어 쉽게 조치가 가능하다&lt;/li&gt;
&lt;/ul&gt;
&lt;h2 data-ke-size=&quot;size26&quot;&gt;Question&lt;/h2&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;CI/CD 파이프라인 구축과 릴리즈 엔지니어링의 차이? (진짜 모름...)&lt;/p&gt;
&lt;h2 data-ke-size=&quot;size26&quot;&gt;Thoughts&lt;/h2&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;이번 챕터는 릴리즈 엔지니어링이라는 생소한 분야에 대한 설명이 이어졌는데, 전부 읽고 난 후에는 CI/CD 파이프라인 구축을 릴리즈 엔지니어링이라는 용어로 설명하는게 아닌가 싶을 정도로 굉장히 유사한 측면이 많다고 느껴졌다.&lt;/p&gt;
&lt;h1&gt;Chapter 9 - Simplicity&lt;/h1&gt;
&lt;h2 data-ke-size=&quot;size26&quot;&gt;Summary&lt;/h2&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;시스템의 안정성과 신속함 사이의 균형을 맞추는 것이 SRE의 역할&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;안정성을 갖춘 프로세스는 신속성을 높이는 경향이 있음.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;-&amp;gt; 신뢰성 있는 프로세스는 짧은 시간 내에 버그를 발견하여 수정할 수 있기 때문에, 개발자들이 개발 과정에서 기능 개발에만 집중할 수 있음&lt;/p&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;br /&gt;-&amp;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;br /&gt;-&amp;gt; 이해하기 쉽고, 수정과 유지보수가 굉장히 용이할수록 확장 또한 쉽기 때문&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;간결함이 갖춰져야 신뢰성을 확보할 수 있음&lt;/p&gt;
&lt;h2 data-ke-size=&quot;size26&quot;&gt;Thoughts&lt;/h2&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;시스템 설계 시 단순한 구조로 설계하기 위해 고민하기 보다는 기능이 많은 시스템 구조 위주로 설계 방향을 잡아왔던 적이 많았어서 굉장히 반성하게 되는 챕터였다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;안정성과 신속함의 균형&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;-&amp;gt; 안정성과 신속함을 서로 상반되는, 일종의 반대급부 개념으로 생각해온 적이 많다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;br /&gt;이번 챕터를 읽으며, 오히려 안정성을 갖춘 시스템일수록 신속한 기능 개발이 가능하다는 점에 크게 공감하게 되었다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;하지만 이미 운영되고 있는 시스템일수록 초기부터 안정성을 염두에 두고 설계된 경우가 드물고,&lt;br /&gt;그만큼 안정성을 확보하기 위한 재설계나 구조 개선에는 많은 시간과 노력이 소요된다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;결과적으로 안정성을 높이기 위한 작업이 진행될수록, 신규 기능 개발은 점점 뒷순위로 밀리게 되고 이로 인해 두 요소 사이의 균형을 잡는 일이 생각보다 훨씬 더 어렵다는 점을 느끼게 된다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;궁극적으로는 프로젝트의 초기에 안정성을 충분히 고려한 설계가 가장 중요하며, 그렇지 못한 상황에서는 조직 차원에서의 명확한 우선순위 정립과 점진적인 개선 전략이 필요하다고 생각하지만...거기에 일정 관리까지 생각한다면 매우 힘든 일이 되지 않을까 싶다.&lt;/p&gt;</description>
      <category>Book</category>
      <category>DevOps</category>
      <category>SRE</category>
      <author>겨울바람_</author>
      <guid isPermaLink="true">https://breeze-winter.tistory.com/58</guid>
      <comments>https://breeze-winter.tistory.com/58#entry58comment</comments>
      <pubDate>Sun, 6 Apr 2025 21:15:09 +0900</pubDate>
    </item>
    <item>
      <title>Book Study - SRE (Site Reliability Engineering) Chapter07</title>
      <link>https://breeze-winter.tistory.com/57</link>
      <description>&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;521&quot;&gt;&lt;span data-url=&quot;https://blog.kakaocdn.net/dn/cmF0mN/btsM4pxwF2V/kixrPOWlT8b2csjZvV6gEK/tfile.dat&quot; data-phocus=&quot;https://blog.kakaocdn.net/dn/cmF0mN/btsM4pxwF2V/kixrPOWlT8b2csjZvV6gEK/tfile.dat&quot;&gt;&lt;img src=&quot;https://blog.kakaocdn.net/dn/cmF0mN/btsM4pxwF2V/kixrPOWlT8b2csjZvV6gEK/tfile.dat&quot; srcset=&quot;https://img1.daumcdn.net/thumb/R1280x0/?scode=mtistory2&amp;fname=https%3A%2F%2Fblog.kakaocdn.net%2Fdn%2FcmF0mN%2FbtsM4pxwF2V%2FkixrPOWlT8b2csjZvV6gEK%2Ftfile.dat&quot; onerror=&quot;this.onerror=null; this.src='//t1.daumcdn.net/tistory_admin/static/images/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;521&quot; data-origin-width=&quot;400&quot; data-origin-height=&quot;521&quot;/&gt;&lt;/span&gt;&lt;/figure&gt;
&lt;/p&gt;
&lt;h1&gt;Chapter 7 - The Evolution of Automation at Google&lt;/h1&gt;
&lt;h2 style=&quot;color: #000000; text-align: start;&quot; data-ke-size=&quot;size26&quot;&gt; Summary&lt;/h2&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;/b&gt; 시스템 규모가 커지고 복잡해짐에 따라 수동 작업은 비효율적이며 휴먼 에러의 발생률이 높아진다. 자동화를 통해 일관성을 유지하고 운영의 신뢰성을 향상시킴&lt;/li&gt;
&lt;li&gt;&lt;b&gt;자동화 사례:&lt;/b&gt; 클러스터 턴업 자동화를 통해 새로운 클러스터를 설정하는데 드는 시간을 줄이고 이를 통해 낮은 지연 시간을 가지는 클러스터를 구축할 수 있게 되었음. 클러스터 턴업 자동화가 더욱 발전하여 Borg가 되었고, 현재의 Kubernetes의 근간이 된 것 같다.&lt;/li&gt;
&lt;li&gt;&lt;b&gt;실패 용인:&lt;/b&gt; 구글에서 진행한 자동화의 실패 사례를 설명해줌. 디스크삭제 프로세스 디버깅 중 스크립트가 운영 중이던 거의 모든 서버에 디스크삭제를 진행. 다행히(?) 소수의 사용자만 문제를 인지했었고, 이를 통해 자동화를 더욱 개선할 수 있었다고 함.&quot;자동화의 우선순위?&quot;&lt;/li&gt;
&lt;/ul&gt;
&lt;h2 style=&quot;color: #000000; text-align: start;&quot; data-ke-size=&quot;size26&quot;&gt; Questions&lt;/h2&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;-&amp;gt; 일정에 쫓기고 있을 때, 자동화를 하면 더 편해진다는 것을 알면서도 쉽게 시도하지 못할 때가 많다. 당장 눈 앞에 맞닥뜨린 일이 수동으로 할 수 있는 일을 자동화하는 것보다도 급하게 느껴지기 때문이다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;만약 정말 간신히 어떤 작업을 자동화 할 수 있다면, 어떤 기준을 우선시하여 자동화를 진행해야 할까?&lt;/p&gt;
&lt;h2 style=&quot;color: #000000; text-align: start;&quot; data-ke-size=&quot;size26&quot;&gt; Thoughts&lt;/h2&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&quot;생각 없이 자동화를 진행하면 새로운 문제들이 생겨나기 마련&quot;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;-&amp;gt; 자동화라는 이름은 굉장히 편해보이고, 그 결과만 봐서는 굉장히 손쉽게 무언가가 이루어지기에 정말 매력적인 작업이라고 느껴진다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;하지만 그 결과를 이룰 때까지의 과정은 결코 만만치가 않은듯. 특히나 여러 사람이 함께 작업하는 환경일수록 고려해야 하는 것들이 너무 많아지기에 굉장히 공감이 되는 문구였다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&quot;자동화 꼭 해야하나?&quot;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;-&amp;gt; 자동화가 가능하다면 반드시 해야 할까?&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;개인적으로는 자동화를 도입하는 것이 좋다고 생각하지만, 모든 사람이 이를 환영하는 것은 아니다. 변화와 개선을 달가워하지 않는 사람도 있고, 조직에 따라 자동화의 필요성이 다르게 평가될 수도 있다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;특히, 자동화의 실패 사례를 보면 구글 같은 대규모 기업에서는 큰 문제 없이 넘어갈 수도 있지만, 다른 환경에서는 상황이 다를 수 있다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;br /&gt;예를 들어, 금융권과 같은 신뢰성이 중요한 산업에서 자동화된 프로세스가 실패한다면 그 여파는 심각할 수 있다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;자동화 뿐만이 아니라 무언가 새로운 기술을 도입할 때에는, 조직의 특성과 리스크를 충분히 고려하는 과정이 필요하지 않을까?&lt;/p&gt;
&lt;h2 style=&quot;color: #000000; text-align: start;&quot; data-ke-size=&quot;size26&quot;&gt; Difficulties&lt;/h2&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&quot;보그에 설정된 작업들은 일주일에 최소한 한 두번은 이동했다.&quot;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;-&amp;gt; 일단, 쿠버네티스로 비유해보면 파드가 다른 워커 노드로 이동됐다는 의미라고 넘겨짚긴 했지만, 아무래도 보그에 대한 잘 알지 못하다보니 이해하는데 오래 걸린 것 같았다.&lt;/p&gt;</description>
      <category>Book</category>
      <category>cicd</category>
      <category>DevOps</category>
      <category>SRE</category>
      <category>자동화</category>
      <author>겨울바람_</author>
      <guid isPermaLink="true">https://breeze-winter.tistory.com/57</guid>
      <comments>https://breeze-winter.tistory.com/57#entry57comment</comments>
      <pubDate>Mon, 31 Mar 2025 22:07:50 +0900</pubDate>
    </item>
    <item>
      <title>Book Study - SRE (Site Reliability Engineering) Chapter05, 06</title>
      <link>https://breeze-winter.tistory.com/56</link>
      <description>&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;521&quot;&gt;&lt;span data-url=&quot;https://blog.kakaocdn.net/dn/birSHe/btsMVYmvhDN/cmKOkhPuBS5d4icvsjgin0/tfile.dat&quot; data-phocus=&quot;https://blog.kakaocdn.net/dn/birSHe/btsMVYmvhDN/cmKOkhPuBS5d4icvsjgin0/tfile.dat&quot;&gt;&lt;img src=&quot;https://blog.kakaocdn.net/dn/birSHe/btsMVYmvhDN/cmKOkhPuBS5d4icvsjgin0/tfile.dat&quot; srcset=&quot;https://img1.daumcdn.net/thumb/R1280x0/?scode=mtistory2&amp;fname=https%3A%2F%2Fblog.kakaocdn.net%2Fdn%2FbirSHe%2FbtsMVYmvhDN%2FcmKOkhPuBS5d4icvsjgin0%2Ftfile.dat&quot; onerror=&quot;this.onerror=null; this.src='//t1.daumcdn.net/tistory_admin/static/images/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;521&quot; data-origin-width=&quot;400&quot; data-origin-height=&quot;521&quot;/&gt;&lt;/span&gt;&lt;/figure&gt;
&lt;/p&gt;
&lt;h1&gt;Chapter 5 - Eliminating Toil&lt;/h1&gt;
&lt;h2 style=&quot;color: #000000; text-align: start;&quot; data-ke-size=&quot;size26&quot;&gt; Summary&lt;/h2&gt;
&lt;h3 data-ke-size=&quot;size23&quot;&gt;삽질(Toil)의 정의&lt;/h3&gt;
&lt;ul style=&quot;list-style-type: disc;&quot; data-ke-list-type=&quot;disc&quot;&gt;
&lt;li&gt;프로덕션 서비스 운영과 직접적으로 연관이 존재&lt;/li&gt;
&lt;li&gt;수작업을 동반함
&lt;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;반복적임&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;/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;/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;/li&gt;
&lt;/ul&gt;
&lt;/li&gt;
&lt;li&gt;서비스의 성장에 비례하여 증가함&lt;/li&gt;
&lt;/ul&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;삽질은 엔지니어의 시간을 소모하고, 자동화와 효율성을 저해하기에 삽질을 줄이거나 제거함으로써 엔지니어는 더 가치 있는 작업에 집중할 수 있다.&lt;/p&gt;
&lt;h3 data-ke-size=&quot;size23&quot;&gt;엔지니어링에 해당하는 업무&lt;/h3&gt;
&lt;ul style=&quot;list-style-type: disc;&quot; data-ke-list-type=&quot;disc&quot;&gt;
&lt;li&gt;소프트웨어 엔지니어링
&lt;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;시스템 엔지니어링
&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;삽질&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;/li&gt;
&lt;li&gt;HR, 팀 및 회사 회의, 업무 보고, 동료 평가 등&lt;/li&gt;
&lt;/ul&gt;
&lt;/li&gt;
&lt;/ul&gt;
&lt;h3 data-ke-size=&quot;size23&quot;&gt;삽질이 불러올 수 있는 문제들&lt;/h3&gt;
&lt;ul style=&quot;list-style-type: disc;&quot; data-ke-list-type=&quot;disc&quot;&gt;
&lt;li&gt;경력 개발이 침체
&lt;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;의욕 저하&lt;/li&gt;
&lt;li&gt;혼란 가중&lt;/li&gt;
&lt;li&gt;성장 저하&lt;/li&gt;
&lt;li&gt;좋지 않은 선례를 남김&lt;/li&gt;
&lt;li&gt;인력 유출 발생&lt;/li&gt;
&lt;li&gt;신뢰에 문제가 생김&lt;/li&gt;
&lt;/ul&gt;
&lt;h1&gt;Chapter 6 - Monitoring Distributed Systems&lt;/h1&gt;
&lt;h2 style=&quot;color: #000000; text-align: start;&quot; data-ke-size=&quot;size26&quot;&gt; Summary&lt;/h2&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;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;/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;로그나 JVM의 프로파일링 인터페이스, 내부 통계 지표를 제공하는 HTTP 핸들러 등을 이용해서 얻은 &lt;b&gt;시스템의 내부 지표들을 토대로 하는 모니터링&lt;/b&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;/li&gt;
&lt;li&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;물리적인 서버, VM, 컨테이너에서 동작하는 커널의 단일 인스턴스를 의미&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;/li&gt;
&lt;li&gt;서로 관련이 없지만 하드웨어만 공유하는 서비스&lt;/li&gt;
&lt;/ul&gt;
&lt;/li&gt;
&lt;/ul&gt;
&lt;/li&gt;
&lt;/ul&gt;
&lt;h3 data-ke-size=&quot;size23&quot;&gt;구글의 모니터링 방식&lt;/h3&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;구글은 최종 사용자의 요청률에서 예측하지 못한 변화를 탐지하는 규칙을 통해 구체적이면서도 심각한 예외를 매우 빠르게 찾아낸다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;모니터링 시스템에 있어 중요한 것은 최대한 간결하면서도 팀 모두가 이해할 수 있도록 유지하는 것이다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;중요도가 낮은 서비스들에 대해서는 화이트박스 모니터링을 수행하지만, 반대의 경우에는 블랙박스 모니터링을 수행한다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;블랙박스 모니터링은 아직 발생하지 않았지만 곧 발생할 문제에 대해서는 거의 도움이 되지 않는다.&lt;/p&gt;
&lt;h3 data-ke-size=&quot;size23&quot;&gt;네 가지 결정적 지표&lt;/h3&gt;
&lt;ul style=&quot;list-style-type: disc;&quot; data-ke-list-type=&quot;disc&quot;&gt;
&lt;li&gt;지연응답
&lt;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;트래픽&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;/li&gt;
&lt;li&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;/li&gt;
&lt;li&gt;긴급한 상황을 미리 예측하는 것과 관련이 깊음&lt;/li&gt;
&lt;/ul&gt;
&lt;/li&gt;
&lt;/ul&gt;</description>
      <category>Book</category>
      <category>DevOps</category>
      <category>SRE</category>
      <category>TOIL</category>
      <category>모니터링</category>
      <category>삽질</category>
      <author>겨울바람_</author>
      <guid isPermaLink="true">https://breeze-winter.tistory.com/56</guid>
      <comments>https://breeze-winter.tistory.com/56#entry56comment</comments>
      <pubDate>Mon, 24 Mar 2025 22:17:08 +0900</pubDate>
    </item>
    <item>
      <title>[o11y] Intro-to-mltp - LogQL</title>
      <link>https://breeze-winter.tistory.com/55</link>
      <description>&lt;h3 data-ke-size=&quot;size23&quot;&gt;LogQL&lt;/h3&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;LogQL은 &lt;code&gt;Log Stream Selector&lt;/code&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;code&gt;Log Stream Selector&lt;/code&gt;는 &lt;code&gt;,&lt;/code&gt;로 분리된 여러 쌍의 Key-Value 형태로 명시된다. 여러 쌍의 Key-Value 데이터들을 &lt;code&gt;{ }&lt;/code&gt; 로 감싸서 &lt;code&gt;Stream Selector&lt;/code&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;Grafana에서 제공하는 &lt;code&gt;intro-to-mltp&lt;/code&gt; 프로젝트에서 &lt;code&gt;Builder&lt;/code&gt;를 통해 &lt;code&gt;label&lt;/code&gt;을 선택하게 되면 아래쪽에 Log Stream Selector를 확인할 수 있다.&lt;/p&gt;
&lt;p&gt;&lt;figure class=&quot;imageblock alignCenter&quot; data-ke-mobileStyle=&quot;widthOrigin&quot; data-origin-width=&quot;547&quot; data-origin-height=&quot;305&quot;&gt;&lt;span data-url=&quot;https://blog.kakaocdn.net/dn/cIoH0L/btsMOQB6kPB/70ULhcrwqEkTaITA6O7OFK/img.png&quot; data-phocus=&quot;https://blog.kakaocdn.net/dn/cIoH0L/btsMOQB6kPB/70ULhcrwqEkTaITA6O7OFK/img.png&quot;&gt;&lt;img src=&quot;https://blog.kakaocdn.net/dn/cIoH0L/btsMOQB6kPB/70ULhcrwqEkTaITA6O7OFK/img.png&quot; srcset=&quot;https://img1.daumcdn.net/thumb/R1280x0/?scode=mtistory2&amp;fname=https%3A%2F%2Fblog.kakaocdn.net%2Fdn%2FcIoH0L%2FbtsMOQB6kPB%2F70ULhcrwqEkTaITA6O7OFK%2Fimg.png&quot; onerror=&quot;this.onerror=null; this.src='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png'; this.srcset='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png';&quot; loading=&quot;lazy&quot; width=&quot;423&quot; height=&quot;236&quot; data-origin-width=&quot;547&quot; data-origin-height=&quot;305&quot;/&gt;&lt;/span&gt;&lt;/figure&gt;
&lt;/p&gt;
&lt;pre class=&quot;routeros&quot;&gt;&lt;code&gt;{level=&quot;error&quot;, beast=&quot;beholder&quot;}&lt;/code&gt;&lt;/pre&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;위 예제의 Log Stream Selector는 모든 Log Stream들 중에서 명시된 Key-Value의 label들을 가지는 스트림들만 쿼리 결과에 포함시키게 된다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;LogQL은 이외에도 여러 연산자를 지원하는데 이 연산자들은 &lt;code&gt;Label Matching&lt;/code&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;=~ : 정규식에 부합함&lt;/li&gt;
&lt;li&gt;!~ : 정규식에 부합하지 않음&lt;/li&gt;
&lt;/ul&gt;
&lt;pre class=&quot;sqf&quot;&gt;&lt;code&gt;{name =~ &quot;mysql.+&quot;} // mysql 이후 어떤 문자가 하나 이상 오는 문자열을 모두 포함 
{name !~ &quot;mysql.+&quot;} // mysql 이후 어떤 문자가 하나 이상 오는 문자열을 모두 미포함 
{name !~ `mysql-\d+`} // mysql 이후 하이픈 + 한자리 이상의 숫자가 오는 문자열을 모두 미포함&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;Builder를 통해 생성된 쿼리를 살펴보면 Log Stream Selector 이외에도 &lt;code&gt;|= ''&lt;/code&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;실제로 해당 표현식을 제외하고 실행시켜도 실행 결과는 다르지 않기 때문에 Builder를 통해 쿼리를 만들 때 일종의 관례처럼 붙여주는게 아닐까 하는 생각이다.&lt;/p&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;code&gt;intro-to-mltp&lt;/code&gt;에서 발생하는 에러는 &lt;code&gt;Not-Null&lt;/code&gt; 제약조건이 걸려있는 컬럼인 &lt;code&gt;name&lt;/code&gt; 컬럼에 &lt;code&gt;null&lt;/code&gt; 값이 삽입되어 발생하는 에러밖에 없는데, 이번에는 LogQL을 통해 이를 좀 더 확실히 확인해보고자 한다.&lt;/p&gt;
&lt;pre class=&quot;1c&quot;&gt;&lt;code&gt;{level=&quot;error&quot;, job=&quot;mythical-server&quot;} |= &quot;error: null value in column \&quot;name\&quot; of relation&quot;&lt;/code&gt;&lt;/pre&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;위의 쿼리는 &lt;code&gt;mythical-server&lt;/code&gt;에서 발생하는 로그 중 &lt;code&gt;level=error&lt;/code&gt; 레이블을 가지고 &lt;code&gt;null value in column &quot;name&quot; of relation&lt;/code&gt; 에러 메세지를 포함한 에러만을 가져오는 쿼리인데, 해당 쿼리를 실행할 경우 아래의 사진처럼 해당 메세지를 포함한 에러 로그만을 가져오는 것을 확인할 수 있다.&lt;/p&gt;
&lt;p&gt;&lt;figure class=&quot;imageblock alignCenter&quot; data-ke-mobileStyle=&quot;widthOrigin&quot; data-origin-width=&quot;1516&quot; data-origin-height=&quot;787&quot;&gt;&lt;span data-url=&quot;https://blog.kakaocdn.net/dn/kUVPt/btsMOeDxt0T/XQZ7egaHbpkxhzXsJHwCv1/img.png&quot; data-phocus=&quot;https://blog.kakaocdn.net/dn/kUVPt/btsMOeDxt0T/XQZ7egaHbpkxhzXsJHwCv1/img.png&quot;&gt;&lt;img src=&quot;https://blog.kakaocdn.net/dn/kUVPt/btsMOeDxt0T/XQZ7egaHbpkxhzXsJHwCv1/img.png&quot; srcset=&quot;https://img1.daumcdn.net/thumb/R1280x0/?scode=mtistory2&amp;fname=https%3A%2F%2Fblog.kakaocdn.net%2Fdn%2FkUVPt%2FbtsMOeDxt0T%2FXQZ7egaHbpkxhzXsJHwCv1%2Fimg.png&quot; onerror=&quot;this.onerror=null; this.src='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png'; this.srcset='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png';&quot; loading=&quot;lazy&quot; width=&quot;754&quot; height=&quot;391&quot; data-origin-width=&quot;1516&quot; data-origin-height=&quot;787&quot;/&gt;&lt;/span&gt;&lt;/figure&gt;
&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;이제 해당 에러 메세지 이외의 다른 메세지를 포함하고 있는 에러를 가져와보자. 쿼리는 다음과 같다.&lt;/p&gt;
&lt;pre class=&quot;pgsql&quot;&gt;&lt;code&gt;{level=&quot;error&quot;, job=&quot;mythical-server&quot;} != &quot;error: null value in column \&quot;name\&quot; of relation&quot;&lt;/code&gt;&lt;/pre&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;위의 쿼리를 실행하면 아래의 사진과 같이 결과가 나오게 된다.&lt;/p&gt;
&lt;p&gt;&lt;figure class=&quot;imageblock alignCenter&quot; data-ke-mobileStyle=&quot;widthOrigin&quot; data-origin-width=&quot;1437&quot; data-origin-height=&quot;827&quot;&gt;&lt;span data-url=&quot;https://blog.kakaocdn.net/dn/qSClw/btsMMAgFLgw/BRKlBQ2bi86sFkvVQSR87k/img.png&quot; data-phocus=&quot;https://blog.kakaocdn.net/dn/qSClw/btsMMAgFLgw/BRKlBQ2bi86sFkvVQSR87k/img.png&quot;&gt;&lt;img src=&quot;https://blog.kakaocdn.net/dn/qSClw/btsMMAgFLgw/BRKlBQ2bi86sFkvVQSR87k/img.png&quot; srcset=&quot;https://img1.daumcdn.net/thumb/R1280x0/?scode=mtistory2&amp;fname=https%3A%2F%2Fblog.kakaocdn.net%2Fdn%2FqSClw%2FbtsMMAgFLgw%2FBRKlBQ2bi86sFkvVQSR87k%2Fimg.png&quot; onerror=&quot;this.onerror=null; this.src='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png'; this.srcset='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png';&quot; loading=&quot;lazy&quot; width=&quot;723&quot; height=&quot;416&quot; data-origin-width=&quot;1437&quot; data-origin-height=&quot;827&quot;/&gt;&lt;/span&gt;&lt;/figure&gt;
&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;이전 글에서 했던 예측과는 다르게 다른 에러를 확인할 수 있었다. 새롭게 발견된 에러 로그의 메세지는 다음과 같다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;code&gt;error: duplicate key value violates unique constraint &quot;illithid_name_key&quot;&lt;/code&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;code&gt;name&lt;/code&gt; 값이 &lt;code&gt;null&lt;/code&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;code&gt;mythical-server&lt;/code&gt;에서 발생하는 로그 중 에러 로그의 비율을 확인해보는 것도 가능하다. 쿼리는 다음과 같다.&lt;/p&gt;
&lt;pre class=&quot;lisp&quot;&gt;&lt;code&gt;(sum(rate({job = &quot;mythical-server&quot;} | level =`error`[5m]))) / sum(rate({job = &quot;mythical-server&quot;} [5m]))&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;code&gt;rate()&lt;/code&gt; 함수는 주어진 시간 범위(&lt;code&gt;[5m]&lt;/code&gt;, &lt;code&gt;[10m]&lt;/code&gt; 등) 내에서 초당 로그 발생률을 계산한다. &lt;code&gt;rate()&lt;/code&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;즉, &lt;code&gt;rate({job=&quot;mythical-server&quot;} [5m])&lt;/code&gt;은 &lt;b&gt;최근 5분 동안 발생한 로그 개수를 300초(5분)로 나눈 값&lt;/b&gt;을 의미한다. 해당 비율들을 모두 합산하여 &lt;code&gt;에러 로그 발생률 / 전체 로그 발생률&lt;/code&gt; 식을 통해 30분 간의 그래프를 확인해보면 다음과 같다.&lt;/p&gt;
&lt;p&gt;&lt;figure class=&quot;imageblock alignCenter&quot; data-ke-mobileStyle=&quot;widthOrigin&quot; data-origin-width=&quot;1820&quot; data-origin-height=&quot;711&quot;&gt;&lt;span data-url=&quot;https://blog.kakaocdn.net/dn/UnGAo/btsMOu66wLp/bLpZ7zfHgaoa4G0Xd8Sso0/img.png&quot; data-phocus=&quot;https://blog.kakaocdn.net/dn/UnGAo/btsMOu66wLp/bLpZ7zfHgaoa4G0Xd8Sso0/img.png&quot;&gt;&lt;img src=&quot;https://blog.kakaocdn.net/dn/UnGAo/btsMOu66wLp/bLpZ7zfHgaoa4G0Xd8Sso0/img.png&quot; srcset=&quot;https://img1.daumcdn.net/thumb/R1280x0/?scode=mtistory2&amp;fname=https%3A%2F%2Fblog.kakaocdn.net%2Fdn%2FUnGAo%2FbtsMOu66wLp%2FbLpZ7zfHgaoa4G0Xd8Sso0%2Fimg.png&quot; onerror=&quot;this.onerror=null; this.src='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png'; this.srcset='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png';&quot; loading=&quot;lazy&quot; width=&quot;778&quot; height=&quot;304&quot; data-origin-width=&quot;1820&quot; data-origin-height=&quot;711&quot;/&gt;&lt;/span&gt;&lt;/figure&gt;
&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;code&gt;rate()&lt;/code&gt; 함수 이외에도 수를 세는 &lt;code&gt;count_over_time()&lt;/code&gt;를 통해서도 동일한 결과 값을 얻을 수 있다.&lt;br /&gt;아래 그림은 시간이 좀 경과한 상태라서 그림 자체는 다르게 보이지만 나오는 결과 값 자체는 동일하다.&lt;/p&gt;
&lt;pre class=&quot;reasonml&quot;&gt;&lt;code&gt;sum(count_over_time({job = &quot;mythical-server&quot;} | level = &quot;error&quot; [5m])) / sum(count_over_time({job = &quot;mythical-server&quot;} [5m])) * 100`&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;1806&quot; data-origin-height=&quot;707&quot;&gt;&lt;span data-url=&quot;https://blog.kakaocdn.net/dn/cXWJ9l/btsMMFCewwz/7JI2iPkBgZvJip1QrIaJ61/img.png&quot; data-phocus=&quot;https://blog.kakaocdn.net/dn/cXWJ9l/btsMMFCewwz/7JI2iPkBgZvJip1QrIaJ61/img.png&quot;&gt;&lt;img src=&quot;https://blog.kakaocdn.net/dn/cXWJ9l/btsMMFCewwz/7JI2iPkBgZvJip1QrIaJ61/img.png&quot; srcset=&quot;https://img1.daumcdn.net/thumb/R1280x0/?scode=mtistory2&amp;fname=https%3A%2F%2Fblog.kakaocdn.net%2Fdn%2FcXWJ9l%2FbtsMMFCewwz%2F7JI2iPkBgZvJip1QrIaJ61%2Fimg.png&quot; onerror=&quot;this.onerror=null; this.src='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png'; this.srcset='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png';&quot; loading=&quot;lazy&quot; width=&quot;766&quot; height=&quot;300&quot; data-origin-width=&quot;1806&quot; data-origin-height=&quot;707&quot;/&gt;&lt;/span&gt;&lt;/figure&gt;
&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;그렇다면 비율을 측정할 때 &lt;code&gt;rate()&lt;/code&gt;와 &lt;code&gt;count_over_time()&lt;/code&gt; 함수 중 어떤 함수를 사용하는 것이 더욱 효율적일까? ChatGPT의 답변은 다음과 같다.&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;/b&gt;
&lt;ul style=&quot;list-style-type: disc;&quot; data-ke-list-type=&quot;disc&quot;&gt;
&lt;li&gt;&lt;code&gt;count_over_time()&lt;/code&gt;은 &lt;b&gt;고정된 총 개수(정수 값)를 반환&lt;/b&gt;하기 때문에, 이를 시계열(time-series) 그래프로 표현하기 어렵습니다.&lt;/li&gt;
&lt;li&gt;반면 &lt;code&gt;rate()&lt;/code&gt;는 &lt;b&gt;초당 로그 발생률(logs/sec)&lt;/b&gt;을 반환하여 변화 추이를 쉽게 볼 수 있습니다.&lt;/li&gt;
&lt;/ul&gt;
&lt;/li&gt;
&lt;li&gt;&lt;b&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;code&gt;count_over_time()&lt;/code&gt;은 5분 동안 &lt;b&gt;정확히 몇 개의 로그가 있었는지&lt;/b&gt;를 반환합니다.&lt;/li&gt;
&lt;li&gt;하지만 &lt;code&gt;rate()&lt;/code&gt;는 &lt;b&gt;로그가 일정하지 않은 경우에도 부드러운 값&lt;/b&gt;을 제공합니다.&lt;/li&gt;
&lt;li&gt;예를 들어, 5분 동안 로그가 몰려 있다가 갑자기 줄어들면 &lt;code&gt;count_over_time()&lt;/code&gt;은 급격한 변화를 보이지만, &lt;code&gt;rate()&lt;/code&gt;는 점진적인 변화를 보여줍니다.&lt;/li&gt;
&lt;/ul&gt;
&lt;/li&gt;
&lt;li&gt;&lt;b&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;code&gt;rate()&lt;/code&gt;는 &lt;b&gt;로그 발생 속도를 기준으로 임계값을 설정할 수 있어 알람 설정에 적합&lt;/b&gt;합니다.&lt;/li&gt;
&lt;li&gt;반면 &lt;code&gt;count_over_time()&lt;/code&gt;은 특정 구간의 총 개수만 반환하므로, 일정 주기마다 급격히 변할 수 있습니다.&lt;/li&gt;
&lt;/ul&gt;
&lt;/li&gt;
&lt;/ul&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;code&gt;count_over_time()&lt;/code&gt; 함수보다는 &lt;code&gt;rate()&lt;/code&gt; 함수를 사용하는게 더 적합하다고 한다.&lt;/p&gt;
&lt;h3 data-ke-size=&quot;size23&quot;&gt;Reference&lt;/h3&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;span&gt;&lt;/span&gt;&lt;a href=&quot;https://grafana.com/docs/loki/latest/query/&quot;&gt;https://grafana.com/docs/loki/latest/query/&lt;/a&gt;&lt;/p&gt;
&lt;figure id=&quot;og_1742263040648&quot; contenteditable=&quot;false&quot; data-ke-type=&quot;opengraph&quot; data-ke-align=&quot;alignCenter&quot; data-og-type=&quot;website&quot; data-og-title=&quot;LogQL: Log query language |  Grafana Loki documentation&quot; data-og-description=&quot;LogQL: Log query language LogQL is Grafana Loki&amp;rsquo;s PromQL-inspired query language. Queries act as if they are a distributed grep to aggregate log sources. LogQL uses labels and operators for filtering. There are two types of LogQL queries: Binary operator&quot; data-og-host=&quot;grafana.com&quot; data-og-source-url=&quot;https://grafana.com/docs/loki/latest/query/&quot; data-og-url=&quot;https://grafana.com/docs/loki/latest/query/&quot; data-og-image=&quot;https://scrap.kakaocdn.net/dn/HshZK/hyYq0VpHa4/narADznif8tbA4TZWFdtBk/img.png?width=1200&amp;amp;height=630&amp;amp;face=0_0_1200_630,https://scrap.kakaocdn.net/dn/h2iq1/hyYqPzCJPv/tITQV5uV2UpBgKyMRRw2q0/img.png?width=1200&amp;amp;height=630&amp;amp;face=0_0_1200_630&quot;&gt;&lt;a href=&quot;https://grafana.com/docs/loki/latest/query/&quot; target=&quot;_blank&quot; rel=&quot;noopener&quot; data-source-url=&quot;https://grafana.com/docs/loki/latest/query/&quot;&gt;
&lt;div class=&quot;og-image&quot; style=&quot;background-image: url('https://scrap.kakaocdn.net/dn/HshZK/hyYq0VpHa4/narADznif8tbA4TZWFdtBk/img.png?width=1200&amp;amp;height=630&amp;amp;face=0_0_1200_630,https://scrap.kakaocdn.net/dn/h2iq1/hyYqPzCJPv/tITQV5uV2UpBgKyMRRw2q0/img.png?width=1200&amp;amp;height=630&amp;amp;face=0_0_1200_630');&quot;&gt;&amp;nbsp;&lt;/div&gt;
&lt;div class=&quot;og-text&quot;&gt;
&lt;p class=&quot;og-title&quot; data-ke-size=&quot;size16&quot;&gt;LogQL: Log query language | Grafana Loki documentation&lt;/p&gt;
&lt;p class=&quot;og-desc&quot; data-ke-size=&quot;size16&quot;&gt;LogQL: Log query language LogQL is Grafana Loki&amp;rsquo;s PromQL-inspired query language. Queries act as if they are a distributed grep to aggregate log sources. LogQL uses labels and operators for filtering. There are two types of LogQL queries: Binary operator&lt;/p&gt;
&lt;p class=&quot;og-host&quot; data-ke-size=&quot;size16&quot;&gt;grafana.com&lt;/p&gt;
&lt;/div&gt;
&lt;/a&gt;&lt;/figure&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;span&gt;&lt;/span&gt;&lt;a href=&quot;https://sbcode.net/grafana/logql/&quot;&gt;https://sbcode.net/grafana/logql/&lt;/a&gt;&lt;/p&gt;
&lt;figure id=&quot;og_1742263046148&quot; contenteditable=&quot;false&quot; data-ke-type=&quot;opengraph&quot; data-ke-align=&quot;alignCenter&quot; data-og-type=&quot;website&quot; data-og-title=&quot;LogQL - Grafana Tutorials&quot; data-og-description=&quot;LogQL Video Lecture Description Now that we have a loki data source we can query it with the LogQL query language. In this video, we will try out many LogQL queries on the Loki data source we've setup. There are two types of LogQL queries: Log queries retu&quot; data-og-host=&quot;sbcode.net&quot; data-og-source-url=&quot;https://sbcode.net/grafana/logql/&quot; data-og-url=&quot;https://sbcode.net/grafana/logql/&quot; data-og-image=&quot;&quot;&gt;&lt;a href=&quot;https://sbcode.net/grafana/logql/&quot; target=&quot;_blank&quot; rel=&quot;noopener&quot; data-source-url=&quot;https://sbcode.net/grafana/logql/&quot;&gt;
&lt;div class=&quot;og-image&quot; style=&quot;background-image: url();&quot;&gt;&amp;nbsp;&lt;/div&gt;
&lt;div class=&quot;og-text&quot;&gt;
&lt;p class=&quot;og-title&quot; data-ke-size=&quot;size16&quot;&gt;LogQL - Grafana Tutorials&lt;/p&gt;
&lt;p class=&quot;og-desc&quot; data-ke-size=&quot;size16&quot;&gt;LogQL Video Lecture Description Now that we have a loki data source we can query it with the LogQL query language. In this video, we will try out many LogQL queries on the Loki data source we've setup. There are two types of LogQL queries: Log queries retu&lt;/p&gt;
&lt;p class=&quot;og-host&quot; data-ke-size=&quot;size16&quot;&gt;sbcode.net&lt;/p&gt;
&lt;/div&gt;
&lt;/a&gt;&lt;/figure&gt;</description>
      <category>DevOps</category>
      <category>DevOps</category>
      <category>Grafana</category>
      <category>LOG</category>
      <category>logql</category>
      <category>LOKI</category>
      <category>mltp</category>
      <category>o11y</category>
      <category>observability</category>
      <category>로그</category>
      <author>겨울바람_</author>
      <guid isPermaLink="true">https://breeze-winter.tistory.com/55</guid>
      <comments>https://breeze-winter.tistory.com/55#entry55comment</comments>
      <pubDate>Tue, 18 Mar 2025 11:00:13 +0900</pubDate>
    </item>
    <item>
      <title>Book Study - SRE (Site Reliability Engineering) Chapter03, 04</title>
      <link>https://breeze-winter.tistory.com/54</link>
      <description>&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;521&quot;&gt;&lt;span data-url=&quot;https://blog.kakaocdn.net/dn/bIVVv2/btsMMA075fi/CsGBXvJ7oHneBdGK6ItX0k/tfile.dat&quot; data-phocus=&quot;https://blog.kakaocdn.net/dn/bIVVv2/btsMMA075fi/CsGBXvJ7oHneBdGK6ItX0k/tfile.dat&quot;&gt;&lt;img src=&quot;https://blog.kakaocdn.net/dn/bIVVv2/btsMMA075fi/CsGBXvJ7oHneBdGK6ItX0k/tfile.dat&quot; srcset=&quot;https://img1.daumcdn.net/thumb/R1280x0/?scode=mtistory2&amp;fname=https%3A%2F%2Fblog.kakaocdn.net%2Fdn%2FbIVVv2%2FbtsMMA075fi%2FCsGBXvJ7oHneBdGK6ItX0k%2Ftfile.dat&quot; onerror=&quot;this.onerror=null; this.src='//t1.daumcdn.net/tistory_admin/static/images/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;521&quot; data-origin-width=&quot;400&quot; data-origin-height=&quot;521&quot;/&gt;&lt;/span&gt;&lt;/figure&gt;
&lt;/p&gt;
&lt;h1&gt;Chapter 3 - Embracing Risk&lt;/h1&gt;
&lt;h2 style=&quot;color: #000000; text-align: start;&quot; data-ke-size=&quot;size26&quot;&gt;  Summary&lt;/h2&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;SRE 조직에서는 &lt;b&gt;완벽한 가용성을 목표로 삼기보다는&lt;/b&gt; 비즈니스 목표와 사용자 기대치를 고려하여 &lt;b&gt;적절한 수준의 신뢰성을 유지하는 것&lt;/b&gt;이 핵심이다. 모든 시스템의 가용성을 100%로 설정하려 하면 &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;가용성 목표치를 100%로 맞추기 위해 &lt;b&gt;더 많은 노력을 기울이는 대신&lt;/b&gt;, 일정 수준의 다운타임을 허용함으로써 &lt;b&gt;운영 비용 절감과 새로운 기능 개발의 균형을 맞추려 한다&lt;/b&gt;.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;서비스의 &lt;b&gt;위험 수용도(Risk Tolerance)&lt;/b&gt; 를 정의할 때는, 해당 서비스의 기능과 시장 내 위치를 고려해야 하며, &lt;b&gt;소비자 대상 서비스인지 인프라스트럭처 서비스인지에 따라 차이가 발생한다&lt;/b&gt;.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;예를 들어, 유튜브와 같은 소비자 대상 서비스는 &lt;b&gt;일시적인 장애가 발생하더라도 사용자가 다른 대안을 찾을 수 있어 상대적으로 낮은 가용성을 설정할 수 있다&lt;/b&gt;. 그러나, &lt;b&gt;구글 앱스와 같은 B2B 서비스는 다양한 기업이 업무에 활용하기 때문에 훨씬 높은 수준의 가용성이 요구된다&lt;/b&gt;.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;또한, &lt;b&gt;발생할 수 있는 장애의 유형을 예측하고, 목표 가용성을 유지했을 때 발생하는 수익이 이를 달성하기 위한 비용을 상쇄할 수 있는지 등을 고려해야 한다&lt;/b&gt;.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;b&gt;인프라스트럭처 서비스의 경우&lt;/b&gt; 응답 속도, 신뢰성, 처리량 등의 요소가 더욱 중요하며, 서비스의 특성에 따라 &lt;b&gt;성공과 실패의 기준이 달라진다&lt;/b&gt;. 이러한 서비스들은 고객이 시스템을 구축할 때 &lt;b&gt;위험과 비용의 균형을 명확히 설정할 수 있도록 서비스 수준을 정확하게 명시해야 한다&lt;/b&gt;. 이를 통해 &lt;b&gt;고객은 자신의 요구사항에 적합한 서비스를 선택할 수 있다&lt;/b&gt;.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;위험 수용도를 정의한 후에는 &lt;b&gt;에러 예산(Error Budget)을 산정해야 한다&lt;/b&gt;. 일반적으로, &lt;b&gt;위험 수용도가 높을수록 에러 예산이 크고, 위험 수용도가 낮을수록 에러 예산이 작아진다&lt;/b&gt;.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;에러 예산이 &lt;b&gt;넉넉하면&lt;/b&gt; 개발팀은 새로운 기능을 빠르게 배포하고, 실험적인 기능을 개발하는 등 &lt;b&gt;더 많은 리스크를 감수할 수 있다&lt;/b&gt;. 반면, &lt;b&gt;에러 예산이 부족하면 안정성 개선 작업에 집중하고, 배포 속도를 늦춰야 한다&lt;/b&gt;.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;따라서 &lt;b&gt;에러 예산은 운영 및 배포 전략을 조정하는 중요한 기준이 되므로&lt;/b&gt;, 서비스에 맞는 &lt;b&gt;위험 수용도를 명확하게 정의하고 이에 따라 적절한 에러 예산을 산정하는 것이 필수적이다&lt;/b&gt;.&lt;/p&gt;
&lt;h2 data-ke-size=&quot;size26&quot;&gt;Thoughts&lt;/h2&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;책 자체가 확실히 여러 사람이 함께 작성한 내용이다 보니, 용어부분에서 일정 부분 설명이 겹치는 경우가 종종 있는 것 같다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;뭔가 자주 반복해서 나올 수록 점점 더 헷갈리는 것 같기도 하고...&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;별개로, 이번 챕터에서 나온 내용 중 서비스의 목적에 따라 위험 수용도를 정의해야 한다는 부분이 굉장히 와닿았다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;상대적으로 민감한 정보를 다루게 되는 금융 혹은 공공 분야의 경우 최신 기술을 사용하기 보다는 이미 검증된 기술들 위주로 사용하기 때문에 에러 예산 측정에 더욱 보수적인 것이 맞는 것 같다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;최근에는 금융 및 공공 분야도 클라우드 네이티브 전환을 목적으로 여러 프로젝트가 발생하고 있지만, 신기술과 시스템 안정성과 보안성, 그리고 예측 가능성 사이에서 적절하게 균형을 잡을 수 있을지가 정말 관건인 것 같다.&lt;/p&gt;
&lt;h1&gt;Chapter 4 - Service Level Objectives&lt;/h1&gt;
&lt;h2 style=&quot;color: #000000; text-align: start;&quot; data-ke-size=&quot;size26&quot;&gt;  Summary&lt;/h2&gt;
&lt;h3 data-ke-size=&quot;size23&quot;&gt;&lt;b&gt;SLI (Service Level Indicator) - 서비스 수준 척도&lt;/b&gt;&lt;/h3&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;대부분의 서비스에서 핵심 SLI로 사용하는 지표는 다음과 같다.&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;/b&gt;: 요청을 처리하는 데 걸리는 시간&lt;/li&gt;
&lt;li&gt;&lt;b&gt;에러율&lt;/b&gt;: 수신한 전체 요청 대비 실패한 요청 비율 (예: HTTP 500 오류 발생률)&lt;/li&gt;
&lt;li&gt;&lt;b&gt;처리량(Throughput)&lt;/b&gt;: 초당 처리할 수 있는 요청 수&lt;/li&gt;
&lt;/ul&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;이러한 값들은 &lt;b&gt;일정 기간 동안 수집된 데이터를 바탕으로 비율, 평균, 백분위수(percentile) 등을 계산하여 분석&lt;/b&gt;된다. 특히, &lt;b&gt;평균보다는 분포(percentile)가 더 중요한 경우가 많다&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;SLI는 주로 &lt;b&gt;쿠버네티스, 프로메테우스, 로그 분석 도구&lt;/b&gt; 등을 통해 &lt;b&gt;서버 측에서 수집&lt;/b&gt;된다. 그러나 &lt;b&gt;사용자의 실제 경험을 측정하기 위해 클라이언트 측 데이터도 함께 수집하는 것이 중요하다&lt;/b&gt;.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;여러 가지 서비스 수준 척도가 존재하지만, SRE 팀에서 가장 중요하게 여기는 지표 중 하나는 &lt;b&gt;서비스의 가용성(Availability)&lt;/b&gt; 이다. 일반적으로 이는 &lt;b&gt;올바른 요청이 성공적으로 처리된 비율&lt;/b&gt;을 의미한다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;SLI를 명확하게 설정하려면 &lt;b&gt;사용자가 원하는 것이 무엇인지 이해하는 것이 중요&lt;/b&gt;하다.&lt;/p&gt;
&lt;ul style=&quot;list-style-type: disc;&quot; data-ke-list-type=&quot;disc&quot;&gt;
&lt;li&gt;&lt;b&gt;사용자가 직접 대면하는 서비스&lt;/b&gt;: &lt;b&gt;가용성, 응답 시간, 처리량&lt;/b&gt;이 중요&lt;/li&gt;
&lt;li&gt;&lt;b&gt;저장소 시스템(Storage systems)&lt;/b&gt;: &lt;b&gt;응답 시간, 가용성, 내구성&lt;/b&gt;이 중요&lt;/li&gt;
&lt;li&gt;&lt;b&gt;빅데이터 시스템(Big Data systems)&lt;/b&gt;: &lt;b&gt;처리량, 종단 간 응답 시간(end-to-end latency)&lt;/b&gt;이 중요&lt;/li&gt;
&lt;/ul&gt;
&lt;hr data-ke-style=&quot;style1&quot; /&gt;
&lt;h3 data-ke-size=&quot;size23&quot;&gt;&lt;b&gt;SLO (Service Level Objectives) - 서비스 수준 목표&lt;/b&gt;&lt;/h3&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;SLI로 측정된 서비스 수준에 대해 &lt;b&gt;목표값 또는 허용 범위를 정의한 것&lt;/b&gt;을 의미한다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;SLO는 다음과 같은 수식으로 표현할 수 있다.&lt;/p&gt;
&lt;ul style=&quot;list-style-type: disc;&quot; data-ke-list-type=&quot;disc&quot;&gt;
&lt;li&gt;&lt;code&gt;SLI &amp;le; 목표치&lt;/code&gt;&lt;/li&gt;
&lt;li&gt;&lt;code&gt;최솟값 &amp;le; SLI &amp;le; 최댓값&lt;/code&gt;&lt;/li&gt;
&lt;/ul&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;SLO를 설정하고 이를 고객에게 공개하면 &lt;b&gt;서비스 동작에 대한 예측 가능성을 높일 수 있다&lt;/b&gt;.&lt;br /&gt;예를 들어, 특정 서비스의 SLO가 &lt;b&gt;&quot;요청당 평균 응답 시간을 100ms 이내로 유지&quot;&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;또한, SLO를 달성하지 못한 비율은 &lt;b&gt;사용자가 서비스 품질을 어떻게 인식하는지를 평가하는 데 유용한 척도&lt;/b&gt;가 된다.&lt;br /&gt;따라서 SLO를 일 단위 또는 주 단위로 추적하여 &lt;b&gt;장기적인 트렌드를 파악하고, 장애 발생 전에 문제 조짐을 미리 감지하는 것이 중요&lt;/b&gt;하다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;하지만 &lt;b&gt;적절한 SLO를 설정하는 것은 생각보다 복잡한 작업&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&gt;불규칙하고 외부 요인에 따라 변하는 지표&lt;/b&gt;는 관리가 어렵다.&lt;/li&gt;
&lt;li&gt;현재의 성능을 기준으로 목표치를 설정하면, 이후 변화에 따라 &lt;b&gt;비현실적인 목표가 될 가능성이 높다&lt;/b&gt;.&lt;/li&gt;
&lt;li&gt;따라서 &lt;b&gt;최대한 단순하게 설정&lt;/b&gt;하고, 서비스 특성을 정확히 반영하는 &lt;b&gt;최소한의 SLO&lt;/b&gt;를 설정하는 것이 바람직하다.&lt;/li&gt;
&lt;/ul&gt;
&lt;hr data-ke-style=&quot;style1&quot; /&gt;
&lt;h3 data-ke-size=&quot;size23&quot;&gt;&lt;b&gt;SLA (Service Level Agreement) - 서비스 수준 협약&lt;/b&gt;&lt;/h3&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;SLA는 &lt;b&gt;SLO가 충족되었을 때 또는 충족되지 않았을 때의 보상 조건을 명시한 계약&lt;/b&gt;을 의미한다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;일반적으로 &lt;b&gt;SRE는 SLA 체결 과정에는 관여하지 않지만&lt;/b&gt;, 서비스 운영 측면에서 &lt;b&gt;SLO를 유지하여 SLA 위반을 방지하는 역할을 담당&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;모든 서비스에 SLA가 존재하는 것은 아니다.&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;/b&gt;은 SLA가 없지만, 검색 기능이 중단되면 &lt;b&gt;기업의 평판 악화 및 광고 수익 감소 등 부정적인 영향을 초래할 수 있다&lt;/b&gt;.&lt;/li&gt;
&lt;li&gt;반면, &lt;b&gt;B2B 서비스(예: Google Workspace, Cloud 서비스)&lt;/b&gt; 는 고객과 &lt;b&gt;명확한 SLA를 체결&lt;/b&gt; 하며, SLA 위반 시 환불 또는 크레딧 제공 등의 보상이 이루어질 수 있다.&lt;/li&gt;
&lt;/ul&gt;</description>
      <category>Book</category>
      <category>DevOps</category>
      <category>google sre</category>
      <category>SLA</category>
      <category>SLI</category>
      <category>Slo</category>
      <category>SRE</category>
      <category>에러 예산</category>
      <author>겨울바람_</author>
      <guid isPermaLink="true">https://breeze-winter.tistory.com/54</guid>
      <comments>https://breeze-winter.tistory.com/54#entry54comment</comments>
      <pubDate>Mon, 17 Mar 2025 09:12:53 +0900</pubDate>
    </item>
    <item>
      <title>[o11y] Intro-to-mltp</title>
      <link>https://breeze-winter.tistory.com/53</link>
      <description>&lt;h3 data-ke-size=&quot;size23&quot;&gt;Project Architecture Diagram&lt;/h3&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;아래의 아키텍처는 Grafana에서 제공하는 &lt;code&gt;intro-to-mltp&lt;/code&gt;라는 프로젝트의 아키텍처로, 본 글은 Grafana의 Loki, Grafana, Tempo, Mimir를 통해 해당 프로젝트에서 발생하는 에러의 원인을 파악하는 것을 목적으로 한다.&lt;/p&gt;
&lt;p&gt;&lt;figure class=&quot;imageblock alignCenter&quot; data-ke-mobileStyle=&quot;widthOrigin&quot; data-origin-width=&quot;5520&quot; data-origin-height=&quot;3825&quot;&gt;&lt;span data-url=&quot;https://blog.kakaocdn.net/dn/BxfZL/btsMIqpYy5a/MBi92I12IdOVTzrnER37u1/img.png&quot; data-phocus=&quot;https://blog.kakaocdn.net/dn/BxfZL/btsMIqpYy5a/MBi92I12IdOVTzrnER37u1/img.png&quot;&gt;&lt;img src=&quot;https://blog.kakaocdn.net/dn/BxfZL/btsMIqpYy5a/MBi92I12IdOVTzrnER37u1/img.png&quot; srcset=&quot;https://img1.daumcdn.net/thumb/R1280x0/?scode=mtistory2&amp;fname=https%3A%2F%2Fblog.kakaocdn.net%2Fdn%2FBxfZL%2FbtsMIqpYy5a%2FMBi92I12IdOVTzrnER37u1%2Fimg.png&quot; onerror=&quot;this.onerror=null; this.src='//t1.daumcdn.net/tistory_admin/static/images/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;489&quot; data-origin-width=&quot;5520&quot; data-origin-height=&quot;3825&quot;/&gt;&lt;/span&gt;&lt;/figure&gt;
&lt;/p&gt;
&lt;h3 data-ke-size=&quot;size23&quot;&gt;Loki&lt;/h3&gt;
&lt;p&gt;&lt;figure class=&quot;imageblock alignLeft&quot; data-ke-mobileStyle=&quot;widthOrigin&quot; data-origin-width=&quot;48&quot; data-origin-height=&quot;56&quot;&gt;&lt;span data-url=&quot;https://blog.kakaocdn.net/dn/bcsm20/btsMFI0eJuC/DCkD1toom0dRYfLJuD98x0/img.png&quot; data-phocus=&quot;https://blog.kakaocdn.net/dn/bcsm20/btsMFI0eJuC/DCkD1toom0dRYfLJuD98x0/img.png&quot;&gt;&lt;img src=&quot;https://blog.kakaocdn.net/dn/bcsm20/btsMFI0eJuC/DCkD1toom0dRYfLJuD98x0/img.png&quot; srcset=&quot;https://img1.daumcdn.net/thumb/R1280x0/?scode=mtistory2&amp;fname=https%3A%2F%2Fblog.kakaocdn.net%2Fdn%2Fbcsm20%2FbtsMFI0eJuC%2FDCkD1toom0dRYfLJuD98x0%2Fimg.png&quot; onerror=&quot;this.onerror=null; this.src='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png'; this.srcset='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png';&quot; loading=&quot;lazy&quot; width=&quot;48&quot; height=&quot;56&quot; data-origin-width=&quot;48&quot; data-origin-height=&quot;56&quot;/&gt;&lt;/span&gt;&lt;/figure&gt;
&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;로그는 어디서든 발생할 수 있기 때문에 로그에서 특정한 내용을 찾으려고 할 때 다소 어려움이 존재할 수 있다. 또한 시스템의 가동 시간이 길어지고 사용량이 많아질 수록 기록이 방대해지면서 저장 공간의 필요성도 커지고 있다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;또한, 방대한 양의 로그를 어떤 식으로 결합하고 의미있는 방식으로 집계할지에 대한 고민이 필요하다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;이에 더해, 영구적이지 않은 저장소에서 정보를 추출하는 것은 불확실성을 내포하고 있기에 영구적으로 로그를 저장할 수 있는 저장소가 필요하다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;Loki는 이러한 문제를 해결하기 위해 탄생한 로그를 위한 데이터베이스다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;Loki는 로그의 전체 텍스트가 아닌 메타데이터만 색인화하여 저장한다. 따라서, 쿼리가 더욱 빨라질 뿐 아니라 색인만을 스토리지에 저장하므로 저장소의 리소스 사용량이 현저히 감소한다.&lt;/p&gt;
&lt;p&gt;&lt;figure class=&quot;imageblock alignCenter&quot; data-ke-mobileStyle=&quot;widthOrigin&quot; data-origin-width=&quot;947&quot; data-origin-height=&quot;285&quot;&gt;&lt;span data-url=&quot;https://blog.kakaocdn.net/dn/bqzmz2/btsMIdxtP04/GtZp3I7vt6itDiCeIQ3KQK/img.png&quot; data-phocus=&quot;https://blog.kakaocdn.net/dn/bqzmz2/btsMIdxtP04/GtZp3I7vt6itDiCeIQ3KQK/img.png&quot;&gt;&lt;img src=&quot;https://blog.kakaocdn.net/dn/bqzmz2/btsMIdxtP04/GtZp3I7vt6itDiCeIQ3KQK/img.png&quot; srcset=&quot;https://img1.daumcdn.net/thumb/R1280x0/?scode=mtistory2&amp;fname=https%3A%2F%2Fblog.kakaocdn.net%2Fdn%2Fbqzmz2%2FbtsMIdxtP04%2FGtZp3I7vt6itDiCeIQ3KQK%2Fimg.png&quot; onerror=&quot;this.onerror=null; this.src='//t1.daumcdn.net/tistory_admin/static/images/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;209&quot; data-origin-width=&quot;947&quot; data-origin-height=&quot;285&quot;/&gt;&lt;/span&gt;&lt;/figure&gt;
&lt;/p&gt;
&lt;h3 data-ke-size=&quot;size23&quot;&gt;Prometheus &amp;amp; Mimir&lt;/h3&gt;
&lt;p&gt;&lt;figure class=&quot;imageblock alignLeft&quot; data-ke-mobileStyle=&quot;widthOrigin&quot; data-origin-width=&quot;1200&quot; data-origin-height=&quot;1190&quot;&gt;&lt;span data-url=&quot;https://blog.kakaocdn.net/dn/WisDW/btsMHbOcAlu/Eo5qu014Bqe92sdjeFbrUK/img.png&quot; data-phocus=&quot;https://blog.kakaocdn.net/dn/WisDW/btsMHbOcAlu/Eo5qu014Bqe92sdjeFbrUK/img.png&quot;&gt;&lt;img src=&quot;https://blog.kakaocdn.net/dn/WisDW/btsMHbOcAlu/Eo5qu014Bqe92sdjeFbrUK/img.png&quot; srcset=&quot;https://img1.daumcdn.net/thumb/R1280x0/?scode=mtistory2&amp;fname=https%3A%2F%2Fblog.kakaocdn.net%2Fdn%2FWisDW%2FbtsMHbOcAlu%2FEo5qu014Bqe92sdjeFbrUK%2Fimg.png&quot; onerror=&quot;this.onerror=null; this.src='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png'; this.srcset='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png';&quot; loading=&quot;lazy&quot; width=&quot;74&quot; height=&quot;73&quot; data-origin-width=&quot;1200&quot; data-origin-height=&quot;1190&quot;/&gt;&lt;/span&gt;&lt;/figure&gt;
&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;br /&gt;메트릭은 시스템이 어떻게 작동하는지에 대한 정량적인 정보를 빠르게 얻는 데 유용하며 숫자로 표현되기 때문에 여러 구성 요소 혹은 시간 경과에 따라 집계할 수 있다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;메트릭 그 자체만으로는 맥락없는 단순한 수치를 나타내기 때문에 문제를 해결하는데 그다지 효과적이지 않다. 구체적인 무언가를 찾기 위해서는 다른 지표를 함께 활용하거나 표현된 숫자가 의미를 갖도록 집계해야 한다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;하지만 메트릭 집계를 위해서는 많은 시간 혹은 리소스가 필요하다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;Prometheus는 메트릭을 위한 데이터베이스지만 기존 데이터베이스와는 다르게 레이블을 사용하여 매우 뛰어난 성능을 자랑한다. 또한 푸시와 풀 기반 모니터링을 제공하기 때문에 애플리케이션 구성 요소가 푸시하는 데이터를 수신할 수도 있고 해당 데이터를 데이터베이스로 가져올 수도 있다.&lt;/p&gt;
&lt;hr data-ke-style=&quot;style1&quot; /&gt;
&lt;p&gt;&lt;figure class=&quot;imageblock alignLeft&quot; data-ke-mobileStyle=&quot;widthOrigin&quot; data-origin-width=&quot;65&quot; data-origin-height=&quot;46&quot;&gt;&lt;span data-url=&quot;https://blog.kakaocdn.net/dn/VHim7/btsMFwMw5WT/bgiwgkYicOdkmsqBPvsjr0/img.png&quot; data-phocus=&quot;https://blog.kakaocdn.net/dn/VHim7/btsMFwMw5WT/bgiwgkYicOdkmsqBPvsjr0/img.png&quot;&gt;&lt;img src=&quot;https://blog.kakaocdn.net/dn/VHim7/btsMFwMw5WT/bgiwgkYicOdkmsqBPvsjr0/img.png&quot; srcset=&quot;https://img1.daumcdn.net/thumb/R1280x0/?scode=mtistory2&amp;fname=https%3A%2F%2Fblog.kakaocdn.net%2Fdn%2FVHim7%2FbtsMFwMw5WT%2FbgiwgkYicOdkmsqBPvsjr0%2Fimg.png&quot; onerror=&quot;this.onerror=null; this.src='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png'; this.srcset='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png';&quot; loading=&quot;lazy&quot; width=&quot;65&quot; height=&quot;46&quot; data-origin-width=&quot;65&quot; data-origin-height=&quot;46&quot;/&gt;&lt;/span&gt;&lt;/figure&gt;
&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;br /&gt;Mimir는 프로메테우스를 위한 장기 스토리지와 멀티 테넌트 기능을 제공한다. 또한, 수평 확장이 가능하고 안정적이다.&lt;/p&gt;
&lt;p&gt;&lt;figure class=&quot;imageblock alignCenter&quot; data-ke-mobileStyle=&quot;widthOrigin&quot; data-origin-width=&quot;3858&quot; data-origin-height=&quot;1925&quot;&gt;&lt;span data-url=&quot;https://blog.kakaocdn.net/dn/Xp94D/btsMIDJszo9/AjzCKtPmxMgFuNPK9sbxeK/img.png&quot; data-phocus=&quot;https://blog.kakaocdn.net/dn/Xp94D/btsMIDJszo9/AjzCKtPmxMgFuNPK9sbxeK/img.png&quot;&gt;&lt;img src=&quot;https://blog.kakaocdn.net/dn/Xp94D/btsMIDJszo9/AjzCKtPmxMgFuNPK9sbxeK/img.png&quot; srcset=&quot;https://img1.daumcdn.net/thumb/R1280x0/?scode=mtistory2&amp;fname=https%3A%2F%2Fblog.kakaocdn.net%2Fdn%2FXp94D%2FbtsMIDJszo9%2FAjzCKtPmxMgFuNPK9sbxeK%2Fimg.png&quot; onerror=&quot;this.onerror=null; this.src='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png'; this.srcset='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png';&quot; loading=&quot;lazy&quot; width=&quot;513&quot; height=&quot;256&quot; data-origin-width=&quot;3858&quot; data-origin-height=&quot;1925&quot;/&gt;&lt;/span&gt;&lt;/figure&gt;
&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;br /&gt;복수의 Prometheus를 운영한다면 Mimir를 활용하는 것이 좋다.&lt;/p&gt;
&lt;h3 data-ke-size=&quot;size23&quot;&gt;Tempo&lt;/h3&gt;
&lt;p&gt;&lt;figure class=&quot;imageblock alignLeft&quot; data-ke-mobileStyle=&quot;widthOrigin&quot; data-origin-width=&quot;184&quot; data-origin-height=&quot;150&quot;&gt;&lt;span data-url=&quot;https://blog.kakaocdn.net/dn/cYh2Mu/btsMH6FlVGD/h641wsGKaK1AAgrejfmIS0/img.png&quot; data-phocus=&quot;https://blog.kakaocdn.net/dn/cYh2Mu/btsMH6FlVGD/h641wsGKaK1AAgrejfmIS0/img.png&quot;&gt;&lt;img src=&quot;https://blog.kakaocdn.net/dn/cYh2Mu/btsMH6FlVGD/h641wsGKaK1AAgrejfmIS0/img.png&quot; srcset=&quot;https://img1.daumcdn.net/thumb/R1280x0/?scode=mtistory2&amp;fname=https%3A%2F%2Fblog.kakaocdn.net%2Fdn%2FcYh2Mu%2FbtsMH6FlVGD%2Fh641wsGKaK1AAgrejfmIS0%2Fimg.png&quot; onerror=&quot;this.onerror=null; this.src='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png'; this.srcset='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png';&quot; loading=&quot;lazy&quot; width=&quot;69&quot; height=&quot;56&quot; data-origin-width=&quot;184&quot; data-origin-height=&quot;150&quot;/&gt;&lt;/span&gt;&lt;/figure&gt;
&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;트레이스는 로그와 비슷하지만 서비스나 구성 요소에 대한 것이 아니라 단일 트랜잭션에 대한 흐름, 즉 여러 서비스에서 처리되는 요청을 추적하여 어디로 갔는지 어디에서 얼마나 걸렸는지에 대한 흐름을 파악할 수 있다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;로그와 마찬가지로 저장해야 할 정보가 방대하기 때문에 많은 비용이 소모될 수 있다. 트레이스는 모든 구성 요소가 계측된 경우에만 유효하게 동작하는데 이는 실제로 트레이스를 통해 의미있는 정보를 얻기 위해서는 많은 설정이 필요하다는 것을 의미한다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;Tempo는 분산 트레이스를 위한 데이터베이스이며, 파이프 기반으로 동작하는 TraceQL을 통해 트레이스에 필요한 데이터 수집에 적합하다.&lt;/p&gt;
&lt;h3 data-ke-size=&quot;size23&quot;&gt;Pyroscope&lt;/h3&gt;
&lt;p&gt;&lt;figure class=&quot;imageblock alignLeft&quot; data-ke-mobileStyle=&quot;widthOrigin&quot; data-origin-width=&quot;202&quot; data-origin-height=&quot;274&quot;&gt;&lt;span data-url=&quot;https://blog.kakaocdn.net/dn/to82d/btsMHcsNTHT/nFnYHzB4jXAWM4a5igV370/img.png&quot; data-phocus=&quot;https://blog.kakaocdn.net/dn/to82d/btsMHcsNTHT/nFnYHzB4jXAWM4a5igV370/img.png&quot;&gt;&lt;img src=&quot;https://blog.kakaocdn.net/dn/to82d/btsMHcsNTHT/nFnYHzB4jXAWM4a5igV370/img.png&quot; srcset=&quot;https://img1.daumcdn.net/thumb/R1280x0/?scode=mtistory2&amp;fname=https%3A%2F%2Fblog.kakaocdn.net%2Fdn%2Fto82d%2FbtsMHcsNTHT%2FnFnYHzB4jXAWM4a5igV370%2Fimg.png&quot; onerror=&quot;this.onerror=null; this.src='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png'; this.srcset='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png';&quot; loading=&quot;lazy&quot; width=&quot;62&quot; height=&quot;84&quot; data-origin-width=&quot;202&quot; data-origin-height=&quot;274&quot;/&gt;&lt;/span&gt;&lt;/figure&gt;
&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;프로파일은 애플리케이션이 얼마나 많은 컴퓨팅 파워 혹은 리소스를 사용하는지에 대한 정보이며, 시간 경과에 따라 특정 간격으로 수집된다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;이는 CPU나 메모리 사용률을 알려준다는 점에서 일종의 메트릭이지만, 전반적인 사용률에 관련된 특정 함수 호출까지 세부적으로 살펴볼 수 있다는 점에서 일종의 트레이스와도 같다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;프로파일은 상당한 오버헤드를 발생시키는데, CPU에 대한 정보 수집 시 CPU에 추가적인 작업이 발생할 수 있기에 성능에 영향을 미칠 수 있다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;Pyroscope는 연속 프로파일을 위한 데이터베이스로 애플리케이션의 성능을 위해 구축됐다. 보통 애플리케이션의 복잡도에 따라 프로파일을 수집하는데 1~10%의 오버헤드가 발생할 수 있는데 Pyroscope는 개별적인 측정항목을 저장하지 않고 전체 프로파일의 형태를 파악하는데 필요한 정보만을 저장한다.&lt;/p&gt;
&lt;h3 data-ke-size=&quot;size23&quot;&gt;Alloy&lt;/h3&gt;
&lt;p&gt;&lt;figure class=&quot;imageblock alignLeft&quot; data-ke-mobileStyle=&quot;widthOrigin&quot; data-origin-width=&quot;1494&quot; data-origin-height=&quot;1512&quot;&gt;&lt;span data-url=&quot;https://blog.kakaocdn.net/dn/dCdxqU/btsMFG9j3SG/FV8k9nU4UoiPWshm2MJ7e1/img.png&quot; data-phocus=&quot;https://blog.kakaocdn.net/dn/dCdxqU/btsMFG9j3SG/FV8k9nU4UoiPWshm2MJ7e1/img.png&quot;&gt;&lt;img src=&quot;https://blog.kakaocdn.net/dn/dCdxqU/btsMFG9j3SG/FV8k9nU4UoiPWshm2MJ7e1/img.png&quot; srcset=&quot;https://img1.daumcdn.net/thumb/R1280x0/?scode=mtistory2&amp;fname=https%3A%2F%2Fblog.kakaocdn.net%2Fdn%2FdCdxqU%2FbtsMFG9j3SG%2FFV8k9nU4UoiPWshm2MJ7e1%2Fimg.png&quot; onerror=&quot;this.onerror=null; this.src='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png'; this.srcset='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png';&quot; loading=&quot;lazy&quot; width=&quot;65&quot; height=&quot;66&quot; data-origin-width=&quot;1494&quot; data-origin-height=&quot;1512&quot;/&gt;&lt;/span&gt;&lt;/figure&gt;
&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;Alloy는 OTel, Prometheus, Pyroscope, Loki 및 기타 여려 메트릭, 로그, 트레이스 및 프로파일 도구에 대한 기본 파이프라인을 제공한다.&lt;/p&gt;
&lt;p&gt;&lt;figure class=&quot;imageblock alignCenter&quot; data-ke-mobileStyle=&quot;widthOrigin&quot; data-origin-width=&quot;821&quot; data-origin-height=&quot;441&quot;&gt;&lt;span data-url=&quot;https://blog.kakaocdn.net/dn/b4PJVI/btsMFvz30pP/2s565G8p6LNBjmPBu0806k/img.png&quot; data-phocus=&quot;https://blog.kakaocdn.net/dn/b4PJVI/btsMFvz30pP/2s565G8p6LNBjmPBu0806k/img.png&quot;&gt;&lt;img src=&quot;https://blog.kakaocdn.net/dn/b4PJVI/btsMFvz30pP/2s565G8p6LNBjmPBu0806k/img.png&quot; srcset=&quot;https://img1.daumcdn.net/thumb/R1280x0/?scode=mtistory2&amp;fname=https%3A%2F%2Fblog.kakaocdn.net%2Fdn%2Fb4PJVI%2FbtsMFvz30pP%2F2s565G8p6LNBjmPBu0806k%2Fimg.png&quot; onerror=&quot;this.onerror=null; this.src='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png'; this.srcset='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png';&quot; loading=&quot;lazy&quot; width=&quot;495&quot; height=&quot;266&quot; data-origin-width=&quot;821&quot; data-origin-height=&quot;441&quot;/&gt;&lt;/span&gt;&lt;/figure&gt;
&lt;/p&gt;
&lt;h3 data-ke-size=&quot;size23&quot;&gt;Beyla&lt;/h3&gt;
&lt;p&gt;&lt;figure class=&quot;imageblock alignLeft&quot; data-ke-mobileStyle=&quot;widthOrigin&quot; data-origin-width=&quot;175&quot; data-origin-height=&quot;219&quot;&gt;&lt;span data-url=&quot;https://blog.kakaocdn.net/dn/dNw6DY/btsMFxdBp1w/T28X7o8qR8evD9xSPrIXPk/img.png&quot; data-phocus=&quot;https://blog.kakaocdn.net/dn/dNw6DY/btsMFxdBp1w/T28X7o8qR8evD9xSPrIXPk/img.png&quot;&gt;&lt;img src=&quot;https://blog.kakaocdn.net/dn/dNw6DY/btsMFxdBp1w/T28X7o8qR8evD9xSPrIXPk/img.png&quot; srcset=&quot;https://img1.daumcdn.net/thumb/R1280x0/?scode=mtistory2&amp;fname=https%3A%2F%2Fblog.kakaocdn.net%2Fdn%2FdNw6DY%2FbtsMFxdBp1w%2FT28X7o8qR8evD9xSPrIXPk%2Fimg.png&quot; onerror=&quot;this.onerror=null; this.src='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png'; this.srcset='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png';&quot; loading=&quot;lazy&quot; width=&quot;60&quot; height=&quot;75&quot; data-origin-width=&quot;175&quot; data-origin-height=&quot;219&quot;/&gt;&lt;/span&gt;&lt;/figure&gt;
&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;Beyla는 HTTP/gRPC 애플리케이션 자동 계측을 가능하게 하는 오픈소스 프로젝트다. 프로젝트는 eBPF를 기반으로 하며, 이를 통해 리눅스 커널의 다양한 지점에 프로그램을 연결할 수 있다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;애플리케이션에 대한 계측을 진행하려면 일반적으로 언어 에이전트를 배포나 패키지에 추가하고, 수동으로 트레이스 포인트를 추가한 후 재배포를 해야 한다. 그러나 Beyla를 사용하면 소스 코드를 수정하지 않고 단일 명령으로 모든 서비스에 대해 계측을 진행할 수 있다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;Beyla의 목표는 eBPF를 활용하여 애플리케이션 가시성(application observability)을 빠르게 시작할 수 있도록 돕는 것이고, 수집된 텔레메트리 데이터는 Grafana Labs의 LGTM 스택을 통해 백엔드 및 인프라 데이터와 결합되어, 원활한 풀 스택 오픈소스 가시성 솔루션을 제공한다.&lt;/p&gt;
&lt;hr data-ke-style=&quot;style1&quot; /&gt;
&lt;h3 data-ke-size=&quot;size23&quot;&gt;Project에서 활용해보기&lt;/h3&gt;
&lt;p&gt;&lt;figure class=&quot;imageblock alignCenter&quot; data-ke-mobileStyle=&quot;widthOrigin&quot; data-origin-width=&quot;1916&quot; data-origin-height=&quot;870&quot;&gt;&lt;span data-url=&quot;https://blog.kakaocdn.net/dn/bK0Kds/btsMHozRiHU/R6wrYO6KZIIabI6yfpV2X1/img.png&quot; data-phocus=&quot;https://blog.kakaocdn.net/dn/bK0Kds/btsMHozRiHU/R6wrYO6KZIIabI6yfpV2X1/img.png&quot;&gt;&lt;img src=&quot;https://blog.kakaocdn.net/dn/bK0Kds/btsMHozRiHU/R6wrYO6KZIIabI6yfpV2X1/img.png&quot; srcset=&quot;https://img1.daumcdn.net/thumb/R1280x0/?scode=mtistory2&amp;fname=https%3A%2F%2Fblog.kakaocdn.net%2Fdn%2FbK0Kds%2FbtsMHozRiHU%2FR6wrYO6KZIIabI6yfpV2X1%2Fimg.png&quot; onerror=&quot;this.onerror=null; this.src='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png'; this.srcset='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png';&quot; loading=&quot;lazy&quot; width=&quot;847&quot; height=&quot;385&quot; data-origin-width=&quot;1916&quot; data-origin-height=&quot;870&quot;/&gt;&lt;/span&gt;&lt;/figure&gt;
&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;Tempo를 통해 각 애플리케이션 서비스를 트레이스 해봤을 때, &lt;code&gt;mythical-server&lt;/code&gt;와 &lt;code&gt;mythical-requester&lt;/code&gt;, &lt;code&gt;postgresql&lt;/code&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;Loki를 통해 &lt;code&gt;mythical-requester&lt;/code&gt;에서 발생하는 에러의 로그를 탐색해보았고, 아래의 사진처럼 &lt;code&gt;POST&lt;/code&gt; 메소드를 통해 각 테이블 엔드 포인트로 요청을 보냈을 때 지속적으로 요청이 실패하는 것을 확인할 수 있었다.&lt;/p&gt;
&lt;p&gt;&lt;figure class=&quot;imageblock alignCenter&quot; data-ke-mobileStyle=&quot;widthOrigin&quot; data-origin-width=&quot;1627&quot; data-origin-height=&quot;657&quot;&gt;&lt;span data-url=&quot;https://blog.kakaocdn.net/dn/cQ6PSe/btsMHQQjZKO/4eJgANBwyDOCOceahEhN2k/img.png&quot; data-phocus=&quot;https://blog.kakaocdn.net/dn/cQ6PSe/btsMHQQjZKO/4eJgANBwyDOCOceahEhN2k/img.png&quot;&gt;&lt;img src=&quot;https://blog.kakaocdn.net/dn/cQ6PSe/btsMHQQjZKO/4eJgANBwyDOCOceahEhN2k/img.png&quot; srcset=&quot;https://img1.daumcdn.net/thumb/R1280x0/?scode=mtistory2&amp;fname=https%3A%2F%2Fblog.kakaocdn.net%2Fdn%2FcQ6PSe%2FbtsMHQQjZKO%2F4eJgANBwyDOCOceahEhN2k%2Fimg.png&quot; onerror=&quot;this.onerror=null; this.src='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png'; this.srcset='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png';&quot; loading=&quot;lazy&quot; width=&quot;820&quot; height=&quot;331&quot; data-origin-width=&quot;1627&quot; data-origin-height=&quot;657&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;1628&quot; data-origin-height=&quot;821&quot;&gt;&lt;span data-url=&quot;https://blog.kakaocdn.net/dn/cHHKmS/btsMFt3qmFJ/de1xf1lI8YOeTwYar1mIrk/img.png&quot; data-phocus=&quot;https://blog.kakaocdn.net/dn/cHHKmS/btsMFt3qmFJ/de1xf1lI8YOeTwYar1mIrk/img.png&quot;&gt;&lt;img src=&quot;https://blog.kakaocdn.net/dn/cHHKmS/btsMFt3qmFJ/de1xf1lI8YOeTwYar1mIrk/img.png&quot; srcset=&quot;https://img1.daumcdn.net/thumb/R1280x0/?scode=mtistory2&amp;fname=https%3A%2F%2Fblog.kakaocdn.net%2Fdn%2FcHHKmS%2FbtsMFt3qmFJ%2Fde1xf1lI8YOeTwYar1mIrk%2Fimg.png&quot; onerror=&quot;this.onerror=null; this.src='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png'; this.srcset='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png';&quot; loading=&quot;lazy&quot; width=&quot;816&quot; height=&quot;412&quot; data-origin-width=&quot;1628&quot; data-origin-height=&quot;821&quot;/&gt;&lt;/span&gt;&lt;/figure&gt;
&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;실패한 로그의 트레이스 ID를 Tempo를 통해 추적해본 결과 아래의 사진처럼 데이터베이스에 &lt;code&gt;INSERT&lt;/code&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;code&gt;mythical-requester&lt;/code&gt;가 &lt;code&gt;mythical-server&lt;/code&gt;로 보낸 요청 자체가 실패한 것이기 때문에 &lt;code&gt;mythical-server&lt;/code&gt;와 &lt;code&gt;mythical-requester&lt;/code&gt;, &lt;code&gt;postgresql&lt;/code&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;1627&quot; data-origin-height=&quot;817&quot;&gt;&lt;span data-url=&quot;https://blog.kakaocdn.net/dn/ONZnQ/btsMIEawKWb/OvjXbA7Ja1JmKcwgFmoqlk/img.png&quot; data-phocus=&quot;https://blog.kakaocdn.net/dn/ONZnQ/btsMIEawKWb/OvjXbA7Ja1JmKcwgFmoqlk/img.png&quot;&gt;&lt;img src=&quot;https://blog.kakaocdn.net/dn/ONZnQ/btsMIEawKWb/OvjXbA7Ja1JmKcwgFmoqlk/img.png&quot; srcset=&quot;https://img1.daumcdn.net/thumb/R1280x0/?scode=mtistory2&amp;fname=https%3A%2F%2Fblog.kakaocdn.net%2Fdn%2FONZnQ%2FbtsMIEawKWb%2FOvjXbA7Ja1JmKcwgFmoqlk%2Fimg.png&quot; onerror=&quot;this.onerror=null; this.src='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png'; this.srcset='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png';&quot; loading=&quot;lazy&quot; width=&quot;703&quot; height=&quot;353&quot; data-origin-width=&quot;1627&quot; data-origin-height=&quot;817&quot;/&gt;&lt;/span&gt;&lt;/figure&gt;
&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;발생하는 에러 메시지를 자세히 확인해보면 다음과 같다.&lt;br /&gt;&lt;code&gt;null value in column &quot;name&quot; of relation &quot;owlbear&quot; violates not-null constraint&lt;/code&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;code&gt;owlbear&lt;/code&gt; 테이블의 &lt;code&gt;name&lt;/code&gt; 컬럼이 &lt;code&gt;null&lt;/code&gt; 값을 허용하지 않는 &lt;code&gt;not-null&lt;/code&gt; 제약조건이 걸려있는데, &lt;code&gt;INSERT&lt;/code&gt; 요청의 &lt;code&gt;name&lt;/code&gt; 값이 &lt;code&gt;null&lt;/code&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;code&gt;owlbear&lt;/code&gt; 테이블의 제약조건을 조회해보자.&lt;/p&gt;
&lt;p&gt;&lt;figure class=&quot;imageblock alignCenter&quot; data-ke-mobileStyle=&quot;widthOrigin&quot; data-origin-width=&quot;952&quot; data-origin-height=&quot;607&quot;&gt;&lt;span data-url=&quot;https://blog.kakaocdn.net/dn/vLkoG/btsMIFgcTYY/JdRe7BTSBqLK5c2m5cL8Y0/img.png&quot; data-phocus=&quot;https://blog.kakaocdn.net/dn/vLkoG/btsMIFgcTYY/JdRe7BTSBqLK5c2m5cL8Y0/img.png&quot;&gt;&lt;img src=&quot;https://blog.kakaocdn.net/dn/vLkoG/btsMIFgcTYY/JdRe7BTSBqLK5c2m5cL8Y0/img.png&quot; srcset=&quot;https://img1.daumcdn.net/thumb/R1280x0/?scode=mtistory2&amp;fname=https%3A%2F%2Fblog.kakaocdn.net%2Fdn%2FvLkoG%2FbtsMIFgcTYY%2FJdRe7BTSBqLK5c2m5cL8Y0%2Fimg.png&quot; onerror=&quot;this.onerror=null; this.src='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png'; this.srcset='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png';&quot; loading=&quot;lazy&quot; width=&quot;719&quot; height=&quot;458&quot; data-origin-width=&quot;952&quot; data-origin-height=&quot;607&quot;/&gt;&lt;/span&gt;&lt;/figure&gt;
&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;예상했던 대로 &lt;code&gt;name&lt;/code&gt; 컬럼의 &lt;code&gt;is_nullable&lt;/code&gt; 값이 &lt;code&gt;NO&lt;/code&gt;로 설정되어 있는 것을 확인할 수 있다. 해당 프로젝트에서 지속적인 에러가 발생한 원인은 바로 요청에 &lt;code&gt;name&lt;/code&gt; 값이 결여되어 있기 때문&lt;/p&gt;
&lt;h3 data-ke-size=&quot;size23&quot;&gt;Reference&lt;/h3&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;a href=&quot;https://github.com/grafana/intro-to-mltp&quot;&gt;&lt;img src=&quot;https://opengraph.githubassets.com/1ca197cbcbe06e9fac5621070da704887c9ee6a4e0291c0e086710fffdb72dcd/grafana/intro-to-mltp&quot; alt=&quot;GitHub - grafana/intro-to-mltp: Introduction to Metrics, Logs, Traces and Profiles session companion code.&quot; width=&quot;622&quot; height=&quot;311&quot; /&gt;&lt;/a&gt;&lt;/p&gt;</description>
      <category>DevOps</category>
      <category>DevOps</category>
      <category>Grafana</category>
      <category>LOKI</category>
      <category>mimir</category>
      <category>mltp</category>
      <category>o11y</category>
      <category>tempo</category>
      <category>TRACE</category>
      <category>모니터링</category>
      <category>트레이스</category>
      <author>겨울바람_</author>
      <guid isPermaLink="true">https://breeze-winter.tistory.com/53</guid>
      <comments>https://breeze-winter.tistory.com/53#entry53comment</comments>
      <pubDate>Tue, 11 Mar 2025 17:51:34 +0900</pubDate>
    </item>
    <item>
      <title>Observability (O11y)</title>
      <link>https://breeze-winter.tistory.com/52</link>
      <description>&lt;p&gt;&lt;figure class=&quot;imageblock alignCenter&quot; data-ke-mobileStyle=&quot;widthOrigin&quot; data-origin-width=&quot;630&quot; data-origin-height=&quot;715&quot;&gt;&lt;span data-url=&quot;https://blog.kakaocdn.net/dn/lRReg/btsMFgu1Gz2/nWlqiE58hOdBaeYq9fkY00/img.png&quot; data-phocus=&quot;https://blog.kakaocdn.net/dn/lRReg/btsMFgu1Gz2/nWlqiE58hOdBaeYq9fkY00/img.png&quot;&gt;&lt;img src=&quot;https://blog.kakaocdn.net/dn/lRReg/btsMFgu1Gz2/nWlqiE58hOdBaeYq9fkY00/img.png&quot; srcset=&quot;https://img1.daumcdn.net/thumb/R1280x0/?scode=mtistory2&amp;fname=https%3A%2F%2Fblog.kakaocdn.net%2Fdn%2FlRReg%2FbtsMFgu1Gz2%2FnWlqiE58hOdBaeYq9fkY00%2Fimg.png&quot; onerror=&quot;this.onerror=null; this.src='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png'; this.srcset='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png';&quot; loading=&quot;lazy&quot; width=&quot;297&quot; height=&quot;337&quot; data-origin-width=&quot;630&quot; data-origin-height=&quot;715&quot;/&gt;&lt;/span&gt;&lt;/figure&gt;
&lt;/p&gt;
&lt;h2 data-ke-size=&quot;size26&quot;&gt;Observability란?&lt;/h2&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;인체는 서로 다른 기능을 수행하는 여러 시스템으로 구성되어 있다. 소화계, 호흡계, 신경계, 근육 등 모든 시스템이 모여 하나의 인체를 형성한다.&lt;/p&gt;
&lt;p&gt;&lt;figure class=&quot;imageblock alignCenter&quot; data-ke-mobileStyle=&quot;widthOrigin&quot; data-origin-width=&quot;817&quot; data-origin-height=&quot;317&quot;&gt;&lt;span data-url=&quot;https://blog.kakaocdn.net/dn/cASfGj/btsMECezCE4/VgWJBypO0clEXsb8dKRKM1/img.png&quot; data-phocus=&quot;https://blog.kakaocdn.net/dn/cASfGj/btsMECezCE4/VgWJBypO0clEXsb8dKRKM1/img.png&quot;&gt;&lt;img src=&quot;https://blog.kakaocdn.net/dn/cASfGj/btsMECezCE4/VgWJBypO0clEXsb8dKRKM1/img.png&quot; srcset=&quot;https://img1.daumcdn.net/thumb/R1280x0/?scode=mtistory2&amp;fname=https%3A%2F%2Fblog.kakaocdn.net%2Fdn%2FcASfGj%2FbtsMECezCE4%2FVgWJBypO0clEXsb8dKRKM1%2Fimg.png&quot; onerror=&quot;this.onerror=null; this.src='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png'; this.srcset='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png';&quot; loading=&quot;lazy&quot; width=&quot;611&quot; height=&quot;237&quot; data-origin-width=&quot;817&quot; data-origin-height=&quot;317&quot;/&gt;&lt;/span&gt;&lt;/figure&gt;
&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;이중 하나 이상의 시스템이 제대로 동작하지 않을 때, 우리는 원인 모를 질병에 시달리거나 기력을 상실할 것이다. 이를 해결하기 위해 우리는 인체의 전문가 즉, 의사를 찾아간다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;의사는 인체의 체온, 혈압, 산소 수치 등과 같은 신체 지표를 우선 측정한다. 해당 진찰을 통해 우리의 신체 지표 중 이상이 발생한 부분을 발견하고 원인을 파악하기 위해 다양한 검진을 진행할 수 있다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;이를 통해 의사는 우리 인체 내부에서 무슨 일이 발생하는지 어디서 발생하는 지를 더 자세히 알아볼 수 있다. 또한, 이를 통해 의사는 문제를 해결하고 동일한 문제가 발생하지 않도록 예방 계획을 설립할 수 있다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;소프트웨어에 대한 Observability 또한 마찬가지로 응용프로그램을 실행하는 인프라 시스템 내부에서 무슨 일이 일어나고 있는지 관찰하여, 잘못된 부분이 존재한다면 원인과 위치를 파악하는 것이다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;소프트웨어 시스템을 관찰하기 위해 수집하는 가장 일반적인 데이터에는 메트릭, 로그, 트레이스 등이 존재한다.&lt;/p&gt;
&lt;h3 data-ke-size=&quot;size23&quot;&gt;Metrics&lt;/h3&gt;
&lt;p&gt;&lt;figure class=&quot;imageblock alignCenter&quot; data-ke-mobileStyle=&quot;widthOrigin&quot; data-origin-width=&quot;1116&quot; data-origin-height=&quot;794&quot;&gt;&lt;span data-url=&quot;https://blog.kakaocdn.net/dn/bgZUZ2/btsMEJYJ9CA/QJI0rzTvCYJJ1Ul85QBEEk/img.png&quot; data-phocus=&quot;https://blog.kakaocdn.net/dn/bgZUZ2/btsMEJYJ9CA/QJI0rzTvCYJJ1Ul85QBEEk/img.png&quot;&gt;&lt;img src=&quot;https://blog.kakaocdn.net/dn/bgZUZ2/btsMEJYJ9CA/QJI0rzTvCYJJ1Ul85QBEEk/img.png&quot; srcset=&quot;https://img1.daumcdn.net/thumb/R1280x0/?scode=mtistory2&amp;fname=https%3A%2F%2Fblog.kakaocdn.net%2Fdn%2FbgZUZ2%2FbtsMEJYJ9CA%2FQJI0rzTvCYJJ1Ul85QBEEk%2Fimg.png&quot; onerror=&quot;this.onerror=null; this.src='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png'; this.srcset='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png';&quot; loading=&quot;lazy&quot; width=&quot;600&quot; height=&quot;427&quot; data-origin-width=&quot;1116&quot; data-origin-height=&quot;794&quot;/&gt;&lt;/span&gt;&lt;/figure&gt;
&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;메트릭은 소프트웨어 시스템의 상태와 성능에 대한 단서를 제공하는 수치적 데이터다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;시간 경과에 따라 시스템 성능에 대한 단서를 제공하는 메트릭 데이터를 수집할 수 있고, 관찰 대상에 따라 수집하는 데이터 유형이 달라질 수 있다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;가장 일반적으로 수집되는 메트릭으로는 응답 시간, 요청의 수, CPU 및 메모리 사용량 등이 있다.&lt;/p&gt;
&lt;h3 data-ke-size=&quot;size23&quot;&gt;Logs&lt;/h3&gt;
&lt;p&gt;&lt;figure class=&quot;imageblock alignCenter&quot; data-ke-mobileStyle=&quot;widthOrigin&quot; data-origin-width=&quot;2660&quot; data-origin-height=&quot;1492&quot;&gt;&lt;span data-url=&quot;https://blog.kakaocdn.net/dn/Fph5g/btsMD1MzTwU/fhQAk6K0lZugUeRtk36X10/img.png&quot; data-phocus=&quot;https://blog.kakaocdn.net/dn/Fph5g/btsMD1MzTwU/fhQAk6K0lZugUeRtk36X10/img.png&quot;&gt;&lt;img src=&quot;https://blog.kakaocdn.net/dn/Fph5g/btsMD1MzTwU/fhQAk6K0lZugUeRtk36X10/img.png&quot; srcset=&quot;https://img1.daumcdn.net/thumb/R1280x0/?scode=mtistory2&amp;fname=https%3A%2F%2Fblog.kakaocdn.net%2Fdn%2FFph5g%2FbtsMD1MzTwU%2FfhQAk6K0lZugUeRtk36X10%2Fimg.png&quot; onerror=&quot;this.onerror=null; this.src='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png'; this.srcset='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png';&quot; loading=&quot;lazy&quot; width=&quot;590&quot; height=&quot;331&quot; data-origin-width=&quot;2660&quot; data-origin-height=&quot;1492&quot;/&gt;&lt;/span&gt;&lt;/figure&gt;
&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;메트릭을 통해 소프트웨어 시스템의 상태가 이상하다는 것을 파악했다면, 로그를 통해 시스템 내부에서 무슨 일이 발생하고 있는지를 파악해야 한다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;로그는 시스템 내부에서 발생한 이벤트에 대한 텍스트 기록이며, 문제가 발생한 시기와 해당 문제와 연관된 이벤트에 대한 단서를 제공한다. 이를 참고하면 시스템 내부에서 발생하고 있는 일에 대해 자세한 맥락 파악이 가능하다.&lt;/p&gt;
&lt;h3 data-ke-size=&quot;size23&quot;&gt;Traces&lt;/h3&gt;
&lt;p&gt;&lt;figure class=&quot;imageblock alignCenter&quot; data-ke-mobileStyle=&quot;widthOrigin&quot; data-origin-width=&quot;2856&quot; data-origin-height=&quot;1238&quot;&gt;&lt;span data-url=&quot;https://blog.kakaocdn.net/dn/bQhabj/btsMFeYjB7r/f0WisEYna5ikTbDKfLknEk/img.png&quot; data-phocus=&quot;https://blog.kakaocdn.net/dn/bQhabj/btsMFeYjB7r/f0WisEYna5ikTbDKfLknEk/img.png&quot;&gt;&lt;img src=&quot;https://blog.kakaocdn.net/dn/bQhabj/btsMFeYjB7r/f0WisEYna5ikTbDKfLknEk/img.png&quot; srcset=&quot;https://img1.daumcdn.net/thumb/R1280x0/?scode=mtistory2&amp;fname=https%3A%2F%2Fblog.kakaocdn.net%2Fdn%2FbQhabj%2FbtsMFeYjB7r%2Ff0WisEYna5ikTbDKfLknEk%2Fimg.png&quot; onerror=&quot;this.onerror=null; this.src='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png'; this.srcset='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png';&quot; loading=&quot;lazy&quot; width=&quot;659&quot; height=&quot;286&quot; data-origin-width=&quot;2856&quot; data-origin-height=&quot;1238&quot;/&gt;&lt;/span&gt;&lt;/figure&gt;
&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;트레이스는 애플리케이션에 대한 요청이 시스템 전체를 경유하는 것을 추적할 수 있다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;우리는 이를 통해 메소드, 서비스와 요청이 어떻게 상호작용하는지 관찰하고 병목 현상이 발생하는 경우 정확한 위치를 추적하여 문제의 발생지를 발견할 수 있다.&lt;/p&gt;
&lt;h3 data-ke-size=&quot;size23&quot;&gt;Reference&lt;/h3&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;Grafana : Observability란 무엇인가?&lt;/p&gt;
&lt;p&gt;&lt;iframe src=&quot;https://www.youtube.com/embed/TQur9GJHIIQ?si=KSEA7Y_nQbpNjo-9&quot; width=&quot;560&quot; height=&quot;315&quot; frameborder=&quot;&quot; allowfullscreen=&quot;true&quot;&gt;&lt;/iframe&gt;&lt;/p&gt;</description>
      <category>DevOps</category>
      <category>DevOps</category>
      <category>Grafana</category>
      <category>o11y</category>
      <category>observability</category>
      <category>SRE</category>
      <category>그라파나</category>
      <category>로그</category>
      <category>메트릭</category>
      <category>모니터링</category>
      <category>트레이스</category>
      <author>겨울바람_</author>
      <guid isPermaLink="true">https://breeze-winter.tistory.com/52</guid>
      <comments>https://breeze-winter.tistory.com/52#entry52comment</comments>
      <pubDate>Mon, 10 Mar 2025 10:22:35 +0900</pubDate>
    </item>
    <item>
      <title>Book Study - SRE (Site Reliability Engineering) Chapter01, 02</title>
      <link>https://breeze-winter.tistory.com/51</link>
      <description>&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;521&quot;&gt;&lt;span data-url=&quot;https://blog.kakaocdn.net/dn/v7okJ/btsMGc6cCZ9/Z61DvPQXc49inSHFG8O2bK/img.png&quot; data-phocus=&quot;https://blog.kakaocdn.net/dn/v7okJ/btsMGc6cCZ9/Z61DvPQXc49inSHFG8O2bK/img.png&quot;&gt;&lt;img src=&quot;https://blog.kakaocdn.net/dn/v7okJ/btsMGc6cCZ9/Z61DvPQXc49inSHFG8O2bK/img.png&quot; srcset=&quot;https://img1.daumcdn.net/thumb/R1280x0/?scode=mtistory2&amp;fname=https%3A%2F%2Fblog.kakaocdn.net%2Fdn%2Fv7okJ%2FbtsMGc6cCZ9%2FZ61DvPQXc49inSHFG8O2bK%2Fimg.png&quot; onerror=&quot;this.onerror=null; this.src='//t1.daumcdn.net/tistory_admin/static/images/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;521&quot; data-origin-width=&quot;400&quot; data-origin-height=&quot;521&quot;/&gt;&lt;/span&gt;&lt;/figure&gt;
&lt;/p&gt;
&lt;h1&gt;Chapter 1 - Introduction&lt;/h1&gt;
&lt;h2 data-ke-size=&quot;size26&quot;&gt;  Summary&lt;/h2&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;시스템의 복잡도와 트래픽이 증가할수록 &lt;b&gt;서비스 관리의 어려움&lt;/b&gt;이 커지며, 운영팀과 개발팀 간의 &lt;b&gt;상이한 목표&lt;/b&gt;로 인해 갈등이 자주 발생한다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;Google은 이를 해결하기 위해 &lt;b&gt;운영을 코드로 해결하는 엔지니어링 접근법&lt;/b&gt;을 도입했고, 이를 &lt;b&gt;SRE(Site Reliability Engineering)&lt;/b&gt; 라고 명명했다. SRE의 도입으로 인해 제품 개발팀과 SRE팀 간의 &lt;b&gt;손쉬운 업무 전환&lt;/b&gt;이 가능해져, 개발팀과 운영팀의 분리에서 발생하는 &lt;b&gt;갈등이 완화&lt;/b&gt;되었다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;또한, SRE팀은 &lt;b&gt;반복적인 운영 작업을 자동화&lt;/b&gt;하고 혁신적인 운영 방식으로 전환하는 역할을 수행한다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;SRE팀은 개발팀의 목표인 &lt;b&gt;빠른 개발 속도&lt;/b&gt;와 운영팀의 목표인 &lt;b&gt;서비스 안정성&lt;/b&gt; 사이의 &lt;b&gt;균형&lt;/b&gt;을 맞추기 위해 &lt;b&gt;SLO(Service Level Objectives)&lt;/b&gt; 를 설정하며, 이를 기준으로 &lt;b&gt;에러 예산(Error Budget)&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;예를 들어, 아래와 같이 SLO와 에러 예산이 설정되었다고 가정해보자.&lt;/p&gt;
&lt;p&gt;&lt;figure class=&quot;imageblock alignCenter&quot; data-ke-mobileStyle=&quot;widthOrigin&quot; data-origin-width=&quot;635&quot; data-origin-height=&quot;395&quot;&gt;&lt;span data-url=&quot;https://blog.kakaocdn.net/dn/ba2v2g/btsMGdjJJuY/nS7Ckg42oJEXZT9CWXpFzk/img.png&quot; data-phocus=&quot;https://blog.kakaocdn.net/dn/ba2v2g/btsMGdjJJuY/nS7Ckg42oJEXZT9CWXpFzk/img.png&quot;&gt;&lt;img src=&quot;https://blog.kakaocdn.net/dn/ba2v2g/btsMGdjJJuY/nS7Ckg42oJEXZT9CWXpFzk/img.png&quot; srcset=&quot;https://img1.daumcdn.net/thumb/R1280x0/?scode=mtistory2&amp;fname=https%3A%2F%2Fblog.kakaocdn.net%2Fdn%2Fba2v2g%2FbtsMGdjJJuY%2FnS7Ckg42oJEXZT9CWXpFzk%2Fimg.png&quot; onerror=&quot;this.onerror=null; this.src='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png'; this.srcset='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png';&quot; loading=&quot;lazy&quot; width=&quot;551&quot; height=&quot;343&quot; data-origin-width=&quot;635&quot; data-origin-height=&quot;395&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;가용 가능한 &lt;b&gt;SLO&lt;/b&gt;를 &lt;b&gt;95%&lt;/b&gt;로 설정하면, &lt;b&gt;에러 예산&lt;/b&gt;은 &lt;b&gt;5%&lt;/b&gt;&lt;/li&gt;
&lt;li&gt;한 달 기준으로 보면 &lt;b&gt;최대 36시간의 다운타임 허용&lt;/b&gt;&lt;/li&gt;
&lt;/ul&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;에러 예산을 활용하면 &lt;b&gt;서비스 신뢰성을 유지하면서도 혁신적인 기능을 빠르게 출시&lt;/b&gt;할 수 있다. 즉, 다운타임을 예측 가능한 상황으로 만들어 &lt;b&gt;SRE의 통제 하에서 운영&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;Google에서 제시하는 SRE의 핵심 업무는 다음과 같다:&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;  &lt;b&gt;모니터링 및 긴급 대응&lt;/b&gt; (Monitoring &amp;amp; Incident Response)&lt;br /&gt;  &lt;b&gt;변화 관리&lt;/b&gt; (Change Management)&lt;br /&gt;  &lt;b&gt;수요 예측 및 수용 계획&lt;/b&gt; (Capacity Planning)&lt;br /&gt;  &lt;b&gt;프로비저닝&lt;/b&gt; (Provisioning)&lt;br /&gt;  &lt;b&gt;성능 최적화&lt;/b&gt; (Performance Optimization)&lt;/p&gt;
&lt;hr data-ke-style=&quot;style1&quot; /&gt;
&lt;h2 data-ke-size=&quot;size26&quot;&gt;❗ &lt;b&gt;Difficulties&lt;/b&gt;&lt;/h2&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;b&gt;생소한 용어&lt;/b&gt;들이 많아서 이해하고 적용하는 데 어려움을 겪었다. 주요 개념들을 정리해보았다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;  &lt;b&gt;Postmortem&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&gt;원인을 분석&lt;/b&gt;하고 &lt;b&gt;재발 방지 대책&lt;/b&gt;을 마련하는 과정&lt;/li&gt;
&lt;li&gt;프로젝트 종료 후 &lt;b&gt;검토 회의&lt;/b&gt;를 진행하여 자유롭게 피드백을 공유하는 방식&lt;/li&gt;
&lt;/ul&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;  &lt;b&gt;SLA (Service Level Agreement)&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&gt;고객이 기대할 수 있는 최소 수준의 성능과 가용성&lt;/b&gt;을 정의하는 계약&lt;/li&gt;
&lt;/ul&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;  &lt;b&gt;SLO (Service Level Objective)&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;공급업체가 SLA를 충족하기 위해 설정하는 &lt;b&gt;내부 목표&lt;/b&gt;&lt;/li&gt;
&lt;/ul&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;  &lt;b&gt;SLI (Service Level Indicator)&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;실제로 SLA 및 SLO를 &lt;b&gt;얼마나 충족하는지 측정하는 지표&lt;/b&gt;&lt;/li&gt;
&lt;/ul&gt;
&lt;hr data-ke-style=&quot;style1&quot; /&gt;
&lt;h1&gt;Chapter 2 - The Production Environment at Google, from the Viewpoint of an SRE&lt;/h1&gt;
&lt;h2 data-ke-size=&quot;size26&quot;&gt;  Summary&lt;/h2&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;Google의 데이터센터 내에는 대규모의 하드웨어 자원이 존재하며, &lt;b&gt;대규모 분산 시스템&lt;/b&gt;을 기반으로 운영된다. 이러한 자원들은 개별 서버 단위가 아닌 &lt;b&gt;분산 클러스터 운영 시스템인 Borg(보그)&lt;/b&gt; 를 통해 관리된다.&lt;/p&gt;
&lt;p&gt;&lt;figure class=&quot;imageblock alignCenter&quot; data-ke-mobileStyle=&quot;widthOrigin&quot; data-origin-width=&quot;745&quot; data-origin-height=&quot;553&quot;&gt;&lt;span data-url=&quot;https://blog.kakaocdn.net/dn/baLEy7/btsMF8COXkW/k8PSKfOfPa4NOjzfQqThe0/img.png&quot; data-phocus=&quot;https://blog.kakaocdn.net/dn/baLEy7/btsMF8COXkW/k8PSKfOfPa4NOjzfQqThe0/img.png&quot;&gt;&lt;img src=&quot;https://blog.kakaocdn.net/dn/baLEy7/btsMF8COXkW/k8PSKfOfPa4NOjzfQqThe0/img.png&quot; srcset=&quot;https://img1.daumcdn.net/thumb/R1280x0/?scode=mtistory2&amp;fname=https%3A%2F%2Fblog.kakaocdn.net%2Fdn%2FbaLEy7%2FbtsMF8COXkW%2Fk8PSKfOfPa4NOjzfQqThe0%2Fimg.png&quot; onerror=&quot;this.onerror=null; this.src='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png'; this.srcset='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png';&quot; loading=&quot;lazy&quot; width=&quot;581&quot; height=&quot;431&quot; data-origin-width=&quot;745&quot; data-origin-height=&quot;553&quot;/&gt;&lt;/span&gt;&lt;/figure&gt;
&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;Borg는 현재 널리 사용되고 있는 &lt;b&gt;쿠버네티스(Kubernetes)의 전신&lt;/b&gt;이며, Borg의 태스크(Task)들은 데이터를 &lt;b&gt;Lustre 및 HDFS&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;Google의 서비스들은 Borg 내에서 &lt;b&gt;Job 단위&lt;/b&gt;로 관리되며,&lt;br /&gt;전 세계적으로 분포된 서비스 간 &lt;b&gt;지연 시간을 최소화하기 위해 GSLB(Global Server Load Balancing)를 활용&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;본 챕터에서는 사용자의 요청이 Google의 프로덕션 환경에서 어떻게 전달되는지를 예시를 통해 설명한다.&lt;/p&gt;
&lt;p&gt;&lt;figure class=&quot;imageblock alignCenter&quot; data-ke-mobileStyle=&quot;widthOrigin&quot; data-origin-width=&quot;900&quot; data-origin-height=&quot;660&quot;&gt;&lt;span data-url=&quot;https://blog.kakaocdn.net/dn/UTMgl/btsMESgMG95/yCkYlLugPRkGox51xKWG5k/img.png&quot; data-phocus=&quot;https://blog.kakaocdn.net/dn/UTMgl/btsMESgMG95/yCkYlLugPRkGox51xKWG5k/img.png&quot;&gt;&lt;img src=&quot;https://blog.kakaocdn.net/dn/UTMgl/btsMESgMG95/yCkYlLugPRkGox51xKWG5k/img.png&quot; srcset=&quot;https://img1.daumcdn.net/thumb/R1280x0/?scode=mtistory2&amp;fname=https%3A%2F%2Fblog.kakaocdn.net%2Fdn%2FUTMgl%2FbtsMESgMG95%2FyCkYlLugPRkGox51xKWG5k%2Fimg.png&quot; onerror=&quot;this.onerror=null; this.src='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png'; this.srcset='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png';&quot; loading=&quot;lazy&quot; width=&quot;583&quot; height=&quot;428&quot; data-origin-width=&quot;900&quot; data-origin-height=&quot;660&quot;/&gt;&lt;/span&gt;&lt;/figure&gt;
&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;사용자는 도메인에 접속하여 DNS로부터 IP 주소를 획득하게 되는데, 이때 GSLB를 통해 사용자와 가장 근접한 위치에 있는 서버의 IP 주소가 할당된다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;사용자의 요청은 GFE를 통해 서비스의 프론트엔드 서버에 전달되며 프론트엔드와 백엔드는 gRPC를 통해 데이터를 주고받는다. 이때, 프론트엔드와 백엔드 역시 GSLB를 통해 연결된다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;Google의 서비스와 데이터센터는 전 세계적으로 분포되어 있기 때문에, GSLB에 장애가 발생하거나 특정 Task가 트래픽을 감당하지 못하면 서비스 운영이 어려워질 수 있다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;이를 예방하기 위해 Google은&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;부하 테스트(Load Testing)&lt;/b&gt;&lt;/li&gt;
&lt;li&gt;&lt;b&gt;여러 지역에서의 시범 테스트&lt;/b&gt;&lt;/li&gt;
&lt;/ul&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;를 통해 &lt;b&gt;각 지역에서 필요한 최소한의 Task 수&lt;/b&gt;를 계산하고,&lt;br /&gt;이를 기반으로 &lt;b&gt;안정적인 서비스를 제공&lt;/b&gt;한다.&lt;/p&gt;
&lt;hr data-ke-style=&quot;style1&quot; /&gt;
&lt;h2 data-ke-size=&quot;size26&quot;&gt;❗ Difficulties&lt;/h2&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;이번 챕터에서는 &lt;b&gt;Borg에 대한 용어&lt;/b&gt;가 자주 언급되므로,&lt;br /&gt;아래 표를 통해 핵심 개념을 정리했다.&lt;/p&gt;
&lt;table data-ke-align=&quot;alignLeft&quot;&gt;
&lt;thead&gt;
&lt;tr&gt;
&lt;th&gt;  &lt;b&gt;구성 요소&lt;/b&gt;&lt;/th&gt;
&lt;th&gt;  &lt;b&gt;설명&lt;/b&gt;&lt;/th&gt;
&lt;/tr&gt;
&lt;/thead&gt;
&lt;tbody&gt;
&lt;tr&gt;
&lt;td&gt;&lt;b&gt;BorgMaster&lt;/b&gt;&lt;/td&gt;
&lt;td&gt;Borg 시스템의 중앙 제어 역할을 하며, API 제공, 작업 예약(스케줄링), 클러스터 관리 등을 수행한다.&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;&lt;b&gt;Scheduler&lt;/b&gt;&lt;/td&gt;
&lt;td&gt;Slave 노드에서 실행할 작업을 결정하고 할당하는 역할. 리소스 가용성과 우선순위에 따라 작업을 배치한다.&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;&lt;b&gt;Borglet&lt;/b&gt;&lt;/td&gt;
&lt;td&gt;각 Slave Node에 설치된 &lt;b&gt;에이전트&lt;/b&gt;로, 컨테이너 관리 및 네트워크 라우팅을 담당한다.&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;&lt;b&gt;작업(Task)&lt;/b&gt;&lt;/td&gt;
&lt;td&gt;Borg 시스템에서 실행되는 개별 작업 단위.&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;&lt;b&gt;할당(Alloc)&lt;/b&gt;&lt;/td&gt;
&lt;td&gt;작업을 실행하기 위해 &lt;b&gt;Scheduler에 의해 Slave 노드에 할당된 자원&lt;/b&gt;을 의미.&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;&lt;b&gt;작업 그룹(Job)&lt;/b&gt;&lt;/td&gt;
&lt;td&gt;여러 개의 Task를 그룹화하여 관리하는 단위.&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;&lt;b&gt;셀(Cell)&lt;/b&gt;&lt;/td&gt;
&lt;td&gt;Borg 시스템에서 클러스터를 관리하는 단위로, 평균적으로 &lt;b&gt;10,000대의 머신&lt;/b&gt;으로 구성됨.&lt;/td&gt;
&lt;/tr&gt;
&lt;/tbody&gt;
&lt;/table&gt;
&lt;hr data-ke-style=&quot;style1&quot; /&gt;
&lt;h2 style=&quot;color: #000000; text-align: start;&quot; data-ke-size=&quot;size26&quot;&gt; &lt;span&gt;&amp;nbsp;&lt;/span&gt;&lt;b&gt;References&lt;/b&gt;&lt;/h2&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;Borg와 관련된 용어&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;a href=&quot;https://tech.osci.kr/kubernetes-%ED%83%84%EC%83%9D-%EB%B0%B0%EA%B2%BD-%EB%B9%84%ED%95%98%EC%9D%B8%EB%93%9C-%EC%8A%A4%ED%86%A0%EB%A6%AC/&quot;&gt;&lt;img src=&quot;https://tech.osci.kr/wp-content/uploads/2023/08/Google_Containers-scaled.jpg&quot; alt=&quot;Kubernetes 탄생 배경 - 오픈소스컨설팅 테크블로그&quot; width=&quot;526&quot; height=&quot;219&quot; /&gt;&lt;/a&gt;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;본 책에서는 SLO, SLA, SLI라는 단어가 직접적으로 언급되지는 않지만, &lt;b&gt;에러 예산&lt;/b&gt;을 조사하는 과정에서 관련된 개념이라 정리해보았다.&lt;br /&gt;&lt;a href=&quot;https://blog.purestorage.com/ko/purely-educational/sla-vs-slo-vs-sli-whats-the-difference-and-why-they-matter/&quot;&gt;&lt;img src=&quot;https://blog.purestorage.com/wp-content/uploads/2023/01/SLA-SLO-SLI.png&quot; alt=&quot;SLA, SLO, SLI: 어떤 차이가 있고, 왜 중요할까요?&quot; width=&quot;510&quot; height=&quot;334&quot; /&gt;&lt;/a&gt;&lt;/p&gt;</description>
      <category>Book</category>
      <category>borg</category>
      <category>DevOps</category>
      <category>SRE</category>
      <category>사이트 신뢰성 엔지니어링</category>
      <category>에러 예산</category>
      <author>겨울바람_</author>
      <guid isPermaLink="true">https://breeze-winter.tistory.com/51</guid>
      <comments>https://breeze-winter.tistory.com/51#entry51comment</comments>
      <pubDate>Sun, 9 Mar 2025 12:00:12 +0900</pubDate>
    </item>
    <item>
      <title>메모리 영역</title>
      <link>https://breeze-winter.tistory.com/50</link>
      <description>&lt;h3 data-ke-size=&quot;size23&quot;&gt;buffers와 cached 영역&lt;/h3&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;커널은 디스크로부터 데이터를 읽거나 사용자의 데이터를 디스크에 저장한다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;하지만 디스크는 매우 느리고 I/O에 상당한 시간이 소요되기 때문에 커널은 디스크에 대한 요청을 빠르게 하기 위해 메모리의 일부를 디스크 요청에 대한 캐싱 영역으로 할당한다.&lt;/p&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;code&gt;buffers&lt;/code&gt;와 &lt;code&gt;cached&lt;/code&gt;라고 부른다.&lt;/p&gt;
&lt;p&gt;&lt;figure class=&quot;imageblock alignCenter&quot; data-ke-mobileStyle=&quot;widthOrigin&quot; data-origin-width=&quot;910&quot; data-origin-height=&quot;517&quot;&gt;&lt;span data-url=&quot;https://blog.kakaocdn.net/dn/vGcfR/btsMagPsun9/Jk3PGgK3Z82NwwMdD740k1/img.png&quot; data-phocus=&quot;https://blog.kakaocdn.net/dn/vGcfR/btsMagPsun9/Jk3PGgK3Z82NwwMdD740k1/img.png&quot;&gt;&lt;img src=&quot;https://blog.kakaocdn.net/dn/vGcfR/btsMagPsun9/Jk3PGgK3Z82NwwMdD740k1/img.png&quot; srcset=&quot;https://img1.daumcdn.net/thumb/R1280x0/?scode=mtistory2&amp;fname=https%3A%2F%2Fblog.kakaocdn.net%2Fdn%2FvGcfR%2FbtsMagPsun9%2FJk3PGgK3Z82NwwMdD740k1%2Fimg.png&quot; onerror=&quot;this.onerror=null; this.src='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png'; this.srcset='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png';&quot; loading=&quot;lazy&quot; width=&quot;591&quot; height=&quot;336&quot; data-origin-width=&quot;910&quot; data-origin-height=&quot;517&quot;/&gt;&lt;/span&gt;&lt;/figure&gt;
&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;커널이 읽어야 할 데이터가 파일의 내용이라면 커널은 &lt;code&gt;bio&lt;/code&gt; 구조체를 생성하고 해당 구조체에 Page Cache 용도로 할당한 메모리 영역을 연결해준다. &lt;code&gt;bio&lt;/code&gt; 구조체는 디바이스 드라이버와 통신하여 디스크로부터 데이터를 읽어 Page Cache에 파일의 내용을 채운다.&lt;/p&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;code&gt;super_block&lt;/code&gt;, &lt;code&gt;inode_block&lt;/code&gt;처럼 파일의 내용이 아닌 파일 시스템을 관리하기 위한 메타 데이터를 읽어올 때는 &lt;code&gt;_get_blk()&lt;/code&gt;같은 내부 함수를 통해 블록 디바이스와 직접 통신한다. 그리고 가져온 데이터를 읽어 Buffer Cache에 저장한다.&lt;/p&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;code&gt;cached&lt;/code&gt;는 파일의 내용을 저장하고 있는 캐시, &lt;code&gt;buffers&lt;/code&gt;는 메타 데이터를 담고 있는 캐시라고 할 수 있다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;code&gt;free&lt;/code&gt; 명령어를 통해 각각의 영역을 얼마나 사용하고 있는지 확인할 수 있다.&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;100&quot;&gt;&lt;span data-url=&quot;https://blog.kakaocdn.net/dn/dPpU4V/btsMaghCijt/xZBNMkwbpXMBojc7mqZih1/img.png&quot; data-phocus=&quot;https://blog.kakaocdn.net/dn/dPpU4V/btsMaghCijt/xZBNMkwbpXMBojc7mqZih1/img.png&quot;&gt;&lt;img src=&quot;https://blog.kakaocdn.net/dn/dPpU4V/btsMaghCijt/xZBNMkwbpXMBojc7mqZih1/img.png&quot; srcset=&quot;https://img1.daumcdn.net/thumb/R1280x0/?scode=mtistory2&amp;fname=https%3A%2F%2Fblog.kakaocdn.net%2Fdn%2FdPpU4V%2FbtsMaghCijt%2FxZBNMkwbpXMBojc7mqZih1%2Fimg.png&quot; onerror=&quot;this.onerror=null; this.src='//t1.daumcdn.net/tistory_admin/static/images/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;87&quot; data-origin-width=&quot;796&quot; data-origin-height=&quot;100&quot;/&gt;&lt;/span&gt;&lt;/figure&gt;
&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;code&gt;buffers&lt;/code&gt;와 &lt;code&gt;cached&lt;/code&gt; 영역은 시스템의 I/O 성능 향상을 위해 커널이 사용하는 영역이지만, 프로세스의 사용량이 많아져 메모리가 부족한 상황이 되면 커널은 해당 영역을 자동으로 반환하기 때문에 &lt;code&gt;free&lt;/code&gt; 명령에서도 해당 영역을 제외한 영역을 실제 사용 가능한 영역으로 계산하게 된다.&lt;/p&gt;
&lt;h3 data-ke-size=&quot;size23&quot;&gt;/proc/meminfo&lt;/h3&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;code&gt;free&lt;/code&gt; 명령어를 통해서도 메모리의 사용량을 확인할 수 있지만, 대략적인 정보만 확인이 가능하다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;현재 메모리의 사용량을 좀 더 세부적으로 확인하고 싶다면 &lt;code&gt;/proc/meminfo&lt;/code&gt;를 통해서 &lt;code&gt;free&lt;/code&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;code&gt;/proc/meminfo&lt;/code&gt;를 통해 파악할 수 있는 정보는 커널의 버전마다 다르지만 공통적으로 나타내는 부분이 존재한다.&lt;/p&gt;
&lt;p&gt;&lt;figure class=&quot;imageblock alignCenter&quot; data-ke-mobileStyle=&quot;widthOrigin&quot; data-origin-width=&quot;899&quot; data-origin-height=&quot;467&quot;&gt;&lt;span data-url=&quot;https://blog.kakaocdn.net/dn/MqBEe/btsL976g01s/52mhnpsB3QDhK2InWN6Vqk/img.png&quot; data-phocus=&quot;https://blog.kakaocdn.net/dn/MqBEe/btsL976g01s/52mhnpsB3QDhK2InWN6Vqk/img.png&quot;&gt;&lt;img src=&quot;https://blog.kakaocdn.net/dn/MqBEe/btsL976g01s/52mhnpsB3QDhK2InWN6Vqk/img.png&quot; srcset=&quot;https://img1.daumcdn.net/thumb/R1280x0/?scode=mtistory2&amp;fname=https%3A%2F%2Fblog.kakaocdn.net%2Fdn%2FMqBEe%2FbtsL976g01s%2F52mhnpsB3QDhK2InWN6Vqk%2Fimg.png&quot; onerror=&quot;this.onerror=null; this.src='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png'; this.srcset='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png';&quot; loading=&quot;lazy&quot; width=&quot;554&quot; height=&quot;288&quot; data-origin-width=&quot;899&quot; data-origin-height=&quot;467&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;SwapCached : &lt;code&gt;swap&lt;/code&gt;으로 빠진 영역 중 다시 메모리로 돌아온 영역을 의미한다.&lt;/li&gt;
&lt;/ul&gt;
&lt;p&gt;&lt;figure class=&quot;imageblock alignCenter&quot; data-ke-mobileStyle=&quot;widthOrigin&quot; data-origin-width=&quot;1240&quot; data-origin-height=&quot;420&quot;&gt;&lt;span data-url=&quot;https://blog.kakaocdn.net/dn/IN6BH/btsL93v5ObA/xmPZ84yOFfVfJ7yrQKmJ7k/img.png&quot; data-phocus=&quot;https://blog.kakaocdn.net/dn/IN6BH/btsL93v5ObA/xmPZ84yOFfVfJ7yrQKmJ7k/img.png&quot;&gt;&lt;img src=&quot;https://blog.kakaocdn.net/dn/IN6BH/btsL93v5ObA/xmPZ84yOFfVfJ7yrQKmJ7k/img.png&quot; srcset=&quot;https://img1.daumcdn.net/thumb/R1280x0/?scode=mtistory2&amp;fname=https%3A%2F%2Fblog.kakaocdn.net%2Fdn%2FIN6BH%2FbtsL93v5ObA%2FxmPZ84yOFfVfJ7yrQKmJ7k%2Fimg.png&quot; onerror=&quot;this.onerror=null; this.src='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png'; this.srcset='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png';&quot; loading=&quot;lazy&quot; width=&quot;754&quot; height=&quot;255&quot; data-origin-width=&quot;1240&quot; data-origin-height=&quot;420&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;&lt;mark style=&quot;background: #ADCCFFA6;&quot;&gt;Active (anon)&lt;/mark&gt; : Page Cache 영역을 제외한 메모리 영역을 의미하며, 주로 프로세스들이 사용하는 메모리 영역을 지칭할 때 많이 사용된다. 그중에서도 비교적 최근에 메모리 영역이 참조되어 &lt;code&gt;swap&lt;/code&gt; 영역으로 이동되지 않을 메모리 영역을 의미한다.&lt;/li&gt;
&lt;li&gt;&lt;mark style=&quot;background: #FFB8EBA6;&quot;&gt;Inactivce (anon)&lt;/mark&gt; : Active (anon)과 같은 영역을 의미하지만, 비교적 참조된 지 오래되어 &lt;code&gt;swap&lt;/code&gt; 영역으로 이동될 수 있는 메모리 영역을 의미한다.&lt;/li&gt;
&lt;li&gt;&lt;mark style=&quot;background: #ADCCFFA6;&quot;&gt;Active (file)&lt;/mark&gt; : &lt;code&gt;anon&lt;/code&gt;과 다르게 파일로 되어있는 이영역은 커널이 I/O 성능 향상을 위해 사용하는 영역을 의미한다. &lt;code&gt;buffers&lt;/code&gt;와 &lt;code&gt;cached&lt;/code&gt; 영역이 여기에 속한다. 그중에서도 비교적 최근에 메모리 영역이 참조되어 &lt;code&gt;swap&lt;/code&gt; 영역으로 이동되지 않을 메모리 영역을 의미한다.&lt;/li&gt;
&lt;li&gt;&lt;mark style=&quot;background: #FFB8EBA6;&quot;&gt;Inactive (file)&lt;/mark&gt; : Active (file)과 같은 영역을 의미하지만, 비교적 참조된 지 오래되어 &lt;code&gt;swap&lt;/code&gt; 영역으로 이동될 수 있는 메모리 영역을 의미한다.&lt;/li&gt;
&lt;/ul&gt;
&lt;blockquote data-ke-style=&quot;style1&quot;&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;Active 영역에서 Inactive 영역으로 이동하는 시기&lt;br /&gt;&lt;br /&gt;&lt;code&gt;anon&lt;/code&gt;과 &lt;code&gt;file&lt;/code&gt; 영역의 메모리는 LRU 기반의 리스트로 관리되고, 각기 Active, Inactive 두 개의 리스트로 나뉜다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;가장 최근에 참조한 메모리가 Active 리스트로 참조 시기가 오래될수록 Inactive 리스트로 이동하고 이후 &lt;code&gt;free&lt;/code&gt; 영역으로 이동하게 된다. &lt;code&gt;free&lt;/code&gt; 영역의 최소 메모리 양을 결정하는 커널 파라미터로는 &lt;code&gt;vm.min_free_kbytes&lt;/code&gt;가 존재한다.&lt;br /&gt;&lt;br /&gt;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;하지만 시간이 지났다고 무조건 이동하는게 아니라 메모리 부족 현상이 발생해서 해제해야 할 메모리를 찾아야 할 때만 이동이 발생하게 된다.&lt;br /&gt;&lt;br /&gt;&lt;/p&gt;
&lt;/blockquote&gt;
&lt;ul style=&quot;list-style-type: disc;&quot; data-ke-list-type=&quot;disc&quot;&gt;
&lt;li&gt;Dirty : Active (file), Inactive (file)과 비슷한 용도다. 커널은 I/O 쓰기 요청이 발생했을 때 바로 블록 디바이스로 명령을 내리지 않고 일정량이 될 때까지 모았다가 한 번이 쓰는 일종의 지연 쓰기 작업을 한다. 이 과정에서 I/O 성능 향상을 위해 커널이 캐시 목적으로 사용하는 영역 중 쓰기 작업이 이루어져서 실제 블록 디바이스의 블록에 씌어져야 할 영역을 의미한다.slab 메모리 영역&lt;code&gt;slab&lt;/code&gt; 메모리 영역은 커널이 내부적으로 사용하는 영역이다. &lt;code&gt;/proc/meminfo&lt;/code&gt;로 확인할 수 있는 영역 중 &lt;code&gt;slab&lt;/code&gt; 항목이 존재한다.&lt;/li&gt;
&lt;/ul&gt;
&lt;p&gt;&lt;figure class=&quot;imageblock alignCenter&quot; data-ke-mobileStyle=&quot;widthOrigin&quot; data-origin-width=&quot;280&quot; data-origin-height=&quot;69&quot;&gt;&lt;span data-url=&quot;https://blog.kakaocdn.net/dn/8PbMh/btsMaQ3JAHw/sBGfmqNcYBIJfUfe2QkXCK/img.png&quot; data-phocus=&quot;https://blog.kakaocdn.net/dn/8PbMh/btsMaQ3JAHw/sBGfmqNcYBIJfUfe2QkXCK/img.png&quot;&gt;&lt;img src=&quot;https://blog.kakaocdn.net/dn/8PbMh/btsMaQ3JAHw/sBGfmqNcYBIJfUfe2QkXCK/img.png&quot; srcset=&quot;https://img1.daumcdn.net/thumb/R1280x0/?scode=mtistory2&amp;fname=https%3A%2F%2Fblog.kakaocdn.net%2Fdn%2F8PbMh%2FbtsMaQ3JAHw%2FsBGfmqNcYBIJfUfe2QkXCK%2Fimg.png&quot; onerror=&quot;this.onerror=null; this.src='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png'; this.srcset='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png';&quot; loading=&quot;lazy&quot; width=&quot;248&quot; height=&quot;61&quot; data-origin-width=&quot;280&quot; data-origin-height=&quot;69&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;&lt;code&gt;slab&lt;/code&gt; : 커널이 직접 사용하는 메모리 영역&lt;/li&gt;
&lt;li&gt;&lt;code&gt;SReclaimable&lt;/code&gt; : &lt;code&gt;slab&lt;/code&gt; 영역 중 재사용될 수 있는 영역으로 캐시 용도로 사용되는 메모리가 주로 포함된다. 메모리 부족 현상이 발생하면 해제되어 프로세스에 할당될 수 있는 영역&lt;/li&gt;
&lt;li&gt;&lt;code&gt;SUnreclaime&lt;/code&gt; : &lt;code&gt;slab&lt;/code&gt; 영역 중 커널이 현재 사용 중이기 때문에 해제해서 재사용될 수 없는 영역&lt;/li&gt;
&lt;/ul&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;code&gt;slab&lt;/code&gt; 영역에 대한 자세한 정보는 &lt;code&gt;slabtop&lt;/code&gt; 명령어를 통해 알아볼 수 있다.&lt;/p&gt;
&lt;p&gt;&lt;figure class=&quot;imageblock alignCenter&quot; data-ke-mobileStyle=&quot;widthOrigin&quot; data-origin-width=&quot;822&quot; data-origin-height=&quot;357&quot;&gt;&lt;span data-url=&quot;https://blog.kakaocdn.net/dn/zCgTi/btsMaMUDzMq/ICuIvAM6Th8QU3cYag6Abk/img.png&quot; data-phocus=&quot;https://blog.kakaocdn.net/dn/zCgTi/btsMaMUDzMq/ICuIvAM6Th8QU3cYag6Abk/img.png&quot;&gt;&lt;img src=&quot;https://blog.kakaocdn.net/dn/zCgTi/btsMaMUDzMq/ICuIvAM6Th8QU3cYag6Abk/img.png&quot; srcset=&quot;https://img1.daumcdn.net/thumb/R1280x0/?scode=mtistory2&amp;fname=https%3A%2F%2Fblog.kakaocdn.net%2Fdn%2FzCgTi%2FbtsMaMUDzMq%2FICuIvAM6Th8QU3cYag6Abk%2Fimg.png&quot; onerror=&quot;this.onerror=null; this.src='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png'; this.srcset='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png';&quot; loading=&quot;lazy&quot; width=&quot;672&quot; height=&quot;292&quot; data-origin-width=&quot;822&quot; data-origin-height=&quot;357&quot;/&gt;&lt;/span&gt;&lt;/figure&gt;
&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;커널은 많은 용량의 메모리를 필요로하지 않기 때문에 메모리를 할당해주는 버디 시스템이 제공하는 4KB 보다 더 작은 양의 메모리를 Slab 할당자를 통해 확보한다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;메모리의 기본 단위인 4KB의 영역을 Slab 할당자가 할당 받은 후 목적별로 나뉘어진 캐시의 크기에 맞게 영역을 나누어 사용한다. 이 경우 종종 페이지 크기의 배수로 나누어 떨어지지 않는 경우도 존재한다.&lt;/p&gt;
&lt;h3 data-ke-size=&quot;size23&quot;&gt;swap 메모리 영역&lt;/h3&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;code&gt;swap&lt;/code&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;code&gt;swap&lt;/code&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;code&gt;swap&lt;/code&gt;에 대한 정보는 &lt;code&gt;free&lt;/code&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;code&gt;/proc/&amp;lt;pid&amp;gt;/smaps&lt;/code&gt; 파일이다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;해당 파일은 프로세스가 사용 중인 메모리의 정보를 저장하고 있다.&lt;/p&gt;
&lt;p&gt;&lt;figure class=&quot;imageblock alignCenter&quot; data-ke-mobileStyle=&quot;widthOrigin&quot; data-origin-width=&quot;427&quot; data-origin-height=&quot;762&quot;&gt;&lt;span data-url=&quot;https://blog.kakaocdn.net/dn/zrycW/btsL8YCsOq6/Zpak57NpMFIj824s7mRifk/img.png&quot; data-phocus=&quot;https://blog.kakaocdn.net/dn/zrycW/btsL8YCsOq6/Zpak57NpMFIj824s7mRifk/img.png&quot;&gt;&lt;img src=&quot;https://blog.kakaocdn.net/dn/zrycW/btsL8YCsOq6/Zpak57NpMFIj824s7mRifk/img.png&quot; srcset=&quot;https://img1.daumcdn.net/thumb/R1280x0/?scode=mtistory2&amp;fname=https%3A%2F%2Fblog.kakaocdn.net%2Fdn%2FzrycW%2FbtsL8YCsOq6%2FZpak57NpMFIj824s7mRifk%2Fimg.png&quot; onerror=&quot;this.onerror=null; this.src='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png'; this.srcset='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png';&quot; loading=&quot;lazy&quot; width=&quot;315&quot; height=&quot;562&quot; data-origin-width=&quot;427&quot; data-origin-height=&quot;762&quot;/&gt;&lt;/span&gt;&lt;/figure&gt;
&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;br /&gt;하지만 이 방법은 프로세스의 메모리 영역별로 살펴봐야 하기 때문에 불편할 수도 있다. 특정 프로세스가 사용하는 전체 &lt;code&gt;swap&lt;/code&gt; 메모리에 대한 정보가 필요한 경우에는 &lt;code&gt;/proc/&amp;lt;pid&amp;gt;/status&lt;/code&gt; 파일을 통해 확인할 수 있다.&lt;/p&gt;
&lt;p&gt;&lt;figure class=&quot;imageblock alignCenter&quot; data-ke-mobileStyle=&quot;widthOrigin&quot; data-origin-width=&quot;383&quot; data-origin-height=&quot;742&quot;&gt;&lt;span data-url=&quot;https://blog.kakaocdn.net/dn/bPbQAG/btsMawdsdXM/4ghcD9G0Jbdpyd2TfQjHQK/img.png&quot; data-phocus=&quot;https://blog.kakaocdn.net/dn/bPbQAG/btsMawdsdXM/4ghcD9G0Jbdpyd2TfQjHQK/img.png&quot;&gt;&lt;img src=&quot;https://blog.kakaocdn.net/dn/bPbQAG/btsMawdsdXM/4ghcD9G0Jbdpyd2TfQjHQK/img.png&quot; srcset=&quot;https://img1.daumcdn.net/thumb/R1280x0/?scode=mtistory2&amp;fname=https%3A%2F%2Fblog.kakaocdn.net%2Fdn%2FbPbQAG%2FbtsMawdsdXM%2F4ghcD9G0Jbdpyd2TfQjHQK%2Fimg.png&quot; onerror=&quot;this.onerror=null; this.src='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png'; this.srcset='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png';&quot; loading=&quot;lazy&quot; width=&quot;290&quot; height=&quot;562&quot; data-origin-width=&quot;383&quot; data-origin-height=&quot;742&quot;/&gt;&lt;/span&gt;&lt;/figure&gt;
&lt;/p&gt;</description>
      <category>Infra</category>
      <category>DevOps</category>
      <category>free</category>
      <category>swap</category>
      <category>데브옵스</category>
      <category>리눅스</category>
      <category>메모리</category>
      <category>쿠버네티스</category>
      <category>클라우드</category>
      <author>겨울바람_</author>
      <guid isPermaLink="true">https://breeze-winter.tistory.com/50</guid>
      <comments>https://breeze-winter.tistory.com/50#entry50comment</comments>
      <pubDate>Thu, 6 Feb 2025 21:46:09 +0900</pubDate>
    </item>
    <item>
      <title>top 명령어 제대로 알고 쓰자</title>
      <link>https://breeze-winter.tistory.com/49</link>
      <description>&lt;h2 data-ke-size=&quot;size26&quot;&gt;top 명령어로 확인하는 프로세스 정보&lt;/h2&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;리눅스 환경에서 사용하는 &lt;code&gt;top&lt;/code&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;code&gt;top&lt;/code&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;871&quot; data-origin-height=&quot;281&quot;&gt;&lt;span data-url=&quot;https://blog.kakaocdn.net/dn/4ryzt/btsL5NuBLkw/Kj8Ta7lWygLh00K8CMU481/img.png&quot; data-phocus=&quot;https://blog.kakaocdn.net/dn/4ryzt/btsL5NuBLkw/Kj8Ta7lWygLh00K8CMU481/img.png&quot;&gt;&lt;img src=&quot;https://blog.kakaocdn.net/dn/4ryzt/btsL5NuBLkw/Kj8Ta7lWygLh00K8CMU481/img.png&quot; srcset=&quot;https://img1.daumcdn.net/thumb/R1280x0/?scode=mtistory2&amp;fname=https%3A%2F%2Fblog.kakaocdn.net%2Fdn%2F4ryzt%2FbtsL5NuBLkw%2FKj8Ta7lWygLh00K8CMU481%2Fimg.png&quot; onerror=&quot;this.onerror=null; this.src='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png'; this.srcset='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png';&quot; loading=&quot;lazy&quot; width=&quot;671&quot; height=&quot;216&quot; data-origin-width=&quot;871&quot; data-origin-height=&quot;281&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;현재 서버의 시간과 서버의 구동 시간이 표시된다.&lt;/li&gt;
&lt;li&gt;사용자가 몇 명이나 로그인해 있는지, 시스템의 &lt;code&gt;Load Average&lt;/code&gt;는 어느 정도인지 보여준다.
&lt;ul style=&quot;list-style-type: disc;&quot; data-ke-list-type=&quot;disc&quot;&gt;
&lt;li&gt;&lt;code&gt;Load Average&lt;/code&gt;란 현재 시스템이 얼마나 많이 동작하고 있는지 보여주고 있는 지표로 해당 수치가 높으면 높을 수록 서버가 열심히 일하고 있다는 것을 의미한다.&lt;/li&gt;
&lt;/ul&gt;
&lt;/li&gt;
&lt;li&gt;현재 시스템에서 구동 중인 프로세스의 수를 나타낸다.&lt;/li&gt;
&lt;li&gt;&lt;code&gt;CPU&lt;/code&gt;, &lt;code&gt;Memory&lt;/code&gt;, &lt;code&gt;Swap&lt;/code&gt; 메모리의 사용량을 의미한다.&lt;/li&gt;
&lt;li&gt;&lt;code&gt;PR&lt;/code&gt;은 프로세스의 실행 우선 순위, 즉 다른 프로세스들보다 먼저 실행되어야 하는지의 여부를 의미힌다.&lt;/li&gt;
&lt;li&gt;&lt;code&gt;NI&lt;/code&gt;는 &lt;code&gt;PR&lt;/code&gt;을 얼마나 조정할 것인지를 결정한다. 기본 &lt;code&gt;PR&lt;/code&gt; 값에 &lt;code&gt;NI&lt;/code&gt;를 더해 실제 &lt;code&gt;PR&lt;/code&gt;의 값이 결정된다.&lt;/li&gt;
&lt;li&gt;&lt;code&gt;VIRT&lt;/code&gt;, &lt;code&gt;RES&lt;/code&gt;, &lt;code&gt;SHR&lt;/code&gt;에 대해서는 아래에서 더 자세히 설명&lt;/li&gt;
&lt;li&gt;&lt;code&gt;S&lt;/code&gt;는 프로세스의 상태를 나타낸다.VIRT, RES, SHR이 세 가지 항목은 프로세스가 현재 사용 중인 메모리에 대한 값이다.&lt;/li&gt;
&lt;/ul&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;code&gt;VIRT&lt;/code&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;code&gt;RES&lt;/code&gt;는 현재 프로세스가 사용하고 있는 물리 메모리의 양을 의미한다. 이는 프로세스에 할당된 가상 메모리 &lt;code&gt;VIRT&lt;/code&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;code&gt;SHR&lt;/code&gt;은 프로세스가 다른 프로세스와 공유하고 있는 공유 메모리의 양을 의미한다. 주로 라이브러리가 이에 해당될 수 있다.&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;482&quot;&gt;&lt;span data-url=&quot;https://blog.kakaocdn.net/dn/w2F2i/btsL6TgieYH/5KG3H7K1KVJbtZ60bp2LO1/img.png&quot; data-phocus=&quot;https://blog.kakaocdn.net/dn/w2F2i/btsL6TgieYH/5KG3H7K1KVJbtZ60bp2LO1/img.png&quot;&gt;&lt;img src=&quot;https://blog.kakaocdn.net/dn/w2F2i/btsL6TgieYH/5KG3H7K1KVJbtZ60bp2LO1/img.png&quot; srcset=&quot;https://img1.daumcdn.net/thumb/R1280x0/?scode=mtistory2&amp;fname=https%3A%2F%2Fblog.kakaocdn.net%2Fdn%2Fw2F2i%2FbtsL6TgieYH%2F5KG3H7K1KVJbtZ60bp2LO1%2Fimg.png&quot; onerror=&quot;this.onerror=null; this.src='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png'; this.srcset='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png';&quot; loading=&quot;lazy&quot; width=&quot;459&quot; height=&quot;327&quot; data-origin-width=&quot;677&quot; data-origin-height=&quot;482&quot;/&gt;&lt;/span&gt;&lt;/figure&gt;
&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;code&gt;VIRT&lt;/code&gt;의 경우에는 실제로는 할당되지 않은 가상의 공간이기 때문에 해당 값이 크다고 해도 문제가 되진 않는다. 실제로 프로세스가 사용하고 있는 메모리는 &lt;code&gt;RES&lt;/code&gt; 영역이기 때문에 메모리 점유율이 높은 프로세스를 찾기 위해서는 &lt;code&gt;RES&lt;/code&gt; 영역이 높은 프로세스를 찾아야 한다.&lt;/p&gt;
&lt;h3 data-ke-size=&quot;size23&quot;&gt;Memory Commit - VIRT와 RES는 왜 구분될까?&lt;/h3&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;code&gt;VIRT&lt;/code&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;code&gt;malloc()&lt;/code&gt;과 같은 시스템 콜을 사용해 자신이 필요로 하는 메모리의 영역을 할당해 줄 것을 요청한다. 이에 대해 커널은 가용 가능한 공간이 있다면 성공 메시지와 함께 해당 프로세스가 사용할 수 있도록 가상의 메모리 주소를 전달한다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;하지만 성공 메시지가 전달 됐다고는 해도 물리 메모리에 해당 영역이 할당된 상태는 아니라는 것이다.&lt;/p&gt;
&lt;p&gt;&lt;figure class=&quot;imageblock alignCenter&quot; data-ke-mobileStyle=&quot;widthOrigin&quot; data-origin-width=&quot;1072&quot; data-origin-height=&quot;419&quot;&gt;&lt;span data-url=&quot;https://blog.kakaocdn.net/dn/bQlinW/btsL75GO4eN/zgKqR3MgWP8OZTbxqjOgjk/img.png&quot; data-phocus=&quot;https://blog.kakaocdn.net/dn/bQlinW/btsL75GO4eN/zgKqR3MgWP8OZTbxqjOgjk/img.png&quot;&gt;&lt;img src=&quot;https://blog.kakaocdn.net/dn/bQlinW/btsL75GO4eN/zgKqR3MgWP8OZTbxqjOgjk/img.png&quot; srcset=&quot;https://img1.daumcdn.net/thumb/R1280x0/?scode=mtistory2&amp;fname=https%3A%2F%2Fblog.kakaocdn.net%2Fdn%2FbQlinW%2FbtsL75GO4eN%2FzgKqR3MgWP8OZTbxqjOgjk%2Fimg.png&quot; onerror=&quot;this.onerror=null; this.src='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png'; this.srcset='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png';&quot; loading=&quot;lazy&quot; width=&quot;783&quot; height=&quot;306&quot; data-origin-width=&quot;1072&quot; data-origin-height=&quot;419&quot;/&gt;&lt;/span&gt;&lt;/figure&gt;
&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;이런 동작 방식을 &lt;code&gt;Memory Commit&lt;/code&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;code&gt;Page fault&lt;/code&gt;가 발생하며, 해당 시점에 커널은 실제 물리 메모리에 프로세스의 가상 메모리 공간을 매핑한다. 이는 &lt;code&gt;Page Table&lt;/code&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;code&gt;RES&lt;/code&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;code&gt;Memory Commit&lt;/code&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;code&gt;fork()&lt;/code&gt; 시스템 콜을 사용하면 커널은 현재 실행 중인 프로세스와 똑같은 프로세스를 하나 더 만들게 되는데, 대부분 &lt;code&gt;fork()&lt;/code&gt; 후 &lt;code&gt;exec()&lt;/code&gt; 시스템 콜을 통해 전혀 다른 프로세스로 변화하게 된다.&lt;/p&gt;
&lt;blockquote data-ke-style=&quot;style1&quot;&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;fork() 후 exec()로 프로세스를 변화시키는 이유&lt;br /&gt;&lt;br /&gt;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;프로세스가 생성될 때 프로세스는 고유의 주소 공간을 가지게 된다. fork() 시스템 콜을 사용해 부모 프로세스를 복사하게 되면 주소 공간까지 동일하게 복사하게 되는데, 이때 Code, Data, Stack, Program Counter까지 모두 복사되기 때문에 복사된 자식 프로세스는 부모 프로세스가 실행한 부분부터 실행하게 된다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;이렇게 되면 시스템의 프로세스는 모두 똑같이 동작하기 때문에 exec()를 통해 새롭게 프로세스를 덮어씌워야 한다.&lt;br /&gt;&lt;br /&gt;&lt;/p&gt;
&lt;/blockquote&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;따라서 이 과정에서 확보한 메모리 영역이 쓸모없어지기 때문에 &lt;code&gt;COW (Copy-On-Write)&lt;/code&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;code&gt;COW&lt;/code&gt; 기법을 사용하기 위해서는 실제 물리 메모리에 할당하는 시기를 지연시키는 &lt;code&gt;Memory Commit&lt;/code&gt;이 필수적이다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;현재 시스템의 &lt;code&gt;Memory Commit&lt;/code&gt;의 상태는 &lt;code&gt;sar&lt;/code&gt; 명령어를 통해 확인할 수 있다.&lt;/p&gt;
&lt;p&gt;&lt;figure class=&quot;imageblock alignCenter&quot; data-ke-mobileStyle=&quot;widthOrigin&quot; data-origin-width=&quot;608&quot; data-origin-height=&quot;112&quot;&gt;&lt;span data-url=&quot;https://blog.kakaocdn.net/dn/bmC4x5/btsL6K4QkmJ/uPCjYdDPpCpfXQ9axMM6u1/img.webp&quot; data-phocus=&quot;https://blog.kakaocdn.net/dn/bmC4x5/btsL6K4QkmJ/uPCjYdDPpCpfXQ9axMM6u1/img.webp&quot; data-alt=&quot;필자의 서버가 터져서 대체 이미지 사용 출처 : https://www.linuxtechi.com/generate-cpu-memory-io-report-sar-command/&quot;&gt;&lt;img src=&quot;https://blog.kakaocdn.net/dn/bmC4x5/btsL6K4QkmJ/uPCjYdDPpCpfXQ9axMM6u1/img.webp&quot; srcset=&quot;https://img1.daumcdn.net/thumb/R1280x0/?scode=mtistory2&amp;fname=https%3A%2F%2Fblog.kakaocdn.net%2Fdn%2FbmC4x5%2FbtsL6K4QkmJ%2FuPCjYdDPpCpfXQ9axMM6u1%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;608&quot; height=&quot;112&quot; data-origin-width=&quot;608&quot; data-origin-height=&quot;112&quot;/&gt;&lt;/span&gt;&lt;figcaption&gt;필자의 서버가 터져서 대체 이미지 사용 출처 : https://www.linuxtechi.com/generate-cpu-memory-io-report-sar-command/&lt;/figcaption&gt;
&lt;/figure&gt;
&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;위의 사진에서 &lt;code&gt;%commit&lt;/code&gt;의 숫자는 시스템의 &lt;code&gt;Memory Commit&lt;/code&gt;의 비율을 나타낸다. 즉, 할당만 되고 실제로 사용되지 않는 메모리의 양이 전체 메모리의 5.05 정도라는 것이다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;수치가 낮다면 순간적으로 해당 메모리에 쓰기 작업이 들어가더라도 전체적으로는 문제가 없지만, 만약 수치가 높다면 순간적으로 시스템에 부하를 일으키거나 커널 응답 불가 현상을 발생시킬 수 있다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;따라서, 커널은 &lt;code&gt;Memory Commit&lt;/code&gt;에 대한 동작 방식을 &lt;code&gt;vm.overcommit_memory&lt;/code&gt;라는 파라미터로 제어할 수 있도록 했다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;code&gt;vm.overcommit_memory&lt;/code&gt;는 0, 1, 2 세 가지 값으로 설정할 수 있다.&lt;/p&gt;
&lt;ul style=&quot;list-style-type: disc;&quot; data-ke-list-type=&quot;disc&quot;&gt;
&lt;li&gt;0 : 기본 설정 값으로, overcommit 할 수 있는 최댓값은 page cache와 swap 영역 그리고 slab reclaimable 이 세 값을 합한 값이 된다. 현재 메모리에 가용 공간이 얼마나 존재하는지는 고려하지 않는다.&lt;/li&gt;
&lt;li&gt;1 : 아무 것도 계산하지 않고 요청 온 모든 메모리에 대해 &lt;code&gt;commit&lt;/code&gt;이 발생한다.&lt;/li&gt;
&lt;li&gt;2 : 제한적으로 &lt;code&gt;commit&lt;/code&gt;을 진행한다. 계산식이 존재하며 &lt;code&gt;vm.overcommit_ratio&lt;/code&gt;에 설정된 비율과 &lt;code&gt;swap&lt;/code&gt; 영역의 크기를 토대로 계산된다.&lt;/li&gt;
&lt;/ul&gt;
&lt;blockquote data-ke-style=&quot;style1&quot;&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;slap reclaimable?&lt;br /&gt;&lt;br /&gt;&lt;code&gt;slab reclaimable&lt;/code&gt;은 Slab 메모리 중에서 &lt;b&gt;필요한 경우 다시 회수(reclaim)&lt;/b&gt; 할 수 있는 메모리의 양을 의미한다. Slab 메모리는 커널이 사용하는 구조체나 객체를 저장하기 위해 할당되며, 커널 공간에서 관리된다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;사용하지 않을 때는 주로 캐시를 위해 할당되는 경우가 많으며, 시스템 메모리가 부족할 때 해제하여 다른 용도로 사용할 수 있다.&lt;br /&gt;&lt;br /&gt;&lt;/p&gt;
&lt;/blockquote&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;code&gt;vm.overcommit_memory&lt;/code&gt;을 통해 &lt;code&gt;swap&lt;/code&gt; 영역이 프로세스에 할당되는 &lt;code&gt;commit memory&lt;/code&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;프로세스의 상태는 SHR 옆에 있는 S 항목을 통해 확인할 수 있다.&lt;/p&gt;
&lt;ul style=&quot;list-style-type: disc;&quot; data-ke-list-type=&quot;disc&quot;&gt;
&lt;li&gt;&lt;code&gt;D&lt;/code&gt; - &lt;code&gt;uninterruptible sleep&lt;/code&gt; 상태로, 디스크 혹은 네트워크 I/O를 대기하고 있는 프로세스를 의미한다. 이 상태의 프로세스들은 대기하는 동안 &lt;code&gt;Run Queue&lt;/code&gt;에서 벗어나 &lt;code&gt;Wait Queue&lt;/code&gt;로 들어가게 된다.&lt;/li&gt;
&lt;li&gt;&lt;code&gt;R&lt;/code&gt; - &lt;code&gt;running&lt;/code&gt; 상태로, 실제로 CPU 자원을 소모하며 실행 중인 프로세스를 의미한다.&lt;/li&gt;
&lt;li&gt;&lt;code&gt;S&lt;/code&gt; - &lt;code&gt;sleeping&lt;/code&gt; 상태의 프로세스로, &lt;code&gt;D&lt;/code&gt; 상태의 프로세스와의 차이점은 요청한 리소스를 즉시 사용할 수 있는지의 여부다.&lt;/li&gt;
&lt;li&gt;&lt;code&gt;T&lt;/code&gt; - &lt;code&gt;traced or stopped&lt;/code&gt; 상태로, &lt;code&gt;strace&lt;/code&gt; 명령어 등으로 프로세스의 시스템 콜을 추적하고 있는 상태를 보여준다.&lt;/li&gt;
&lt;li&gt;&lt;code&gt;Z&lt;/code&gt; - &lt;code&gt;zombie&lt;/code&gt; 상태의 프로세스로 부모 프로세스가 죽은 자식 프로세스를 의미한다.&lt;/li&gt;
&lt;/ul&gt;
&lt;p&gt;&lt;figure class=&quot;imageblock alignCenter&quot; data-ke-mobileStyle=&quot;widthOrigin&quot; data-origin-width=&quot;1027&quot; data-origin-height=&quot;792&quot;&gt;&lt;span data-url=&quot;https://blog.kakaocdn.net/dn/zysob/btsL7NGn3Hl/BUO57OccTSosjnu4S8D3KK/img.png&quot; data-phocus=&quot;https://blog.kakaocdn.net/dn/zysob/btsL7NGn3Hl/BUO57OccTSosjnu4S8D3KK/img.png&quot;&gt;&lt;img src=&quot;https://blog.kakaocdn.net/dn/zysob/btsL7NGn3Hl/BUO57OccTSosjnu4S8D3KK/img.png&quot; srcset=&quot;https://img1.daumcdn.net/thumb/R1280x0/?scode=mtistory2&amp;fname=https%3A%2F%2Fblog.kakaocdn.net%2Fdn%2Fzysob%2FbtsL7NGn3Hl%2FBUO57OccTSosjnu4S8D3KK%2Fimg.png&quot; onerror=&quot;this.onerror=null; this.src='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png'; this.srcset='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png';&quot; loading=&quot;lazy&quot; width=&quot;686&quot; height=&quot;529&quot; data-origin-width=&quot;1027&quot; data-origin-height=&quot;792&quot;/&gt;&lt;/span&gt;&lt;/figure&gt;
&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;좀비 상태 프로세스의 경우 스케줄러에 의해 선택되지 않기 때문에 CPU를 사용하지 않고, 이미 사용이 중지된 프로세스이기 때문에 메모리 또한 사용하지 않는다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;하지만, PID를 점유하기 때문에 좀비 프로세스가 쌓이다 보면 PID가 고갈되게 되고 PID 할당이 불가능해진다.&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;code&gt;PR&lt;/code&gt;과 &lt;code&gt;NI&lt;/code&gt;는 커널이 프로세스를 스케줄링할 때 사용하는 우선순위를 나타내는 값이다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;참고로 프로세스 스케줄링이 진행되는 구조는 다음과 같다.&lt;/p&gt;
&lt;p&gt;&lt;figure class=&quot;imageblock alignCenter&quot; data-ke-mobileStyle=&quot;widthOrigin&quot; data-origin-width=&quot;570&quot; data-origin-height=&quot;487&quot;&gt;&lt;span data-url=&quot;https://blog.kakaocdn.net/dn/edJQ4g/btsL8mheRUb/NMokkeKJRkNmPAkUocv5o0/img.png&quot; data-phocus=&quot;https://blog.kakaocdn.net/dn/edJQ4g/btsL8mheRUb/NMokkeKJRkNmPAkUocv5o0/img.png&quot;&gt;&lt;img src=&quot;https://blog.kakaocdn.net/dn/edJQ4g/btsL8mheRUb/NMokkeKJRkNmPAkUocv5o0/img.png&quot; srcset=&quot;https://img1.daumcdn.net/thumb/R1280x0/?scode=mtistory2&amp;fname=https%3A%2F%2Fblog.kakaocdn.net%2Fdn%2FedJQ4g%2FbtsL8mheRUb%2FNMokkeKJRkNmPAkUocv5o0%2Fimg.png&quot; onerror=&quot;this.onerror=null; this.src='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png'; this.srcset='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png';&quot; loading=&quot;lazy&quot; width=&quot;570&quot; height=&quot;487&quot; data-origin-width=&quot;570&quot; data-origin-height=&quot;487&quot;/&gt;&lt;/span&gt;&lt;/figure&gt;
&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;CPU마다 Run Queue라는 것이 존재하며, Run Queue에는 우선순위 별로 프로세스가 연결되어 있다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;스케줄러는 유휴 상태에 있던 프로세스가 깨어나거나 특정 프로세스가 스케줄링을 양보하는 등의 경우에 현재 Run Queue에 있는 프로세스들 중 가장 우선순위가 높은 프로세스를 꺼내서 디스패처에 넘겨준다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;디스패처는 현재 실행 중인 프로세스의 정보를 다른 곳에 저장한 후 넘겨받은 프로세스의 정보를 가지고 다시 연산하도록 요청한다.&lt;/p&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;code&gt;PR&lt;/code&gt;이며, &lt;code&gt;nice&lt;/code&gt; 명령어를 통해 우선순위 값을 낮추는데 이를 표시하는 값을 &lt;code&gt;NI&lt;/code&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;기본적으로 모든 프로세스는 20의 우선순위를 갖는데, 여기에 &lt;code&gt;nice&lt;/code&gt; 명령어를 통해 &lt;code&gt;NI&lt;/code&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;이때 유의해야 할 점은 프로세스의 우선순위를 낮췄다고 해도, CPU 코어의 수와 동일한 프로세스가 실행되고 있다면 경합이 발생하지 않기 때문에 비슷한 시간에 종료되게 된다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;만약 동일한 우선순위를 가진 A, B 프로세스가 실행 중일 때 새롭게 우선순위가 낮은 C 프로세스가 생성되면, A 프로세스는 다른 CPU의 Run Queue로 옮겨간다.&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;539&quot;&gt;&lt;span data-url=&quot;https://blog.kakaocdn.net/dn/c4HO8t/btsL7qSaL3e/52AndzKdKAc5kset31vpo0/img.png&quot; data-phocus=&quot;https://blog.kakaocdn.net/dn/c4HO8t/btsL7qSaL3e/52AndzKdKAc5kset31vpo0/img.png&quot;&gt;&lt;img src=&quot;https://blog.kakaocdn.net/dn/c4HO8t/btsL7qSaL3e/52AndzKdKAc5kset31vpo0/img.png&quot; srcset=&quot;https://img1.daumcdn.net/thumb/R1280x0/?scode=mtistory2&amp;fname=https%3A%2F%2Fblog.kakaocdn.net%2Fdn%2Fc4HO8t%2FbtsL7qSaL3e%2F52AndzKdKAc5kset31vpo0%2Fimg.png&quot; onerror=&quot;this.onerror=null; this.src='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png'; this.srcset='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png';&quot; loading=&quot;lazy&quot; width=&quot;712&quot; height=&quot;362&quot; data-origin-width=&quot;1060&quot; data-origin-height=&quot;539&quot;/&gt;&lt;/span&gt;&lt;/figure&gt;
&lt;/p&gt;
&lt;h3 data-ke-size=&quot;size23&quot;&gt;Load Average&lt;/h3&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;Load Average란 &lt;code&gt;R&lt;/code&gt;, &lt;code&gt;D&lt;/code&gt; 상태에 놓여있는 프로세스 개수의 1분, 5분, 15분마다의 평균 값을 나타내는 값이다. 이는 얼마나 많은 프로세스가 실행 중이거나, 실행 대기 중인지를 의미하는 수치이다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;Load Average가 높다는 것은 많은 수의 프로세스가 실행 중이거나 I/O 처리를 위해 대기 상태에 있다는 것이며, 낮다는 것은 실행 중이거나 대기 중인 프로세스가 적다는 것을 의미한다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;프로세스의 수를 헤아리는 값이기 때문에 CPU의 코어 수에 따라 값의 의미는 상대적이다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;Load Average가 높은 상황일 때 우리는 그 원인을 파악해야 하는데, 이를 위해서는 CPU를 점유하고 있는 프로세스가 많다는 의미인지 I/O 병목 때문에 I/O 대기 중인 프로세스가 많은 것인지 알아낼 필요가 있다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;부하를 일으키는 프로세스는 CPU 자원을 많이 차지하는 &lt;code&gt;nr_running&lt;/code&gt;이라 불리는 프로세스와 &lt;code&gt;nr_uninterruptible&lt;/code&gt;로 표현되는 I/O 바운드 프로세스 크게 두 종류로 나눌 수 있다.&lt;/p&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;code&gt;top&lt;/code&gt; 명령어로는 구체적으로 어떤 부하가 발생하고 있는지 파악하기 어렵기 때문에 &lt;code&gt;vmstat&lt;/code&gt; 명령어를 통해 부하의 원인을 명확하게 살펴볼 수 있다.&lt;/p&gt;
&lt;p&gt;&lt;figure class=&quot;imageblock alignCenter&quot; data-ke-mobileStyle=&quot;widthOrigin&quot; data-origin-width=&quot;846&quot; data-origin-height=&quot;420&quot;&gt;&lt;span data-url=&quot;https://blog.kakaocdn.net/dn/duPAUc/btsL7PKQu5L/rDK9TU8LQ5kRNpN0WDRkN1/img.png&quot; data-phocus=&quot;https://blog.kakaocdn.net/dn/duPAUc/btsL7PKQu5L/rDK9TU8LQ5kRNpN0WDRkN1/img.png&quot;&gt;&lt;img src=&quot;https://blog.kakaocdn.net/dn/duPAUc/btsL7PKQu5L/rDK9TU8LQ5kRNpN0WDRkN1/img.png&quot; srcset=&quot;https://img1.daumcdn.net/thumb/R1280x0/?scode=mtistory2&amp;fname=https%3A%2F%2Fblog.kakaocdn.net%2Fdn%2FduPAUc%2FbtsL7PKQu5L%2FrDK9TU8LQ5kRNpN0WDRkN1%2Fimg.png&quot; onerror=&quot;this.onerror=null; this.src='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png'; this.srcset='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png';&quot; loading=&quot;lazy&quot; width=&quot;715&quot; height=&quot;355&quot; data-origin-width=&quot;846&quot; data-origin-height=&quot;420&quot;/&gt;&lt;/span&gt;&lt;/figure&gt;
&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;첫 번째 열의 &lt;code&gt;r&lt;/code&gt;은 CPU 자원을 소모하는 &lt;code&gt;nr_running&lt;/code&gt; 프로세스의 개수를 나타내고 &lt;code&gt;b&lt;/code&gt;는 I/O 자원을 차지하고 있는 &lt;code&gt;nr_uninterruptible&lt;/code&gt; 프로세스의 개수를 나타낸다.&lt;/p&gt;</description>
      <category>DevOps</category>
      <category>DevOps</category>
      <category>Linux</category>
      <category>TOP</category>
      <category>가상메모리</category>
      <category>백엔드</category>
      <category>부하</category>
      <category>서버</category>
      <category>인프라</category>
      <category>프로세스</category>
      <author>겨울바람_</author>
      <guid isPermaLink="true">https://breeze-winter.tistory.com/49</guid>
      <comments>https://breeze-winter.tistory.com/49#entry49comment</comments>
      <pubDate>Tue, 4 Feb 2025 23:41:43 +0900</pubDate>
    </item>
    <item>
      <title>단일 트랜잭션 유지 그리고 PGMQ</title>
      <link>https://breeze-winter.tistory.com/48</link>
      <description>&lt;h2 data-ke-size=&quot;size26&quot;&gt;Overview&lt;/h2&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;애플리케이션을 개발하다 보면 어떤 특정한 목적을 위해 비동기 처리를 구현해야 하는 상황을 종종 마주하게 된다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;비동기 처리를 구현하기 위해 흔히 Apache Kafka 혹은 RabbitMQ와 같은 메시지 큐를 도입하곤 한다.&lt;/p&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;code&gt;Application Modernization&lt;/code&gt;이라는 명목 하에 여러 기업 뿐만 아니라 공공까지도 MSA와 클라우드 사용이 주가 되는 &lt;code&gt;Cloud Native&lt;/code&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;MSA 환경에서는 각 서비스가 설계자의 의도에 따라 분리되어 있고 모놀리틱 방식에 비해 결합도가 떨어지기 때문에 메시지 큐를 얼마나 잘 활용하느냐가 MSA 환경 구축의 난이도 혹은 기능 구현 여부를 판가름한다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;이러한 상황에서 메시지 큐의 대표격이라 할 수 있는 Apache Kafka와 RabbitMQ의 사용 난이도와 학습 곡선은 꽤나 가파른 편이다. 특히 Kafka의 경우 학습 난이도와 난해함으로 악명이 자자하다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;필자 또한 채팅 기능 구현을 위해 RabbitMQ를 사용해본 경험이 있는데 RabbitMQ의 난이도 또한 그리 쉽지만은 않았다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;거기에 메시지 큐 도구 자체의 복잡성뿐만 아니라 DB와 메시지 큐 간의 데이터 일관성 유지까지 고려한다면, 클라우드 네이티브 환경 구축이라는 목표는 꽤 아득히 먼 곳에 있는 것처럼 느껴진다.&lt;/p&gt;
&lt;h2 data-ke-size=&quot;size26&quot;&gt;단일 트랜잭션 유지&lt;/h2&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;메시지 큐와 데이터베이스 간 데이터 일관성을 유지하기 위해서는 DB와 메시지 큐를 단일 트랜잭션으로 묶어야 한다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;예를 들어, 비즈니스 로직에 의해 데이터베이스를 수정한 다음 메시지 큐에 메시지를 보냈지만, 이후의 로직에서 에러가 발생하면 동일 트랜잭션에서 수행된 데이터베이스는 Rollback이 발생할 것이다. 하지만 메시지 큐에 보낸 메시지를 다시 롤백시키기에는 어려움이 뒤따른다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;이런 문제를 해결하기 위해 트랜잭션에 데이터가 완전히 커밋된 이후 메시지 큐에 데이터를 전송하도록 구현하는 방법이 존재하지만, 알 수 없는 네트워크 상의 이유로 메시지 전송에 실패할 수도 있다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;이러한 실패에 대비하려면 메시지를 어딘가에 저장해둬야 한다. 하지만 메시지를 어딘가에 저장해둬야 한다는 전제는 우리를 다시 데이터 일관성에 대해 고민하게 만든다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;우리는 이러한 문제를 &lt;code&gt;Transactional Messaging&lt;/code&gt;을 통해 해결할 수 있다. 이는 메시지를 데이터베이스 트랜잭션의 일부로 발행하는 것을 의미한다. 즉, 비즈니스 로직에서 데이터베이스를 수정하는 것과 메시지 큐에 메시지를 발행하는 일련의 과정을 원자적으로 수행하여 데이터 일관성을 보장한는 것이다.&lt;/p&gt;
&lt;h2 data-ke-size=&quot;size26&quot;&gt;Transactional Messaging을 구현하는 두 가지 방법&lt;/h2&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;마이크로서비스 패턴의 저자인 크리스 리처드슨이 제시하는 두 가지 패턴에 대해 간략히 소개하려고 한다. 두 패턴 모두 &lt;code&gt;Outbox&lt;/code&gt;라는 테이블을 메시지 큐로 사용한다.&lt;/p&gt;
&lt;h3 data-ke-size=&quot;size23&quot;&gt;Transactional Outbox Pattern&lt;/h3&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;메시지를 바로 메시지 큐에 보내지 않고 데이터베이스의 아웃박스 테이블에 넣는 방식이다. 이어 데이터베이스 트랜잭션이 커밋되면 주기적으로 아웃박스 테이블 내용을 읽어 메시지 큐에 전달하는 방식이다.&lt;/p&gt;
&lt;p&gt;&lt;figure class=&quot;imageblock alignCenter&quot; data-ke-mobileStyle=&quot;widthOrigin&quot; data-origin-width=&quot;1298&quot; data-origin-height=&quot;461&quot;&gt;&lt;span data-url=&quot;https://blog.kakaocdn.net/dn/blwiiT/btsLkEC0AqC/dnJYaxw63Xnf1SirwWML31/img.png&quot; data-phocus=&quot;https://blog.kakaocdn.net/dn/blwiiT/btsLkEC0AqC/dnJYaxw63Xnf1SirwWML31/img.png&quot; data-alt=&quot;https://microservices.io/patterns/data/transactional-outbox.html&quot;&gt;&lt;img src=&quot;https://blog.kakaocdn.net/dn/blwiiT/btsLkEC0AqC/dnJYaxw63Xnf1SirwWML31/img.png&quot; srcset=&quot;https://img1.daumcdn.net/thumb/R1280x0/?scode=mtistory2&amp;fname=https%3A%2F%2Fblog.kakaocdn.net%2Fdn%2FblwiiT%2FbtsLkEC0AqC%2FdnJYaxw63Xnf1SirwWML31%2Fimg.png&quot; onerror=&quot;this.onerror=null; this.src='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png'; this.srcset='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png';&quot; loading=&quot;lazy&quot; width=&quot;532&quot; height=&quot;189&quot; data-origin-width=&quot;1298&quot; data-origin-height=&quot;461&quot;/&gt;&lt;/span&gt;&lt;figcaption&gt;https://microservices.io/patterns/data/transactional-outbox.html&lt;/figcaption&gt;
&lt;/figure&gt;
&lt;/p&gt;
&lt;h3 data-ke-size=&quot;size23&quot;&gt;Transactional Log Tailing Pattern&lt;/h3&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;아웃박스 테이블에 메시지 큐에 전달한 메시지를 저장하는 것까지는 Transactional Outbox 패턴과 동일하지만 아웃박스 테이블의 데이터를 읽는 방식에서 차이가 있다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;해당 패턴의 이름대로 데이터베이스의 트랜잭션 로그를 읽고 아웃박스 테이블의 데이터 변경만을 필터링하여 메시지 큐에 전달한다.&lt;/p&gt;
&lt;p&gt;&lt;figure class=&quot;imageblock alignCenter&quot; data-ke-mobileStyle=&quot;widthOrigin&quot; data-origin-width=&quot;731&quot; data-origin-height=&quot;580&quot;&gt;&lt;span data-url=&quot;https://blog.kakaocdn.net/dn/bC9Nd1/btsLjiVrJg3/54lI4IZRVGK604PkiNsIv1/img.png&quot; data-phocus=&quot;https://blog.kakaocdn.net/dn/bC9Nd1/btsLjiVrJg3/54lI4IZRVGK604PkiNsIv1/img.png&quot; data-alt=&quot;https://microservices.io/patterns/data/transaction-log-tailing.html&quot;&gt;&lt;img src=&quot;https://blog.kakaocdn.net/dn/bC9Nd1/btsLjiVrJg3/54lI4IZRVGK604PkiNsIv1/img.png&quot; srcset=&quot;https://img1.daumcdn.net/thumb/R1280x0/?scode=mtistory2&amp;fname=https%3A%2F%2Fblog.kakaocdn.net%2Fdn%2FbC9Nd1%2FbtsLjiVrJg3%2F54lI4IZRVGK604PkiNsIv1%2Fimg.png&quot; onerror=&quot;this.onerror=null; this.src='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png'; this.srcset='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png';&quot; loading=&quot;lazy&quot; width=&quot;424&quot; height=&quot;336&quot; data-origin-width=&quot;731&quot; data-origin-height=&quot;580&quot;/&gt;&lt;/span&gt;&lt;figcaption&gt;https://microservices.io/patterns/data/transaction-log-tailing.html&lt;/figcaption&gt;
&lt;/figure&gt;
&lt;/p&gt;
&lt;h2 data-ke-size=&quot;size26&quot;&gt;우발적 복잡성의 증가&lt;/h2&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;비동기 처리를 위해 메시지 큐를 도입하고, 데이터베이스와 메시지 큐 사이의 데이터 일관성을 보장하기 위해 위에서 소개한 패턴 등을 적용하는 과정은 우발적 복잡성을 증가시킨다.&lt;/p&gt;
&lt;h3 data-ke-size=&quot;size23&quot;&gt;PGMQ (Postgres Message Queue)&lt;/h3&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;이러한 우발적 복잡성의 증가를 막기 위해 PostgreSQL의 &lt;code&gt;PGMQ(Postgres Message Queue)&lt;/code&gt;를 활용하여 위에서 언급한 문제들을 해결할 수 있다. PostgreSQL과 PGMQ를 같이 사용하면 데이터베이스 단일 트랜잭션으로 두 가지 작업을 한 번에 묶을 수 있다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;비즈니스 로직을 수행하다 오류가 발생할 경우, 데이터베이스뿐만 아니라 PGMQ에 넣었던 메시지 또한 롤백한다는 것이다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;PGMQ의 사용법은 PostgreSQL 내에서 SQL을 사용하기 때문에 Kafka나 RabbitMQ에 비해 복잡성이 현저히 낮은 편에 속한다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;또한 PGMQ의 메시지를 읽어 Kafka로 전송하는 메시지 릴레이를 추가할 수 있기 때문에 굉장히 다양한 환경에서 적용이 가능하다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;PGMQ는 자체적으로 메시지 삭제 기능을 제공하기 때문에 PGMQ를 사용하는 것으로 아웃박스 테이블을 구현하고 메시지를 추가 및 삭제하는 로직을 추가로 작성할 필요없이 우발적 복잡성을 현저히 낮출 수 있다.&lt;/p&gt;
&lt;h2 data-ke-size=&quot;size26&quot;&gt;Conclusion&lt;/h2&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;사실 개발판에서는 &quot;은탄환은 없다&quot;라는 말이 자주 쓰인다. 한 번에 모든 것을 해결해주는 만능 도구는 없다는 의미인데, PGMQ 또한 적절한 환경과 상황이 갖추어 졌을 때 사용해야 그 진가를 발휘한다고 생각한다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;이번 포스팅은 아래의 아티클을 참조하여 작성되었다. MSA 환경에 대해 많이 고민하고 있던 필자에게 매우 도움이 된 글이기에 한 번씩 읽어본다면 분명 도움이 될거라고 생각한다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;a href=&quot;https://yozm.wishket.com/magazine/detail/2833/&quot; target=&quot;_blank&quot; rel=&quot;noopener&amp;nbsp;noreferrer&quot;&gt;https://yozm.wishket.com/magazine/detail/2833/&lt;/a&gt;&lt;/p&gt;</description>
      <category>Dev</category>
      <category>Kafka</category>
      <category>MSA</category>
      <category>pgmq</category>
      <category>postgresql</category>
      <category>rabbitmq</category>
      <category>단일 트랜잭션</category>
      <category>메시지 큐</category>
      <category>원자성</category>
      <category>클라우드 네이티브</category>
      <category>트랜잭션</category>
      <author>겨울바람_</author>
      <guid isPermaLink="true">https://breeze-winter.tistory.com/48</guid>
      <comments>https://breeze-winter.tistory.com/48#entry48comment</comments>
      <pubDate>Sun, 15 Dec 2024 21:09:39 +0900</pubDate>
    </item>
    <item>
      <title>[Go] 함수 고급</title>
      <link>https://breeze-winter.tistory.com/47</link>
      <description>&lt;p&gt;&lt;figure class=&quot;imageblock alignCenter&quot; data-ke-mobileStyle=&quot;widthOrigin&quot; data-origin-width=&quot;1000&quot; data-origin-height=&quot;1000&quot;&gt;&lt;span data-url=&quot;https://blog.kakaocdn.net/dn/6cIAy/btsKBh98d0b/8ZVYWLwDBbjQXfkgHDAYHK/img.png&quot; data-phocus=&quot;https://blog.kakaocdn.net/dn/6cIAy/btsKBh98d0b/8ZVYWLwDBbjQXfkgHDAYHK/img.png&quot;&gt;&lt;img src=&quot;https://blog.kakaocdn.net/dn/6cIAy/btsKBh98d0b/8ZVYWLwDBbjQXfkgHDAYHK/img.png&quot; srcset=&quot;https://img1.daumcdn.net/thumb/R1280x0/?scode=mtistory2&amp;fname=https%3A%2F%2Fblog.kakaocdn.net%2Fdn%2F6cIAy%2FbtsKBh98d0b%2F8ZVYWLwDBbjQXfkgHDAYHK%2Fimg.png&quot; onerror=&quot;this.onerror=null; this.src='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png'; this.srcset='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png';&quot; loading=&quot;lazy&quot; width=&quot;392&quot; height=&quot;392&quot; data-origin-width=&quot;1000&quot; data-origin-height=&quot;1000&quot;/&gt;&lt;/span&gt;&lt;/figure&gt;
&lt;/p&gt;
&lt;h2 data-ke-size=&quot;size26&quot;&gt;가변 인수 함수&lt;/h2&gt;
&lt;pre class=&quot;angelscript&quot;&gt;&lt;code&gt;fmt.Println()
fmt.Println(1)
fmt.Println(1, 2, 3, 4, 5)&lt;/code&gt;&lt;/pre&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;code&gt;fmt&lt;/code&gt; 패키지의 &lt;code&gt;Println&lt;/code&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;code&gt;...&lt;/code&gt; 키워드를 사용해서 가변 인수를 생성할 수 있다. 인수 타입 앞에 &lt;code&gt;...&lt;/code&gt;를 붙여서 해당 타입 인수를 여러 개 받는 가변 인수임을 표시하면 된다.&lt;/p&gt;
&lt;pre class=&quot;fortran&quot;&gt;&lt;code&gt;func sum(nums ...int) int {
    sum := 0

    fmt.Printf(&quot;nums 타입 : %T\n&quot;, nums)
    for _, v := range nums {
        sum += v
    } 
    return sum
}

func main() {
    fmt.Println(sum(1, 2, 3, 4, 5)) // nums 타입 : []int 15
    fmt.Println(sum(10, 20))        // nums 타입 : []int 30
    fmt.Println(sum())              // nums 타입 : []int 0
}&lt;/code&gt;&lt;/pre&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;출력되는 타입에서 확인할 수 있듯이 가변 인수는 함수 내에서 슬라이스 타입으로 처리된다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;code&gt;fmt.Println()&lt;/code&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;code&gt;...interface{}&lt;/code&gt; 타입으로 인수를 받으면 모든 타입의 가변 인수를 받을 수 있다.&lt;/p&gt;
&lt;pre class=&quot;go&quot;&gt;&lt;code&gt;func Print(args ...interface{}) string {
    for _, arg := range args {
        switch f := arg.(type) {
        case bool:
            val := arg.(bool)
        case float64:
            val := arg.(float64)
        case int:
            val := arg.(int)
        }
    }
}&lt;/code&gt;&lt;/pre&gt;
&lt;h2 data-ke-size=&quot;size26&quot;&gt;&lt;code&gt;defer&lt;/code&gt; 지연 실행&lt;/h2&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;함수가 종료되기 직전에 실행해야 하는 코드가 존재할 때 사용하는 &lt;code&gt;defer&lt;/code&gt; 에 대해 알아보자. 대표적으로 OS 내부 자원을 사용하는 경우, 사용 후 반드시 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;code&gt;defer&lt;/code&gt;를 사용해 해당 기능을 수행할 수 있다.&lt;/p&gt;
&lt;pre class=&quot;go&quot;&gt;&lt;code&gt;func main() {
    f, err := os.Create(&quot;test.txt&quot;)
    if err != nil {
        fmt.Println(&quot;failed to create a file&quot;)
        return
    }

    defer f.Close()
    defer fmt.Println(&quot;defer 동작&quot;)

    fmt.Println(&quot;파일에 Hello World를 쓴다.&quot;)
    fmt.Fprintln(f, &quot;Hello World!&quot;)
}&lt;/code&gt;&lt;/pre&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;위의 &lt;code&gt;main&lt;/code&gt; 함수를 실행하면 생성된 텍스트 파일에 Hello World! 를 작성한 후 함수의 마지막 단계에 &quot;defer 동작&quot; 이라는 문구가 콘솔에 출력되는 것을 확인할 수 있다.&lt;/p&gt;
&lt;h2 data-ke-size=&quot;size26&quot;&gt;함수 타입 변수&lt;/h2&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;함수 타입 변수란 함수를 값으로 갖는 변수를 의미한다. 포인터는 0과 1로 나타낼 수 있는 숫자를 메모리 주소 값으로 가진다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;작성된 코드 중 100번 라인에서 &lt;code&gt;f()&lt;/code&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;CPU의 &lt;code&gt;프로그램 카운터(PC)&lt;/code&gt;는 다음 실행할 라인을 나타내는 레지스터인데, 코드 실행 중 &lt;code&gt;f()&lt;/code&gt; 함수가 호출되면 프로그램 카운터는 &lt;code&gt;f()&lt;/code&gt; 함수가 시작되는 라인인 100번 라인을 값으로 가지며, 다음 차례에 100번 라인부터 명령을 실행하게 된다.&lt;/p&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;code&gt;함수 포인터 (function pointer)&lt;/code&gt;라고 부른다.&lt;/p&gt;
&lt;pre class=&quot;go&quot;&gt;&lt;code&gt;func add(a, b int) int {
    return a + b
}

func mul(a, b int) int {
    return a * b
}

func getOperator(op string) func (int, int) int {
    if op == &quot;+&quot; {
        return add
    } else if op == &quot;*&quot; {
        return mul
    } else {
        return nil
    }
}

func main() {
    var operator func (int, int) int
    operator = getOperator(&quot;*&quot;)

    var result = operator(3, 4)
    fmt.Println(result) // 12
}&lt;/code&gt;&lt;/pre&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;이러한 함수 타입 변수를 사용할 경우 별칭을 사용하여 함수 정의를 줄여 쓸 수 있다.&lt;/p&gt;
&lt;pre class=&quot;go&quot;&gt;&lt;code&gt;type opFunc func (int, int) int

func getOperator(op string) opFunc {
    ...
}&lt;/code&gt;&lt;/pre&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 class=&quot;go&quot;&gt;&lt;code&gt;return func(a, b int) int {
    return a + b
}&lt;/code&gt;&lt;/pre&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;함수 리터럴은 필요한 변수를 내부 상태로 가질 수 있다. 함수 리터럴 내부에서 사용되는 외부 변수는 자동으로 함수 내부 상태로 저장된다.&lt;/p&gt;
&lt;pre class=&quot;go&quot;&gt;&lt;code&gt;func main() {
    i := 0

    f := func() {
        i += 10
    }

    i++

    f()

    fmt.Println(i) // 11
}&lt;/code&gt;&lt;/pre&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;code&gt;i&lt;/code&gt;는 함수 외부에 선언되어 있는 외부 변수다. 함수 리터럴 내부에서 외부 변수에 접근할 때 필요한 변수를 내부 상태로 가져와서 접근할 수 있도록 한다. 결과적으로 &lt;code&gt;i&lt;/code&gt;의 값은 &lt;code&gt;f()&lt;/code&gt; 함수에 의해 10이 더해져 11이 된다.&lt;/p&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;code&gt;캡쳐&lt;/code&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;code&gt;값 참조를 사용할 때 외부 변수에 영향을 미치기 싫다면 해당 값을 변수로 받아 값을 복사하는 것을 권장한다.&lt;/code&gt;&lt;/p&gt;
&lt;pre class=&quot;go&quot;&gt;&lt;code&gt;type Writer func(string)

func writeHello(writer Writer) {
    writer(&quot;Hello World!&quot;)
}

func main() {
    f, err := os.Create(&quot;test.txt&quot;)
    if err != nil {
        fmt.Println(&quot;Failed to create a file&quot;)
        return
    }

    defer f.Close()

    writeHello(func(msg string) {
        fmt.Fprintf(f, msg)
    })
}&lt;/code&gt;&lt;/pre&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;파일에 &lt;code&gt;msg&lt;/code&gt;를 쓰는 함수 리터럴을 만들어서 &lt;code&gt;writeHello()&lt;/code&gt; 함수의 인수로 사용했다. &lt;code&gt;writeHello()&lt;/code&gt; 함수는 함수 리터럴을 &quot;Hello World!&quot; 문자열을 인수로 호출했기 때문에 위 예제를 실행하면 &lt;code&gt;test.txt&lt;/code&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;code&gt;writeHello()&lt;/code&gt; 함수는 인수로 오는 &lt;code&gt;writer&lt;/code&gt;를 호출했을 때 해당 함수가 어떻게 수행될지 알 수 없다. 이렇듯 외부에서 로직을 주입하는 방식으로 객체지향적인 코드 작성이 가능하다.&lt;/p&gt;</description>
      <category>Dev</category>
      <category>defer</category>
      <category>go</category>
      <category>Golang</category>
      <category>가변 인수 함수</category>
      <category>객체지향</category>
      <category>람다 함수</category>
      <category>의존성 주입</category>
      <category>함수</category>
      <category>함수 리터럴</category>
      <category>함수 타입 변수</category>
      <author>겨울바람_</author>
      <guid isPermaLink="true">https://breeze-winter.tistory.com/47</guid>
      <comments>https://breeze-winter.tistory.com/47#entry47comment</comments>
      <pubDate>Mon, 2 Dec 2024 22:29:40 +0900</pubDate>
    </item>
    <item>
      <title>[k8s] NodePort, LoadBalancer, Ingress</title>
      <link>https://breeze-winter.tistory.com/46</link>
      <description>&lt;h2 data-ke-size=&quot;size26&quot;&gt;NodePort&lt;/h2&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;code&gt;NodePort&lt;/code&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;/b&gt;
&lt;ul style=&quot;list-style-type: disc;&quot; data-ke-list-type=&quot;disc&quot;&gt;
&lt;li&gt;Kubernetes 노드의 IP와 특정 포트를 사용하여 외부에서 접근&lt;/li&gt;
&lt;li&gt;기본적으로 30000~32767 범위의 포트를 사용&lt;/li&gt;
&lt;li&gt;클러스터의 모든 노드에서 해당 포트로 서비스에 접근할 수 있다&lt;/li&gt;
&lt;/ul&gt;
&lt;/li&gt;
&lt;li&gt;&lt;b&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;/li&gt;
&lt;li&gt;추가적인 리소스 필요 없이 노드 IP로 접근 가능&lt;/li&gt;
&lt;/ul&gt;
&lt;/li&gt;
&lt;li&gt;&lt;b&gt;단점&lt;/b&gt;
&lt;ul style=&quot;list-style-type: disc;&quot; data-ke-list-type=&quot;disc&quot;&gt;
&lt;li&gt;포트 범위가 제한적이며, 노드의 IP 주소를 알아야 합니다&lt;/li&gt;
&lt;li&gt;노드가 다운되면 해당 노드의 서비스에 접근 불가&lt;/li&gt;
&lt;/ul&gt;
&lt;/li&gt;
&lt;/ul&gt;
&lt;p&gt;&lt;figure class=&quot;imageblock alignCenter&quot; data-ke-mobileStyle=&quot;widthOrigin&quot; data-origin-width=&quot;562&quot; data-origin-height=&quot;680&quot;&gt;&lt;span data-url=&quot;https://blog.kakaocdn.net/dn/QpMkd/btsKE4Eprst/xhavTuC6fO6FOWIXVBxcVK/img.png&quot; data-phocus=&quot;https://blog.kakaocdn.net/dn/QpMkd/btsKE4Eprst/xhavTuC6fO6FOWIXVBxcVK/img.png&quot;&gt;&lt;img src=&quot;https://blog.kakaocdn.net/dn/QpMkd/btsKE4Eprst/xhavTuC6fO6FOWIXVBxcVK/img.png&quot; srcset=&quot;https://img1.daumcdn.net/thumb/R1280x0/?scode=mtistory2&amp;fname=https%3A%2F%2Fblog.kakaocdn.net%2Fdn%2FQpMkd%2FbtsKE4Eprst%2FxhavTuC6fO6FOWIXVBxcVK%2Fimg.png&quot; onerror=&quot;this.onerror=null; this.src='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png'; this.srcset='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png';&quot; loading=&quot;lazy&quot; width=&quot;474&quot; height=&quot;574&quot; data-origin-width=&quot;562&quot; data-origin-height=&quot;680&quot;/&gt;&lt;/span&gt;&lt;/figure&gt;
&lt;/p&gt;
&lt;pre class=&quot;yaml&quot;&gt;&lt;code&gt;apiVersion: v1 
kind: Service 
metadata:   
    name: my-service 
spec:   
    type: NodePort   
    ports:     
      - port: 80       
        targetPort: 8080       
        nodePort: 30001&lt;/code&gt;&lt;/pre&gt;
&lt;hr data-ke-style=&quot;style1&quot; /&gt;
&lt;h2 data-ke-size=&quot;size26&quot;&gt;LoadBalancer&lt;/h2&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;code&gt;LoadBalancer&lt;/code&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;/b&gt;
&lt;ul style=&quot;list-style-type: disc;&quot; data-ke-list-type=&quot;disc&quot;&gt;
&lt;li&gt;클라우드 환경(AWS, GCP, Azure 등)에서 외부 로드 밸런서를 자동으로 생성하여 IP를 할당&lt;/li&gt;
&lt;li&gt;서비스마다 고유한 외부 IP가 할당되며, 주로 프로덕션 환경에서 사용&lt;/li&gt;
&lt;li&gt;온프레미스 환경에서도 &lt;code&gt;Metallb&lt;/code&gt;와 같은 소프트웨어를 사용하여 구성 가능&lt;/li&gt;
&lt;/ul&gt;
&lt;/li&gt;
&lt;li&gt;&lt;b&gt;장점&lt;/b&gt;
&lt;ul style=&quot;list-style-type: disc;&quot; data-ke-list-type=&quot;disc&quot;&gt;
&lt;li&gt;외부 IP가 자동으로 할당되어 쉽게 접근 가능하며, 높은 가용성 제공&lt;/li&gt;
&lt;li&gt;클라우드 로드 밸런서와 연동하여 트래픽을 분산&lt;/li&gt;
&lt;/ul&gt;
&lt;/li&gt;
&lt;li&gt;&lt;b&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;/li&gt;
&lt;/ul&gt;
&lt;/li&gt;
&lt;/ul&gt;
&lt;p&gt;&lt;figure class=&quot;imageblock alignCenter&quot; data-ke-mobileStyle=&quot;widthOrigin&quot; data-origin-width=&quot;584&quot; data-origin-height=&quot;855&quot;&gt;&lt;span data-url=&quot;https://blog.kakaocdn.net/dn/bTzM2J/btsKGPFsmIx/rQt2pIbk7TnVCim04B3nJK/img.png&quot; data-phocus=&quot;https://blog.kakaocdn.net/dn/bTzM2J/btsKGPFsmIx/rQt2pIbk7TnVCim04B3nJK/img.png&quot;&gt;&lt;img src=&quot;https://blog.kakaocdn.net/dn/bTzM2J/btsKGPFsmIx/rQt2pIbk7TnVCim04B3nJK/img.png&quot; srcset=&quot;https://img1.daumcdn.net/thumb/R1280x0/?scode=mtistory2&amp;fname=https%3A%2F%2Fblog.kakaocdn.net%2Fdn%2FbTzM2J%2FbtsKGPFsmIx%2FrQt2pIbk7TnVCim04B3nJK%2Fimg.png&quot; onerror=&quot;this.onerror=null; this.src='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png'; this.srcset='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png';&quot; loading=&quot;lazy&quot; width=&quot;446&quot; height=&quot;653&quot; data-origin-width=&quot;584&quot; data-origin-height=&quot;855&quot;/&gt;&lt;/span&gt;&lt;/figure&gt;
&lt;/p&gt;
&lt;pre class=&quot;yaml&quot;&gt;&lt;code&gt;apiVersion: v1
kind: Service
metadata:
  name: my-loadbalancer-service
spec:
  type: LoadBalancer
  ports:
    - port: 80
      targetPort: 8080&lt;/code&gt;&lt;/pre&gt;
&lt;hr data-ke-style=&quot;style1&quot; /&gt;
&lt;h2 data-ke-size=&quot;size26&quot;&gt;Ingress&lt;/h2&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;code&gt;Ingress&lt;/code&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;/b&gt;
&lt;ul style=&quot;list-style-type: disc;&quot; data-ke-list-type=&quot;disc&quot;&gt;
&lt;li&gt;&lt;code&gt;Ingress Controller&lt;/code&gt;를 통해 도메인과 경로 기반 라우팅 규칙을 정의하고 서비스로 연결&lt;/li&gt;
&lt;li&gt;HTTPS를 지원하며, SSL 인증서를 설정하여 보안 트래픽을 처리할 수 있다&lt;/li&gt;
&lt;/ul&gt;
&lt;/li&gt;
&lt;li&gt;&lt;b&gt;장점&lt;/b&gt;
&lt;ul style=&quot;list-style-type: disc;&quot; data-ke-list-type=&quot;disc&quot;&gt;
&lt;li&gt;도메인 및 경로 기반 라우팅이 가능하여, IP 주소를 절약하고 트래픽을 효율적으로 관리&lt;/li&gt;
&lt;li&gt;하나의 IP로 여러 도메인이나 서브도메인을 관리 가능&lt;/li&gt;
&lt;/ul&gt;
&lt;/li&gt;
&lt;li&gt;&lt;b&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;code&gt;Ingress Controller&lt;/code&gt;를 설치해야 하며, 설정이 다소 복잡할 수 있다.&lt;/li&gt;
&lt;li&gt;클러스터 환경에 따라 지원되는 Ingress Controller가 제한될 수 있다.&lt;/li&gt;
&lt;/ul&gt;
&lt;/li&gt;
&lt;/ul&gt;
&lt;p&gt;&lt;figure class=&quot;imageblock alignCenter&quot; data-ke-mobileStyle=&quot;widthOrigin&quot; data-origin-width=&quot;1239&quot; data-origin-height=&quot;568&quot;&gt;&lt;span data-url=&quot;https://blog.kakaocdn.net/dn/spT6m/btsKE09QCUG/oMjTk6UneehZSdAQWl7VB0/img.png&quot; data-phocus=&quot;https://blog.kakaocdn.net/dn/spT6m/btsKE09QCUG/oMjTk6UneehZSdAQWl7VB0/img.png&quot;&gt;&lt;img src=&quot;https://blog.kakaocdn.net/dn/spT6m/btsKE09QCUG/oMjTk6UneehZSdAQWl7VB0/img.png&quot; srcset=&quot;https://img1.daumcdn.net/thumb/R1280x0/?scode=mtistory2&amp;fname=https%3A%2F%2Fblog.kakaocdn.net%2Fdn%2FspT6m%2FbtsKE09QCUG%2FoMjTk6UneehZSdAQWl7VB0%2Fimg.png&quot; onerror=&quot;this.onerror=null; this.src='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png'; this.srcset='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png';&quot; loading=&quot;lazy&quot; width=&quot;692&quot; height=&quot;317&quot; data-origin-width=&quot;1239&quot; data-origin-height=&quot;568&quot;/&gt;&lt;/span&gt;&lt;/figure&gt;
&lt;/p&gt;
&lt;pre class=&quot;yaml&quot;&gt;&lt;code&gt;apiVersion: networking.k8s.io/v1
kind: Ingress
metadata:
  name: my-ingress
spec:
  rules:
    - host: example.com
      http:
        paths:
          - path: /service1
            pathType: Prefix
            backend:
              service:
                name: service1
                port:
                  number: 80
          - path: /service2
            pathType: Prefix
            backend:
              service:
                name: service2
                port:
                  number: 80&lt;/code&gt;&lt;/pre&gt;
&lt;hr data-ke-style=&quot;style1&quot; /&gt;
&lt;h2 data-ke-size=&quot;size26&quot;&gt;Ingress, LoadBalancer, NodePort의 차이점&lt;/h2&gt;
&lt;table style=&quot;height: 173px;&quot; width=&quot;1024&quot; data-ke-align=&quot;alignLeft&quot;&gt;
&lt;thead&gt;
&lt;tr&gt;
&lt;th&gt;유형&lt;/th&gt;
&lt;th&gt;특징&lt;/th&gt;
&lt;th&gt;장점&lt;/th&gt;
&lt;th&gt;단점&lt;/th&gt;
&lt;th&gt;사용 사례&lt;/th&gt;
&lt;/tr&gt;
&lt;/thead&gt;
&lt;tbody&gt;
&lt;tr&gt;
&lt;td&gt;&lt;b&gt;NodePort&lt;/b&gt;&lt;/td&gt;
&lt;td&gt;노드의 특정 포트를 통해 서비스 노출&lt;/td&gt;
&lt;td&gt;설정이 간단하고 추가 리소스가 필요 없음&lt;/td&gt;
&lt;td&gt;포트 범위가 제한적이며 노드 IP가 필요&lt;/td&gt;
&lt;td&gt;개발 환경, 간단한 서비스 노출&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;&lt;b&gt;LoadBalancer&lt;/b&gt;&lt;/td&gt;
&lt;td&gt;클라우드 로드 밸런서를 통해 외부 노출&lt;/td&gt;
&lt;td&gt;외부 IP 자동 할당, 높은 가용성과 로드 밸런싱 제공&lt;/td&gt;
&lt;td&gt;클라우드 비용 발생, 온프레미스 지원 제한&lt;/td&gt;
&lt;td&gt;클라우드 환경의 프로덕션 서비스&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;&lt;b&gt;Ingress&lt;/b&gt;&lt;/td&gt;
&lt;td&gt;도메인 기반 라우팅, SSL 지원 가능&lt;/td&gt;
&lt;td&gt;도메인과 경로 기반의 정교한 라우팅, IP 절약&lt;/td&gt;
&lt;td&gt;Ingress Controller 설정 필요, 다소 복잡&lt;/td&gt;
&lt;td&gt;다수의 서비스, 도메인 기반의 접근이 필요한 경우&lt;/td&gt;
&lt;/tr&gt;
&lt;/tbody&gt;
&lt;/table&gt;</description>
      <category>Infra</category>
      <category>ingress</category>
      <category>k8s</category>
      <category>loadbalancer</category>
      <category>nodeport</category>
      <category>Service</category>
      <category>네트워크</category>
      <category>오블완</category>
      <category>티스토리챌린지</category>
      <author>겨울바람_</author>
      <guid isPermaLink="true">https://breeze-winter.tistory.com/46</guid>
      <comments>https://breeze-winter.tistory.com/46#entry46comment</comments>
      <pubDate>Tue, 12 Nov 2024 17:11:35 +0900</pubDate>
    </item>
    <item>
      <title>[k8s] k8s Security basic</title>
      <link>https://breeze-winter.tistory.com/45</link>
      <description>&lt;h3 data-ke-size=&quot;size23&quot;&gt;Overview&lt;/h3&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;쿠버네티스 환경에서의 보안이란 쿠버네티스 클러스터의 안정성과 보안성을 유지하는 것을 의미한다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;쿠버네티스는 클러스터의 보안을 위해 많은 기능을 제공하지만, 이번 포스팅에서는 CKA 자격증 준비를 위해 공부했던 세 가지 기능에 대한 소개를 하려고 한다.&lt;/p&gt;
&lt;ul style=&quot;list-style-type: disc;&quot; data-ke-list-type=&quot;disc&quot;&gt;
&lt;li&gt;Network Policy&lt;/li&gt;
&lt;li&gt;Role-Based Access Control&lt;/li&gt;
&lt;li&gt;Secret&lt;/li&gt;
&lt;/ul&gt;
&lt;h3 data-ke-size=&quot;size23&quot;&gt;Network Policy&lt;/h3&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;쿠버네티스의 통신 정책은 기본적으로 모든 Pod 간 통신을 허용한다. 이는 Pod 간 통신을 원활하게 해주지만, 원치 않는 통신에 Pod를 노출시킬 수 있다는 위험 또한 존재한다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;외부의 위협으로부터 Pod를 보호하기 위해 사용되는 쿠버네티스 보안 기능이 바로 네트워크 정책(Network Policy)이다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;네트워크 정책은 Pod 간 통신 또는 다른 네트워크 엔드포인트와 Pod 사이의 통신을 제어하는 기능이다. 네트워크 정책을 사용하면 IP 주소, 포트, 프로토콜 및 레이블(label), 네임스페이스와 같은 다양한 기준에 따라 트래픽을 허용하거나 거부하는 규칙을 정의할 수 있다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;네트워크 정책은 쿠버네티스 네트워크 플러그인 아키텍처를 사용하여 구현되기 때문에 네트워크 플러그인마다 네트워크 정책에 대한 지원 수준에 차이 존재할 수 있다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;네트워크 정책 설정 시 다음 세 가지 요소를 중점으로 살펴보아야 한다.&lt;/p&gt;
&lt;ul style=&quot;list-style-type: disc;&quot; data-ke-list-type=&quot;disc&quot;&gt;
&lt;li&gt;&lt;code&gt;podSelector&lt;/code&gt; : 정책이 적용될 Pod를 지정한다&lt;/li&gt;
&lt;li&gt;&lt;code&gt;policyTypes&lt;/code&gt; : 정책이 적용될 트래픽을 지정한다&lt;/li&gt;
&lt;li&gt;&lt;code&gt;ingress/egress&lt;/code&gt; : 수신 트래픽 / 발신 트래픽에 대한 세부 내용을 지정한다&lt;/li&gt;
&lt;/ul&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;아래의 예시 YAML 파일을 통해 사용 사례를 살펴보자.&lt;/p&gt;
&lt;pre class=&quot;yaml&quot;&gt;&lt;code&gt;apiVersion: networking.k8s.io/v1    # NetworkPolicy 리소스의 API 버전 
kind: NetworkPolicy                 
metadata:
  name: test-network-policy        
  namespace: default                
spec:
  podSelector: 
    matchLabels:
      role: db                      # 이 정책이 적용될 Pod를 선택. 여기서는 &quot;role=db&quot; 레이블을 가진 Pod들에 적용됨
  policyTypes:
    - Ingress                       # 인바운드 트래픽에 대한 규칙 정의
    - Egress                        # 아웃바운드 트래픽에 대한 규칙 정의
  ingress:
    - from:
        - ipBlock:
            cidr: 172.17.0.0/16     # 인바운드 트래픽을 허용할 IP 범위 설정. 여기서는 &quot;172.17.0.0/16&quot; 대역의 IP에서 오는 요청 허용
            except:
              - 172.17.1.0/24       # 위의 cidr에서 제외할 IP 범위 설정. &quot;172.17.1.0/24&quot; 대역은 제외하여 차단
        - namespaceSelector:
            matchLabels:
              project: myproject    # 특정 네임스페이스에서 오는 트래픽을 허용. 여기서는 &quot;project=myproject&quot; 레이블이 있는 네임스페이스에서 오는 요청 허용
        - podSelector:
            matchLabels:
              role: frontend        # 특정 레이블을 가진 Pod에서 오는 트래픽을 허용. 여기서는 &quot;role=frontend&quot; 레이블이 있는 Pod에서 오는 요청 허용
      ports:
        - protocol: TCP             # 허용할 프로토콜 지정. 여기서는 TCP만 허용
          port: 6379                # 허용할 포트 번호. 여기서는 6379 포트만 허용
  egress:
    - to:
        - ipBlock:
            cidr: 10.0.0.0/24       # 아웃바운드 트래픽이 허용될 IP 범위 설정. &quot;10.0.0.0/24&quot; 대역으로 나가는 트래픽 허용
      ports:
        - protocol: TCP             # 허용할 프로토콜 지정. 여기서는 TCP만 허용
          port: 5978                # 허용할 포트 번호. 여기서는 5978 포트만 허용
&lt;/code&gt;&lt;/pre&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;위의 YAML 파일은 쿠버네티스의 공식 문서 중 Network Policy 문서의 예제 YAML 파일을 가져온 것이다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;YAML 파일 내에 작성된 주석을 통해 네트워크 정책의 동작 방식에 대해 유심히 살펴보면 클라우드 서비스에서 제공하는 보안 그룹과 유사하게 동작할 것을 예상할 수 있다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;이러한 동작을 통해 Pod에서 동작하는 애플리케이션이 어떤 요청을 받을 수 있고, 어디로 요청 혹은 응답을 보낼 수 있는지 결정하고 통제한다. 이에 더해 Pod의 네트워크 트래픽을 제한하는 것으로 다양한 외부 공격으로부터 클러스터를 보호할 수 있다.&lt;/p&gt;
&lt;h3 data-ke-size=&quot;size23&quot;&gt;Role-Based Access Control&lt;/h3&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;쿠버네티스에 존재하는 여러 리소스들에 대한 권한을 사용자 및 서비스 계정의 역할을 정의하는 것으로 제어하는 것을 &lt;code&gt;RBAC&lt;/code&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;code&gt;Role&lt;/code&gt;, 클러스터 수준에서 역할을 설정하는 &lt;code&gt;ClusterRole&lt;/code&gt;을 사용해 보안 규칙을 정의하고, 정의한 규칙을 사용자의 모음인 &lt;code&gt;Group&lt;/code&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;929&quot; data-origin-height=&quot;586&quot;&gt;&lt;span data-url=&quot;https://blog.kakaocdn.net/dn/btkMcP/btsKFogD3Yb/4XpRls8hILBvdOm2eyggRk/img.png&quot; data-phocus=&quot;https://blog.kakaocdn.net/dn/btkMcP/btsKFogD3Yb/4XpRls8hILBvdOm2eyggRk/img.png&quot;&gt;&lt;img src=&quot;https://blog.kakaocdn.net/dn/btkMcP/btsKFogD3Yb/4XpRls8hILBvdOm2eyggRk/img.png&quot; srcset=&quot;https://img1.daumcdn.net/thumb/R1280x0/?scode=mtistory2&amp;fname=https%3A%2F%2Fblog.kakaocdn.net%2Fdn%2FbtkMcP%2FbtsKFogD3Yb%2F4XpRls8hILBvdOm2eyggRk%2Fimg.png&quot; onerror=&quot;this.onerror=null; this.src='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png'; this.srcset='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png';&quot; loading=&quot;lazy&quot; width=&quot;703&quot; height=&quot;443&quot; data-origin-width=&quot;929&quot; data-origin-height=&quot;586&quot;/&gt;&lt;/span&gt;&lt;/figure&gt;
&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;역할 기반 접근 제어를 설정하기 위해서는 다음 세 가지 요소를 중점적으로 살펴봐야 한다.&lt;/p&gt;
&lt;ul style=&quot;list-style-type: disc;&quot; data-ke-list-type=&quot;disc&quot;&gt;
&lt;li&gt;&lt;code&gt;Role&lt;/code&gt; : 쿠버네티스 리소스에 대한 권한의 집합이다. 특정 네임스페이스에서만 유효한 권한 할당&lt;/li&gt;
&lt;li&gt;&lt;code&gt;RoleBinding&lt;/code&gt; : 특정 사용자 혹은 사용자의 계정에 &lt;code&gt;Role&lt;/code&gt;을 할당한다&lt;/li&gt;
&lt;li&gt;&lt;code&gt;ClusterRole&lt;/code&gt; : &lt;code&gt;Role&lt;/code&gt;과 유사하지만, 클러스터 전체에서 유효한 권한을 할당한다.&lt;code&gt;ClusterRoleBinding&lt;/code&gt;을 통해 사용자 또는 사용자의 계정과 연결을 정의해야 한다&lt;/li&gt;
&lt;/ul&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;다음은 각각 &lt;code&gt;Role&lt;/code&gt;을 생성하여 특정 사용자에게 연결하는 &lt;code&gt;Rolebinding&lt;/code&gt;에 대한 예제 YAML 파일이다.&lt;/p&gt;
&lt;pre class=&quot;routeros&quot;&gt;&lt;code&gt;apiVersion: rbac.authorization.k8s.io/v1
kind: Role
metadata:
  namespace: default
  name: pod-reader
rules:
- apiGroups: [&quot;&quot;]                   
  resources: [&quot;pods&quot;]                  # 접근할 수 있는 리소스 지정 
  verbs: [&quot;get&quot;, &quot;watch&quot;, &quot;list&quot;]      # 리소스에 대한 작업 권한 부여 

--- 

apiVersion: rbac.authorization.k8s.io/v1
kind: RoleBinding
metadata:
  name: read-pods
  namespace: default
subjects:
- kind: User
  name: jane
  apiGroup: rbac.authorization.k8s.io
roleRef:
  kind: Role
  name: pod-reader
  apiGroup: rbac.authorization.k8s.io&lt;/code&gt;&lt;/pre&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;리소스에 대한 작업 권한을 부여하는 &lt;code&gt;verbs&lt;/code&gt;는 총 9가지가 존재한다.&lt;/p&gt;
&lt;ul style=&quot;list-style-type: disc;&quot; data-ke-list-type=&quot;disc&quot;&gt;
&lt;li&gt;&lt;code&gt;create&lt;/code&gt; : 새로운 리소스 생성할 수 있는 권한&lt;/li&gt;
&lt;li&gt;&lt;code&gt;get&lt;/code&gt; : 개별 리소스 세부 정보를 조회할 수 있는 권한&lt;/li&gt;
&lt;li&gt;&lt;code&gt;list&lt;/code&gt; : 리소스 목록을 조회할 수 있는 권한&lt;/li&gt;
&lt;li&gt;&lt;code&gt;update&lt;/code&gt; : 기존 리소스 내용 전체를 수정할 수 있는 권한&lt;/li&gt;
&lt;li&gt;&lt;code&gt;patch&lt;/code&gt; : 기존 리소스 내용을 부분적으로 수정할 수 있는 권한&lt;/li&gt;
&lt;li&gt;&lt;code&gt;delete&lt;/code&gt; : 특정 리소스를 삭제할 수 있는 권한&lt;/li&gt;
&lt;li&gt;&lt;code&gt;deletecollection&lt;/code&gt; : 여러 리소스를 한 번에 삭제하는 권한. 일괄 삭제할 때 사용&lt;/li&gt;
&lt;li&gt;&lt;code&gt;watch&lt;/code&gt; : 리소스의 변경 사항을 실시간으로 모니터링 할 수 있는 권한&lt;/li&gt;
&lt;li&gt;&lt;code&gt;impersonate&lt;/code&gt; : 다른 사용자 혹은 계정으로 가장하는 권한. 다른 사용자 계정으로 API 요청을 수행할 수 있음&lt;/li&gt;
&lt;/ul&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;이처럼, 역할 기반 접근 제어를 적용할 경우 클러스터 관리자는 리소스에 대한 접근 권한을 관리할 수 있다. 이를 통해 다양한 사용자와 서비스 계정 간 접근 권한을 분리하여, 권한이 부여되지 않은 사용자가 민감한 데이터를 볼 수 없게끔 보호할 수 있다.&lt;/p&gt;
&lt;h3 data-ke-size=&quot;size23&quot;&gt;Secret&lt;/h3&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;시크릿은 암호화된 정보, 토큰, 비밀번호와 같은 중요한 데이터를 안전하게 저장하기 위한 리소스다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;시크릿 정보는 컨테이너 이미지에 포함되지 않으며, 애플리케이션을 실행하는 동안 환경 변수 또는 볼륨 마운트를 통해 컨테이너에 전달된다.&lt;/p&gt;
&lt;p&gt;&lt;figure class=&quot;imageblock alignCenter&quot; data-ke-mobileStyle=&quot;widthOrigin&quot; data-origin-width=&quot;563&quot; data-origin-height=&quot;661&quot;&gt;&lt;span data-url=&quot;https://blog.kakaocdn.net/dn/bP9eqq/btsKDFj9tBm/PhvQzvOJJJeyKcGUenLsYK/img.png&quot; data-phocus=&quot;https://blog.kakaocdn.net/dn/bP9eqq/btsKDFj9tBm/PhvQzvOJJJeyKcGUenLsYK/img.png&quot;&gt;&lt;img src=&quot;https://blog.kakaocdn.net/dn/bP9eqq/btsKDFj9tBm/PhvQzvOJJJeyKcGUenLsYK/img.png&quot; srcset=&quot;https://img1.daumcdn.net/thumb/R1280x0/?scode=mtistory2&amp;fname=https%3A%2F%2Fblog.kakaocdn.net%2Fdn%2FbP9eqq%2FbtsKDFj9tBm%2FPhvQzvOJJJeyKcGUenLsYK%2Fimg.png&quot; onerror=&quot;this.onerror=null; this.src='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png'; this.srcset='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png';&quot; loading=&quot;lazy&quot; width=&quot;345&quot; height=&quot;405&quot; data-origin-width=&quot;563&quot; data-origin-height=&quot;661&quot;/&gt;&lt;/span&gt;&lt;/figure&gt;
&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;br /&gt;시크릿은 일반적으로 &lt;code&gt;Base64&lt;/code&gt;로 인코딩된 &lt;code&gt;Key-Value&lt;/code&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;시크릿은 Pod와 독립적으로 생성되며, 민감 정보를 &lt;code&gt;etcd&lt;/code&gt; 저장소에 보관하다가 Pod가 해당 정보를 필요로 할 때 컨테이너에 제공해준다.&lt;/p&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 class=&quot;makefile&quot;&gt;&lt;code&gt;$echo -n &quot;admin&quot; | base64
YWRtaW4=
$echo -n &quot;1234&quot; | base64
MTIzNA==&lt;/code&gt;&lt;/pre&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;시크릿에 정보를 저장하기 위해서는 우선 평문이 아닌 &lt;code&gt;Base64&lt;/code&gt;로 인코딩한 문자열을 필드로 넣어줘야 한다.&lt;/p&gt;
&lt;pre class=&quot;routeros&quot;&gt;&lt;code&gt;apiVersion: v1
kind: Secret
metadata:
  name: secret-sa-sample
type: Opaque
data:
  USERNAME: YWRtaW4=
  PASSWORD: MTIzNA==&lt;/code&gt;&lt;/pre&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;code&gt;Opaque&lt;/code&gt; : 일반적인 용도의 시크릿. &lt;code&gt;ConfigMaps&lt;/code&gt;와 동일한 목적으로 사용할 수 있으며, 민감한 데이터를 컨테이너에 전달하는 용도로도 사용할 수 있다&lt;/li&gt;
&lt;li&gt;&lt;code&gt;dockerconfigjson&lt;/code&gt; : 도커 이미지 저장소 인증 정보. private 레지스트리에 접근하기 위한 인증 절차를 치룰 때 사용하는 용도&lt;/li&gt;
&lt;li&gt;&lt;code&gt;tls&lt;/code&gt; : TLS 인증서를 시크릿으로 관리할 수 있도록 한다. TLS 인증서를 시크릿을 통해 저장하면, Pod나 Service 같은 리소스에서 TLS 인증서를 가져다 통신을 암호화 할 수 있다&lt;/li&gt;
&lt;li&gt;&lt;code&gt;service-account-token&lt;/code&gt; : Service Account에 대한 인증 정보를 담은 토큰을 시크릿으로 생성&lt;/li&gt;
&lt;/ul&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;시크릿은 인코딩-디코딩 방식을 통해 암복호화가 이루어지기 때문에 완벽하게 데이터를 보호하는 방법이라고 볼 수 없다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;그렇기 때문에 역할 기반 접근 제어를 통해 권한이 있는 사용자만 시크릿 정보를 조회할 수 있도록 제한하거나, 별도의 암호화 플러그인과 KMS를 통해 시크릿을 완전히 암호화 하는 것을 권장한다.&lt;/p&gt;
&lt;h4 data-ke-size=&quot;size20&quot;&gt;Reference&lt;/h4&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;a href=&quot;https://yozm.wishket.com/magazine/detail/1953/&quot; target=&quot;_blank&quot; rel=&quot;noopener&amp;nbsp;noreferrer&quot;&gt;https://yozm.wishket.com/magazine/detail/1953/&lt;/a&gt;&lt;/p&gt;</description>
      <category>Infra</category>
      <category>k8s</category>
      <category>RBAC</category>
      <category>Role</category>
      <category>rolebinding</category>
      <category>Secret</category>
      <category>Security</category>
      <category>보안</category>
      <category>오블완</category>
      <category>쿠버네티스</category>
      <category>티스토리챌린지</category>
      <author>겨울바람_</author>
      <guid isPermaLink="true">https://breeze-winter.tistory.com/45</guid>
      <comments>https://breeze-winter.tistory.com/45#entry45comment</comments>
      <pubDate>Mon, 11 Nov 2024 16:02:54 +0900</pubDate>
    </item>
    <item>
      <title>[Go] 인터페이스</title>
      <link>https://breeze-winter.tistory.com/44</link>
      <description>&lt;p&gt;&lt;figure class=&quot;imageblock alignCenter&quot; data-ke-mobileStyle=&quot;widthOrigin&quot; data-origin-width=&quot;1000&quot; data-origin-height=&quot;1000&quot;&gt;&lt;span data-url=&quot;https://blog.kakaocdn.net/dn/6cIAy/btsKBh98d0b/8ZVYWLwDBbjQXfkgHDAYHK/img.png&quot; data-phocus=&quot;https://blog.kakaocdn.net/dn/6cIAy/btsKBh98d0b/8ZVYWLwDBbjQXfkgHDAYHK/img.png&quot;&gt;&lt;img src=&quot;https://blog.kakaocdn.net/dn/6cIAy/btsKBh98d0b/8ZVYWLwDBbjQXfkgHDAYHK/img.png&quot; srcset=&quot;https://img1.daumcdn.net/thumb/R1280x0/?scode=mtistory2&amp;fname=https%3A%2F%2Fblog.kakaocdn.net%2Fdn%2F6cIAy%2FbtsKBh98d0b%2F8ZVYWLwDBbjQXfkgHDAYHK%2Fimg.png&quot; onerror=&quot;this.onerror=null; this.src='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png'; this.srcset='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png';&quot; loading=&quot;lazy&quot; width=&quot;392&quot; height=&quot;392&quot; data-origin-width=&quot;1000&quot; data-origin-height=&quot;1000&quot;/&gt;&lt;/span&gt;&lt;/figure&gt;
&lt;/p&gt;
&lt;h2 data-ke-size=&quot;size26&quot;&gt;Interface&lt;/h2&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;인터페이스란 구현을 포함하지 않는 메소드 집합으로, 구체화된 타입이 아닌 인터페이스만 가지고 메소드를 호출할 수 있어 추후 프로그램 요구사항 변경 시 유연하게 대처할 수 있다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;Go에서는 인터페이스 구현 여부를 특정 타입이 인터페이스에 해당하는 메소드를 가지고 있는지로 판단하는 덕 타이핑을 지원한다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;인터페이스를 사용하는 것으로 객체 간 상호작용을 정의할 수 있으며, 덕 타이핑을 통해 사용자 중심의 코딩이 가능하다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;인터페이스 선언 방법은 다음과 같다.&lt;/p&gt;
&lt;pre class=&quot;routeros&quot;&gt;&lt;code&gt;type DuckInterface interface {
    Fly()
    Walk(distance int) int
}&lt;/code&gt;&lt;/pre&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;인터페이스 또한 구조체처럼 타입의 한 종류이기 때문에 &lt;code&gt;type&lt;/code&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;인터페이스에서는 메소드 구현을 포함하지 않는다&lt;/li&gt;
&lt;/ul&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;필자의 생각으로 가장 주의깊게 봐야하는 것이 바로 2번 항목이다. 메소드 포스팅에서도 그러했듯이 Java를 사용해본 적이 있는 개발자들에게 인터페이스라는 이름이 굉장히 친숙할 것이다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;하지만, Go에서는 메소드 오버로딩을 지원하지 않기 때문에 매개변수와 반환 값이 다르더라도 동일한 메소드명을 사용할 수 없다.&lt;/p&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 class=&quot;go&quot;&gt;&lt;code&gt;package main

import &quot;fmt&quot;

type Stringer interface {
    String() string
}

type Student struct {
    Name string
    Age int
}

func (s Student) String() string {
    return fmt.Sprintf(&quot;이름 : %s, 나이 : %d&quot;, s.Name, s.Age)
}

func main() {
    student := Student{ &quot;철수&quot;, 12 }
    var stringer Stringer

    stringer = student

    fmt.Printf(&quot;%s\n&quot;, string.String()) // 이름 : 철수, 나이 : 12
}&lt;/code&gt;&lt;/pre&gt;
&lt;h3 data-ke-size=&quot;size23&quot;&gt;덕 타이핑&lt;/h3&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;Go언어에서는 어떤 타입이 인터페이스를 포함하고 있는지 여부를 결정할 때 덕 타이핑 방식을 사용한다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;덕 타이핑이란 타입 선언 시 인터페이스 구현 여부를 명시적으로 나타낼 필요 없이 인터페이스에 정의한 메소드 포함 여부만으로 결정하는 방식이다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;덕 타이핑 방식의 장점은 사용자 중심의 코딩을 할 수 있다는 점이다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;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;구조체가 다른 구조체를 포함하는 필드를 가질 수 있듯이, 인터페이스 또한 다른 인터페이스를 포함할 수 있다. 이를 포함된 인터페이스라고 한다.&lt;/p&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 class=&quot;elm&quot;&gt;&lt;code&gt;type Reader interface {
    Read() (n int, err error)
    Close() error
}

type Writer interface {
    Write() (n int, err error)
    Close() error
}

type ReadWriter interface {
    Reader // Reader의 메소드 집합을 포함한다. 
    Writer // Writer의 메소드 집합을 포함한다.
}&lt;/code&gt;&lt;/pre&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;위의 예제에서 &lt;code&gt;ReadWriter&lt;/code&gt; 인터페이스에 포함된 &lt;code&gt;Reader&lt;/code&gt;, &lt;code&gt;Writer&lt;/code&gt; 인터페이스에 동일한 이름의 메소드인 &lt;code&gt;Close()&lt;/code&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;code&gt;Close()&lt;/code&gt; 메소드가 하나로 합쳐져서 하나의 &lt;code&gt;Close()&lt;/code&gt; 메소드만 &lt;code&gt;ReadWriter&lt;/code&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;code&gt;Read()&lt;/code&gt;, &lt;code&gt;Write()&lt;/code&gt;, &lt;code&gt;Close()&lt;/code&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;&lt;code&gt;interface{}&lt;/code&gt;는 메소드가 없는 빈 인터페이스로 모든 타입을 받을 수 있는 함수, 메소드, 변수값을 만들 때 주로 사용된다.&lt;/p&gt;
&lt;pre class=&quot;go&quot;&gt;&lt;code&gt;package main

import &quot;fmt&quot;

func PrintVal(v interface{}) {
    switch t := v.(type) {
    case int:
        fmt.Println(&quot;v is int&quot;)
    case float64: 
        fmt.Println(&quot;v is float64&quot;)
    case string:
        fmt.Println(&quot;v is string&quot;)
    default:
        fmt.Println(&quot;Not Supported Type&quot;)
    }
}

type Student struct {
    Name string
}

func main() {
    PrintVal(10)           // v is int
    PrintVal(3.14)         // v is float64
    PrintVal(&quot;hello&quot;)      // v is string
    Printval(Student{15})  // Not Supported Type
}&lt;/code&gt;&lt;/pre&gt;
&lt;h3 data-ke-size=&quot;size23&quot;&gt;인터페이스의 기본값은 nil&lt;/h3&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;인터페이스의 기본값은 유효하지 않은 메모리 주소를 나타내는 &lt;code&gt;nil&lt;/code&gt;이다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;만약 인터페이스 변수를 초기화하지 않고 사용하게 될 경우 인터페이스의 기본값은 &lt;code&gt;nil&lt;/code&gt;이기 때문에, 런 타임 에러가 발생하게 된다.&lt;/p&gt;
&lt;pre class=&quot;go&quot;&gt;&lt;code&gt;type Attacker interface {
    Attack()
}

func main() {
    var att Attacker
    att.Attack() // runtime error: invalid memory address or nil pointer dereference
}&lt;/code&gt;&lt;/pre&gt;
&lt;h3 data-ke-size=&quot;size23&quot;&gt;인터페이스 변환&lt;/h3&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;인터페이스 변수를 타입 변환을 통해서 구체화된 다른 타입이나 다른 인터페이스로도 변환할 수 있다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;구체화된 다른 타입으로 변환하는 방법은 인터페이스를 본래의 구체화된 타입으로 복원할 때 주로 사용한다. 사용 방법은 인터페이스 변수 뒤에 &lt;code&gt;.&lt;/code&gt;을 찍고 &lt;code&gt;()&lt;/code&gt;안에 변경하려는 타입을 써주면 된다.&lt;/p&gt;
&lt;pre class=&quot;routeros&quot;&gt;&lt;code&gt;var a Interface
t := a.(ConcreteType)&lt;/code&gt;&lt;/pre&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;구체적인 사용 예시를 위해 코드를 작성해보자.&lt;/p&gt;
&lt;pre class=&quot;go&quot;&gt;&lt;code&gt;package main

import &quot;fmt&quot;

type Stringer interface {
    String() string
}

type Student struct {
    Name string
}

func (s *Student) String() string {
    return fmt.Sprintf(&quot;Student Name: %s&quot;, s.Name)
}

func PrintAge(stringer Stringer) {
    s := string.(*Student)
    fmt.Printf(&quot;Name: %s\n&quot;, s.Name) 
}

func main() {
    s := &amp;amp;Student{&quot;철수&quot;}
    PrintAge(s) // Name: 철수
}&lt;/code&gt;&lt;/pre&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;위의 코드에서 &lt;code&gt;Stringer&lt;/code&gt; 인터페이스를 &lt;code&gt;*Student&lt;/code&gt;타입으로 형변환시켰다. 그 이유는 &lt;code&gt;Stringer&lt;/code&gt; 인터페이스는 &lt;code&gt;String()&lt;/code&gt; 메소드만 존재하기 때문에 &lt;code&gt;Name&lt;/code&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;code&gt;Name&lt;/code&gt; 필드를 호출하기 위해 인터페이스를 형변환시킨 것이다. &lt;code&gt;PrintAge()&lt;/code&gt;로 넘겨진 &lt;code&gt;stringer&lt;/code&gt; 인스턴스 변수 내부에 &lt;code&gt;*Student&lt;/code&gt; 타입 인스턴스를 가리키고 있었기 때문에 에러 없이 변환이 이루어진다.&lt;/p&gt;
&lt;p&gt;&lt;figure class=&quot;imageblock alignCenter&quot; data-ke-mobileStyle=&quot;widthOrigin&quot; data-origin-width=&quot;729&quot; data-origin-height=&quot;94&quot;&gt;&lt;span data-url=&quot;https://blog.kakaocdn.net/dn/5MffW/btsKCfeU7Vg/e6iuvkkYWGCURdJncKpQLK/img.png&quot; data-phocus=&quot;https://blog.kakaocdn.net/dn/5MffW/btsKCfeU7Vg/e6iuvkkYWGCURdJncKpQLK/img.png&quot;&gt;&lt;img src=&quot;https://blog.kakaocdn.net/dn/5MffW/btsKCfeU7Vg/e6iuvkkYWGCURdJncKpQLK/img.png&quot; srcset=&quot;https://img1.daumcdn.net/thumb/R1280x0/?scode=mtistory2&amp;fname=https%3A%2F%2Fblog.kakaocdn.net%2Fdn%2F5MffW%2FbtsKCfeU7Vg%2Fe6iuvkkYWGCURdJncKpQLK%2Fimg.png&quot; onerror=&quot;this.onerror=null; this.src='//t1.daumcdn.net/tistory_admin/static/images/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;84&quot; data-origin-width=&quot;729&quot; data-origin-height=&quot;94&quot;/&gt;&lt;/span&gt;&lt;/figure&gt;
&lt;/p&gt;
&lt;h3 data-ke-size=&quot;size23&quot;&gt;다른 인터페이스로 타입 변환하기&lt;/h3&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;인터페이스를 또 다른 인터페이스로 변환할 수 있다. 구체화된 타입으로 변환할 때와는 달리 변경되는 인터페이스가 변경 전 인터페이스를 포함하지 않아도 된다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;하지만 인터페이스가 가리키고 있는 실제 인스턴스가 변환하고자 하는 다른 인터페이스를 포함해야 한다.&lt;/p&gt;
&lt;p&gt;&lt;figure class=&quot;imageblock alignCenter&quot; data-ke-mobileStyle=&quot;widthOrigin&quot; data-origin-width=&quot;642&quot; data-origin-height=&quot;323&quot;&gt;&lt;span data-url=&quot;https://blog.kakaocdn.net/dn/bcGQlp/btsKD2EX58W/6cr0yWpn4JkzS5fJjHaR80/img.png&quot; data-phocus=&quot;https://blog.kakaocdn.net/dn/bcGQlp/btsKD2EX58W/6cr0yWpn4JkzS5fJjHaR80/img.png&quot;&gt;&lt;img src=&quot;https://blog.kakaocdn.net/dn/bcGQlp/btsKD2EX58W/6cr0yWpn4JkzS5fJjHaR80/img.png&quot; srcset=&quot;https://img1.daumcdn.net/thumb/R1280x0/?scode=mtistory2&amp;fname=https%3A%2F%2Fblog.kakaocdn.net%2Fdn%2FbcGQlp%2FbtsKD2EX58W%2F6cr0yWpn4JkzS5fJjHaR80%2Fimg.png&quot; onerror=&quot;this.onerror=null; this.src='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png'; this.srcset='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png';&quot; loading=&quot;lazy&quot; width=&quot;561&quot; height=&quot;282&quot; data-origin-width=&quot;642&quot; data-origin-height=&quot;323&quot;/&gt;&lt;/span&gt;&lt;/figure&gt;
&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;br /&gt;&lt;code&gt;Student&lt;/code&gt;타입이 &lt;code&gt;Interface A&lt;/code&gt;와 &lt;code&gt;Interface B&lt;/code&gt; 인터페이스를 모두 포함하고 있는 경우에 다음과 같이 &lt;code&gt;Student&lt;/code&gt; 인스턴스를 가리키고 있는 &lt;code&gt;Interface A&lt;/code&gt; 변수 &lt;code&gt;a&lt;/code&gt;는 &lt;code&gt;Interface B&lt;/code&gt;로 변환이 가능하다.&lt;/p&gt;
&lt;pre class=&quot;stylus&quot;&gt;&lt;code&gt;var a Interface_A = Student{}
b := a.(Interface_B)&lt;/code&gt;&lt;/pre&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;타입 변환이 아예 불가능한 타입이라면 컴파일 타임 에러가 발생하고 문법적으로 문제 없지만, 실행 도중 타입 변환에 실패하는 경우에는 런 타임 에러가 발생한다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;이를 예방하기 위해 타입 변환 가능 여부를 확인하여 런 타임 에러가 발생하지 않는 타입 변환 방법에 대해 알아보자.&lt;/p&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;code&gt;false&lt;/code&gt;로 반환되며, 런 타임 에러는 발생하지 않는다.&lt;/p&gt;
&lt;pre class=&quot;routeros&quot;&gt;&lt;code&gt;var a Interface
t, ok := a.(Type)&lt;/code&gt;&lt;/pre&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;만약 변환에 실패하더라도 런 타임 에러를 방지할 수 있기 때문에 인터페이스 변환 시 아래의 코드와 같이 변환 여부를 확인하기를 추천한다.&lt;/p&gt;
&lt;pre class=&quot;go&quot;&gt;&lt;code&gt;func ReadFile(reader Reader) {
    if c, ok := reader.(Closer); ok {
        ...
    }
}&lt;/code&gt;&lt;/pre&gt;</description>
      <category>Dev</category>
      <category>go</category>
      <category>Golang</category>
      <category>객체지향</category>
      <category>백엔드</category>
      <category>오블완</category>
      <category>인터페이스</category>
      <category>티스토리챌린지</category>
      <author>겨울바람_</author>
      <guid isPermaLink="true">https://breeze-winter.tistory.com/44</guid>
      <comments>https://breeze-winter.tistory.com/44#entry44comment</comments>
      <pubDate>Sun, 10 Nov 2024 19:09:17 +0900</pubDate>
    </item>
    <item>
      <title>[Go] 메소드</title>
      <link>https://breeze-winter.tistory.com/43</link>
      <description>&lt;p&gt;&lt;figure class=&quot;imageblock alignCenter&quot; data-ke-mobileStyle=&quot;widthOrigin&quot; data-origin-width=&quot;1000&quot; data-origin-height=&quot;1000&quot;&gt;&lt;span data-url=&quot;https://blog.kakaocdn.net/dn/6cIAy/btsKBh98d0b/8ZVYWLwDBbjQXfkgHDAYHK/img.png&quot; data-phocus=&quot;https://blog.kakaocdn.net/dn/6cIAy/btsKBh98d0b/8ZVYWLwDBbjQXfkgHDAYHK/img.png&quot;&gt;&lt;img src=&quot;https://blog.kakaocdn.net/dn/6cIAy/btsKBh98d0b/8ZVYWLwDBbjQXfkgHDAYHK/img.png&quot; srcset=&quot;https://img1.daumcdn.net/thumb/R1280x0/?scode=mtistory2&amp;fname=https%3A%2F%2Fblog.kakaocdn.net%2Fdn%2F6cIAy%2FbtsKBh98d0b%2F8ZVYWLwDBbjQXfkgHDAYHK%2Fimg.png&quot; onerror=&quot;this.onerror=null; this.src='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png'; this.srcset='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png';&quot; loading=&quot;lazy&quot; width=&quot;392&quot; height=&quot;392&quot; data-origin-width=&quot;1000&quot; data-origin-height=&quot;1000&quot;/&gt;&lt;/span&gt;&lt;/figure&gt;
&lt;/p&gt;
&lt;h2 data-ke-size=&quot;size26&quot;&gt;Method&lt;/h2&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;이전에 Java를 사용해본 사람이라며 Method라는 이름이 친숙하게 느껴질 것이다. 필자 또한 Java를 주로 사용했기 때문에 이번 파트를 공부하며 Method라는 명칭이 굉장히 반갑게 느껴졌다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;하지만 Go는 Java와 달리 클래스가 존재하지 않기 때문에 어떤 식으로 메소드를 사용하는지 의구심이 들었다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;Go에서의 메소드는 구조체 바깥에서 정의되며 리시버를 통해 구조체와 연결된다.&lt;/p&gt;
&lt;h3 data-ke-size=&quot;size23&quot;&gt;Receiver&lt;/h3&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;메소드는 구조체 바깥에서 선언되기 때문에 메소드가 어떤 구조체에 속하는지 표시할 방법이 필요하다. 이를 위해 리시버를 사용하여 메소드가 속한 구조체를 알려준다.&lt;/p&gt;
&lt;h3 data-ke-size=&quot;size23&quot;&gt; Method 선언&lt;/h3&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;메소드를 선언하기 위해서는 리시버를 &lt;code&gt;()&lt;/code&gt;로 명시해야 한다.&lt;/p&gt;
&lt;pre class=&quot;autoit&quot;&gt;&lt;code&gt;func (r Rabbit) info() int {
    return r.width * r.height
}&lt;/code&gt;&lt;/pre&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;위의 코드에서 &lt;code&gt;(r Rabbit)&lt;/code&gt;부분이 리시버다. 리시버를 통해 우리는 &lt;code&gt;info()&lt;/code&gt; 메소드가 &lt;code&gt;Rabbit&lt;/code&gt; 타입에 속해있다는 것을 알 수 있다. 구조체 변수로 선언된 &lt;code&gt;r&lt;/code&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;code&gt;int&lt;/code&gt; 타입 또한 별칭 타입을 만들면 메소드를 가질 수 있다.&lt;/p&gt;
&lt;pre class=&quot;go&quot;&gt;&lt;code&gt;package main

import &quot;fmt&quot;

type myInt int

func (a myInt) add(b int) int {
    return int(a) + b
}

func main() {
    var a myInt = 10
    fmt.Println(a.add(30)) // 40
    var b int = 20
    fmt.Println(myInt(b).add(50)) // 70
}&lt;/code&gt;&lt;/pre&gt;
&lt;h2 data-ke-size=&quot;size26&quot;&gt;포인터 메소드 vs 값 타입 메소드&lt;/h2&gt;
&lt;pre class=&quot;reasonml&quot;&gt;&lt;code&gt;package main

import &quot;fmt&quot;

type account struct {
    balance int
    firstName string
    lastName string
}

func (a1 *account) withdrawPointer(amount int) {
    a1.balance -= amount
}

func (a2 account) withdrawValue(amount int) {
    a2.balance -= amount
}

func (a3 account) withdrawReturnValue(amount int) account {
    a3.balance -= amoun
    return a3
}

func main() {
    var mainA *account = &amp;amp;account{ 100, &quot;Joe&quot;, &quot;Park&quot; }
    mainA.withdrawPointer(30)
    fmt.Println(mainA.balance) //70

    mainA.withdrawValue(20)
    fmt.Println(mainA.balance) // 70

    var mainB account = mainA.withdrawReturnValue(20)
    fmt.Println(mainB.balance) // 50

    mainB.withdrawPointer(30)
    fmt.Println(mainB.balance) // 20    
}&lt;/code&gt;&lt;/pre&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;위의 코드에서 &lt;code&gt;withdrawPointer()&lt;/code&gt; 메소드는 리시버로 포인터를 갖기 때문에 &lt;code&gt;*account&lt;/code&gt; 타입에 속하고, &lt;code&gt;withdrawValue()&lt;/code&gt;, &lt;code&gt;withdrawReturnValue()&lt;/code&gt; 메소드는 값 타입을 리시버로 갖기 때문에 &lt;code&gt;account&lt;/code&gt;에 속한다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;포인터 메소드를 호출하면 포인터가 가리키고 있는 메모리의 주소값이 복사되지만, 값 타입 메소드를 호출하면 리시버 타입의 모든 값이 복사된다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;code&gt;withdrawPoint()&lt;/code&gt; 메소드가 호출되면 &lt;code&gt;mainA&lt;/code&gt; 포인터 변수가 갖는 값인 메모리 주소가 복사되기 때문에 &lt;code&gt;a1&lt;/code&gt;과 &lt;code&gt;mainA&lt;/code&gt;는 동일한 인스턴스를 가리키게 된다. 그렇기 때문에 &lt;code&gt;a1&lt;/code&gt;의 &lt;code&gt;balance&lt;/code&gt;를 변경하게 되면 &lt;code&gt;mainA&lt;/code&gt;의 &lt;code&gt;balance&lt;/code&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;code&gt;withdrawValue()&lt;/code&gt; 메소드를 호출할 때는 값이 복사되어 전달되기 때문에 &lt;code&gt;a2&lt;/code&gt;와 &lt;code&gt;mainA&lt;/code&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;code&gt;a2&lt;/code&gt;의 &lt;code&gt;balance&lt;/code&gt;를 변경해도 &lt;code&gt;mainA&lt;/code&gt;의 &lt;code&gt;balance&lt;/code&gt;는 변경되지 않는다. 이를 해결하기 위해서 &lt;code&gt;withdrawReturnValue()&lt;/code&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;code&gt;withdrawReturnValue()&lt;/code&gt; 메소드에서는 값 복사가 호출 시와 결과값 반환 시 두 번 이루어진다. 이는 &lt;code&gt;mainA&lt;/code&gt;, &lt;code&gt;a3&lt;/code&gt;, &lt;code&gt;mainB&lt;/code&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;&lt;code&gt;mainA&lt;/code&gt;는 포인터 변수이고, &lt;code&gt;withdrawValue()&lt;/code&gt;는 &lt;code&gt;account&lt;/code&gt; 값 타입을 리시버로 받는 메소드다. 포인터인 &lt;code&gt;mainA&lt;/code&gt;는 바로 호출할 수 없고 &lt;code&gt;(*main).withdrawValue(20)&lt;/code&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;Go에서는 자동으로 &lt;code&gt;mainA&lt;/code&gt;의 값으로 변환하여 호출한다. 반대의 경우에도 동일한데, &lt;code&gt;mainB.withdrawPointer(30)&lt;/code&gt;에서 &lt;code&gt;mainB&lt;/code&gt;는 &lt;code&gt;account&lt;/code&gt; 값 타입 변수이고 &lt;code&gt;withdrawPointer()&lt;/code&gt;는 &lt;code&gt;*account&lt;/code&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;code&gt;(&amp;amp;mainB).withdrawPointer(30)&lt;/code&gt;처럼 포인터로 변환 후 호출해야 하지만 Go에서는 자동으로 &lt;code&gt;mainB&lt;/code&gt;의 메모리 주소값으로 변환하여 호출한다.&lt;/p&gt;</description>
      <category>Dev</category>
      <category>go</category>
      <category>Golang</category>
      <category>Method</category>
      <category>메소드</category>
      <category>백엔드</category>
      <category>오블완</category>
      <category>티스토리챌린지</category>
      <category>포인터</category>
      <category>함수</category>
      <author>겨울바람_</author>
      <guid isPermaLink="true">https://breeze-winter.tistory.com/43</guid>
      <comments>https://breeze-winter.tistory.com/43#entry43comment</comments>
      <pubDate>Sat, 9 Nov 2024 19:05:51 +0900</pubDate>
    </item>
    <item>
      <title>[k8s] Kubernetes Network</title>
      <link>https://breeze-winter.tistory.com/42</link>
      <description>&lt;h2 data-ke-size=&quot;size26&quot;&gt;Overview&lt;/h2&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;Kubernetes 환경에서 이루어지는 네트워킹은 크게 4가지로 분류할 수 있다.&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;Pod와 Pod 간의 통신&lt;/li&gt;
&lt;li&gt;Pod와 Service 간의 통신&lt;/li&gt;
&lt;li&gt;외부와 Service 간의 통신서로 결합된 컨테이너와 컨테이너 간의 통신Docker 환경에서 생성된 컨테이너의 기본적인 네트워크 동작 구조를 그림으로 표현하면 아래와 같다.&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;482&quot; data-origin-height=&quot;408&quot;&gt;&lt;span data-url=&quot;https://blog.kakaocdn.net/dn/C1aQn/btsKBUHox5p/QWVnFLfdxP6zNLNXVhwDmk/img.png&quot; data-phocus=&quot;https://blog.kakaocdn.net/dn/C1aQn/btsKBUHox5p/QWVnFLfdxP6zNLNXVhwDmk/img.png&quot;&gt;&lt;img src=&quot;https://blog.kakaocdn.net/dn/C1aQn/btsKBUHox5p/QWVnFLfdxP6zNLNXVhwDmk/img.png&quot; srcset=&quot;https://img1.daumcdn.net/thumb/R1280x0/?scode=mtistory2&amp;fname=https%3A%2F%2Fblog.kakaocdn.net%2Fdn%2FC1aQn%2FbtsKBUHox5p%2FQWVnFLfdxP6zNLNXVhwDmk%2Fimg.png&quot; onerror=&quot;this.onerror=null; this.src='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png'; this.srcset='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png';&quot; loading=&quot;lazy&quot; width=&quot;418&quot; height=&quot;354&quot; data-origin-width=&quot;482&quot; data-origin-height=&quot;408&quot;/&gt;&lt;/span&gt;&lt;/figure&gt;
&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;br /&gt;Docker에서는 기본적으로 같은 노드 내의 컨테이너끼리의 통신은 위 그림과 같이 &lt;code&gt;docker0&lt;/code&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;code&gt;veth&lt;/code&gt;라는 가상 네트워크 인터페이스를 고유하게 가지며 따라서 각각의 &lt;code&gt;veth&lt;/code&gt; IP 주소 값으로 통신할 수 있다. 하지만 두 컨테이너가 동일한 &lt;code&gt;veth&lt;/code&gt;에 할당되는 경우도 존재한다.&lt;/p&gt;
&lt;p&gt;&lt;figure class=&quot;imageblock alignCenter&quot; data-ke-mobileStyle=&quot;widthOrigin&quot; data-origin-width=&quot;482&quot; data-origin-height=&quot;455&quot;&gt;&lt;span data-url=&quot;https://blog.kakaocdn.net/dn/bPg916/btsKBnpMc4U/Ya65GrX4Rjp0czmxj9JSE0/img.png&quot; data-phocus=&quot;https://blog.kakaocdn.net/dn/bPg916/btsKBnpMc4U/Ya65GrX4Rjp0czmxj9JSE0/img.png&quot;&gt;&lt;img src=&quot;https://blog.kakaocdn.net/dn/bPg916/btsKBnpMc4U/Ya65GrX4Rjp0czmxj9JSE0/img.png&quot; srcset=&quot;https://img1.daumcdn.net/thumb/R1280x0/?scode=mtistory2&amp;fname=https%3A%2F%2Fblog.kakaocdn.net%2Fdn%2FbPg916%2FbtsKBnpMc4U%2FYa65GrX4Rjp0czmxj9JSE0%2Fimg.png&quot; onerror=&quot;this.onerror=null; this.src='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png'; this.srcset='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png';&quot; loading=&quot;lazy&quot; width=&quot;355&quot; height=&quot;335&quot; data-origin-width=&quot;482&quot; data-origin-height=&quot;455&quot;/&gt;&lt;/span&gt;&lt;/figure&gt;
&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;br /&gt;위 그림에서는 두 개의 컨테이너가 모두 &lt;code&gt;veth0&lt;/code&gt;라는 동일한 네트워크를 사용한다. 외부에서 바라봤을 때 두 개의 컨테이너는 동일한 IP로 인식되기 때문에 동일한 네트워크 상의 컨테이너는 &lt;code&gt;port&lt;/code&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;즉, Pod 내에서 컨테이너는 각자 고유한 포트 번호를 사용해야 한다. 이러한 네트워크 인터페이스를 제공해주는 특별한 컨테이너가 있다.&lt;/p&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;code&gt;pause&lt;/code&gt;라는 명령으로 실행된 컨테이너는 각 Pod마다 존재하며 다른 컨테이너들에게 네트워크 인터페이스를 제공하는 역할만 담당한다.&lt;/p&gt;
&lt;h3 data-ke-size=&quot;size23&quot;&gt;Pod와 Pod 간의 통신&lt;/h3&gt;
&lt;ul style=&quot;list-style-type: disc;&quot; data-ke-list-type=&quot;disc&quot;&gt;
&lt;li&gt;싱글 노드 Pod 네트워크&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;854&quot; data-origin-height=&quot;620&quot;&gt;&lt;span data-url=&quot;https://blog.kakaocdn.net/dn/bU58yd/btsKADz72B9/i5J20jCh6dvIZLR7qp9BvK/img.png&quot; data-phocus=&quot;https://blog.kakaocdn.net/dn/bU58yd/btsKADz72B9/i5J20jCh6dvIZLR7qp9BvK/img.png&quot;&gt;&lt;img src=&quot;https://blog.kakaocdn.net/dn/bU58yd/btsKADz72B9/i5J20jCh6dvIZLR7qp9BvK/img.png&quot; srcset=&quot;https://img1.daumcdn.net/thumb/R1280x0/?scode=mtistory2&amp;fname=https%3A%2F%2Fblog.kakaocdn.net%2Fdn%2FbU58yd%2FbtsKADz72B9%2Fi5J20jCh6dvIZLR7qp9BvK%2Fimg.png&quot; onerror=&quot;this.onerror=null; this.src='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png'; this.srcset='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png';&quot; loading=&quot;lazy&quot; width=&quot;594&quot; height=&quot;431&quot; data-origin-width=&quot;854&quot; data-origin-height=&quot;620&quot;/&gt;&lt;/span&gt;&lt;/figure&gt;
&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;br /&gt;Pod의 특징 중 하나로 각 Pod는 &lt;code&gt;veth&lt;/code&gt;를 통해 고유한 IP 주소를 가진다. 각 Pod는 &lt;code&gt;kubenet&lt;/code&gt; 혹은 &lt;code&gt;CNI&lt;/code&gt;로 구성된 네트워크 인터페이스를 통하여 고유한 IP 주소로 서로 통신할 수 있다.&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;멀티 노드 Pod 네트워크&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;1788&quot; data-origin-height=&quot;1029&quot;&gt;&lt;span data-url=&quot;https://blog.kakaocdn.net/dn/dtTDwK/btsKBUgkMxy/YE2KMFZbtgeStdqnh1Hhi1/img.png&quot; data-phocus=&quot;https://blog.kakaocdn.net/dn/dtTDwK/btsKBUgkMxy/YE2KMFZbtgeStdqnh1Hhi1/img.png&quot;&gt;&lt;img src=&quot;https://blog.kakaocdn.net/dn/dtTDwK/btsKBUgkMxy/YE2KMFZbtgeStdqnh1Hhi1/img.png&quot; srcset=&quot;https://img1.daumcdn.net/thumb/R1280x0/?scode=mtistory2&amp;fname=https%3A%2F%2Fblog.kakaocdn.net%2Fdn%2FdtTDwK%2FbtsKBUgkMxy%2FYE2KMFZbtgeStdqnh1Hhi1%2Fimg.png&quot; onerror=&quot;this.onerror=null; this.src='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png'; this.srcset='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png';&quot; loading=&quot;lazy&quot; width=&quot;678&quot; height=&quot;390&quot; data-origin-width=&quot;1788&quot; data-origin-height=&quot;1029&quot;/&gt;&lt;/span&gt;&lt;/figure&gt;
&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;br /&gt;여러 개의 워커 노드 사이에 각각 다른 노드에 존재하는 Pod가 서로 통신하려면 라우터를 거쳐서 통신하게 된다.Pod는 기본적으로 쉽게 대체될 수 있기 때문에 Pod간 네트워크만으로는 쿠버네티스 시스템을 내구성 있게 구축할 수 없다.&lt;/p&gt;
&lt;h3 data-ke-size=&quot;size23&quot;&gt;Pod와 Service 간의 통신&lt;/h3&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;이를 해결하기 위해 서비스 앞단에 &lt;code&gt;reverse-proxy&lt;/code&gt;혹은 &lt;code&gt;Load Balancer&lt;/code&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;code&gt;proxy&lt;/code&gt;로 연결을 하면 &lt;code&gt;proxy&lt;/code&gt;의 역할은 서버들 목록을 관리하며 현재 살아있는 서버에게 트래픽을 전달하는 것이다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;이는 다음의 요구사항을 만족해야 한다.&lt;/p&gt;
&lt;ul style=&quot;list-style-type: disc;&quot; data-ke-list-type=&quot;disc&quot;&gt;
&lt;li&gt;&lt;code&gt;proxy&lt;/code&gt; 서버 스스로 내구성이 있어야 하며 장애에 대응할 수 있어야 함&lt;/li&gt;
&lt;li&gt;트래픽을 전달할 서버 리스트를 가지고 있어야 함&lt;/li&gt;
&lt;li&gt;서버 리스트 내 서버들이 정상적인지 확인할 수 있는 방법을 알아야 함&lt;/li&gt;
&lt;/ul&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;쿠버네티스 리소스 중 &lt;code&gt;Service&lt;/code&gt;가 이러한 요구사항을 만족한다. &lt;code&gt;Service&lt;/code&gt;는 각 Pod로 트래픽을 포워딩해주는 프록시 역할을 한다.&lt;/p&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;code&gt;selector&lt;/code&gt;라는 것을 이용하여 트래픽을 전달받을 Pod들을 결정한다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;Service 또한 Pod 네트워크와 동일하게 가상 IP 주소지만, Pod 네트워크가 실질적으로 가상 이더넷 네트워크 인터페이스가 설정되어 &lt;code&gt;ifconfig&lt;/code&gt; 명령어로 조회되는 것과 다르게 &lt;code&gt;ifconfig&lt;/code&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;또한 라우팅 테이블에서도 Service 네트워크에 대한 경로를 찾아볼 수 없다. 이는 Service와 Pod 간의 통신이 &lt;code&gt;netfilter&lt;/code&gt;와 &lt;code&gt;kube-proxy&lt;/code&gt;를 통해 이루어지기 때문이다.&lt;/p&gt;
&lt;p&gt;&lt;figure class=&quot;imageblock alignCenter&quot; data-ke-mobileStyle=&quot;widthOrigin&quot; data-origin-width=&quot;842&quot; data-origin-height=&quot;498&quot;&gt;&lt;span data-url=&quot;https://blog.kakaocdn.net/dn/FZ6GK/btsKCeTcn5g/lpL8FZyLwR1mhBpCv2mbQK/img.png&quot; data-phocus=&quot;https://blog.kakaocdn.net/dn/FZ6GK/btsKCeTcn5g/lpL8FZyLwR1mhBpCv2mbQK/img.png&quot;&gt;&lt;img src=&quot;https://blog.kakaocdn.net/dn/FZ6GK/btsKCeTcn5g/lpL8FZyLwR1mhBpCv2mbQK/img.png&quot; srcset=&quot;https://img1.daumcdn.net/thumb/R1280x0/?scode=mtistory2&amp;fname=https%3A%2F%2Fblog.kakaocdn.net%2Fdn%2FFZ6GK%2FbtsKCeTcn5g%2FlpL8FZyLwR1mhBpCv2mbQK%2Fimg.png&quot; onerror=&quot;this.onerror=null; this.src='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png'; this.srcset='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png';&quot; loading=&quot;lazy&quot; width=&quot;522&quot; height=&quot;309&quot; data-origin-width=&quot;842&quot; data-origin-height=&quot;498&quot;/&gt;&lt;/span&gt;&lt;/figure&gt;
&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;Client Pod에서 Target Pod로 요청을 보내면 Pod의 이더넷 인터페이스가 해당 요청을 전달받은 후, 해당 Target Pod의 IP를 알지 못하기 때문에 다음 게이트웨이로 해당 요청을 전달한다.&lt;/p&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;code&gt;veth0&lt;/code&gt;는 다음 게이트웨이인 &lt;code&gt;cbr&lt;/code&gt;로 요청을 전달하지만, &lt;code&gt;cbr&lt;/code&gt;은 &lt;code&gt;bridge&lt;/code&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;code&gt;netfilter&lt;/code&gt;가 해당 요청의 목적지 IP를 실제 Target Pod의 IP로 변환하여 다음 게이트웨이로 전달한다. 이후 최종적으로 &lt;code&gt;eth0&lt;/code&gt;에 의해 Target Pod로 요청이 라우팅되게 되는 것이다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;쿠버네티스 1.2 버전 이후에서는 해당 패킷의 주소 변환 과정의 비용을 줄이기 위해 &lt;code&gt;kube-proxy&lt;/code&gt;의 역할을 &lt;code&gt;netfilter&lt;/code&gt;의 규칙을 알맞게 수정하는 것만을 담당하는 것으로 축소시켰다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;위의 그림 또한 쿠버네티스 1.2 버전 이후를 예시로 들어 그려진 것이다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;code&gt;kube-proxy&lt;/code&gt;는 마스터 노드의 API Server에서 정보를 수신하기 때문에 클러스터의 변화를 감지할 수 있고, 이를 통해 지속적으로 &lt;code&gt;iptables&lt;/code&gt;를 업데이트하여 &lt;code&gt;netfilter&lt;/code&gt;의 규칙을 최신화한다. 하지만 이러한 방식은 클러스터 내부에 위치한 Pod간 통신에서만 동작한다.&lt;/p&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;code&gt;NodePort&lt;/code&gt;, &lt;code&gt;Load Balancer&lt;/code&gt;, &lt;code&gt;Ingress&lt;/code&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;code&gt;NodePort&lt;/code&gt;, &lt;code&gt;Load Balancer&lt;/code&gt;, &lt;code&gt;Ingress&lt;/code&gt;에 대해서는 다음 포스팅에서 자세히 다뤄볼 예정이다.&lt;/p&gt;</description>
      <category>Infra</category>
      <category>ETH</category>
      <category>ingress</category>
      <category>k8s</category>
      <category>k8s pod</category>
      <category>k8s service</category>
      <category>네트워크</category>
      <category>오블완</category>
      <category>쿠버네티스</category>
      <category>티스토리챌린지</category>
      <category>파드 간 통신</category>
      <author>겨울바람_</author>
      <guid isPermaLink="true">https://breeze-winter.tistory.com/42</guid>
      <comments>https://breeze-winter.tistory.com/42#entry42comment</comments>
      <pubDate>Fri, 8 Nov 2024 14:20:01 +0900</pubDate>
    </item>
    <item>
      <title>[Go] 슬라이스</title>
      <link>https://breeze-winter.tistory.com/41</link>
      <description>&lt;p&gt;&lt;figure class=&quot;imageblock alignCenter&quot; data-ke-mobileStyle=&quot;widthOrigin&quot; data-origin-width=&quot;1000&quot; data-origin-height=&quot;1000&quot;&gt;&lt;span data-url=&quot;https://blog.kakaocdn.net/dn/6cIAy/btsKBh98d0b/8ZVYWLwDBbjQXfkgHDAYHK/img.png&quot; data-phocus=&quot;https://blog.kakaocdn.net/dn/6cIAy/btsKBh98d0b/8ZVYWLwDBbjQXfkgHDAYHK/img.png&quot;&gt;&lt;img src=&quot;https://blog.kakaocdn.net/dn/6cIAy/btsKBh98d0b/8ZVYWLwDBbjQXfkgHDAYHK/img.png&quot; srcset=&quot;https://img1.daumcdn.net/thumb/R1280x0/?scode=mtistory2&amp;fname=https%3A%2F%2Fblog.kakaocdn.net%2Fdn%2F6cIAy%2FbtsKBh98d0b%2F8ZVYWLwDBbjQXfkgHDAYHK%2Fimg.png&quot; onerror=&quot;this.onerror=null; this.src='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png'; this.srcset='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png';&quot; loading=&quot;lazy&quot; width=&quot;392&quot; height=&quot;392&quot; data-origin-width=&quot;1000&quot; data-origin-height=&quot;1000&quot;/&gt;&lt;/span&gt;&lt;/figure&gt;
&lt;/p&gt;
&lt;h2 data-ke-size=&quot;size26&quot;&gt;Slice란?&lt;/h2&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;슬라이스는 Go 언어에서 제공하는 자료구조 중 하나로, 동적으로 크기를 증가시키는 동적 배열이다. 또한, 슬라이싱 기능을 이용해 배열의 일부를 나타내는 슬라이스를 만들 수 있다.&lt;/p&gt;
&lt;h2 data-ke-size=&quot;size26&quot;&gt;Slice 선언&lt;/h2&gt;
&lt;h3 data-ke-size=&quot;size23&quot;&gt; &lt;code&gt;{}&lt;/code&gt;를 이용한 초기화&lt;/h3&gt;
&lt;pre class=&quot;cs&quot;&gt;&lt;code&gt;var slice []int&lt;/code&gt;&lt;/pre&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;일반적인 배열과 달리 &lt;code&gt;[]&lt;/code&gt; 내부에 배열의 개수를 적지 않고 선언한다. 별도로 슬라이스의 크기를 지정해주지 않으면, 길이가 0인 슬라이스가 생성된다.&lt;/p&gt;
&lt;pre class=&quot;pgsql&quot;&gt;&lt;code&gt;var slice []int

if len(slice) == 0 {
    fmt.Println(&quot;slice is Empty&quot;)
}
// slice is Empty&lt;/code&gt;&lt;/pre&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;슬라이스 초기화 시점에 슬라이스 내부에 포함되는 값들을 미리 설정하기 위해서는 배열처럼 &lt;code&gt;{}&lt;/code&gt;를 이용하여 요소값을 지정해야 한다.&lt;/p&gt;
&lt;pre class=&quot;yaml&quot;&gt;&lt;code&gt;var slice1 = []int {1, 2, 3} // [1, 2, 3]
var slice2 = []int {1, 5:2, 10:3} // [1, 0, 0, 0, 0, 2, 0, 0, 0, 0, 3]&lt;/code&gt;&lt;/pre&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;code&gt;slice1&lt;/code&gt;은 1, 2, 3을 값으로 갖는 슬라이스가 되며, &lt;code&gt;slice2&lt;/code&gt;는 인덱스 1은 1, 인덱스 5는 2, 인덱스 10은 3을 각각 값으로 갖고 나머지는 0인 슬라이스로 초기화 된다.&lt;/p&gt;
&lt;h3 data-ke-size=&quot;size23&quot;&gt; &lt;code&gt;make()&lt;/code&gt;를 이용한 초기화&lt;/h3&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;내장 함수 &lt;code&gt;make()&lt;/code&gt;를 사용하는 방법도 있다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;함수의 첫 번째 인자로 생성하고자 하는 슬라이스의 타입을 입력하고, 두 번째 인자로 해당 슬라이스의 길이를 작성한다.&lt;/p&gt;
&lt;pre class=&quot;go&quot;&gt;&lt;code&gt;var slice = make([]int, 3) // [0, 0, 0]&lt;/code&gt;&lt;/pre&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;슬라이스에 포함된 값은 &lt;code&gt;int&lt;/code&gt; 타입의 기본 값인 0으로 초기화된다.&lt;/p&gt;
&lt;h2 data-ke-size=&quot;size26&quot;&gt;Slice 요소 추가&lt;/h2&gt;
&lt;h3 data-ke-size=&quot;size23&quot;&gt;➕ append&lt;/h3&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;슬라이스의 요소를 조회하거나 순회하는 방식은 일반 배열과 동일하지만, 값을 추가하는 방식에서 일반적인 배열과 차이점이 존재한다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;일반적인 배열은 초기화 시점에서 길이가 정해지면 길이를 늘릴 수 없지만, 슬라이스는 요소를 추가하는 것으로 길이를 늘릴 수 있다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;요소를 추가하기 위해서는 내장 함수인 &lt;code&gt;append()&lt;/code&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;code&gt;append()&lt;/code&gt;의 결과를 대입할 값이 필요하다.&lt;/p&gt;
&lt;pre class=&quot;yaml&quot;&gt;&lt;code&gt;var slice = []int {1, 2, 3}

sliceForAppend := append(slice, 4)

fmt.Println(slice) // output &amp;gt; 1, 2, 3
fmt.Println(sliceForAppend) // output &amp;gt; 1, 2, 3, 4&lt;/code&gt;&lt;/pre&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;code&gt;append()&lt;/code&gt;를 사용해 &lt;code&gt;slice&lt;/code&gt;에 4를 추가해 반환한 값을 &lt;code&gt;sliceForAppend&lt;/code&gt;에 대입한다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;code&gt;append()&lt;/code&gt;를 사용하면 여러 개의 값을 한 번에 추가할 수도 있다.&lt;/p&gt;
&lt;pre class=&quot;yaml&quot;&gt;&lt;code&gt;var slice = []int {1, 2, 3}

sliceForAppend := append(slice, 4, 5, 6)

fmt.Println(slice) // output &amp;gt; 1, 2, 3
fmt.Println(sliceForAppend) // output &amp;gt; 1, 2, 3, 4, 5, 6&lt;/code&gt;&lt;/pre&gt;
&lt;h2 data-ke-size=&quot;size26&quot;&gt;Slice 동작 원리&lt;/h2&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;슬라이스는 내장 타입으로 내부 구현이 감춰져 있지만, &lt;code&gt;SliceHeader&lt;/code&gt; 구조체를 사용해 내부 구현을 살펴볼 수 있다. 슬라이스 내부 정의는 다음과 같다.&lt;/p&gt;
&lt;pre class=&quot;go&quot;&gt;&lt;code&gt;type SliceHeader struct {
    Data uintptr // 실제 배열을 가리키는 포인터
    Len int      // 요소 개수 
      Cap int      // 실제 배열의 길이
}&lt;/code&gt;&lt;/pre&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;슬라이스 구조체는 배열을 가리키는 포인터와 요소 개수를 나타내는 &lt;code&gt;len&lt;/code&gt;, 전체 배열 길이를 나타내는 &lt;code&gt;cap&lt;/code&gt; 필드로 구성되어 있다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;슬라이스가 실제 배열을 가리키는 포인터를 가지고 있기 때문에 쉽게 크기가 다른 배열을 가리키도록 변경할 수 있고, 슬라이스 변수 대입 시 배열에 비해서 사용되는 메모리나 속도에 이점이 있다.&lt;/p&gt;
&lt;pre class=&quot;go&quot;&gt;&lt;code&gt;var slice := make([]int, 3)
var slice2 := make([]int, 3, 5)&lt;/code&gt;&lt;/pre&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;code&gt;slice&lt;/code&gt;는 &lt;code&gt;len&lt;/code&gt;이 3이고 &lt;code&gt;cap&lt;/code&gt; 또한 3이다. &lt;code&gt;slice2&lt;/code&gt;는 &lt;code&gt;len&lt;/code&gt;이 3이고 &lt;code&gt;cap&lt;/code&gt;은 5다. 그림을 통해 살펴보면 다음과 같다.&lt;/p&gt;
&lt;p&gt;&lt;figure class=&quot;imageblock alignCenter&quot; data-ke-mobileStyle=&quot;widthOrigin&quot; data-origin-width=&quot;962&quot; data-origin-height=&quot;201&quot;&gt;&lt;span data-url=&quot;https://blog.kakaocdn.net/dn/bjQCTt/btsKzrfqEJI/6xzq9zQE8pvdxQQDNnbC5k/img.png&quot; data-phocus=&quot;https://blog.kakaocdn.net/dn/bjQCTt/btsKzrfqEJI/6xzq9zQE8pvdxQQDNnbC5k/img.png&quot;&gt;&lt;img src=&quot;https://blog.kakaocdn.net/dn/bjQCTt/btsKzrfqEJI/6xzq9zQE8pvdxQQDNnbC5k/img.png&quot; srcset=&quot;https://img1.daumcdn.net/thumb/R1280x0/?scode=mtistory2&amp;fname=https%3A%2F%2Fblog.kakaocdn.net%2Fdn%2FbjQCTt%2FbtsKzrfqEJI%2F6xzq9zQE8pvdxQQDNnbC5k%2Fimg.png&quot; onerror=&quot;this.onerror=null; this.src='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png'; this.srcset='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png';&quot; loading=&quot;lazy&quot; width=&quot;831&quot; height=&quot;174&quot; data-origin-width=&quot;962&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; Slice와 배열의 동작 차이&lt;/h3&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;슬라이스의 내부 구현이 배열과 다르기 때문에 동작 또한 배열과 다르다. 예제를 통해 차이를 살펴보자.&lt;/p&gt;
&lt;pre class=&quot;angelscript&quot;&gt;&lt;code&gt;package main

import &quot;fmt&quot;

func changeArray(array2 [5]int) {
    array2[2] = 200
}

func changeSlice(slice2 []int) {
    slice2[2] = 200
}

func main() {
    array := [5]int{1, 2, 3, 4, 5}
    slice := []int{1, 2, 3, 4, 5}

    changeArray(array)
    changeSlice(slice)

    fmt.Println(&quot;array:&quot;, array) // array: [1, 2, 3, 4, 5]
    fmt.Println(&quot;slice:&quot;, slice) // slice: [1, 2, 200, 4, 5]
}&lt;/code&gt;&lt;/pre&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;배열의 값은 변경되지 않는 반면, 슬라이스는 값이 변경되는 것을 확인할 수 있다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;Go언어에서 모든 값의 대입은 복사로 일어난다. 함수에 인수로 전달될 때나 다른 변수에 대입할 때나 값의 이동은 복사로 일어난다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;복사는 타입의 값이 복사되는데, 포인터의 경우 포인터의 값인 메모리 주소가 복사되고 구조체가 복사될 때는 구조체의 모든 필드가 복사된다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;code&gt;changeArray()&lt;/code&gt; 함수에 인자로 들어간 &lt;code&gt;array&lt;/code&gt;는 &lt;code&gt;int&lt;/code&gt; 타입의 요소 다섯 개를 가지고 있다. 즉, &lt;code&gt;array&lt;/code&gt;의 크기는 40byte인 것을 알 수 있다. (8 x 5 = 40) &lt;code&gt;changeArray()&lt;/code&gt; 함수가 동작했을 때 &lt;code&gt;array&lt;/code&gt;의 값이 &lt;code&gt;array2&lt;/code&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;code&gt;array&lt;/code&gt;와 &lt;code&gt;array2&lt;/code&gt;는 별도의 메모리 공간을 가지는 아예 별개의 배열인 것이다. 그렇기 때문에, &lt;code&gt;array2&lt;/code&gt;의 요소값을 변경해도 &lt;code&gt;array&lt;/code&gt;의 요소값에는 아무런 영향이 없는 것이다.&lt;/p&gt;
&lt;p&gt;&lt;figure class=&quot;imageblock alignCenter&quot; data-ke-mobileStyle=&quot;widthOrigin&quot; data-origin-width=&quot;609&quot; data-origin-height=&quot;327&quot;&gt;&lt;span data-url=&quot;https://blog.kakaocdn.net/dn/dIuQcj/btsKAf6pK06/ZzBPZ5tEFW8a25qvEsRf80/img.png&quot; data-phocus=&quot;https://blog.kakaocdn.net/dn/dIuQcj/btsKAf6pK06/ZzBPZ5tEFW8a25qvEsRf80/img.png&quot;&gt;&lt;img src=&quot;https://blog.kakaocdn.net/dn/dIuQcj/btsKAf6pK06/ZzBPZ5tEFW8a25qvEsRf80/img.png&quot; srcset=&quot;https://img1.daumcdn.net/thumb/R1280x0/?scode=mtistory2&amp;fname=https%3A%2F%2Fblog.kakaocdn.net%2Fdn%2FdIuQcj%2FbtsKAf6pK06%2FZzBPZ5tEFW8a25qvEsRf80%2Fimg.png&quot; onerror=&quot;this.onerror=null; this.src='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png'; this.srcset='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png';&quot; loading=&quot;lazy&quot; width=&quot;386&quot; height=&quot;207&quot; data-origin-width=&quot;609&quot; data-origin-height=&quot;327&quot;/&gt;&lt;/span&gt;&lt;/figure&gt;
&lt;/p&gt;
&lt;h3 data-ke-size=&quot;size23&quot;&gt; &lt;code&gt;append()&lt;/code&gt;를 사용했을 때 발생할 수 있는 문제 (1)&lt;/h3&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;code&gt;append()&lt;/code&gt; 함수가 호출되면 먼저 슬라이스에 값을 추가할 수 있는 빈 공간이 있는지 확인한다. 남은 빈 공간은 &lt;code&gt;cap&lt;/code&gt;에서 &lt;code&gt;len&lt;/code&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;code&gt;len&lt;/code&gt; 값을 증가시킨다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;다음 코드를 수행했을 때 &lt;code&gt;slice1&lt;/code&gt;과 &lt;code&gt;slice2&lt;/code&gt;에서 발생하는 문제점과 원인에 대해서 알아보자.&lt;/p&gt;
&lt;pre class=&quot;yaml&quot;&gt;&lt;code&gt;package main

import &quot;fmt&quot;

func main() {
    slice1 := make(int[], 3, 5)

    slice2 := append(slice1, 4, 5)
    fmt.Println(&quot;slice1:&quot;, slice1, len(slice1), cap(slice1)) 
    // slice1: [0, 0, 0], 3, 5 
    fmt.Println(&quot;slice2:&quot;, slice2, len(slice2), cap(slice2))
    // slice2: [0, 0, 0, 4, 5], 5, 5

    slice1[1] = 100

    fmt.Println(&quot;slice1:&quot;, slice1, len(slice1), cap(slice1)) 
    // slice1: [0, 100, 0], 3, 5 
    fmt.Println(&quot;slice2:&quot;, slice2, len(slice2), cap(slice2))
    // slice2: [0, 100, 0, 4, 5], 5, 5

    slice1 := append(slice1, 500)

    fmt.Println(&quot;slice1:&quot;, slice1, len(slice1), cap(slice1)) 
    // slice1: [0, 100, 0, 500], 4, 5 
    fmt.Println(&quot;slice2:&quot;, slice2, len(slice2), cap(slice2))
    // slice2: [0, 100, 0, 500, 5], 5, 5
}&lt;/code&gt;&lt;/pre&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;출력된 결과를 보면 알 수 있다시피 &lt;code&gt;slice1&lt;/code&gt;에 값을 변경, 추가했을 때 &lt;code&gt;slice2&lt;/code&gt;에도 해당 변화가 영향을 미치는 것을 확인할 수 있다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;이는 두 슬라이스 모두 동일한 배열의 주소를 포인터로 가지고 있기 때문이다. 즉, 다음의 그림과 같은 상황이 발생한다.&lt;/p&gt;
&lt;p&gt;&lt;figure class=&quot;imageblock alignCenter&quot; data-ke-mobileStyle=&quot;widthOrigin&quot; data-origin-width=&quot;1276&quot; data-origin-height=&quot;512&quot;&gt;&lt;span data-url=&quot;https://blog.kakaocdn.net/dn/en6ZbX/btsKA8k8eGu/e9BkrmdTqvggiaaHG4EMs0/img.png&quot; data-phocus=&quot;https://blog.kakaocdn.net/dn/en6ZbX/btsKA8k8eGu/e9BkrmdTqvggiaaHG4EMs0/img.png&quot;&gt;&lt;img src=&quot;https://blog.kakaocdn.net/dn/en6ZbX/btsKA8k8eGu/e9BkrmdTqvggiaaHG4EMs0/img.png&quot; srcset=&quot;https://img1.daumcdn.net/thumb/R1280x0/?scode=mtistory2&amp;fname=https%3A%2F%2Fblog.kakaocdn.net%2Fdn%2Fen6ZbX%2FbtsKA8k8eGu%2Fe9BkrmdTqvggiaaHG4EMs0%2Fimg.png&quot; onerror=&quot;this.onerror=null; this.src='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png'; this.srcset='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png';&quot; loading=&quot;lazy&quot; width=&quot;820&quot; height=&quot;329&quot; data-origin-width=&quot;1276&quot; data-origin-height=&quot;512&quot;/&gt;&lt;/span&gt;&lt;/figure&gt;
&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;슬라이스가 배열의 주소를 포인터로 가지고 있고, &lt;code&gt;append()&lt;/code&gt; 사용 시 해당 주소값을 가지고 있는 포인터가 복사된다는 것을 알고있지 못한다면 이렇듯 예기치 못한 문제에 마주칠 수 있다.&lt;/p&gt;
&lt;h3 data-ke-size=&quot;size23&quot;&gt; &lt;code&gt;append()&lt;/code&gt;를 사용했을 때 발생할 수 있는 문제 (2)&lt;/h3&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;code&gt;append()&lt;/code&gt; 함수가 호출되면 빈 공간이 있는지 확인한 후 빈 공간이 충분하지 않다면 통상적으로 기존 배열의 2배 크기의 새로운 배열을 마련한다. 그리고 새로운 배열에 기존 배열의 모든 요소를 복사한 뒤, 맨 뒤에 새 값을 추가한다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;이 과정에서 &lt;code&gt;cap&lt;/code&gt;은 새로운 배열의 길이가 되고, &lt;code&gt;len&lt;/code&gt;은 기존 길이에 추가한 개수만큼 더한 값이 되며, 포인터는 새로운 배열을 가리키는 슬라이스 구조체를 반환한다.&lt;/p&gt;
&lt;pre class=&quot;go&quot; data-ke-language=&quot;go&quot;&gt;&lt;code&gt;package main

import &quot;fmt&quot;

func main() {
    slice1 := []int{1, 2, 3}
    slice2 := append(slice1, 4, 5)

    fmt.Println(&quot;slice1:&quot;, slice1, len(slice1), cap(slice1)) 
    // slice1: [1, 2, 3], 3, 3 
    fmt.Println(&quot;slice2:&quot;, slice2, len(slice2), cap(slice2))
    // slice2: [1, 2, 3, 4, 5], 5, 6

    slice[1] = 100

    fmt.Println(&quot;slice1:&quot;, slice1, len(slice1), cap(slice1)) 
    // slice1: [1, 100, 3], 3, 3 
    fmt.Println(&quot;slice2:&quot;, slice2, len(slice2), cap(slice2))
    // slice2: [1, 2, 3, 4, 5], 5, 6

    slice1 := append(slice1, 500)

    fmt.Println(&quot;slice1:&quot;, slice1, len(slice1), cap(slice1)) 
    // slice1: [1, 100, 3, 500], 4, 6 
    fmt.Println(&quot;slice2:&quot;, slice2, len(slice2), cap(slice2))
    // slice2: [1, 2, 3, 4, 5], 5, 6
}&lt;/code&gt;&lt;/pre&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;code&gt;append()&lt;/code&gt;함수로 인해 &lt;code&gt;slice1&lt;/code&gt;과 &lt;code&gt;slice2&lt;/code&gt;가 서로 다른 배열의 주소를 포인터로 가지게 됐기 때문에 &lt;code&gt;slice1&lt;/code&gt;의 변화에도 &lt;code&gt;slice2&lt;/code&gt;는 영향을 받지 않는다.&lt;/p&gt;
&lt;h2 data-ke-size=&quot;size26&quot;&gt;Slicing&lt;/h2&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;슬라이싱은 배열의 일부를 집어내는 기능을 의미한다. 슬라이싱을 사용하면 그 결과로 슬라이스를 반환한다.&lt;/p&gt;
&lt;pre class=&quot;angelscript&quot;&gt;&lt;code&gt;array[startIndex:endIndex] // 시작 인덱스 ~ 끝 인덱스 - 1&lt;/code&gt;&lt;/pre&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;슬라이싱을 통해 반환된 슬라이스는 포인터 값으로 메모리 주소를 갖기 때문에 배열의 중간을 가리킬 수도 있다.&lt;/p&gt;
&lt;pre class=&quot;pgsql&quot;&gt;&lt;code&gt;array := [5]int{1, 2, 3, 4, 5}
slice := array[1:2]

fmt.Println(&quot;array:&quot;, array)
fmt.Println(&quot;slice:&quot;, slice, len(slice), cap(slice))

array[1] = 100

fmt.Println(&quot;array:&quot;, array)
fmt.Println(&quot;slice:&quot;, slice, len(slice), cap(slice))

slice := append(slice, 500)

fmt.Println(&quot;array:&quot;, array)
fmt.Println(&quot;slice:&quot;, slice, len(slice), cap(slice))&lt;/code&gt;&lt;/pre&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;위의 코드를 그림으로 표현하면 아래의 그림과 같다. 배열을 슬라이싱을 통해 슬라이싱을 통해 가져오면 &lt;code&gt;cap&lt;/code&gt;은 배열의 총 길이에서 시작 인덱스를 뺀 4로, &lt;code&gt;len&lt;/code&gt;은 슬라이스에 포함된 요소의 수만큼 각각 할당된다.&lt;/p&gt;
&lt;p&gt;&lt;figure class=&quot;imageblock alignCenter&quot; data-ke-mobileStyle=&quot;widthOrigin&quot; data-origin-width=&quot;1028&quot; data-origin-height=&quot;548&quot;&gt;&lt;span data-url=&quot;https://blog.kakaocdn.net/dn/bdGoL8/btsKAItC60u/wTKZRozhkPrIHdfEKkHJ2k/img.png&quot; data-phocus=&quot;https://blog.kakaocdn.net/dn/bdGoL8/btsKAItC60u/wTKZRozhkPrIHdfEKkHJ2k/img.png&quot;&gt;&lt;img src=&quot;https://blog.kakaocdn.net/dn/bdGoL8/btsKAItC60u/wTKZRozhkPrIHdfEKkHJ2k/img.png&quot; srcset=&quot;https://img1.daumcdn.net/thumb/R1280x0/?scode=mtistory2&amp;fname=https%3A%2F%2Fblog.kakaocdn.net%2Fdn%2FbdGoL8%2FbtsKAItC60u%2FwTKZRozhkPrIHdfEKkHJ2k%2Fimg.png&quot; onerror=&quot;this.onerror=null; this.src='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png'; this.srcset='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png';&quot; loading=&quot;lazy&quot; width=&quot;753&quot; height=&quot;401&quot; data-origin-width=&quot;1028&quot; data-origin-height=&quot;548&quot;/&gt;&lt;/span&gt;&lt;/figure&gt;
&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;시작 인덱스와 끝 인덱스 2개만 사용할 때 &lt;code&gt;cap&lt;/code&gt;은 배열의 전체 길이에서 시작 인덱스를 뺀 값이 된다. 인덱스를 3개 사용하여 &lt;code&gt;cap&lt;/code&gt;까지 조절이 가능하다.&lt;/p&gt;
&lt;pre class=&quot;groovy&quot;&gt;&lt;code&gt;array[startIndex:endIndex:maxIndex]&lt;/code&gt;&lt;/pre&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;시작 인덱스부터 끝 인덱스 하나 전까지 추출하고 최대 인덱스까지만 배열을 사용한다는 의미다. 즉, 슬라이스의 &lt;code&gt;cap&lt;/code&gt; 값은 최대 인덱스 - 시작인덱스가 된다. 다음 예시를 살펴보자.&lt;/p&gt;
&lt;pre class=&quot;angelscript&quot;&gt;&lt;code&gt;slice1 := []int {1, 2, 3, 4, 5}
slice2 := slice1[1:3:4]&lt;/code&gt;&lt;/pre&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;인덱스 1부터 2까지 값을 추출한 &lt;code&gt;slice2&lt;/code&gt;는 &lt;code&gt;[2, 3]&lt;/code&gt;이 되고, &lt;code&gt;cap&lt;/code&gt;은 최대 인덱스가 4이므로 1이 된다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;이 경우 &lt;code&gt;slice1[1:3:4]&lt;/code&gt;의 의미는 &lt;code&gt;slice1&lt;/code&gt;이 가리키는 전체 배열 길이를 전부 사용하는 것이 아니라 인덱스 4까지만 배열을 사용하겠다는 의미가 된다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;즉, 배열의 두 번째부터 네 번째까지만 배열을 사용하게 된다.&lt;/p&gt;
&lt;h2 data-ke-size=&quot;size26&quot;&gt;Slice 복제&lt;/h2&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;슬라이스가 서로 동일한 배열을 가르키기 때문에 발생하는 문제를 해결하기 위해, 두 슬라이스가 서로 다른 배열을 가르키게 만드는 방법 또한 존재한다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;바로 슬라이스를 복제하는 것이다. &lt;code&gt;slice1&lt;/code&gt;이 가리키는 배열과 똑같은 배열을 복제한 뒤 &lt;code&gt;slice2&lt;/code&gt;가 가리키게 한다면 서로 다른 배열을 가리키기 때문에 &lt;code&gt;slice1&lt;/code&gt;의 변화에도 &lt;code&gt;slice2&lt;/code&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;code&gt;append()&lt;/code&gt; 함수를 사용하는 방법 또한 존재하지만, 내장 함수인 &lt;code&gt;copy()&lt;/code&gt;를 활용하는 방법을 알아보자.&lt;/p&gt;
&lt;pre class=&quot;swift&quot;&gt;&lt;code&gt;func copy(dst, src []Type) int&lt;/code&gt;&lt;/pre&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;code&gt;copy()&lt;/code&gt; 함수의 첫 번째 인수로 복사한 결과를 저장하는 슬라이스 변수를 넣고, 두 번째 인수로 복사 대상이 되는 슬라이스 변수를 넣는다. 반환값은 실제로 복사된 요소의 개수다.&lt;/p&gt;
&lt;h2 data-ke-size=&quot;size26&quot;&gt;Slice 요소 삭제&lt;/h2&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;슬라이스의 중간에 위치한 요소를 삭제하는 방법에 대해서도 알아보자. 아래의 그림처럼 중간의 요소를 삭제하고 다른 요소의 위치를 옮겨야 한다고 가정해보자.&lt;/p&gt;
&lt;p&gt;&lt;figure class=&quot;imageblock alignCenter&quot; data-ke-mobileStyle=&quot;widthOrigin&quot; data-origin-width=&quot;279&quot; data-origin-height=&quot;363&quot;&gt;&lt;span data-url=&quot;https://blog.kakaocdn.net/dn/tqu2M/btsKBjGQP2C/RIPvJyVz0RICrPezuTz7W0/img.png&quot; data-phocus=&quot;https://blog.kakaocdn.net/dn/tqu2M/btsKBjGQP2C/RIPvJyVz0RICrPezuTz7W0/img.png&quot;&gt;&lt;img src=&quot;https://blog.kakaocdn.net/dn/tqu2M/btsKBjGQP2C/RIPvJyVz0RICrPezuTz7W0/img.png&quot; srcset=&quot;https://img1.daumcdn.net/thumb/R1280x0/?scode=mtistory2&amp;fname=https%3A%2F%2Fblog.kakaocdn.net%2Fdn%2Ftqu2M%2FbtsKBjGQP2C%2FRIPvJyVz0RICrPezuTz7W0%2Fimg.png&quot; onerror=&quot;this.onerror=null; this.src='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png'; this.srcset='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png';&quot; loading=&quot;lazy&quot; width=&quot;229&quot; height=&quot;298&quot; data-origin-width=&quot;279&quot; data-origin-height=&quot;363&quot;/&gt;&lt;/span&gt;&lt;/figure&gt;
&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;code&gt;append()&lt;/code&gt; 함수를 사용해 위의 그림을 구현해보자.&lt;/p&gt;
&lt;pre class=&quot;lisp&quot;&gt;&lt;code&gt;slice = append(slice[:idx], slice[idx+1:])&lt;/code&gt;&lt;/pre&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;굉장히 간단하다. 한 번 각 인덱스가 어떤 역할을 하는지 분석해보자.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;code&gt;slice[:idx]&lt;/code&gt;는 슬라이스의 처음부터 끝 요소의 바로 앞에 위치한 요소까지 집어낸 슬라이스다. 지우고자 하는 인덱스의 요소는 포함되지 않는다. 위의 그림에서 지워야 하는 값은 3이기 때문에 &lt;code&gt;slice[:idx&lt;/code&gt;는 &lt;code&gt;[1, 2]&lt;/code&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;code&gt;slice[idx+1:]&lt;/code&gt;은 &lt;code&gt;idx&lt;/code&gt;의 하나 뒤의 값부터 끝까지 집어낸 슬라이스다. &lt;code&gt;slice[idx+1:]&lt;/code&gt;는 &lt;code&gt;[4, 5, 6]&lt;/code&gt;이 될 것이다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;마지막으로 &lt;code&gt;append()&lt;/code&gt;를 통해 &lt;code&gt;[1, 2]&lt;/code&gt;와 &lt;code&gt;[4, 5, 6]&lt;/code&gt;을 이어주면 &lt;code&gt;[1, 2, 4, 5, 6]&lt;/code&gt;이 된다.&lt;/p&gt;
&lt;h2 data-ke-size=&quot;size26&quot;&gt;Slice 요소 추가&lt;/h2&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;슬라이스 중간에 요소를 추가하는 방법에 대해서 알아보자. 전개되는 상황은 아래의 그림과 동일하다.&lt;/p&gt;
&lt;p&gt;&lt;figure class=&quot;imageblock alignCenter&quot; data-ke-mobileStyle=&quot;widthOrigin&quot; data-origin-width=&quot;450&quot; data-origin-height=&quot;531&quot;&gt;&lt;span data-url=&quot;https://blog.kakaocdn.net/dn/cOlXze/btsKAFRfYHv/4sCQK5l9LDZdghHXq1MohK/img.png&quot; data-phocus=&quot;https://blog.kakaocdn.net/dn/cOlXze/btsKAFRfYHv/4sCQK5l9LDZdghHXq1MohK/img.png&quot;&gt;&lt;img src=&quot;https://blog.kakaocdn.net/dn/cOlXze/btsKAFRfYHv/4sCQK5l9LDZdghHXq1MohK/img.png&quot; srcset=&quot;https://img1.daumcdn.net/thumb/R1280x0/?scode=mtistory2&amp;fname=https%3A%2F%2Fblog.kakaocdn.net%2Fdn%2FcOlXze%2FbtsKAFRfYHv%2F4sCQK5l9LDZdghHXq1MohK%2Fimg.png&quot; onerror=&quot;this.onerror=null; this.src='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png'; this.srcset='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png';&quot; loading=&quot;lazy&quot; width=&quot;411&quot; height=&quot;485&quot; data-origin-width=&quot;450&quot; data-origin-height=&quot;531&quot;/&gt;&lt;/span&gt;&lt;/figure&gt;
&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;code&gt;append()&lt;/code&gt; 함수를 사용하여 위의 그림을 구현하는 코드는 아래와 같다.&lt;/p&gt;
&lt;pre class=&quot;pgsql&quot;&gt;&lt;code&gt;slice = append(slice, 0)
copy(slice[idx+1:], slice[idx:])
slice[idx] = 100&lt;/code&gt;&lt;/pre&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;code&gt;append(slice, 0)&lt;/code&gt;으로 슬라이스 맨 뒤에 요소를 추가한다. 내장 함수 &lt;code&gt;copy()&lt;/code&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;code&gt;slice[idx + 1:]&lt;/code&gt; 즉 삽입하려는 위치 하나 다음부터 끝까지는 &lt;code&gt;slice[idx:]&lt;/code&gt; 위치부터 복사한다. 그렇게되면 한 칸씩 밀려서 복사가 이루어지게 된다. 이후 &lt;code&gt;idx&lt;/code&gt;의 위치에 100을 삽입한다.&lt;/p&gt;
&lt;h2 data-ke-size=&quot;size26&quot;&gt;Slice 정렬&lt;/h2&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;code&gt;int&lt;/code&gt; 타입의 슬라이스를 정렬하는 방법은 매우 간단하다.&lt;/p&gt;
&lt;pre class=&quot;yaml&quot;&gt;&lt;code&gt;s := []int{5, 2, 3, 4, 1}
sort.Ints(s)
fmt.Println(s) // [1, 2, 3, 4, 5]&lt;/code&gt;&lt;/pre&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;그렇다면 구조체 타입의 슬라이스를 정렬하려면 어떻게 해야 할까? 구조체 슬라이스를 정렬하기 위해서는 &lt;code&gt;Sort()&lt;/code&gt; 함수 사용에 필요한 &lt;code&gt;Len()&lt;/code&gt;, &lt;code&gt;Less()&lt;/code&gt;, &lt;code&gt;Swap()&lt;/code&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;Golang에서의 메소드 구현 방식에 대한 자세한 설명은 다음 포스팅에서 다뤄보기로 하고 우선 코드를 살펴보자.&lt;/p&gt;
&lt;pre class=&quot;go&quot;&gt;&lt;code&gt;package main

import (
    &quot;fmt&quot;
    &quot;sort&quot;
)

type Student struct {
    Name string
    Age int
}

type Students []Student 

func (s Students) Len() int { return len(s) }
func (s Students) Less(i, j int) bool { return s[i].Age &amp;lt; s[j].Age }
func (s Students) Swap(i, j int) { s[i], s[j] = s[j], s[i] }

func main {
    s := []Student {
        {&quot;화랑&quot;, 31}, {&quot;백두산&quot;, 52}, {&quot;류&quot;, 42},
        {&quot;켄&quot;, 37}, {&quot;송하나&quot;, 17}
    }

    sort.Sort(Students(s))
    fmt.Println(s)
}

// [{&quot;송하나&quot;, 17}, {&quot;화랑&quot;, 31}, {&quot;켄&quot;, 37}, {&quot;류&quot;, 42}, {&quot;백두산&quot;, 52}]&lt;/code&gt;&lt;/pre&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;code&gt;[]Student&lt;/code&gt;의 별칭 타입인 &lt;code&gt;Students&lt;/code&gt;를 만들고 &lt;code&gt;Len()&lt;/code&gt;, &lt;code&gt;Less()&lt;/code&gt;, &lt;code&gt;Swap()&lt;/code&gt; 메소드를 구현하는 것으로 &lt;code&gt;sort.Interface&lt;/code&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;code&gt;Less()&lt;/code&gt; 메소드를 통해 &lt;code&gt;Student&lt;/code&gt; 구조체의 &lt;code&gt;Age&lt;/code&gt; 필드를 통해 값을 비교하도록 선언했다.&lt;/p&gt;</description>
      <category>Dev</category>
      <category>go</category>
      <category>Golang</category>
      <category>데브옵스</category>
      <category>배열</category>
      <category>백엔드</category>
      <category>슬라이스</category>
      <category>슬라이싱</category>
      <category>알고리즘</category>
      <category>오블완</category>
      <category>쿠버네티스</category>
      <category>티스토리챌린지</category>
      <author>겨울바람_</author>
      <guid isPermaLink="true">https://breeze-winter.tistory.com/41</guid>
      <comments>https://breeze-winter.tistory.com/41#entry41comment</comments>
      <pubDate>Thu, 7 Nov 2024 22:09:40 +0900</pubDate>
    </item>
    <item>
      <title>[Traefik] K8s Traefik 설치, External-IP 설정 및 HTTPS 적용 방법</title>
      <link>https://breeze-winter.tistory.com/40</link>
      <description>&lt;p&gt;&lt;figure class=&quot;imageblock alignCenter&quot; data-ke-mobileStyle=&quot;widthOrigin&quot; data-origin-width=&quot;792&quot; data-origin-height=&quot;300&quot;&gt;&lt;span data-url=&quot;https://blog.kakaocdn.net/dn/bgpoSV/btsJIi9U6wE/fWH67wizFkGk88gcaEAXK1/img.webp&quot; data-phocus=&quot;https://blog.kakaocdn.net/dn/bgpoSV/btsJIi9U6wE/fWH67wizFkGk88gcaEAXK1/img.webp&quot;&gt;&lt;img src=&quot;https://blog.kakaocdn.net/dn/bgpoSV/btsJIi9U6wE/fWH67wizFkGk88gcaEAXK1/img.webp&quot; srcset=&quot;https://img1.daumcdn.net/thumb/R1280x0/?scode=mtistory2&amp;fname=https%3A%2F%2Fblog.kakaocdn.net%2Fdn%2FbgpoSV%2FbtsJIi9U6wE%2FfWH67wizFkGk88gcaEAXK1%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;673&quot; height=&quot;255&quot; data-origin-width=&quot;792&quot; data-origin-height=&quot;300&quot;/&gt;&lt;/span&gt;&lt;/figure&gt;
&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;사용하던 서버의 리소스들을 Docker-Compose에서 Kubernetes를 사용하는 방식으로 마이그레이션을 진행하는 과정에서 Reverse Proxy 겸, LoadBalancer로 사용했던 Traefik 또한 Kubernetes Cluster로 옮겨야 했다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;마이그레이션을 진행하는 것을 계기로 겸사겸사 Kubernetes의 Ingress Controller로 사용되는 Traefik에 외부 접속이 가능하도록 External-IP를 할당해주는 방법과 Letsencrypt의 acme를 사용하여 HTTPS 인증서 사용이 가능하도록 하는 방법에 대한 포스팅을 적어보려고 한다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;Traefik에 대한 대략적인 내용은 아래의 포스팅을 참조하자&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;a href=&quot;https://breeze-winter.tistory.com/20&quot;&gt;Traefik 이란?&lt;/a&gt;&lt;/p&gt;
&lt;h4 data-ke-size=&quot;size20&quot;&gt;사전 준비 사항&lt;/h4&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;1. 외부 접속이 가능한 공인 DNS 필요&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;2. Kubernetes Cluster가 구성되어 있는 환경 필요 (필자는 On-Premise 환경에서 진행했다.)&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;3. On-Premise 환경에 구축된 Kubernetes Cluster에 외부 접속을 하기 위한 LoadBalancer가 필요&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;4. 인증서 저장을 위한 PVC 필요&lt;/p&gt;
&lt;h4 data-ke-size=&quot;size20&quot;&gt;Metallb LoadBalancer IPAddressPool&lt;/h4&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;Metallb LoadBalancer를 사용하게 되면, Metallb가 할당할 수 있는 IP 대역폭을 지정해주어야 하는데 이때 대역폭을 외부 접근이 가능한 IP 주소가 포함되도록 설정해야 한다. 예를 들어 외부 접근이 가능한 IP 주소가 123.123.123.123이라면 Address Pool은 123.123.123.123 - 123.123.123.133 과 같이 설정되어야 한다.&lt;/p&gt;
&lt;pre class=&quot;yaml&quot;&gt;&lt;code&gt;apiVersion: metallb.io/v1beta1
kind: IPAddressPool
metadata:
  name: address-pool
  namespace: metallb
spec:
  addresses:
  - 123.123.123.123 - 123.123.123.133&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;IPAddressPool을 할당하기 이전에 아래의 명령어로 이미 사용되고 있는 IP 주소인지 아닌지 확인하는 것을 권장한다.&lt;/p&gt;
&lt;pre class=&quot;routeros&quot;&gt;&lt;code&gt;for i in {1..9}; do ping -c 1 123.123.123.12$i; done&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;위의 IP는 어디까지나 예시이기 때문에 본인 환경의 IP 주소 상황을 고려하여 각자 다르게 적용하도록 하자. 참고로 IP 주소는 &lt;code&gt;ifconfig&lt;/code&gt; 명령어를 통해 확인할 수 있다.&lt;/p&gt;
&lt;h4 data-ke-size=&quot;size20&quot;&gt;Helm을 통해 Traefik 설치&lt;/h4&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;Helm을 통해 바로 Traefik을 설치하지 않고 환경에 맞게 커스터 마이징 하기 위해 pull 명령어를 통해 Traefik의 리소스 파일들을 내려받는다.&lt;/p&gt;
&lt;pre class=&quot;angelscript&quot;&gt;&lt;code&gt;helm repo add traefik https://helm.traefik.io/traefik
helm repo update
helm pull traefik/traefik

tar xvfz traefik-28.3.0.tgz
rm -rf traefik-28.3.0.tgz
mv traefik traefik-28.3.0
cd traefik-28.3.0/

cp values.yaml my-values.yaml&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;기존의 설정 파일을 my-values.yaml로 복사하여 새로운 Helm value 파일을 생성한다. 복사한 my-values.yaml 파일을 환경에 맞도록 다음과 같이 수정한다.&lt;/p&gt;
&lt;pre class=&quot;yaml&quot;&gt;&lt;code&gt;ports:
  traefik:
    port: 9000
    expose:
      default: true
    exposedPort: 9000
    protocol: TCP

...

affinity:
 podAntiAffinity:
   requiredDuringSchedulingIgnoredDuringExecution:
     - labelSelector:
         matchExpressions:
         - key: app
           operator: In
           values:
           - traefik
       topologyKey: kubernetes.io/hostname&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;Traefik Dashboard 접근을 위해 traefik EntryPoint에 외부 접속이 허용되도록 만들고, 동일한 Node에 Traefik Pod가 추가 실행되지 않도록 Affinity 설정을 한다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;my-values.yaml 파일은 추후 HTTPS 인증서 적용을 위해 다시 살펴봐야 하기 때문에 어느 정도 눈에 익혀두는 편이 좋다. 여기까지 진행이 완료됐다면 Traefik 전용 네임스페이스를 생성하자.&lt;/p&gt;
&lt;pre class=&quot;properties&quot;&gt;&lt;code&gt;kubectl create ns traefik
kubectl ns traefik&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;네임스페이스가 제대로 변경됐는지 확인하고 Helm을 통해 변경된 변수 파일을 적용하여 Traefik을 설치하자.&lt;/p&gt;
&lt;pre class=&quot;applescript&quot;&gt;&lt;code&gt;helm install traefik -f my-values.yaml .&lt;/code&gt;&lt;/pre&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;큰 문제가 없다면 정상적으로 설치가 진행되고 Pod와 SVC 같은 리소스가 정상적으로 동작하는 것을 확인할 수 있을 것이다. SVC를 조회하여 External-IP가 Metallb에서 지정한 AddressPool에 맞게 설정되었는지 확인하자.&lt;/p&gt;
&lt;p&gt;&lt;figure class=&quot;imageblock alignCenter&quot; data-ke-mobileStyle=&quot;widthOrigin&quot; data-origin-width=&quot;1072&quot; data-origin-height=&quot;86&quot;&gt;&lt;span data-url=&quot;https://blog.kakaocdn.net/dn/K8B56/btsJIrZQk6G/FAL2sVdnRUk1LZaeGgNP10/img.png&quot; data-phocus=&quot;https://blog.kakaocdn.net/dn/K8B56/btsJIrZQk6G/FAL2sVdnRUk1LZaeGgNP10/img.png&quot;&gt;&lt;img src=&quot;https://blog.kakaocdn.net/dn/K8B56/btsJIrZQk6G/FAL2sVdnRUk1LZaeGgNP10/img.png&quot; srcset=&quot;https://img1.daumcdn.net/thumb/R1280x0/?scode=mtistory2&amp;fname=https%3A%2F%2Fblog.kakaocdn.net%2Fdn%2FK8B56%2FbtsJIrZQk6G%2FFAL2sVdnRUk1LZaeGgNP10%2Fimg.png&quot; onerror=&quot;this.onerror=null; this.src='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png'; this.srcset='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png';&quot; loading=&quot;lazy&quot; width=&quot;1072&quot; height=&quot;86&quot; data-origin-width=&quot;1072&quot; data-origin-height=&quot;86&quot;/&gt;&lt;/span&gt;&lt;/figure&gt;
&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;확인을 위해 브라우저에 http://{External-IP}:9000/dashboard/# 을 입력하여 Traefik Dashboard에 정상적으로 접근이 가능한지 확인해보자. 정상적으로 리소스가 실행되고 있다면 아래의 사진과 같은 Traefik Dashboard에 접근할 수 있다.&lt;/p&gt;
&lt;p&gt;&lt;figure class=&quot;imageblock alignCenter&quot; data-ke-mobileStyle=&quot;widthOrigin&quot; data-origin-width=&quot;1901&quot; data-origin-height=&quot;863&quot;&gt;&lt;span data-url=&quot;https://blog.kakaocdn.net/dn/bHTP4z/btsJIsxKaVy/Kd2iefdYWM8G06zT91RZP0/img.png&quot; data-phocus=&quot;https://blog.kakaocdn.net/dn/bHTP4z/btsJIsxKaVy/Kd2iefdYWM8G06zT91RZP0/img.png&quot;&gt;&lt;img src=&quot;https://blog.kakaocdn.net/dn/bHTP4z/btsJIsxKaVy/Kd2iefdYWM8G06zT91RZP0/img.png&quot; srcset=&quot;https://img1.daumcdn.net/thumb/R1280x0/?scode=mtistory2&amp;fname=https%3A%2F%2Fblog.kakaocdn.net%2Fdn%2FbHTP4z%2FbtsJIsxKaVy%2FKd2iefdYWM8G06zT91RZP0%2Fimg.png&quot; onerror=&quot;this.onerror=null; this.src='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png'; this.srcset='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png';&quot; loading=&quot;lazy&quot; width=&quot;1901&quot; height=&quot;863&quot; data-origin-width=&quot;1901&quot; data-origin-height=&quot;863&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;여기까지 됐다면, Traefik 설치는 정상적으로 완료된 것이다.&amp;nbsp;&lt;/p&gt;
&lt;h4 data-ke-size=&quot;size20&quot;&gt;IngressRoute 설정&lt;/h4&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;Traefik은 EntryPoint로 들어온 요청을 Router로 분기하여 각 SVC에 전달한다. 요청을 분기하기 위한 IngressRouter를 작성해보자.&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p&gt;&lt;figure class=&quot;imageblock alignCenter&quot; data-ke-mobileStyle=&quot;widthOrigin&quot; data-origin-width=&quot;3165&quot; data-origin-height=&quot;1515&quot;&gt;&lt;span data-url=&quot;https://blog.kakaocdn.net/dn/mrt1n/btsJG5jqyB3/Crbe4inXbykdbre03QKji0/img.png&quot; data-phocus=&quot;https://blog.kakaocdn.net/dn/mrt1n/btsJG5jqyB3/Crbe4inXbykdbre03QKji0/img.png&quot;&gt;&lt;img src=&quot;https://blog.kakaocdn.net/dn/mrt1n/btsJG5jqyB3/Crbe4inXbykdbre03QKji0/img.png&quot; srcset=&quot;https://img1.daumcdn.net/thumb/R1280x0/?scode=mtistory2&amp;fname=https%3A%2F%2Fblog.kakaocdn.net%2Fdn%2Fmrt1n%2FbtsJG5jqyB3%2FCrbe4inXbykdbre03QKji0%2Fimg.png&quot; onerror=&quot;this.onerror=null; this.src='//t1.daumcdn.net/tistory_admin/static/images/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;370&quot; data-origin-width=&quot;3165&quot; data-origin-height=&quot;1515&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;우선 요청이 들어오는 것을 확인할 수 있도록 Pod를 생성해보자.&amp;nbsp;&lt;/p&gt;
&lt;pre id=&quot;code_1726981740964&quot; class=&quot;bash&quot; data-ke-language=&quot;bash&quot; data-ke-type=&quot;codeblock&quot;&gt;&lt;code&gt;# coffee-svc-deploy.yaml

apiVersion: apps/v1
kind: Deployment
metadata:
  name: coffee
  namespace: default
spec:
  replicas: 2
  selector:
    matchLabels:
      app: coffee
  template:
    metadata:
      labels:
        app: coffee
    spec:
      containers:
      - name: coffee
        image: nginxdemos/nginx-hello:plain-text
        ports:
        - containerPort: 8080
---
apiVersion: v1
kind: Service
metadata:
  name: coffee-svc
  namespace: default
spec:
  ports:
  - port: 80
    targetPort: 8080
    protocol: TCP
    name: http
  selector:
    app: coffee&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;coffee라는 이름의 nginx-hello 이미지를 사용한 컨테이너를 배포하는 yaml 파일이다. nginx-hello 는 서버의 IP 주소와 이름 등을 화면에 출력하는 간단한 서비스다.&amp;nbsp;&lt;/p&gt;
&lt;pre id=&quot;code_1726981911850&quot; class=&quot;bash&quot; style=&quot;background-color: #f8f8f8; color: #383a42; text-align: start;&quot; data-ke-type=&quot;codeblock&quot; data-ke-language=&quot;bash&quot;&gt;&lt;code&gt;kubectl apply -f coffee-svc-deploy.yaml&lt;/code&gt;&lt;/pre&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;위의 명령어를 통해 실행시키면 Pod 2개와 SVC, Delpoyment가 동작하는 것을 확인할 수 있다. 이제 coffee의 SVC와 Traefik의 EntryPoint를 연결시켜야 한다. SVC와 Traefik EntryPoint의 연결을 담당하는 것이 위에서 설명했던 IngressRoute다.&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;IngressRoute에 분기 조건을 적용하면 해당 조건에 맞게 Traefik이 트래픽을 라우팅하여 알맞은 SVC로 전달한다. Traefik은 사용자가 편리하게 해당 기능을 구현할 수 있도록 자체적으로 IngressRoute라는 CRD를 제공한다.&amp;nbsp;&lt;/p&gt;
&lt;pre id=&quot;code_1726982183097&quot; class=&quot;bash&quot; style=&quot;background-color: #f8f8f8; color: #383a42; text-align: start;&quot; data-ke-type=&quot;codeblock&quot; data-ke-language=&quot;bash&quot;&gt;&lt;code&gt;# coffee-crd-ingressroute.yaml

apiVersion: traefik.io/v1alpha1
kind: IngressRoute
metadata:
  name: coffee-ingressroutetls
  namespace: default
spec:
  entryPoints:
  - web
  routes:
  - match: Host(`your.domain.example`)
    kind: Rule
    services:
    - name: coffee-svc
      port: 80&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;web EntryPoint에 전달되는 트래픽 중 your.domain.example 이라는 호스트를 가진 트래픽만 coffee-svc에 전달한다는 내용이다. web의 경우 my-values.yaml을 살펴보면 기본적으로 지정되어 있는 EntryPoint 중 하나이다. 이외에도 websecure, traefik 등이 있다.&lt;/p&gt;
&lt;pre id=&quot;code_1726982484585&quot; class=&quot;bash&quot; data-ke-language=&quot;bash&quot; data-ke-type=&quot;codeblock&quot;&gt;&lt;code&gt;kubectl apply -f coffee-crd-ingressroute.yaml&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;위의 명령어로 IngressRoute 리소스를 생성하자. 공인 도메인을 사용 중이라면 match.Host에 설정한 도메인으로 접속할 수 있다. 아직 HTTPS 적용이 되지 않아 안전한지 않은 사이트라고 표시되겠지만 접속은 가능하다.&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;360&quot; data-origin-height=&quot;96&quot;&gt;&lt;span data-url=&quot;https://blog.kakaocdn.net/dn/b0CB7L/btsJHPf5eDv/mUiEBUGPREgHixfHgr4l7k/img.png&quot; data-phocus=&quot;https://blog.kakaocdn.net/dn/b0CB7L/btsJHPf5eDv/mUiEBUGPREgHixfHgr4l7k/img.png&quot;&gt;&lt;img src=&quot;https://blog.kakaocdn.net/dn/b0CB7L/btsJHPf5eDv/mUiEBUGPREgHixfHgr4l7k/img.png&quot; srcset=&quot;https://img1.daumcdn.net/thumb/R1280x0/?scode=mtistory2&amp;fname=https%3A%2F%2Fblog.kakaocdn.net%2Fdn%2Fb0CB7L%2FbtsJHPf5eDv%2FmUiEBUGPREgHixfHgr4l7k%2Fimg.png&quot; onerror=&quot;this.onerror=null; this.src='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png'; this.srcset='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png';&quot; loading=&quot;lazy&quot; width=&quot;360&quot; height=&quot;96&quot; data-origin-width=&quot;360&quot; data-origin-height=&quot;96&quot;/&gt;&lt;/span&gt;&lt;/figure&gt;
&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;만약 공인 도메인이 없다면, /etc/hosts 파일에 Traefik SVC의 External-IP와 match.Host에 설정한 도메인을 입력해두면 브라우저를 통해 접속이 가능하지만 외부 네트워크에서의 접속은 불가능하다.&lt;/p&gt;
&lt;h4 data-ke-size=&quot;size20&quot;&gt;Let'sEncrypt ACME를 통해 HTTPS 인증서 등록하기&lt;/h4&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;우리가 만든 사이트를 안전하게 만들기 위해 Let'sEncrypt를 통해 인증서를 발급받아 Traefik에 적용시켜보자. Traefik 인그레스를 사용할 경우 사용자는 인증서를 다수의 웹서버에 등록할 필요 없이 단일 Traefik 설정 파일을 통해 관리할 수 있다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;우선 Let'sEncrypt 는 CA로 ACME 프로토콜을 사용하여 x509 인증서를 자동으로 발급받는 것이 가능하도록 해준다. 즉, Let'sEncrypt 는 유효한 인증서를 무료로 사용자에게 제공해주며 ACME 프로토콜을 통해 자동으로 인증서를 발급받도록 한다.&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;Traefik은 Let'sEncrypt를 공식적으로 지원하기 때문에 굉장히 간편하게 사용이 가능하다. 우선 인증서를 저장하기 위해 PVC를 생성하도록 하자. 필자는 OpenEBS를 사용했다.&lt;/p&gt;
&lt;pre id=&quot;code_1726983336143&quot; class=&quot;bash&quot; data-ke-language=&quot;bash&quot; data-ke-type=&quot;codeblock&quot;&gt;&lt;code&gt;apiVersion: v1
kind: PersistentVolumeClaim
metadata:
  name: traefik-acme-pvc
  namespace: traefik
spec:
  accessModes:
  - ReadWriteOnce  
  resources:
    requests:
      storage: 1Gi
  storageClassName: &quot;openebs-hostpath&quot;&lt;/code&gt;&lt;/pre&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;Storage 할당을 꽤 과하게 한 것 같지만 우선 무시하고 진행하도록 하자.&amp;nbsp;&lt;/p&gt;
&lt;pre id=&quot;code_1726983403801&quot; class=&quot;bash&quot; data-ke-language=&quot;bash&quot; data-ke-type=&quot;codeblock&quot;&gt;&lt;code&gt;k apply -f traefik-acme-pvc.yaml&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;PVC가 정상적으로 생성됐다면, 이전에 커스터 마이징 했던 my-values.yaml 파일을 살펴보도록 하자. 볼륨 마운트를 위해 persistence 부분을 찾아 아래와 같이 수정해보자.&lt;/p&gt;
&lt;pre id=&quot;code_1726983519797&quot; class=&quot;bash&quot; data-ke-language=&quot;bash&quot; data-ke-type=&quot;codeblock&quot;&gt;&lt;code&gt;persistence:
  # -- Enable persistence using Persistent Volume Claims
  # ref: http://kubernetes.io/docs/user-guide/persistent-volumes/
  # It can be used to store TLS certificates, see `storage` in certResolvers
  enabled: true
  name: data
  #  existingClaim: &quot;&quot;
  accessMode: ReadWriteOnce
  size: 1Gi
  storageClass: openebs-hostpath
  # volumeName: &quot;&quot;
  path: /data/letsencrypt
  annotations: {}
  existingClaim: traefik-acme-pvc
  # -- Only mount a subpath of the Volume into the pod
  # subPath: &quot;&quot;&lt;/code&gt;&lt;/pre&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;그리고 인증서 적용을 위해 certResolvers, additionalArguments 부분을 수정해줘야 한다.&amp;nbsp;&lt;/p&gt;
&lt;pre id=&quot;code_1726983589680&quot; class=&quot;bash&quot; data-ke-language=&quot;bash&quot; data-ke-type=&quot;codeblock&quot;&gt;&lt;code&gt;# Ref: https://doc.traefik.io/traefik/https/acme/#certificate-resolvers
# See EXAMPLES.md for more details.
certResolvers:
  myresolver:
  acme:
    email: your@email.com
    storage: /data/letsencrypt/acme.json 
    httpChallenge:
      entryPoint: web

...

additionalArguments:
  - &quot;--certificatesresolvers.myresolver.acme.email=your@email.com&quot;
  - &quot;--certificatesresolvers.myresolver.acme.storage=/data/letsencrypt/acme.json&quot;
  - &quot;--certificatesresolvers.myresolver.acme.httpchallenge.entrypoint=web&quot;&lt;/code&gt;&lt;/pre&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;위처럼 수정이 완료된 my-values.yaml 파일을 저장하고 해당 내용을 다시 적용시키자. 특별한 사유가 없다면 안전하게 기존의 리소스들을 삭제하고 다시 설치하는 것을 추천한다.&lt;/p&gt;
&lt;pre id=&quot;code_1726983747386&quot; class=&quot;bash&quot; data-ke-language=&quot;bash&quot; data-ke-type=&quot;codeblock&quot;&gt;&lt;code&gt;helm uninstall traefik
helm install traefik -f my-values.yaml .&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;Traefik 리소스들이 재시작 되었다면, 이전 작성해두었던 IngressRoute 파일을 수정해줘야 한다.&amp;nbsp;&lt;/p&gt;
&lt;pre id=&quot;code_1726983806763&quot; class=&quot;bash&quot; style=&quot;background-color: #f8f8f8; color: #383a42; text-align: start;&quot; data-ke-language=&quot;bash&quot; data-ke-type=&quot;codeblock&quot;&gt;&lt;code&gt;apiVersion: traefik.io/v1alpha1
kind: IngressRoute
metadata:
  name: coffee-ingressroutetls
  namespace: default
spec:
  entryPoints:
  - websecure
  routes:
  - match: Host(`your.domain.com`)
    kind: Rule
    services:
    - name: coffee-svc
      port: 80
  tls:
    certResolver: myresolver&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;web EntryPoint를 websecure로 변경하고 tls.certResolver를 추가했다. 이제 변경사항이 적용되도록 IngressRoute도 다시 실행시키자.&amp;nbsp;&lt;/p&gt;
&lt;pre id=&quot;code_1726983930608&quot; class=&quot;bash&quot; style=&quot;background-color: #f8f8f8; color: #383a42; text-align: start;&quot; data-ke-type=&quot;codeblock&quot; data-ke-language=&quot;bash&quot;&gt;&lt;code&gt;kubectl apply -f coffee-crd-ingressroute.yaml&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;여기까지 됐다면 이전처럼 안전하지 않은 사이트라는 경고 없이 HTTPS로 접속이 가능해진다.&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;485&quot; data-origin-height=&quot;212&quot;&gt;&lt;span data-url=&quot;https://blog.kakaocdn.net/dn/EKsB9/btsJHWzkHPD/ZBEUWJmUrB4ogVbQrfnD20/img.png&quot; data-phocus=&quot;https://blog.kakaocdn.net/dn/EKsB9/btsJHWzkHPD/ZBEUWJmUrB4ogVbQrfnD20/img.png&quot;&gt;&lt;img src=&quot;https://blog.kakaocdn.net/dn/EKsB9/btsJHWzkHPD/ZBEUWJmUrB4ogVbQrfnD20/img.png&quot; srcset=&quot;https://img1.daumcdn.net/thumb/R1280x0/?scode=mtistory2&amp;fname=https%3A%2F%2Fblog.kakaocdn.net%2Fdn%2FEKsB9%2FbtsJHWzkHPD%2FZBEUWJmUrB4ogVbQrfnD20%2Fimg.png&quot; onerror=&quot;this.onerror=null; this.src='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png'; this.srcset='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png';&quot; loading=&quot;lazy&quot; width=&quot;480&quot; height=&quot;210&quot; data-origin-width=&quot;485&quot; data-origin-height=&quot;212&quot;/&gt;&lt;/span&gt;&lt;/figure&gt;
&lt;/p&gt;</description>
      <category>Infra</category>
      <category>Acme</category>
      <category>https</category>
      <category>ingress</category>
      <category>ingressroute</category>
      <category>k8s</category>
      <category>kubernetes</category>
      <category>LetsEncrypt</category>
      <category>traefik</category>
      <category>외부접속</category>
      <category>쿠버네티스</category>
      <author>겨울바람_</author>
      <guid isPermaLink="true">https://breeze-winter.tistory.com/40</guid>
      <comments>https://breeze-winter.tistory.com/40#entry40comment</comments>
      <pubDate>Sun, 22 Sep 2024 14:48:47 +0900</pubDate>
    </item>
    <item>
      <title>jdeps와 jlink를 활용한 Java 기반 컨테이너 경량화</title>
      <link>https://breeze-winter.tistory.com/39</link>
      <description>&lt;p&gt;&lt;figure class=&quot;imageblock alignCenter&quot; data-ke-mobileStyle=&quot;widthOrigin&quot; data-origin-width=&quot;1560&quot; data-origin-height=&quot;760&quot;&gt;&lt;span data-url=&quot;https://blog.kakaocdn.net/dn/bi7PT9/dJMcaa5EZzB/7pohlbJupMbej1Qg0jCtck/img.png&quot; data-phocus=&quot;https://blog.kakaocdn.net/dn/bi7PT9/dJMcaa5EZzB/7pohlbJupMbej1Qg0jCtck/img.png&quot;&gt;&lt;img src=&quot;https://blog.kakaocdn.net/dn/bi7PT9/dJMcaa5EZzB/7pohlbJupMbej1Qg0jCtck/img.png&quot; srcset=&quot;https://img1.daumcdn.net/thumb/R1280x0/?scode=mtistory2&amp;fname=https%3A%2F%2Fblog.kakaocdn.net%2Fdn%2Fbi7PT9%2FdJMcaa5EZzB%2F7pohlbJupMbej1Qg0jCtck%2Fimg.png&quot; onerror=&quot;this.onerror=null; this.src='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png'; this.srcset='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png';&quot; loading=&quot;lazy&quot; width=&quot;515&quot; height=&quot;251&quot; data-origin-width=&quot;1560&quot; data-origin-height=&quot;760&quot;/&gt;&lt;/span&gt;&lt;/figure&gt;
&lt;/p&gt;
&lt;h3 data-ke-size=&quot;size23&quot;&gt;Overview&lt;/h3&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;컨테이너로 배포되는 애플리케이션은 컨테이너 이미지의 크기가 작을수록 빠르게 실행하고 확장할 수 있으며 이미지 보관 및 전송에 드는 비용이 절감된다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;Java 기반의 애플리케이션은 JVM이 함께 배포되어야 하기 때문에 Go 언어와 같은 바이너리 형태로 배포되는 애플리케이션에 비해 컨테이너 이미지의 크기가 비대하다.&lt;/p&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;code&gt;jdeps&lt;/code&gt;와 &lt;code&gt;jlink&lt;/code&gt;가 각각 어떤 명령어인지 알아보고 해당 명령어를 통해 Java 기반의 애플리케이션 컨테이너를 경량화 하는 방법에 대해 알아보고자 한다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;본 포스팅은 AWS 기술 블로그와 일본 개발자분의 포스팅을 토대로 작성되었다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;a href=&quot;https://qiita.com/yTakada-gxp/items/f681d28f31e999e5dfae&quot;&gt;https://qiita.com/yTakada-gxp/items/f681d28f31e999e5dfae&lt;/a&gt;&lt;br /&gt;&lt;a href=&quot;https://aws.amazon.com/ko/blogs/tech/amazon-corretto-base-container-diet/&quot;&gt;https://aws.amazon.com/ko/blogs/tech/amazon-corretto-base-container-diet/&lt;/a&gt;&lt;/p&gt;
&lt;h3 data-ke-size=&quot;size23&quot;&gt;jdeps&lt;/h3&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;a href=&quot;https://docs.oracle.com/en/java/javase/11/tools/jdeps.html#GUID-A543FEBE-908A-49BF-996C-39499367ADB4&quot;&gt;https://docs.oracle.com/en/java/javase/11/tools/jdeps.html#GUID-A543FEBE-908A-49BF-996C-39499367ADB4&lt;/a&gt;&lt;br /&gt;&lt;code&gt;jdeps&lt;/code&gt; 명령어는 .class 파일의 패키지 혹은 클래스 레벨의 의존성을 보여준다. 모듈화가 적용된 &lt;code&gt;jdk9&lt;/code&gt; 버전 이후에만 동작하기 때문에 버전 지정시 주의해야 한다. &lt;code&gt;jdeps&lt;/code&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;code&gt;--ignore-missing-deps&lt;/code&gt; : 결락되어 있는 의존성을 무시한다. 즉, 의존성을 알 수 없는 모듈은 제외&lt;br /&gt;&lt;code&gt;--print-module-deps&lt;/code&gt; : 모듈 의존성을 콤마로 구분된 리스트 형태로 출력한다.&lt;br /&gt;&lt;code&gt;--class-path &amp;lt;path&amp;gt;&lt;/code&gt; : 클래스 파일의 위치를 지정한다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;위에 설명한 옵션들을 사용해서 의존성이 존재하는 모듈을 추출하는 코드는 다음과 같다.&lt;/p&gt;
&lt;pre class=&quot;haml&quot;&gt;&lt;code&gt;$ jdeps \
    --ignore-missing-deps \
    --print-module-deps \
    -q \
    -R \ ##모든 런타임의 종속성을 재귀적으로 탐색한다
    --multi-release 17 \ ##의존성을 분석할 버전을 지정한다. 
    --class-path=&quot;./&amp;lt;app_name&amp;gt;/BOOT-INF/lib/*&quot; \
    --module-path=&quot;./&amp;lt;app_name&amp;gt;/BOOT-INF/lib/*&quot; \
    build/libs/&amp;lt;jar_file_name&amp;gt;.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;SpringBoot를 사용하는 경우 &lt;code&gt;jar&lt;/code&gt;파일 내부에 &lt;code&gt;/BOOT-INF/lib/&lt;/code&gt;라는 디렉토리에 의존 라이브러리를 보유하고, 실행될 때 읽어오는 구조를 가지고 있다. 해당 &lt;code&gt;jar&lt;/code&gt;을 &lt;code&gt;jdeps&lt;/code&gt;의 클래스패스에 지정할 필요가 있다.&lt;/p&gt;
&lt;h3 data-ke-size=&quot;size23&quot;&gt;jlink&lt;/h3&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;a href=&quot;https://docs.oracle.com/en/java/javase/11/tools/jlink.html&quot;&gt;https://docs.oracle.com/en/java/javase/11/tools/jlink.html&lt;/a&gt;&lt;br /&gt;&lt;code&gt;jlink&lt;/code&gt; 명령어를 통해 모듈과 모듈의 의존성을 커스텀 런타임 이미지에 결집하고 최적화 할 수 있다. &lt;code&gt;jlink&lt;/code&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;code&gt;--add-modules [...]&lt;/code&gt; : 모듈과 모드를 기본 루트 모듈에 추가한다. 기본 루트 모듈은 비어있다.&lt;br /&gt;&lt;code&gt;--strip-debug&lt;/code&gt; : 출력에서 디버그 정보를 제거한다.&lt;br /&gt;&lt;code&gt;--no-man-page&lt;/code&gt; : &lt;code&gt;man pages&lt;/code&gt;를 제거한다.&lt;br /&gt;&lt;code&gt;--no-header-files&lt;/code&gt; : &lt;code&gt;header files&lt;/code&gt;를 제거한다.&lt;br /&gt;&lt;code&gt;--compress=2&lt;/code&gt; : 리소스의 압축을 가능하게 한다.&lt;br /&gt;&amp;nbsp; &amp;nbsp; &lt;code&gt;0&lt;/code&gt; : 압축 불가&lt;br /&gt;&amp;nbsp; &amp;nbsp; &lt;code&gt;1&lt;/code&gt; : 상수 문자열 공유&lt;br /&gt;&amp;nbsp; &amp;nbsp; &lt;code&gt;2&lt;/code&gt; : ZIP&lt;br /&gt;&lt;code&gt;--output path&lt;/code&gt; : 런타임 이미지를 생성할 위치를 지정한다.&lt;/p&gt;
&lt;pre class=&quot;livescript&quot;&gt;&lt;code&gt;$ jlink \ --verbose \. ##상세한 추적을 활성화 하여 로깅합니다.
    --add-modules [] \ ## jdeps 명령어를 통해 확인한 의존성을 입력한다.
    --strip-debug \
    --no-man-pages \  
    --no-header-files \ 
    --compress=2 \ 
    --output &amp;lt;path&amp;gt;&lt;/code&gt;&lt;/pre&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;code&gt;jdeps&lt;/code&gt;와 &lt;code&gt;jlink&lt;/code&gt;를 활용하여 만들어진 &lt;code&gt;jre&lt;/code&gt; 파일은 해당 자바 애플리케이션을 실행하기 위한 최소 모듈만을 포함하고 있으며 최소한의 CLI만 포함된 상태로 생성된다.&lt;/p&gt;
&lt;h3 data-ke-size=&quot;size23&quot;&gt;Write Dockerfile&lt;/h3&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;위에서 작성한 &lt;code&gt;jdeps&lt;/code&gt;와 &lt;code&gt;jlink&lt;/code&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;해당 Dockerfile은 애플리케이션이 이미 &lt;code&gt;jar&lt;/code&gt;파일로 빌드된 상태임을 가정하고 작성되었다. 또한, 클라우드 플랫폼 중립적으로 작성하기 위해 &lt;code&gt;amazon-correctto&lt;/code&gt;가 아닌 &lt;code&gt;eclipse-temurin&lt;/code&gt;을 기반으로 작성했다.&lt;/p&gt;
&lt;pre class=&quot;routeros&quot;&gt;&lt;code&gt;# Stage 1
FROM eclipse-temurin:&amp;lt;version&amp;gt;-alpine as deps

COPY ./&amp;lt;jar_file_name&amp;gt;.jar /app/&amp;lt;jar_file_name&amp;gt;.jar

RUN mkdir /app/unpacked &amp;amp;&amp;amp; \
    cd /app/unpacked &amp;amp;&amp;amp; \
    unzip ../&amp;lt;jar_file_name&amp;gt;.jar &amp;amp;&amp;amp; \
    cd .. &amp;amp;&amp;amp; \
    $JAVA_HOME/bin/jdeps \
    --ignore-missing-deps \ 
    --print-module-deps \ 
    -q \ 
    --recursive \ 
    --multi-release &amp;lt;version&amp;gt; \ 
    --class-path=&quot;./unpacked/BOOT-INF/lib/*&quot; \ 
    --module-path=&quot;./unpacked/BOOT-INF/lib/*&quot; \ 
    ./&amp;lt;jar_file_name&amp;gt;.jar &amp;gt; /deps.info

# Stage 2
FROM eclipse-temurin:&amp;lt;version&amp;gt;-alpine as temurin-jdk

RUN apk add --no-cache binutils

COPY --from=deps /apo/deps.info /deps.info

RUN $JAVA_HOME/bin/jlink \
        --verbose \
        --add-modules $(cat /deps.info) \ 
        --strip-debug \ 
        --no-man-pages \ 
        --no-header-files \ 
        --compress=2 \ 
        --output /&amp;lt;custom_jre_name&amp;gt;

# Stage 3
FROM alpine:&amp;lt;version&amp;gt;
ENV JAVA_HOME=/jre
ENV PATH=&quot;${JAVA_HOME}/bin:${PATH}&quot;

COPY --from=temurin-jdk /&amp;lt;custom_jre_name&amp;gt; $JAVA_HOME

ARG APPLICATION_USER=appuser
RUN adduser --no-create-home -u 1000 -D $APPLICATION_USER

RUN mkdir /app &amp;amp;&amp;amp; \
    chown -R $APPLICATION_USER /app

USER 1000

COPY --chown=1000:1000 ./&amp;lt;jar_file_name&amp;gt;.jar /app/&amp;lt;jar_file_name&amp;gt;.jar
WORKDIR /app

EXPOSE 8080
ENTRYPOINT [ &quot;/jre/bin/java&quot;, &quot;-jar&quot;, &quot;/app/&amp;lt;jar_file_name&amp;gt;.jar&quot; ]&lt;/code&gt;&lt;/pre&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;code&gt;Stage 1&lt;/code&gt;은 &lt;code&gt;temurin:&amp;lt;version&amp;gt;-alpine&lt;/code&gt; 이미지를 베이스 이미지로 사용하여 &lt;code&gt;jdeps&lt;/code&gt;를 이용한 의존성 분석 및 분석 결과를 &lt;code&gt;deps.info&lt;/code&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;code&gt;Stage 2&lt;/code&gt;는 &lt;code&gt;Stage 1&lt;/code&gt;과 동일한 이미지와 생성된 의존성 목록을 바탕으로 &lt;code&gt;jlink&lt;/code&gt;를 사용해 경량화된 커스텀 &lt;code&gt;jre&lt;/code&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;code&gt;Stage 3&lt;/code&gt;는 &lt;code&gt;alpine:&amp;lt;version&amp;gt;&lt;/code&gt;을 베이스로 &lt;code&gt;Stage 2&lt;/code&gt;에서 생성된 커스텀 &lt;code&gt;jre&lt;/code&gt;를 사용하여 최종 이미지를 생성한다. 이 과정에서 보안을 위해 별도의 유저를 생성하여 권한을 부여하고 &lt;code&gt;jar&lt;/code&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;code&gt;Dockerfile&lt;/code&gt;을 &lt;code&gt;docker build -t &amp;lt;image_name&amp;gt;:&amp;lt;version&amp;gt; .&lt;/code&gt; 명령을 통해 이미지로 빌드시킨다. &lt;code&gt;Dockerfile&lt;/code&gt; 설정을 잘못 적지 않았다면 성공적으로 확연하게 줄어든 &lt;code&gt;43MB&lt;/code&gt; 용량의 이미지가 생성된 것을 확인할 수 있다.&lt;/p&gt;
&lt;p&gt;&lt;figure class=&quot;imageblock alignCenter&quot; data-ke-mobileStyle=&quot;widthOrigin&quot; data-origin-width=&quot;813&quot; data-origin-height=&quot;39&quot;&gt;&lt;span data-url=&quot;https://blog.kakaocdn.net/dn/bO0Uq2/btsJzgeA6lR/tUdy6ICCoYslaEf7Q5BiEk/img.png&quot; data-phocus=&quot;https://blog.kakaocdn.net/dn/bO0Uq2/btsJzgeA6lR/tUdy6ICCoYslaEf7Q5BiEk/img.png&quot;&gt;&lt;img src=&quot;https://blog.kakaocdn.net/dn/bO0Uq2/btsJzgeA6lR/tUdy6ICCoYslaEf7Q5BiEk/img.png&quot; srcset=&quot;https://img1.daumcdn.net/thumb/R1280x0/?scode=mtistory2&amp;fname=https%3A%2F%2Fblog.kakaocdn.net%2Fdn%2FbO0Uq2%2FbtsJzgeA6lR%2FtUdy6ICCoYslaEf7Q5BiEk%2Fimg.png&quot; onerror=&quot;this.onerror=null; this.src='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png'; this.srcset='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png';&quot; loading=&quot;lazy&quot; width=&quot;813&quot; height=&quot;39&quot; data-origin-width=&quot;813&quot; data-origin-height=&quot;39&quot;/&gt;&lt;/span&gt;&lt;/figure&gt;
&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;비교를 위해 &lt;code&gt;alpine jdk&lt;/code&gt;만을 써서 빌드한 이미지의 용량과 비교해보자. 아래는 비교를 위해 작성한 간단한 &lt;code&gt;Dockerfile&lt;/code&gt;이다.&lt;/p&gt;
&lt;pre class=&quot;dockerfile&quot;&gt;&lt;code&gt;FROM eclipse-temurin:17-alpine

CMD [&quot;./gradlew&quot;, &quot;clean&quot;, &quot;build&quot;]

VOLUME /tmp

ARG JAR_FILE=./app.jar

COPY ${JAR_FILE} app.jar

EXPOSE 8080

ENTRYPOINT [&quot;java&quot;,&quot;-jar&quot;,&quot;/app.jar&quot;]&lt;/code&gt;&lt;/pre&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;code&gt;jdeps&lt;/code&gt;와 &lt;code&gt;jlink&lt;/code&gt;를 사용하지 않았기 때문에 의존성이 최적화되지 않아 불필요한 의존성까지 모두 포함된 이미지가 생성된다.&lt;/p&gt;
&lt;p&gt;&lt;figure class=&quot;imageblock alignCenter&quot; data-ke-mobileStyle=&quot;widthOrigin&quot; data-origin-width=&quot;813&quot; data-origin-height=&quot;37&quot;&gt;&lt;span data-url=&quot;https://blog.kakaocdn.net/dn/dK3zmm/btsJz7gyn6S/qzlxzF67AdbJD8RYSTA78k/img.png&quot; data-phocus=&quot;https://blog.kakaocdn.net/dn/dK3zmm/btsJz7gyn6S/qzlxzF67AdbJD8RYSTA78k/img.png&quot;&gt;&lt;img src=&quot;https://blog.kakaocdn.net/dn/dK3zmm/btsJz7gyn6S/qzlxzF67AdbJD8RYSTA78k/img.png&quot; srcset=&quot;https://img1.daumcdn.net/thumb/R1280x0/?scode=mtistory2&amp;fname=https%3A%2F%2Fblog.kakaocdn.net%2Fdn%2FdK3zmm%2FbtsJz7gyn6S%2FqzlxzF67AdbJD8RYSTA78k%2Fimg.png&quot; onerror=&quot;this.onerror=null; this.src='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png'; this.srcset='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png';&quot; loading=&quot;lazy&quot; width=&quot;813&quot; height=&quot;37&quot; data-origin-width=&quot;813&quot; data-origin-height=&quot;37&quot;/&gt;&lt;/span&gt;&lt;/figure&gt;
&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;리사이징된 이미지와 비교했을 때 이미지의 용량이 약 80~90% 감소됐다.&lt;/p&gt;
&lt;p&gt;&lt;figure class=&quot;imageblock alignCenter&quot; data-ke-mobileStyle=&quot;widthOrigin&quot; data-origin-width=&quot;784&quot; data-origin-height=&quot;61&quot;&gt;&lt;span data-url=&quot;https://blog.kakaocdn.net/dn/wK7re/btsJAjnCSes/U3Auw0SiLKkOmcFX62SxQk/img.png&quot; data-phocus=&quot;https://blog.kakaocdn.net/dn/wK7re/btsJAjnCSes/U3Auw0SiLKkOmcFX62SxQk/img.png&quot;&gt;&lt;img src=&quot;https://blog.kakaocdn.net/dn/wK7re/btsJAjnCSes/U3Auw0SiLKkOmcFX62SxQk/img.png&quot; srcset=&quot;https://img1.daumcdn.net/thumb/R1280x0/?scode=mtistory2&amp;fname=https%3A%2F%2Fblog.kakaocdn.net%2Fdn%2FwK7re%2FbtsJAjnCSes%2FU3Auw0SiLKkOmcFX62SxQk%2Fimg.png&quot; onerror=&quot;this.onerror=null; this.src='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png'; this.srcset='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png';&quot; loading=&quot;lazy&quot; width=&quot;784&quot; height=&quot;61&quot; data-origin-width=&quot;784&quot; data-origin-height=&quot;61&quot;/&gt;&lt;/span&gt;&lt;/figure&gt;
&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;실행 또한 성공적으로 되는 것을 확인할 수 있다.&lt;/p&gt;
&lt;p&gt;&lt;figure class=&quot;imageblock alignCenter&quot; data-ke-mobileStyle=&quot;widthOrigin&quot; data-origin-width=&quot;944&quot; data-origin-height=&quot;539&quot;&gt;&lt;span data-url=&quot;https://blog.kakaocdn.net/dn/pnR3E/btsJzdIWRwq/X94xkqOcrzXgswTKx0kcp1/img.png&quot; data-phocus=&quot;https://blog.kakaocdn.net/dn/pnR3E/btsJzdIWRwq/X94xkqOcrzXgswTKx0kcp1/img.png&quot;&gt;&lt;img src=&quot;https://blog.kakaocdn.net/dn/pnR3E/btsJzdIWRwq/X94xkqOcrzXgswTKx0kcp1/img.png&quot; srcset=&quot;https://img1.daumcdn.net/thumb/R1280x0/?scode=mtistory2&amp;fname=https%3A%2F%2Fblog.kakaocdn.net%2Fdn%2FpnR3E%2FbtsJzdIWRwq%2FX94xkqOcrzXgswTKx0kcp1%2Fimg.png&quot; onerror=&quot;this.onerror=null; this.src='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png'; this.srcset='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png';&quot; loading=&quot;lazy&quot; width=&quot;944&quot; height=&quot;539&quot; data-origin-width=&quot;944&quot; data-origin-height=&quot;539&quot;/&gt;&lt;/span&gt;&lt;/figure&gt;
&lt;/p&gt;</description>
      <category>DevOps</category>
      <category>ALPINE</category>
      <category>Docker</category>
      <category>Java</category>
      <category>spring boot</category>
      <category>경량화</category>
      <category>리눅스</category>
      <category>이미지</category>
      <category>컨테이너</category>
      <author>겨울바람_</author>
      <guid isPermaLink="true">https://breeze-winter.tistory.com/39</guid>
      <comments>https://breeze-winter.tistory.com/39#entry39comment</comments>
      <pubDate>Thu, 12 Sep 2024 14:02:28 +0900</pubDate>
    </item>
    <item>
      <title>[Terraform] 테라폼 기초 (3)</title>
      <link>https://breeze-winter.tistory.com/38</link>
      <description>&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;603&quot;&gt;&lt;span data-url=&quot;https://blog.kakaocdn.net/dn/bW5ERR/btsJjqtYhTm/lkkkhdi932CyRJcPYCFzQk/img.png&quot; data-phocus=&quot;https://blog.kakaocdn.net/dn/bW5ERR/btsJjqtYhTm/lkkkhdi932CyRJcPYCFzQk/img.png&quot;&gt;&lt;img src=&quot;https://blog.kakaocdn.net/dn/bW5ERR/btsJjqtYhTm/lkkkhdi932CyRJcPYCFzQk/img.png&quot; srcset=&quot;https://img1.daumcdn.net/thumb/R1280x0/?scode=mtistory2&amp;fname=https%3A%2F%2Fblog.kakaocdn.net%2Fdn%2FbW5ERR%2FbtsJjqtYhTm%2Flkkkhdi932CyRJcPYCFzQk%2Fimg.png&quot; onerror=&quot;this.onerror=null; this.src='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png'; this.srcset='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png';&quot; loading=&quot;lazy&quot; width=&quot;1280&quot; height=&quot;603&quot; data-origin-width=&quot;1280&quot; data-origin-height=&quot;603&quot;/&gt;&lt;/span&gt;&lt;/figure&gt;
&lt;/p&gt;
&lt;h2 data-ke-size=&quot;size26&quot;&gt;Provider&lt;/h2&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;테라폼은 &lt;code&gt;terraform&lt;/code&gt; 바이너리 파일을 시작으로 로컬 환경이나 배포 서버와 같은 원격 환경에서 원하는 대상을 호출하는 방식으로 실행된다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;이때 호출되는 대상은 프로바이더가 제공하는 API를 호출해 상호작용 하게 된다. 즉, 테라폼이 대상과의 상호작용을 할 수 있도록 하는 것을 프로바이더라 한다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;각 프로바이더의 API 구현은 서로 다르지만, 테라폼의 고유 문법으로 동일한 동작을 수행하도록 구현되어 있다. 프로바이더는 플러그인 형태로 테라폼에 결합되어 대상이 되는 클라우드, SaaS, 기타 서비스 API를 사용해 동작을 수행한다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;각 프로바이더는 테라폼이 관리하는 리소스 유형과 데이터 소스를 사용할 수 있도록 연결한다. 그렇기 때문에 테라폼은 프로바이더 없이는 어떤 종류의 인프라와 서비스도 관리할 수 없다는 의미다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;대부분의 프로바이더는 대상 인프라 환경이나 서비스 환경에 대해 리소스를 관리하므로, 프로바이더를 구성할 때는 대상과의 연결과 인증에 대한 정보가 제공되어야 한다.&lt;/p&gt;
&lt;h2 data-ke-size=&quot;size26&quot;&gt;Provider Config&lt;/h2&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;프로바이더 구성에 대한 요구사항은 공식 레지스트리 사이트인 테레폼 레지스트리에 공개되어 있는 구성 방식을 참고하는 것이 좋다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;a href=&quot;https://registry.terraform.io/&quot; target=&quot;_blank&quot; rel=&quot;noopener&quot;&gt;https://registry.terraform.io/&lt;/a&gt;&lt;/p&gt;
&lt;h3 data-ke-size=&quot;size23&quot;&gt;Required_Providers&lt;/h3&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;code&gt;terraform&lt;/code&gt; 블록의 &lt;code&gt;required_provieders&lt;/code&gt; 블록 내에 &lt;code&gt;&amp;lt;로컬 이름&amp;gt; = { }&lt;/code&gt;으로 여러 개의 프로바이더를 정의할 수 있다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;로컬 이름은 테라폼 모듈 내에서 고유해야 한다. 로컬 이름과 리소스 접두사는 각각 독립적으로 선언되며, 각 프로바이더의 소스 경로가 지정되면 프로바이더의 고유 접두사가 제공된다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;만약 동일한 접두사를 사용하는 프로바이더가 선언되는 경우 로컬 이름을 달리해 관련 리소스에서 어떤 프로바이더를 사용하는지 명시적으로 지정할 수 있다.&lt;/p&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;code&gt;main.tf&lt;/code&gt;에서처럼 동일한 &lt;code&gt;http&lt;/code&gt; 이름을 사용하는 다수의 프로바이더가 있는 경우 각 프로바이더에 고유한 이름을 부여하고 리소스와 데이터 소스에 어떤 프로바이더를 사용할지 &lt;code&gt;provider&lt;/code&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;code&gt;source&lt;/code&gt;에 대해 다수의 정의는 불가능하다.&lt;/p&gt;
&lt;pre class=&quot;nix&quot;&gt;&lt;code&gt;terraform {
    required_providers {
        architect-http = {
            source = &quot;architect-team/http&quot;
            version = &quot;~&amp;gt; 3.0&quot;
        }
        http = {
            source = &quot;hashicorp/http&quot;
        }
        aws-http = {
            source = &quot;terraform-aws-modules/http&quot;
        }
    }
}

data &quot;http&quot; &quot;example&quot; {
    provider = aws-http
    url = &quot;https://checkpoint-api.hashicorp.com/v1/check/terraform&quot;
    request_headers = {
        Accept = &quot;application/json&quot;
    }
}&lt;/code&gt;&lt;/pre&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;위의 코드에서 &lt;code&gt;required_providers&lt;/code&gt; 블록 내부의 요소들을 하나씩 살펴보자. &lt;code&gt;architect-http&lt;/code&gt;, &lt;code&gt;http&lt;/code&gt;, &lt;code&gt;aws-http&lt;/code&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;code&gt;source&lt;/code&gt;의 경우 프로바이더 다운로드 경로를 지정하고, &lt;code&gt;version&lt;/code&gt;은 버전 제약을 명시한다. &lt;code&gt;version&lt;/code&gt;을 생략하는 경우 가장 최신 버전으로 선택된다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;아래의 예제 코드를 참고하자.&lt;/p&gt;
&lt;pre class=&quot;dts&quot;&gt;&lt;code&gt;terraform {
    required_providers {
        &amp;lt;프로바이더 로컬 이름&amp;gt; = {
            source = [&amp;lt;호스트 주소&amp;gt;/]&amp;lt;네임스페이스&amp;gt;/&amp;lt;유형&amp;gt;
            version = &amp;lt;버전 제약&amp;gt;
        }
    }
}&lt;/code&gt;&lt;/pre&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;code&gt;source&lt;/code&gt;를 좀 더 분석해보면, 호스트 주소는 프로바이더를 배포하는 주소로 기본값은 테라폼의 레지스트리인 &lt;code&gt;registry.terraform.io&lt;/code&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;네임스페이스는 지정된 레지스트리 내에서 구분하는 네임스페이스로, 공개된 레지스트리 및 Terraform Cloud의 비공개 레지스트리의 프로바이더를 게시하는 조직을 의미한다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;유형은 프로바이더에서 관리되는 플랫폼이나 서비스 이름으로 일반적으로 접두사와 일치하지만 일부 예외가 존재한다.&lt;/p&gt;
&lt;h3 data-ke-size=&quot;size23&quot;&gt;Multiple Provider&lt;/h3&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;동일한 프로바이더를 사용하지만 다른 조건을 갖는 경우, 사용되는 리소스마다 별도로 선언된 프로바이더를 지정해야 하는 경우가 있다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;예를 들어, AWS 프로바이더를 사용하는데 서로 다른 권한의 IAM을 갖는 Access ID 또는 리전을 지정해야 하는 경우다. 이때는 프로바이더 선언에서 &lt;code&gt;alias&lt;/code&gt;를 명시하고 사용하는 리소스와 데이터 소스에서는 &lt;code&gt;provider&lt;/code&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;code&gt;provider&lt;/code&gt; 메타인수에 지정되지 않은 경우 &lt;code&gt;alias&lt;/code&gt;가 없는 프로바이더가 기본 프로바이더로 동작한다. 아래의 예제를 통해 살펴보자.&lt;/p&gt;
&lt;pre class=&quot;routeros&quot;&gt;&lt;code&gt;provider &quot;aws&quot; {
    region = &quot;us-west-1&quot;
}

provider &quot;aws&quot; {
    alias = &quot;seoul&quot;
    region = &quot;ap-northeast-2&quot;
}

resource &quot;aws_instance&quot; &quot;app_server&quot; {
    provider = aws.seoul
    ami = &quot;ami-...&quot;
    instance_type = &quot;t2.micro&quot;
}&lt;/code&gt;&lt;/pre&gt;
&lt;h3 data-ke-size=&quot;size23&quot;&gt;AWS Provider&lt;/h3&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;AWS 리소스를 관리하는 AWS 프로바이더를 통해 리소스를 프로비저닝하기 위해서는, 가입된 계정의 액세스 키와 비밀 액세스 키가 필요하다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;발급받은 자격증명을 AWS 프로바이더에서 사용하기 위해서는 환경 변수, 파일, 테라폼 구성에 추가하는 각각의 방식을 사용해야 한다. 이번에는 테라폼 구성에 추가하는 방식을 통해 확인해보자.&lt;/p&gt;
&lt;pre class=&quot;routeros&quot;&gt;&lt;code&gt;terraform {
    required_providers {
        aws = {
            source = &quot;hashicorp/aws&quot;
            version = &quot;~&amp;gt; 4.0&quot;
        }
    }

    required_version = &quot;&amp;gt;= 1.0&quot;
}

provider &quot;aws&quot; {
    region = &quot;ap-northeast-2&quot;
    access_key = &quot;&amp;lt;my_access_key&amp;gt;&quot;
    secret_key = &quot;&amp;lt;my_secret_key&amp;gt;&quot;
}

data &quot;aws_ami&quot; &quot;amzn2&quot; {
    most_recent = true
    owners = [&quot;amazon&quot;]

    filter {
        name = &quot;owner-alias&quot;
        values = [&quot;amazon&quot;]
    }

    filter {
        name = &quot;name&quot;
        values = [&quot;amzn2-ami-hvm*&quot;]
    }
}

resource &quot;aws_instance&quot; &quot;app_server&quot; {
    ami = data.aws_ami.amzn2.id
    instance_type = &quot;t2.micro&quot;

    tags = {
        Name = &quot;ExampleAppServerInstance&quot;
    }
}&lt;/code&gt;&lt;/pre&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;위의 코드의 내용 중 몇 가지를 간단하게 분석해보자. &lt;code&gt;required_version&lt;/code&gt;은 해당 코드를 실행하기 위한 테라폼의 버전을 의미하며 해당 코드에서는 1.0버전 이상의 테라폼만이 코드를 동작시킬 수 있다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;code&gt;data.aws_ami.amzn2&lt;/code&gt;는 AWS의 AMI를 조회하기 위한 데이터 블럭이며, &lt;code&gt;most_recent = true&lt;/code&gt;는 가장 최신 버전의 AMI를 가져오도록 하며 &lt;code&gt;owners&lt;/code&gt;는 AMI의 소유자를 설정하는데 해당 코드에서는 AWS 소유의 AMI만을 필터링한다.&lt;/p&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;code&gt;filter&lt;/code&gt;는 &lt;code&gt;owner-alias&lt;/code&gt;가 amazon인 AMI를 찾는다는 의미이고, 두 번째는 &lt;code&gt;amzn2-ami-hvm*&lt;/code&gt;로 시작하는 AMI를 찾는다는 의미다.&lt;/p&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;code&gt;resource&lt;/code&gt; 블럭은 AWS 인스턴스를 생성하기 위한 블럭이다. &lt;code&gt;ami&lt;/code&gt;에서는 위의 &lt;code&gt;data&lt;/code&gt; 블럭에서 조회한 AMI의 ID를 사용하여 인스턴스를 생성한다. 이때 생성된 인스턴스에 &lt;code&gt;ExampleAppServerInstance&lt;/code&gt;라는 태그를 붙인다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;환경변수를 통해 엑세스 키와 시크릿 키를 추가하는 방법도 있다.&lt;/p&gt;
&lt;pre class=&quot;pf&quot;&gt;&lt;code&gt;# Linux / Unix
export AWS_ACCESS_KEY_ID = &amp;lt;my_access_key&amp;gt;
export AWS_SECRET_ACCESS_KEY = &amp;lt;my_secret_key&amp;gt;

# Windows
set AWS_ACCESS_KEY_ID = &amp;lt;my_access_key&amp;gt;
set AWS_SECRET_ACCESS_KEY = &amp;lt;my_secret_key&amp;gt;

# Windows Powershell
$Env:AWS_ACCESS_KEY_ID = &amp;lt;my_access_key&amp;gt;
$Env:AWS_SECRET_ACCESS_KEY = &amp;lt;my_secret_key&amp;gt;&lt;/code&gt;&lt;/pre&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;위처럼 OS에 환경변수로 자격증명을 등록하고 기존에 작성했던 코드에서 &lt;code&gt;provider.access_key&lt;/code&gt;, &lt;code&gt;provider.secret_key&lt;/code&gt;를 제외하면 된다.&lt;/p&gt;
&lt;h2 data-ke-size=&quot;size26&quot;&gt;State&lt;/h2&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;테라폼은 &lt;code&gt;Stateful&lt;/code&gt;한 애플리케이션이다. 프로비저닝 결과에 따른 &lt;code&gt;State&lt;/code&gt;를 저장하고 프로비저닝한 모든 내용을 저장된 상태로 추적한다. 로컬 실행 환경에서는 &lt;code&gt;terraform.tfstate&lt;/code&gt; 파일에 JSON 형태로 저장되고, 팀이나 조직에서의 공동 관리를 위해서는 원격 저장소에 저장해 공유하는 방식을 활용한다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;State에는 작업자가 정의한 코드와 실제 반영된 프로비저닝 결과를 저장하고, 이 정보를 토대로 이후의 리소스 생성, 수정, 삭제에 대한 동작 판단 작업을 수행한다.&lt;/p&gt;
&lt;h3 data-ke-size=&quot;size23&quot;&gt;Why State?&lt;/h3&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;테라폼은 &lt;code&gt;State&lt;/code&gt;를 사용해 대상 환경에서 어떤 리소스가 테라폼으로 관리되는 리소스인지 판별하고 결과를 기록한다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;code&gt;State&lt;/code&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;테라폼 구성으로 프로비저닝된 결과를 캐싱하는 역할을 수행한다.&lt;/li&gt;
&lt;/ul&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;code&gt;terraform plan&lt;/code&gt;을 실행하면 암묵적으로 &lt;code&gt;refresh&lt;/code&gt; 동작을 수행하면서 리소스 생성의 대상과 &lt;code&gt;State&lt;/code&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;code&gt;State&lt;/code&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;code&gt;terraform plan -refresh=false&lt;/code&gt;를 통해 대상 환경과의 동기화 과정을 생략할 수 있다.&lt;/p&gt;
&lt;h3 data-ke-size=&quot;size23&quot;&gt;State Synchronization&lt;/h3&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;테라폼 구성 파일은 기존 &lt;code&gt;State&lt;/code&gt;와 구성을 비교해 실행 계획에서 생성, 수정, 삭제 여부를 결정한다.&lt;/p&gt;
&lt;p&gt;&lt;figure class=&quot;imageblock alignCenter&quot; data-ke-mobileStyle=&quot;widthOrigin&quot; data-origin-width=&quot;800&quot; data-origin-height=&quot;365&quot;&gt;&lt;span data-url=&quot;https://blog.kakaocdn.net/dn/bneLqn/btsJh7I9ugd/ISaWFLReucYIEJFRBOpjX0/img.png&quot; data-phocus=&quot;https://blog.kakaocdn.net/dn/bneLqn/btsJh7I9ugd/ISaWFLReucYIEJFRBOpjX0/img.png&quot;&gt;&lt;img src=&quot;https://blog.kakaocdn.net/dn/bneLqn/btsJh7I9ugd/ISaWFLReucYIEJFRBOpjX0/img.png&quot; srcset=&quot;https://img1.daumcdn.net/thumb/R1280x0/?scode=mtistory2&amp;fname=https%3A%2F%2Fblog.kakaocdn.net%2Fdn%2FbneLqn%2FbtsJh7I9ugd%2FISaWFLReucYIEJFRBOpjX0%2Fimg.png&quot; onerror=&quot;this.onerror=null; this.src='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png'; this.srcset='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png';&quot; loading=&quot;lazy&quot; width=&quot;723&quot; height=&quot;330&quot; data-origin-width=&quot;800&quot; data-origin-height=&quot;365&quot;/&gt;&lt;/span&gt;&lt;/figure&gt;
&lt;/p&gt;
&lt;h3 data-ke-size=&quot;size23&quot;&gt;Workspace&lt;/h3&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;code&gt;State&lt;/code&gt;를 관리하는 논리적인 가상 공간을 워크스페이스라고 한다. 테라폼 구성 파일은 동일하지만 작업자는 서로 다른 &lt;code&gt;State&lt;/code&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;code&gt;default&lt;/code&gt;로 정의된다. 로컬 작업 환경의 워크스페이스를 관리하기 위한 CLI 명령어로 &lt;code&gt;workspace&lt;/code&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;code&gt;terraform workspace list&lt;/code&gt; 명령어를 통해 존재하는 워크스페이스의 목록을 확인할 수 있으며, 현재 사용하고 있는 워크스페이스 이름 옆에는 &lt;code&gt;*&lt;/code&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;code&gt;terraform workspace new &amp;lt;워크스페이스 이름&amp;gt;&lt;/code&gt; 명령어를 통해 &lt;code&gt;default&lt;/code&gt;가 아닌 새로운 워크스페이스를 생성하고 &lt;code&gt;terraform plan&lt;/code&gt;을 실행하면, 기존 &lt;code&gt;default&lt;/code&gt; 워크스페이스와는 다른 &lt;code&gt;State&lt;/code&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;code&gt;terraform.workspace&lt;/code&gt;를 사용해 워크스페이스 이름을 조회하여 조건을 부여하는 것도 가능하다.&lt;/p&gt;
&lt;pre class=&quot;nix&quot;&gt;&lt;code&gt;resource &quot;aws_instance&quot; &quot;web&quot; {
    count = &quot;${terraform.workspace == &quot;default&quot; ? 5 : 1}&quot;
    ami = &quot;ami-...&quot;
    instance_type = &quot;t2.micro&quot;

    tags = {
        Name = &quot;Hello World - ${terraform.workspace}&quot;
    }
}&lt;/code&gt;&lt;/pre&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;다수의 워크스페이스를 사용하면 다음과 같은 장점이 있다.&lt;/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;깃의 브랜치 전략처럼 동일한 구성에서 서로 다른 리소스 결과를 관리할 수 있다.&lt;/li&gt;
&lt;/ul&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;code&gt;State&lt;/code&gt;가 동일한 저장소(로컬 또는 백엔드)에 저장되어 &lt;code&gt;State&lt;/code&gt; 접근 권한 관리가 불가능하다.&lt;/li&gt;
&lt;li&gt;모든 환경이 동일한 리소스를 요구하지 않을 수 있으므로 테라폼 구성에 분기 처리가 다수 발생할 수 있다.&lt;/li&gt;
&lt;li&gt;프로비저닝 대상에 대한 인증 요소를 완벽히 분리하기가 어렵다.&lt;/li&gt;
&lt;/ul&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;워크스페이스 사용의 근본적인 단점은 완벽한 격리가 불가능하다는 것이다. 이를 해결하기 위해 루트 모듈을 별도로 구성하는 디렉토리 기반의 레이아웃을 사용할 수도 있다.&lt;/p&gt;</description>
      <category>DevOps</category>
      <category>AWS</category>
      <category>Cloud</category>
      <category>DevOps</category>
      <category>IAC</category>
      <category>terraform</category>
      <category>데브옵스</category>
      <category>자동화</category>
      <category>클라우드</category>
      <category>테라폼</category>
      <author>겨울바람_</author>
      <guid isPermaLink="true">https://breeze-winter.tistory.com/38</guid>
      <comments>https://breeze-winter.tistory.com/38#entry38comment</comments>
      <pubDate>Wed, 28 Aug 2024 22:31:30 +0900</pubDate>
    </item>
    <item>
      <title>[Terraform] 테라폼 기초 (2)</title>
      <link>https://breeze-winter.tistory.com/37</link>
      <description>&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;603&quot;&gt;&lt;span data-url=&quot;https://blog.kakaocdn.net/dn/L9bAr/btsI8PHC07a/DloPb17uc5lQqkGXd8VMT1/img.png&quot; data-phocus=&quot;https://blog.kakaocdn.net/dn/L9bAr/btsI8PHC07a/DloPb17uc5lQqkGXd8VMT1/img.png&quot;&gt;&lt;img src=&quot;https://blog.kakaocdn.net/dn/L9bAr/btsI8PHC07a/DloPb17uc5lQqkGXd8VMT1/img.png&quot; srcset=&quot;https://img1.daumcdn.net/thumb/R1280x0/?scode=mtistory2&amp;fname=https%3A%2F%2Fblog.kakaocdn.net%2Fdn%2FL9bAr%2FbtsI8PHC07a%2FDloPb17uc5lQqkGXd8VMT1%2Fimg.png&quot; onerror=&quot;this.onerror=null; this.src='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png'; this.srcset='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png';&quot; loading=&quot;lazy&quot; width=&quot;1280&quot; height=&quot;603&quot; data-origin-width=&quot;1280&quot; data-origin-height=&quot;603&quot;/&gt;&lt;/span&gt;&lt;/figure&gt;
&lt;/p&gt;
&lt;h2 data-ke-size=&quot;size26&quot;&gt;Terraform 조건식&lt;/h2&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;테라폼에서의 조건식은 삼항 연산자 형태를 갖는다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;code&gt;&amp;lt;조건 정의&amp;gt; ? &amp;lt;true&amp;gt; : &amp;lt;false&amp;gt;&lt;/code&gt;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;조건식의 각 조건은 비교 대상의 형태가 다르면 테라폼 실행 시 조건 비교를 위해 형태를 추론하여 자동으로 변환되는데, 혼란이 발생할 수 있기 때문에 명시적인 형태로 작성하는 편이 좋다.&lt;/p&gt;
&lt;pre class=&quot;cs&quot;&gt;&lt;code&gt;var.example ? tostring(12) : &quot;hello&quot; # 권장
var.example ? &quot;12&quot; : &quot;hello&quot; # 권장&lt;/code&gt;&lt;/pre&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;code&gt;count&lt;/code&gt;와 조건식을 결합하여 사용하는 경우 특정 조건에 따라 리소스 생성 여부를 선택하는 방식으로 작성할 수 있다.&lt;/p&gt;
&lt;pre class=&quot;routeros&quot;&gt;&lt;code&gt;variable &quot;enable_file&quot; {
    default = true
}

resource &quot;local_file&quot; &quot;foo&quot; {
    count = var.enable_file ? 1 : 0
    content = &quot;foo!&quot;
    filename = &quot;${path.module}/foo.bar&quot;
}

output &quot;content&quot; {
    value = var.enable_file ? local_file.foo[0].content : &quot;&quot;
}&lt;/code&gt;&lt;/pre&gt;
&lt;h2 data-ke-size=&quot;size26&quot;&gt;Terraform 함수&lt;/h2&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;테라폼은 여러 내장 함수를 포함하고 있지만, 사용자가 구현하는 별도의 사용자 정의 함수를 지원하지 않는다. 함수 종류에는 숫자, 문자열, 컬렉션, 인코딩, 파일 시스템, 날짜/시간, 해시/암호화, IP 네트워크, 형변환 등이 있다.&lt;/p&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;code&gt;terraform plan&lt;/code&gt;, &lt;code&gt;apply&lt;/code&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;code&gt;terraform console&lt;/code&gt;을 통해 함수와 값을 입력하여 곧장 확인하는 것이 더욱 효과적이다.&lt;/p&gt;
&lt;p&gt;&lt;figure class=&quot;imageblock alignCenter&quot; data-ke-mobileStyle=&quot;widthOrigin&quot; data-origin-width=&quot;770&quot; data-origin-height=&quot;83&quot;&gt;&lt;span data-url=&quot;https://blog.kakaocdn.net/dn/bY7SA8/btsI8rtszOm/gNqAnz9M9UglCXiH4vd171/img.png&quot; data-phocus=&quot;https://blog.kakaocdn.net/dn/bY7SA8/btsI8rtszOm/gNqAnz9M9UglCXiH4vd171/img.png&quot;&gt;&lt;img src=&quot;https://blog.kakaocdn.net/dn/bY7SA8/btsI8rtszOm/gNqAnz9M9UglCXiH4vd171/img.png&quot; srcset=&quot;https://img1.daumcdn.net/thumb/R1280x0/?scode=mtistory2&amp;fname=https%3A%2F%2Fblog.kakaocdn.net%2Fdn%2FbY7SA8%2FbtsI8rtszOm%2FgNqAnz9M9UglCXiH4vd171%2Fimg.png&quot; onerror=&quot;this.onerror=null; this.src='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png'; this.srcset='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png';&quot; loading=&quot;lazy&quot; width=&quot;742&quot; height=&quot;80&quot; data-origin-width=&quot;770&quot; data-origin-height=&quot;83&quot;/&gt;&lt;/span&gt;&lt;/figure&gt;
&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;code&gt;exit&lt;/code&gt;를 입력하여 &lt;code&gt;console&lt;/code&gt;에서 빠져나올 수 있다.&lt;/p&gt;
&lt;h2 data-ke-size=&quot;size26&quot;&gt;Terraform Provisioner&lt;/h2&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;프로비저너는 프로바이더로 실행되지 않는 커맨드와 파일 복사 같은 역할을 수행한다. 프로비저너로 실행된 결과는 테라폼의 상태 파일과 동기화 되지 않으므로 프로비저닝에 대한 결과가 항상 같다고 보장할 수 없다. 따라서 프로비저너 사용을 최소화 하는 것이 좋다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;프로비저너의 경우 리소스 프로비저닝 이후 동작하도록 구성할 수 있다. 예를 들어 AWS의 EC2 인스턴스를 생성하고 난 후 CLI를 통해 별도 작업을 수행하는 상황을 가정해보자.&lt;/p&gt;
&lt;pre class=&quot;routeros&quot;&gt;&lt;code&gt;variable &quot;sensitive_content&quot; {
    default = &quot;secret&quot;
    sensitive = true
}

resource &quot;local_file&quot; &quot;foo&quot; {
    content = upper(var.sensitive_content)
    filename = &quot;${path.module}/foo.bar&quot;

    provisioner &quot;local-exec&quot; {
        command = &quot;echo The content is ${self.content}&quot;
    }

    provisioner &quot;local-exec&quot; {
        command = &quot;abc&quot;
        on_failure = continue
    }

    provisioner &quot;local-exec&quot; {
        when = destroy
        command = &quot;echo The deleting filename is ${self.filename}&quot;
    }
}&lt;/code&gt;&lt;/pre&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;프로비저너는 선언된 리소스 블록의 작업이 종료되고 나서 지정한 동작을 수행한다. 작성된 예제와 같이 다수의 프로비저너를 반복적으로 선언할 수 있다.&lt;/p&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;code&gt;terraform apply&lt;/code&gt; 명령어를 통해 &lt;code&gt;main.tf&lt;/code&gt; 파일의 결과를 확인해보면 다음과 같다.&lt;/p&gt;
&lt;p&gt;&lt;figure class=&quot;imageblock alignCenter&quot; data-ke-mobileStyle=&quot;widthOrigin&quot; data-origin-width=&quot;893&quot; data-origin-height=&quot;266&quot;&gt;&lt;span data-url=&quot;https://blog.kakaocdn.net/dn/vkaQd/btsI9iCxpnC/fdJvQRsK6WjlI3yp2TFOL1/img.png&quot; data-phocus=&quot;https://blog.kakaocdn.net/dn/vkaQd/btsI9iCxpnC/fdJvQRsK6WjlI3yp2TFOL1/img.png&quot;&gt;&lt;img src=&quot;https://blog.kakaocdn.net/dn/vkaQd/btsI9iCxpnC/fdJvQRsK6WjlI3yp2TFOL1/img.png&quot; srcset=&quot;https://img1.daumcdn.net/thumb/R1280x0/?scode=mtistory2&amp;fname=https%3A%2F%2Fblog.kakaocdn.net%2Fdn%2FvkaQd%2FbtsI9iCxpnC%2FfdJvQRsK6WjlI3yp2TFOL1%2Fimg.png&quot; onerror=&quot;this.onerror=null; this.src='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png'; this.srcset='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png';&quot; loading=&quot;lazy&quot; width=&quot;679&quot; height=&quot;202&quot; data-origin-width=&quot;893&quot; data-origin-height=&quot;266&quot;/&gt;&lt;/span&gt;&lt;/figure&gt;
&lt;/p&gt;
&lt;pre class=&quot;nginx&quot;&gt;&lt;code&gt;provisioner &quot;local-exec&quot; {
    command = &quot;echo The content is ${self.content}&quot;
}&lt;/code&gt;&lt;/pre&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;첫 번째 프로비저너가 수행되기 때문에 &lt;code&gt;Provisioning with 'local-exec'&lt;/code&gt;라는 문구가 출력되지만, 출력하려는 &lt;code&gt;self.content&lt;/code&gt;의 &lt;code&gt;sensitive&lt;/code&gt;가 &lt;code&gt;true&lt;/code&gt;로 되어있기 때문에 &lt;code&gt;(output suppressed due to sensitive value in config)&lt;/code&gt;가 화면에 표기된다.&lt;/p&gt;
&lt;pre class=&quot;bash&quot;&gt;&lt;code&gt;provisioner &quot;local-exec&quot; {
    command = &quot;abc&quot;
    on_failure = continue
}&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;code&gt;abc&lt;/code&gt;라는 커맨드가 존재하지 않기 때문에 &lt;code&gt;apply&lt;/code&gt; 명령어가 실패해야 하지만 &lt;code&gt;on_failure&lt;/code&gt;이 &lt;code&gt;continue&lt;/code&gt;이기 때문에 중지되지 않고 다음 프로비저너로 넘어가는 것을 확인할 수 있다.&lt;/p&gt;
&lt;pre class=&quot;nginx&quot;&gt;&lt;code&gt;provisioner &quot;local-exec&quot; {
    when = destroy
    command = &quot;echo The deleting filename is ${self.filename}&quot;
}&lt;/code&gt;&lt;/pre&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;세 번째 프로비저너는 &lt;code&gt;when&lt;/code&gt;이 &lt;code&gt;destroy&lt;/code&gt;로 설정되어 있기 때문에 &lt;code&gt;terraform destroy&lt;/code&gt; 명령어가 수행될 때 동작한다. &lt;code&gt;terraform destroy&lt;/code&gt; 명령어를 수행하면 아래와 같은 결과를 확인할 수 있다.&lt;/p&gt;
&lt;p&gt;&lt;figure class=&quot;imageblock alignCenter&quot; data-ke-mobileStyle=&quot;widthOrigin&quot; data-origin-width=&quot;995&quot; data-origin-height=&quot;145&quot;&gt;&lt;span data-url=&quot;https://blog.kakaocdn.net/dn/bHphXr/btsI8Yj1UNx/DSPphx5xowoIcfKTtjirW0/img.png&quot; data-phocus=&quot;https://blog.kakaocdn.net/dn/bHphXr/btsI8Yj1UNx/DSPphx5xowoIcfKTtjirW0/img.png&quot;&gt;&lt;img src=&quot;https://blog.kakaocdn.net/dn/bHphXr/btsI8Yj1UNx/DSPphx5xowoIcfKTtjirW0/img.png&quot; srcset=&quot;https://img1.daumcdn.net/thumb/R1280x0/?scode=mtistory2&amp;fname=https%3A%2F%2Fblog.kakaocdn.net%2Fdn%2FbHphXr%2FbtsI8Yj1UNx%2FDSPphx5xowoIcfKTtjirW0%2Fimg.png&quot; onerror=&quot;this.onerror=null; this.src='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png'; this.srcset='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png';&quot; loading=&quot;lazy&quot; width=&quot;771&quot; height=&quot;112&quot; data-origin-width=&quot;995&quot; data-origin-height=&quot;145&quot;/&gt;&lt;/span&gt;&lt;/figure&gt;
&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;의도했던 문구가 정상적으로 출력됐다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;프로비저너에는 &lt;code&gt;file&lt;/code&gt;, &lt;code&gt;local-exec&lt;/code&gt;, &lt;code&gt;remote-exec&lt;/code&gt;의 세 종류가 있다.&lt;/p&gt;
&lt;h3 data-ke-size=&quot;size23&quot;&gt;local-exec provisioner&lt;/h3&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;code&gt;local-exec&lt;/code&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;code&gt;command&lt;/code&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;&lt;code&gt;&amp;lt;&amp;lt;&lt;/code&gt; 연산자를 통해 여러 줄의 명령어를 입력 가능&lt;/li&gt;
&lt;/ul&gt;
&lt;/li&gt;
&lt;li&gt;&lt;code&gt;working_dir&lt;/code&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;code&gt;command&lt;/code&gt;를 실행할 디렉터리를 지정해야 한다.&lt;/li&gt;
&lt;li&gt;상대/절대 경로로 설정&lt;/li&gt;
&lt;/ul&gt;
&lt;/li&gt;
&lt;li&gt;&lt;code&gt;interpreter&lt;/code&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;/li&gt;
&lt;li&gt;&lt;code&gt;environment&lt;/code&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;code&gt;key = value&lt;/code&gt; 형태로 설정한다.&lt;/li&gt;
&lt;/ul&gt;
&lt;/li&gt;
&lt;/ul&gt;
&lt;pre class=&quot;nginx&quot;&gt;&lt;code&gt;resource &quot;null_resource&quot; &quot;example&quot; {
    provisioner &quot;local-exec&quot; {
        command = &amp;lt;&amp;lt;EOF
            echo Hello! &amp;gt; file.txt
            echo $ENV &amp;gt;&amp;gt; file.txt
            EOF

        interpreter = [&quot;bash&quot;, &quot;-c&quot;]

        working_dir = &quot;/tmp&quot;

        environment = {
            ENV = &quot;world!&quot;
        }
    }
}&lt;/code&gt;&lt;/pre&gt;
&lt;h3 data-ke-size=&quot;size23&quot;&gt;connection&lt;/h3&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;code&gt;remote-exec&lt;/code&gt;와 &lt;code&gt;file&lt;/code&gt; 프로비저너를 사용하기 위해서는 원격지에 연결할 &lt;code&gt;SSH&lt;/code&gt;, &lt;code&gt;WinRM&lt;/code&gt; 연결 정보가 필요하다.&lt;/p&gt;
&lt;pre class=&quot;routeros&quot;&gt;&lt;code&gt;resource &quot;null_resource&quot; &quot;example&quot; {

    connection {
        type = &quot;ssh&quot;
        user = &quot;root&quot;
        password = var.root_password
        host = var.host
    }

    provisioner &quot;file&quot; {
        source = &quot;conf/myapp.conf&quot;
        destination = &quot;/etc/myapp.conf&quot;
    }

    provisioner &quot;file&quot; {
        source = &quot;conf/myapp.conf&quot;
        destination = &quot;C:/App/myapp.conf&quot;

        connection {
            type = &quot;winrm&quot;
            user = &quot;Administrator&quot;
            password = var.admin_password
            host = var.host
        }
    }
}&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;code&gt;connection&lt;/code&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;code&gt;connection&lt;/code&gt;에서 사용가능한 인수의 목록은 다음과 같다. 원격 연결을 사용하는 일이 잦기 때문에 표로 정리해두었다. 참고하도록 하자&lt;/p&gt;
&lt;table data-ke-align=&quot;alignLeft&quot;&gt;
&lt;thead&gt;
&lt;tr&gt;
&lt;th&gt;인수&lt;/th&gt;
&lt;th&gt;연결 타입&lt;/th&gt;
&lt;th&gt;설명&lt;/th&gt;
&lt;th&gt;기본값&lt;/th&gt;
&lt;/tr&gt;
&lt;/thead&gt;
&lt;tbody&gt;
&lt;tr&gt;
&lt;td&gt;type&lt;/td&gt;
&lt;td&gt;SSH/WinRM&lt;/td&gt;
&lt;td&gt;연결 유형으로 ssh 또는 winrm&lt;/td&gt;
&lt;td&gt;ssh&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;user&lt;/td&gt;
&lt;td&gt;SSH/WinRM&lt;/td&gt;
&lt;td&gt;연결에 사용되는 사용자&lt;/td&gt;
&lt;td&gt;ssh: root winrm: Administrator&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;password&lt;/td&gt;
&lt;td&gt;SSH/WinRM&lt;/td&gt;
&lt;td&gt;연결에 사용되는 비밀번호&lt;/td&gt;
&lt;td&gt;&amp;nbsp;&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;host&lt;/td&gt;
&lt;td&gt;SSH/WinRM&lt;/td&gt;
&lt;td&gt;(필수) 연결 대상 주소&lt;/td&gt;
&lt;td&gt;&amp;nbsp;&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;port&lt;/td&gt;
&lt;td&gt;SSH/WinRM&lt;/td&gt;
&lt;td&gt;연결 대상의 타임별 사용 포트&lt;/td&gt;
&lt;td&gt;ssh: 22 winrm: 5985&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;timeout&lt;/td&gt;
&lt;td&gt;SSH/WinRM&lt;/td&gt;
&lt;td&gt;연결 시도에 대한 대기 값&lt;/td&gt;
&lt;td&gt;5m&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;script_path&lt;/td&gt;
&lt;td&gt;SSH/WinRM&lt;/td&gt;
&lt;td&gt;스크립트 복제 시 생성되는 경로&lt;/td&gt;
&lt;td&gt;(추가 설명)&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;private_key&lt;/td&gt;
&lt;td&gt;SSH&lt;/td&gt;
&lt;td&gt;연결 시 사용할 SSH key를 지정하며, password 인수보다 우선함&lt;/td&gt;
&lt;td&gt;&amp;nbsp;&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;certificate&lt;/td&gt;
&lt;td&gt;SSH&lt;/td&gt;
&lt;td&gt;서명된 CA 인증서로 사용 시 private_key와 함께 사용&lt;/td&gt;
&lt;td&gt;&amp;nbsp;&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;agent&lt;/td&gt;
&lt;td&gt;SSH&lt;/td&gt;
&lt;td&gt;ssh-agent를 사용해 인증하지 않는 경우 false로 설정하며 Windows의 경우 Pageant만 사용 가능&lt;/td&gt;
&lt;td&gt;&amp;nbsp;&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;agent_identity&lt;/td&gt;
&lt;td&gt;SSH&lt;/td&gt;
&lt;td&gt;인증을 위한 ssh-agent의 기본 사용자&lt;/td&gt;
&lt;td&gt;&amp;nbsp;&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;host_key&lt;/td&gt;
&lt;td&gt;SSH&lt;/td&gt;
&lt;td&gt;원격 호스트 또는 서명된 CA의 연결을 확인하는 데 사용되는 공개키&lt;/td&gt;
&lt;td&gt;&amp;nbsp;&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;target_platform&lt;/td&gt;
&lt;td&gt;SSH&lt;/td&gt;
&lt;td&gt;연결 대상 플랫폼으로 windows 또는 unix&lt;/td&gt;
&lt;td&gt;unix&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;https&lt;/td&gt;
&lt;td&gt;WinRM&lt;/td&gt;
&lt;td&gt;true인 경우 HTTPS로 연결&lt;/td&gt;
&lt;td&gt;false&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;insecure&lt;/td&gt;
&lt;td&gt;WinRM&lt;/td&gt;
&lt;td&gt;true인 경우 HTTPS 유효성 무시&lt;/td&gt;
&lt;td&gt;false&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;use_ntlm&lt;/td&gt;
&lt;td&gt;WinRM&lt;/td&gt;
&lt;td&gt;true인 경우 NTLM 인증을 사용&lt;/td&gt;
&lt;td&gt;false&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;cacert&lt;/td&gt;
&lt;td&gt;WinRM&lt;/td&gt;
&lt;td&gt;유효성 검증을 위한 CA 인증서&lt;/td&gt;
&lt;td&gt;&amp;nbsp;&lt;/td&gt;
&lt;/tr&gt;
&lt;/tbody&gt;
&lt;/table&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;원격 연결이 요구되는 프로비저너의 경우 스크립트 파일을 원격 시스템에 업로드하여 해당 시스템의 기본 쉘에서 실행하도록 하므로 script_path의 경우 적절한 위치를 지정하도록 하자. 경로는 난수인 %RAND% 경로가 포함되어 생성된다.&lt;/p&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;code&gt;/tmp/terraform_%RAND%.sh&lt;/code&gt; 다. &lt;code&gt;Bastion Host&lt;/code&gt;를 통해 연결하는 경우에도 관련 인수를 지원한다. 인수는 다음과 같다.&lt;/p&gt;
&lt;table data-ke-align=&quot;alignLeft&quot;&gt;
&lt;thead&gt;
&lt;tr&gt;
&lt;th&gt;인수&lt;/th&gt;
&lt;th&gt;설명&lt;/th&gt;
&lt;th&gt;기본값&lt;/th&gt;
&lt;/tr&gt;
&lt;/thead&gt;
&lt;tbody&gt;
&lt;tr&gt;
&lt;td&gt;bastion_host&lt;/td&gt;
&lt;td&gt;io 설정하게 되면 바스티온 호스트 연결이 활성화되며, 연결 대상 호스트를 지정&lt;/td&gt;
&lt;td&gt;&amp;nbsp;&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;bastion_host_key&lt;/td&gt;
&lt;td&gt;호스트 연결을 위한 공개키&lt;/td&gt;
&lt;td&gt;&amp;nbsp;&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;bastion_port&lt;/td&gt;
&lt;td&gt;호스트 연결을 위한 포트 번호&lt;/td&gt;
&lt;td&gt;port 인수 값&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;bastion_user&lt;/td&gt;
&lt;td&gt;호스트에 연결할 사용자&lt;/td&gt;
&lt;td&gt;user 인수 값&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;bastion_password&lt;/td&gt;
&lt;td&gt;호스트 연결에 사용할 비밀번호&lt;/td&gt;
&lt;td&gt;password 인수 값&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;bastion_private_key&lt;/td&gt;
&lt;td&gt;호스트 연결에 사용할 SSH 키파일&lt;/td&gt;
&lt;td&gt;private_key 인수 값&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;bastion_certificate&lt;/td&gt;
&lt;td&gt;서명된 CA 인증서 내용으로 bastion_private_key와 함께 사용&lt;/td&gt;
&lt;td&gt;&amp;nbsp;&lt;/td&gt;
&lt;/tr&gt;
&lt;/tbody&gt;
&lt;/table&gt;
&lt;h3 data-ke-size=&quot;size23&quot;&gt;file provisioner&lt;/h3&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;code&gt;file&lt;/code&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;code&gt;source&lt;/code&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;code&gt;content&lt;/code&gt;와 함께 사용할 수 없다.&lt;/li&gt;
&lt;/ul&gt;
&lt;/li&gt;
&lt;li&gt;&lt;code&gt;content&lt;/code&gt;
&lt;ul style=&quot;list-style-type: disc;&quot; data-ke-list-type=&quot;disc&quot;&gt;
&lt;li&gt;연결 대상에 복사할 내용을 정의하며 대상이 디렉토리인 경우 &lt;code&gt;tf-file-content&lt;/code&gt; 파일이 생성되고, 파일인 경우 해당 파일에 내용이 기록된다.&lt;/li&gt;
&lt;li&gt;&lt;code&gt;source&lt;/code&gt;와 함께 사용할 수 없다.&lt;/li&gt;
&lt;/ul&gt;
&lt;/li&gt;
&lt;li&gt;&lt;code&gt;destination&lt;/code&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;code&gt;SSH&lt;/code&gt; 연결의 경우 대상 디렉토리가 존재해야 하며, &lt;code&gt;WinRM&lt;/code&gt;은 디렉토리가 없다면 자동으로 생성된다.&lt;/li&gt;
&lt;/ul&gt;
&lt;/li&gt;
&lt;/ul&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;디렉토리를 대상으로 하는 경우 &lt;code&gt;source&lt;/code&gt;의 경로 작성 방법에 따라 동작에 차이가 발생한다. 예를 들어 &lt;code&gt;destination&lt;/code&gt;이 &lt;code&gt;/tmp&lt;/code&gt;인 경우 &lt;code&gt;source&lt;/code&gt;가 &lt;code&gt;/foo&lt;/code&gt;와 같이 뒤에 &lt;code&gt;/&lt;/code&gt;가 붙지 않는 형태일 때는 원격 대상에 &lt;code&gt;/tmp/foo&lt;/code&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;code&gt;source&lt;/code&gt;가 &lt;code&gt;/foo/&lt;/code&gt;와 같이 뒤에 &lt;code&gt;/&lt;/code&gt;가 붙는다면, &lt;code&gt;/foo&lt;/code&gt; 디렉토리 내부에 위치한 파일들만 &lt;code&gt;/tmp&lt;/code&gt; 디렉토리 내부로 업로드 된다.&lt;/p&gt;
&lt;h3 data-ke-size=&quot;size23&quot;&gt;remote-exec provisioner&lt;/h3&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;code&gt;remote-exec&lt;/code&gt;는 원격지 환경에서 실행할 명령어와 스크립트를 정의한다. 예를 들면 AWS의 EC2 인스턴스를 생성하고 해당 가상 환경에서 명령을 실행하고 패키지를 설치하는 등의 동작을 의미한다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;사용되는 인수는 다음과 같으며, 각 인수는 서로 배타적이다.&lt;/p&gt;
&lt;ul style=&quot;list-style-type: disc;&quot; data-ke-list-type=&quot;disc&quot;&gt;
&lt;li&gt;&lt;code&gt;inline&lt;/code&gt;
&lt;ul style=&quot;list-style-type: disc;&quot; data-ke-list-type=&quot;disc&quot;&gt;
&lt;li&gt;명령에 대한 목록으로 &lt;code&gt;[ ]&lt;/code&gt; 블록 내에 &lt;code&gt;&quot; &quot;&lt;/code&gt;로 묶인 다수의 명령을 &lt;code&gt;,&lt;/code&gt;로 구분하여 구성한다.&lt;/li&gt;
&lt;/ul&gt;
&lt;/li&gt;
&lt;li&gt;&lt;code&gt;script&lt;/code&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;/ul&gt;
&lt;/li&gt;
&lt;li&gt;&lt;code&gt;scripts&lt;/code&gt;
&lt;ul style=&quot;list-style-type: disc;&quot; data-ke-list-type=&quot;disc&quot;&gt;
&lt;li&gt;로컬의 스크립트 경로의 목록으로 &lt;code&gt;[ ]&lt;/code&gt; 블록 내에 &lt;code&gt;&quot; &quot;&lt;/code&gt;로 묶인 다수의 스크립트 경로를 &lt;code&gt;,&lt;/code&gt;로 구분하여 구성한다.&lt;/li&gt;
&lt;/ul&gt;
&lt;/li&gt;
&lt;/ul&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;code&gt;script&lt;/code&gt; 또는 &lt;code&gt;scripts&lt;/code&gt;의 대상 스크립트 실행에 필요한 인수는 관련 구성에서 선언할 수 없으므로 필요할 때 &lt;code&gt;file&lt;/code&gt; 프로바이더로 해당 스크립트를 업로드하고 &lt;code&gt;inline&lt;/code&gt; 인수를 활용해 스크립트에 인수를 추가한다.&lt;/p&gt;
&lt;pre class=&quot;routeros&quot;&gt;&lt;code&gt;resource &quot;aws_instance&quot; &quot;web&quot; {
    ...

    connection {
        type = &quot;ssh&quot;
        user = &quot;root&quot;
        password = var.root_password
        host = self.public_ip
    }

    provisioner &quot;file&quot; {
        source = &quot;script.sh&quot;
        destination = &quot;/tmp/script.sh&quot;
    }

    provisioner &quot;remote-exec&quot; {
        inline = [
            &quot;chmod -x /tmp/script.sh&quot;,
            &quot;/tmp/script.sh args&quot;,
        ]
    }
}&lt;/code&gt;&lt;/pre&gt;
&lt;h2 data-ke-size=&quot;size26&quot;&gt;null_resource&lt;/h2&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;code&gt;null_resource&lt;/code&gt;는 아무 작업도 수행하지 않는 리소스를 구현한다. 해당 리소스는 테라폼 프로비저닝 동작을 설계하면서 사용자가 의도적으로 프로비저닝하는 동작을 조율해야 하는 상황이 발생하며, 프로바이더가 제공하는 리소스 수명주기 관리만으로는 이를 해결하기 어렵기 때문이다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;주로 다음과 같은 상황에서 사용된다.&lt;/p&gt;
&lt;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;모듈, 반복문, 데이터 소스, 로컬 변수와 함께 사용&lt;/li&gt;
&lt;li&gt;출력을 위한 데이터 가공&lt;/li&gt;
&lt;/ul&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;AWS EC2 인스턴스를 프로비저닝하면서 웹 서비스를 실행시키고자 한다.&lt;/li&gt;
&lt;li&gt;웹 서비스는 노출되어야 하는 고정 IP가 필요하기 때문에 AWS EIP 리소스를 생성해야 한다.&lt;/li&gt;
&lt;/ul&gt;
&lt;pre class=&quot;routeros&quot;&gt;&lt;code&gt;resource &quot;aws_instance&quot; &quot;foo&quot; {
    ami = &quot;ami-5189a661&quot;
    instance_type = &quot;t2.micro&quot;

    private_ip = &quot;10.0.0.12&quot;
    subnet_id = aws_subnet.tf_test_subnet.id

    provisioner &quot;remote-exec&quot; {
        inline = [
            &quot;echo ${aws_eip.bar.public_ip}&quot;
        ]
    }
}

resource &quot;aws_eip&quot; &quot;bar&quot; {
    vpc = true

    instance = aws.instance.foo.id
    associate_with_private_ip = &quot;10.0.0.12&quot;
    depends_on = [aws_internet_gateway.gw]
}&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;aws_eip가 생성하는 고정된 IP를 할당하기 위해서는 대상인 `aws_instance`의 `id`값이 필요하다. 하지만 `aws_instance`가 프로비저닝 되기 위해서는 `aws_eip`가 생성하는 `public_ip`가 필요하다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;어디까지나 예시이기 때문에 `terraform plan` 명령어를 실행해도 정상적으로 동작하지는 않지만, 만약 정상적인 코드라고 가정했을 때 위와 같은 순환 참조가 발생할 경우 `Cycle` 에러가 발생하게 된다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;상호 참조되는 종속성을 끊기 위해서는 둘 중 하나의 실행 시점을 한 단계 뒤로 미뤄야 한다. 이런 경우 실행에 간격을 추가하여 실제 리소스와는 무관한 동작을 수행하기 위해 `null_resource`를 활용한다.&lt;/p&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 class=&quot;routeros&quot;&gt;&lt;code&gt;resource &quot;aws_instance&quot; &quot;foo&quot; {
    ami = &quot;ami-5189a661&quot;
    instance_type = &quot;t2.micro&quot;

    private_ip = &quot;10.0.0.12&quot;
    subnet_id = aws_subnet.tf_test_subnet.id
}

resource &quot;aws_eip&quot; &quot;bar&quot; {
    vpc = true

    instance = aws.instance.foo.id
    associate_with_private_ip = &quot;10.0.0.12&quot;
    depends_on = [aws_internet_gateway.gw]
}

resource &quot;null_resource&quot; &quot;barz&quot; {
    provisioner &quot;remote-exec&quot; {
        connection {
            host = aws_eip.bar.public_ip
        }
        inline = [
            &quot;echo ${aws_eip.bar.public_ip}&quot;
        ]
    }
}&lt;/code&gt;&lt;/pre&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;code&gt;null_resource&lt;/code&gt;는 정의된 속성이 &lt;code&gt;id&lt;/code&gt;가 전부이므로, 선언된 내부의 구성이 변경되더라도 새로운 Plan 과정에서 실행 게획에 포함되지 못한다.&lt;/p&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;code&gt;null_resource&lt;/code&gt;에 정의된 내용을 강제로 다시 실행하기 위한 인수로 &lt;code&gt;trigger&lt;/code&gt;가 제공된다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;code&gt;trigger&lt;/code&gt;는 임의의 string 형태의 map 데이터를 정의하는데, 정의된 값이 변경되면 &lt;code&gt;null_resource&lt;/code&gt; 내부에 정의된 행위를 다시 실행한다.&lt;/p&gt;
&lt;pre class=&quot;routeros&quot;&gt;&lt;code&gt;resource &quot;null_resource&quot; &quot;foo&quot; {
    ...
    trigger = {
        ec2_id = aws.instance.bar.id # instance의 id가 변경되는 경우 재실행
    }
}

resource &quot;null_resource&quot; &quot;bar&quot; {
    ...
    trigger = {
        ec2_id = time() # 테라폼으로 실행 계획을 생성할 때마다 재실행
    }
}&lt;/code&gt;&lt;/pre&gt;
&lt;h2 data-ke-size=&quot;size26&quot;&gt;terraform_data&lt;/h2&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;테라폼 1.4 버전이 릴리즈되면서 기존 &lt;code&gt;null_resource&lt;/code&gt; 리소스를 대체하는 &lt;code&gt;terraform_data&lt;/code&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;code&gt;terraform_data&lt;/code&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;code&gt;null_resource&lt;/code&gt;와 동일하며 강제 재실행을 위한 &lt;code&gt;triggers_replace&lt;/code&gt;와 상태 저장을 위한 &lt;code&gt;input&lt;/code&gt; 인수와 &lt;code&gt;input&lt;/code&gt;에 저장된 값을 출력하는 &lt;code&gt;output&lt;/code&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;code&gt;triggers_replace&lt;/code&gt;에 정의되는 값이 기존 map 형태에서 tuple로 변경되어 쓰임이 더 간단해졌다.&lt;/p&gt;
&lt;pre class=&quot;nginx&quot;&gt;&lt;code&gt;resource &quot;terraform_data&quot; &quot;foo&quot; {
    triggers_replace = [
        aws_instance.bar.id,
        aws_instance.barz.id
    ]

    input = &quot;world&quot;
}

output &quot;terraform_data_output&quot; {
    value = terraform_data.foo.output # 출력 결과 : world
}&lt;/code&gt;&lt;/pre&gt;
&lt;h2 data-ke-size=&quot;size26&quot;&gt;moved Block&lt;/h2&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;테라폼의 &lt;code&gt;State&lt;/code&gt;에 기록되는 리소스 주소의 이름이 변경되면 기존 리소스는 삭제되고 새로운 리소스가 생성되지만, 테라폼 리소스의 이름을 변경해야 하는 상황이 발생할 수 있다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;리소스의 이름은 변경되지만 이미 테라폼으로 프로비저닝된 환경을 그대로 유지하고자 하는 경우 테라폼 1.1 버전부터 &lt;code&gt;moved&lt;/code&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;code&gt;moved&lt;/code&gt; 블록은 &lt;code&gt;State&lt;/code&gt;에 접근 권한이 없는 사용자라도 변경되는 주소를 리소스 영향 없이 반영할 수 있다.&lt;/p&gt;
&lt;pre class=&quot;nginx&quot;&gt;&lt;code&gt;resource &quot;local_file&quot; &quot;a&quot; {
    content = &quot;foo!&quot;
    filename = &quot;${path.module}/foo.bar&quot;
}

output &quot;file_content&quot; {
    value = local_file.a.content
}&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;code&gt;local_file&lt;/code&gt;의 이름을 &lt;code&gt;a&lt;/code&gt;에서 &lt;code&gt;b&lt;/code&gt;로 변경해야 할 때, 단순히 리소스의 이름을 &lt;code&gt;a&lt;/code&gt;에서 &lt;code&gt;b&lt;/code&gt;로 수정한 뒤 &lt;code&gt;plan&lt;/code&gt;을 수행하면, &lt;code&gt;a&lt;/code&gt; 리소스를 삭제하고 &lt;code&gt;b&lt;/code&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;code&gt;a&lt;/code&gt;의 프로비저닝 결과를 유지한 채로 이름을 변경하기 위해서는 &lt;code&gt;moved&lt;/code&gt; 블록을 활용하면 된다.&lt;/p&gt;
&lt;pre class=&quot;nix&quot;&gt;&lt;code&gt;resource &quot;local_file&quot; &quot;b&quot; {
    content = &quot;foo!&quot;
    filename = &quot;${path.module}/foo.bar&quot;
}

moved {
    from = local_file.a
    to = local_file.b
}

output &quot;file_content&quot; {
    value = local_file.a.content
}&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;code&gt;moved&lt;/code&gt; 블록을 추가하고 &lt;code&gt;plan&lt;/code&gt; 명령어를 다시 실행하면 제거되거나 새롭게 생성되는 리소스가 존재한다는 메시지는 보이지 않고, 출력 결과에 &lt;code&gt;local_file.a&lt;/code&gt; 주소가 &lt;code&gt;local_file.b&lt;/code&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;code&gt;apply&lt;/code&gt;를 수행하고 이후 &lt;code&gt;moved&lt;/code&gt; 블록을 삭제하면 새로운 리소스 주소가 사용되어 리팩토링이 완료된 것을 확인할 수 있다.&lt;/p&gt;</description>
      <category>DevOps</category>
      <category>AWS</category>
      <category>Cloud</category>
      <category>DevOps</category>
      <category>IAC</category>
      <category>terraform</category>
      <category>데브옵스</category>
      <category>자동화</category>
      <category>클라우드</category>
      <category>테라폼</category>
      <author>겨울바람_</author>
      <guid isPermaLink="true">https://breeze-winter.tistory.com/37</guid>
      <comments>https://breeze-winter.tistory.com/37#entry37comment</comments>
      <pubDate>Mon, 19 Aug 2024 22:26:42 +0900</pubDate>
    </item>
    <item>
      <title>[Helm] Helm Quick Start</title>
      <link>https://breeze-winter.tistory.com/36</link>
      <description>&lt;p&gt;&lt;figure class=&quot;imageblock alignCenter&quot; data-ke-mobileStyle=&quot;widthOrigin&quot; data-origin-width=&quot;512&quot; data-origin-height=&quot;512&quot;&gt;&lt;span data-url=&quot;https://blog.kakaocdn.net/dn/J8eV5/btsI0yGtjWx/qnsiXgxWEXIKKTUjLrA5yk/img.png&quot; data-phocus=&quot;https://blog.kakaocdn.net/dn/J8eV5/btsI0yGtjWx/qnsiXgxWEXIKKTUjLrA5yk/img.png&quot;&gt;&lt;img src=&quot;https://blog.kakaocdn.net/dn/J8eV5/btsI0yGtjWx/qnsiXgxWEXIKKTUjLrA5yk/img.png&quot; srcset=&quot;https://img1.daumcdn.net/thumb/R1280x0/?scode=mtistory2&amp;fname=https%3A%2F%2Fblog.kakaocdn.net%2Fdn%2FJ8eV5%2FbtsI0yGtjWx%2FqnsiXgxWEXIKKTUjLrA5yk%2Fimg.png&quot; onerror=&quot;this.onerror=null; this.src='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png'; this.srcset='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png';&quot; loading=&quot;lazy&quot; width=&quot;211&quot; height=&quot;211&quot; data-origin-width=&quot;512&quot; data-origin-height=&quot;512&quot;/&gt;&lt;/span&gt;&lt;/figure&gt;
&lt;/p&gt;
&lt;h2 data-ke-size=&quot;size26&quot;&gt;Helm?&lt;/h2&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;헬름은 The Package Manager for Kubernetes, 즉 쿠버네티스의 패키지 관리를 위한 툴이다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;여기서 패키지란, Helm &lt;code&gt;Charts&lt;/code&gt;를 패키징한 것을 의미한다. 여기서 차트란 쿠버네티스의 리소스 &lt;code&gt;yaml&lt;/code&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;code&gt;Deployment&lt;/code&gt; 혹은 &lt;code&gt;Service&lt;/code&gt; 등을 포함하는 일종의 리소스 모음을 의미한다.&lt;/p&gt;
&lt;h3 data-ke-size=&quot;size23&quot;&gt;Helm Chart Structure&lt;/h3&gt;
&lt;p&gt;&lt;figure class=&quot;imageblock alignCenter&quot; data-ke-mobileStyle=&quot;widthOrigin&quot; data-origin-width=&quot;1322&quot; data-origin-height=&quot;433&quot;&gt;&lt;span data-url=&quot;https://blog.kakaocdn.net/dn/dhUYX8/btsIZ5EzUGe/EOkso28GkfdgjEsM2B94O0/img.png&quot; data-phocus=&quot;https://blog.kakaocdn.net/dn/dhUYX8/btsIZ5EzUGe/EOkso28GkfdgjEsM2B94O0/img.png&quot;&gt;&lt;img src=&quot;https://blog.kakaocdn.net/dn/dhUYX8/btsIZ5EzUGe/EOkso28GkfdgjEsM2B94O0/img.png&quot; srcset=&quot;https://img1.daumcdn.net/thumb/R1280x0/?scode=mtistory2&amp;fname=https%3A%2F%2Fblog.kakaocdn.net%2Fdn%2FdhUYX8%2FbtsIZ5EzUGe%2FEOkso28GkfdgjEsM2B94O0%2Fimg.png&quot; onerror=&quot;this.onerror=null; this.src='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png'; this.srcset='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png';&quot; loading=&quot;lazy&quot; width=&quot;778&quot; height=&quot;255&quot; data-origin-width=&quot;1322&quot; data-origin-height=&quot;433&quot;/&gt;&lt;/span&gt;&lt;/figure&gt;
&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;Helm의 공식문서를 살펴보면 헬름 차트는 이와 같은 구조를 갖는다. 구조에서 확인할 수 있는 각 파일 및 폴더는 다음과 같은 역할을 한다.&lt;/p&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;code&gt;templates/&lt;/code&gt; 디렉토리는 &lt;code&gt;template&lt;/code&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;Helm이 Chart를 검사할 때 &lt;code&gt;templates/&lt;/code&gt; 내부에 존재하는 파일들을 &lt;code&gt;template rendering engine&lt;/code&gt;으로 전송한다. 그리고 템플릿 엔진은 탬플릿의 실행 결과를 취합하여 쿠버네티스로 보낸다.&lt;br /&gt;&lt;br /&gt;&lt;code&gt;values.yaml&lt;/code&gt; 파일은 Chart 내부의 기본 값을 포함하는데, 해당 파일의 값을 &lt;code&gt;helm install&lt;/code&gt;, &lt;code&gt;helm upgrade&lt;/code&gt; 중에 템플릿 값에 덮어씌울 수 있다. 즉, &lt;code&gt;value.yaml&lt;/code&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;code&gt;Chart.yaml&lt;/code&gt; 파일은 Chart의 버전, 이름 등 메타 정보와 설명을 포함하며, 템플릿 내부에서 접근할 수 있다.&lt;/p&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;code&gt;charts/&lt;/code&gt; 디렉토리는 다른 차트 파일들을 포함하기 위한 용도다.&lt;/p&gt;
&lt;h2 data-ke-size=&quot;size26&quot;&gt;Deploy Nginx using helm&lt;/h2&gt;
&lt;h3 data-ke-size=&quot;size23&quot;&gt;Create template files&lt;/h3&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;원하는 텍스트 에디터를 사용해 &lt;code&gt;deployment.yaml&lt;/code&gt;과 &lt;code&gt;service.yaml&lt;/code&gt;을 작성해보자. 필자는 &lt;code&gt;templates/&lt;/code&gt; 디렉토리 내부에 nginx 생성을 위한 &lt;code&gt;deployment.yaml&lt;/code&gt;과 &lt;code&gt;service.yaml&lt;/code&gt;을 아래와 같이 각각 작성했다.&lt;/p&gt;
&lt;pre class=&quot;yaml&quot;&gt;&lt;code&gt;# templates/deployment.yaml
apiVersion: apps/v1
kind: Deployment
metadata:
  name: nginx-deployment
  labels:
    app: nginx-for-helm
spec:
  selector:
    matchLabels:
      app: nginx-for-helm
  replicas: 2
  template:
    metadata:
      labels:
        app: nginx-for-helm
    spec:
      containers:
      - name: nginx
        image: nginx:latest
        ports:
        - containerPort: 80&lt;/code&gt;&lt;/pre&gt;
&lt;pre class=&quot;yaml&quot;&gt;&lt;code&gt;# templates/service.yaml
apiVersion: v1
kind: Service
metadata:
  name: nginx-service
  labels:
    run: nginx-for-helm
spec:
  ports:
  - port: 80
    protocol: TCP
  selector:
    run: nginx-for-helm&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;code&gt;tree&lt;/code&gt; 명령어로 다음과 같은 구조를 확인할 수 있다.&lt;/p&gt;
&lt;p&gt;&lt;figure class=&quot;imageblock alignCenter&quot; data-ke-mobileStyle=&quot;widthOrigin&quot; data-origin-width=&quot;643&quot; data-origin-height=&quot;118&quot;&gt;&lt;span data-url=&quot;https://blog.kakaocdn.net/dn/zgnXO/btsI0UPRBrS/0MWyLiZaiAbZZnWKCsHGU0/img.png&quot; data-phocus=&quot;https://blog.kakaocdn.net/dn/zgnXO/btsI0UPRBrS/0MWyLiZaiAbZZnWKCsHGU0/img.png&quot;&gt;&lt;img src=&quot;https://blog.kakaocdn.net/dn/zgnXO/btsI0UPRBrS/0MWyLiZaiAbZZnWKCsHGU0/img.png&quot; srcset=&quot;https://img1.daumcdn.net/thumb/R1280x0/?scode=mtistory2&amp;fname=https%3A%2F%2Fblog.kakaocdn.net%2Fdn%2FzgnXO%2FbtsI0UPRBrS%2F0MWyLiZaiAbZZnWKCsHGU0%2Fimg.png&quot; onerror=&quot;this.onerror=null; this.src='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png'; this.srcset='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png';&quot; loading=&quot;lazy&quot; width=&quot;632&quot; height=&quot;116&quot; data-origin-width=&quot;643&quot; data-origin-height=&quot;118&quot;/&gt;&lt;/span&gt;&lt;/figure&gt;
&lt;/p&gt;
&lt;h3 data-ke-size=&quot;size23&quot;&gt;Create Chart.yaml&lt;/h3&gt;
&lt;p&gt;&lt;figure class=&quot;imageblock alignCenter&quot; data-ke-mobileStyle=&quot;widthOrigin&quot; data-origin-width=&quot;1219&quot; data-origin-height=&quot;739&quot;&gt;&lt;span data-url=&quot;https://blog.kakaocdn.net/dn/dqu4AM/btsI0Q7FeMl/kfOwXyWF1iW1j1KNHEgen1/img.png&quot; data-phocus=&quot;https://blog.kakaocdn.net/dn/dqu4AM/btsI0Q7FeMl/kfOwXyWF1iW1j1KNHEgen1/img.png&quot;&gt;&lt;img src=&quot;https://blog.kakaocdn.net/dn/dqu4AM/btsI0Q7FeMl/kfOwXyWF1iW1j1KNHEgen1/img.png&quot; srcset=&quot;https://img1.daumcdn.net/thumb/R1280x0/?scode=mtistory2&amp;fname=https%3A%2F%2Fblog.kakaocdn.net%2Fdn%2Fdqu4AM%2FbtsI0Q7FeMl%2FkfOwXyWF1iW1j1KNHEgen1%2Fimg.png&quot; onerror=&quot;this.onerror=null; this.src='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png'; this.srcset='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png';&quot; loading=&quot;lazy&quot; width=&quot;796&quot; height=&quot;483&quot; data-origin-width=&quot;1219&quot; data-origin-height=&quot;739&quot;/&gt;&lt;/span&gt;&lt;/figure&gt;
&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;code&gt;Chart.yaml&lt;/code&gt; 에 대해 나와있는 공식문서를 살펴보면 &lt;code&gt;Chart.yaml&lt;/code&gt; 파일에 포함되는 요소가 굉장히 많은 것을 확인할 수 있다. 이 중 필수적으로 들어가야하는 것은 &lt;code&gt;apiVersion&lt;/code&gt;과 &lt;code&gt;name&lt;/code&gt;, &lt;code&gt;version&lt;/code&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;code&gt;apiVersion&lt;/code&gt;은 Helm Chart에 대한 버전을 의미하며 과거에는 &lt;code&gt;v1&lt;/code&gt;을 주로 사용했지만 Helm 3부터는 &lt;code&gt;v2&lt;/code&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;code&gt;name&lt;/code&gt;은 해당 Chart의 이름을 의미하며 &lt;code&gt;version&lt;/code&gt; 또한 Chart의 버전을 의미하기에 두 요소는 임의로 작성하면 된다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;위 내용을 바탕으로 &lt;code&gt;Chart.yaml&lt;/code&gt; 파일을 작성해보자. &lt;code&gt;Chart.yaml&lt;/code&gt; 파일은 &lt;code&gt;templates/&lt;/code&gt; 디렉토리와 동일한 depth에 작성해주면 된다.&lt;/p&gt;
&lt;pre class=&quot;yaml&quot;&gt;&lt;code&gt;apiVersion: v2
name: chart-for-nginx
version: 0.0.&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;code&gt;tree&lt;/code&gt; 명령어로 다음과 같은 구조를 확인할 수 있다.&lt;/p&gt;
&lt;p&gt;&lt;figure class=&quot;imageblock alignCenter&quot; data-ke-mobileStyle=&quot;widthOrigin&quot; data-origin-width=&quot;634&quot; data-origin-height=&quot;140&quot;&gt;&lt;span data-url=&quot;https://blog.kakaocdn.net/dn/SlFRz/btsI0uYmbIy/H0kkmIxpjJVqjZ7mcZiC80/img.png&quot; data-phocus=&quot;https://blog.kakaocdn.net/dn/SlFRz/btsI0uYmbIy/H0kkmIxpjJVqjZ7mcZiC80/img.png&quot;&gt;&lt;img src=&quot;https://blog.kakaocdn.net/dn/SlFRz/btsI0uYmbIy/H0kkmIxpjJVqjZ7mcZiC80/img.png&quot; srcset=&quot;https://img1.daumcdn.net/thumb/R1280x0/?scode=mtistory2&amp;fname=https%3A%2F%2Fblog.kakaocdn.net%2Fdn%2FSlFRz%2FbtsI0uYmbIy%2FH0kkmIxpjJVqjZ7mcZiC80%2Fimg.png&quot; onerror=&quot;this.onerror=null; this.src='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png'; this.srcset='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png';&quot; loading=&quot;lazy&quot; width=&quot;602&quot; height=&quot;133&quot; data-origin-width=&quot;634&quot; data-origin-height=&quot;140&quot;/&gt;&lt;/span&gt;&lt;/figure&gt;
&lt;/p&gt;
&lt;h3 data-ke-size=&quot;size23&quot;&gt;Create values.yaml&lt;/h3&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;code&gt;values.yaml&lt;/code&gt; 파일을 사용하면 외부에서 템플릿의 값을 동적으로 변경할 수 있다. &lt;code&gt;values.yaml&lt;/code&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;code&gt;values.yaml&lt;/code&gt; 파일에는 템플릿에 적용하고자 하는 값을 작성해줘야 한다. 만약 Nginx 배포에 사용되는 이미지를 동적으로 적용하고자 한다면, &lt;code&gt;image: nginx:latest&lt;/code&gt;와 같은 값을 &lt;code&gt;values.yaml&lt;/code&gt; 파일에 작성해둬야 한다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;위의 예시대로 이미지를 동적으로 변경하고자 한다면 템플릿의 이미지 필드를 다음과 같이 템플릿 문법을 통해 대체하면 된다.&lt;/p&gt;
&lt;pre class=&quot;yaml&quot;&gt;&lt;code&gt;apiVersion: apps/v1
kind: Deployment
metadata:
  name: nginx-deployment
  labels:
    app: nginx-for-helm
spec:
  selector:
    matchLabels:
      app: nginx-for-helm
  replicas: 2
  template:
    metadata:
      labels:
        app: nginx-for-helm
    spec:
      containers:
      - name: nginx
        image: {{ .Values.image }}
        ports:
        - containerPort: 80&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;code&gt;{{ .Values.image }}&lt;/code&gt; 라는 문법은 &lt;code&gt;values.yaml&lt;/code&gt; 파일에 위치한 필드들 중 &lt;code&gt;image&lt;/code&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;code&gt;tree&lt;/code&gt; 명령어로 다음과 같은 구조를 확인할 수 있다.&lt;/p&gt;
&lt;p&gt;&lt;figure class=&quot;imageblock alignCenter&quot; data-ke-mobileStyle=&quot;widthOrigin&quot; data-origin-width=&quot;628&quot; data-origin-height=&quot;166&quot;&gt;&lt;span data-url=&quot;https://blog.kakaocdn.net/dn/5IaV0/btsI0QT9H4e/dsp0HPSrG9mub7P0i7m9kK/img.png&quot; data-phocus=&quot;https://blog.kakaocdn.net/dn/5IaV0/btsI0QT9H4e/dsp0HPSrG9mub7P0i7m9kK/img.png&quot;&gt;&lt;img src=&quot;https://blog.kakaocdn.net/dn/5IaV0/btsI0QT9H4e/dsp0HPSrG9mub7P0i7m9kK/img.png&quot; srcset=&quot;https://img1.daumcdn.net/thumb/R1280x0/?scode=mtistory2&amp;fname=https%3A%2F%2Fblog.kakaocdn.net%2Fdn%2F5IaV0%2FbtsI0QT9H4e%2Fdsp0HPSrG9mub7P0i7m9kK%2Fimg.png&quot; onerror=&quot;this.onerror=null; this.src='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png'; this.srcset='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png';&quot; loading=&quot;lazy&quot; width=&quot;605&quot; height=&quot;160&quot; data-origin-width=&quot;628&quot; data-origin-height=&quot;166&quot;/&gt;&lt;/span&gt;&lt;/figure&gt;
&lt;/p&gt;
&lt;h3 data-ke-size=&quot;size23&quot;&gt;Deploy&lt;/h3&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;이렇게 작성된 Helm Chart를 통해 Nginx를 배포해보자. &lt;code&gt;helm install &amp;lt;release-name&amp;gt; &amp;lt;chart-name&amp;gt;&lt;/code&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;code&gt;release-name&lt;/code&gt;은 사용자가 임의로 작성 가능하며, &lt;code&gt;chart-name&lt;/code&gt;의 경우 지금까지 작성한 Helm Chart의 위치를 입력하면 된다. 성공적으로 Helm Chart를 작성했다면 아래의 그림과 같이 성공적으로 배포가 된 것을 확인할 수 있다.&lt;/p&gt;
&lt;p&gt;&lt;figure class=&quot;imageblock alignCenter&quot; data-ke-mobileStyle=&quot;widthOrigin&quot; data-origin-width=&quot;856&quot; data-origin-height=&quot;164&quot;&gt;&lt;span data-url=&quot;https://blog.kakaocdn.net/dn/dXWW0v/btsIZOb0IvB/PqwWoAipYifPO53peKGm4k/img.png&quot; data-phocus=&quot;https://blog.kakaocdn.net/dn/dXWW0v/btsIZOb0IvB/PqwWoAipYifPO53peKGm4k/img.png&quot;&gt;&lt;img src=&quot;https://blog.kakaocdn.net/dn/dXWW0v/btsIZOb0IvB/PqwWoAipYifPO53peKGm4k/img.png&quot; srcset=&quot;https://img1.daumcdn.net/thumb/R1280x0/?scode=mtistory2&amp;fname=https%3A%2F%2Fblog.kakaocdn.net%2Fdn%2FdXWW0v%2FbtsIZOb0IvB%2FPqwWoAipYifPO53peKGm4k%2Fimg.png&quot; onerror=&quot;this.onerror=null; this.src='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png'; this.srcset='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png';&quot; loading=&quot;lazy&quot; width=&quot;726&quot; height=&quot;139&quot; data-origin-width=&quot;856&quot; data-origin-height=&quot;164&quot;/&gt;&lt;/span&gt;&lt;/figure&gt;
&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;code&gt;deployment.yaml&lt;/code&gt; 파일에서 &lt;code&gt;replica&lt;/code&gt; 값을 2로 설정했기 때문에 2개의 Pod가 동작 중이며, &lt;code&gt;Deployment&lt;/code&gt; 리소스와 &lt;code&gt;Service&lt;/code&gt; 리소스 또한 정상적으로 실행 중인 것을 확인할 수 있다.&lt;/p&gt;
&lt;p&gt;&lt;figure class=&quot;imageblock alignCenter&quot; data-ke-mobileStyle=&quot;widthOrigin&quot; data-origin-width=&quot;1366&quot; data-origin-height=&quot;235&quot;&gt;&lt;span data-url=&quot;https://blog.kakaocdn.net/dn/d4u66a/btsIZKAQfyi/I1Dl0wzUjbje1YsXbyPKkk/img.png&quot; data-phocus=&quot;https://blog.kakaocdn.net/dn/d4u66a/btsIZKAQfyi/I1Dl0wzUjbje1YsXbyPKkk/img.png&quot;&gt;&lt;img src=&quot;https://blog.kakaocdn.net/dn/d4u66a/btsIZKAQfyi/I1Dl0wzUjbje1YsXbyPKkk/img.png&quot; srcset=&quot;https://img1.daumcdn.net/thumb/R1280x0/?scode=mtistory2&amp;fname=https%3A%2F%2Fblog.kakaocdn.net%2Fdn%2Fd4u66a%2FbtsIZKAQfyi%2FI1Dl0wzUjbje1YsXbyPKkk%2Fimg.png&quot; onerror=&quot;this.onerror=null; this.src='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png'; this.srcset='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png';&quot; loading=&quot;lazy&quot; width=&quot;808&quot; height=&quot;139&quot; data-origin-width=&quot;1366&quot; data-origin-height=&quot;235&quot;/&gt;&lt;/span&gt;&lt;/figure&gt;
&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;code&gt;image&lt;/code&gt; 또한 우리가 &lt;code&gt;values.yaml&lt;/code&gt;을 통해 동적으로 지정한 &lt;code&gt;nginx:latest&lt;/code&gt; 이미지가 적용되어 있는 것을 확인할 수 있다.&lt;/p&gt;
&lt;h2 data-ke-size=&quot;size26&quot;&gt;Release Template&lt;/h2&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;위에서 작성 예제에는 한 가지 문제점이 있는데, &lt;code&gt;deployment.yaml&lt;/code&gt;의 &lt;code&gt;metadata.name&lt;/code&gt; 값이 &lt;code&gt;nginx-deployment&lt;/code&gt;로 고정되어 있기 때문에 하나의 &lt;code&gt;release&lt;/code&gt;만 생성이 가능하다는 것이다.&lt;/p&gt;
&lt;pre class=&quot;yaml&quot;&gt;&lt;code&gt;apiVersion: apps/v1
kind: Deployment
metadata:
  name: nginx-deployment
  labels:
    app: nginx-for-helm
spec:
  selector:
    matchLabels:
      app: nginx-for-helm
  replicas: 2
  template:
    metadata:
      labels:
        app: nginx-for-helm
    spec:
      containers:
      - name: nginx
        image: {{ .Values.image }}
        ports:
        - containerPort: 80&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;code&gt;helm install first-nginx .&lt;/code&gt; 명령어를 실행한 뒤에 &lt;code&gt;helm install second-nginx .&lt;/code&gt; 명령어를 수행하게 되면 아래와 같이 에러가 발생하며 &lt;code&gt;release&lt;/code&gt;에 실패하는 것을 확인할 수 있다.&lt;/p&gt;
&lt;p&gt;&lt;figure class=&quot;imageblock alignCenter&quot; data-ke-mobileStyle=&quot;widthOrigin&quot; data-origin-width=&quot;1603&quot; data-origin-height=&quot;231&quot;&gt;&lt;span data-url=&quot;https://blog.kakaocdn.net/dn/cFaSt8/btsI1GcadFW/0xl74jGc9mGbu7VapzjSM1/img.png&quot; data-phocus=&quot;https://blog.kakaocdn.net/dn/cFaSt8/btsI1GcadFW/0xl74jGc9mGbu7VapzjSM1/img.png&quot;&gt;&lt;img src=&quot;https://blog.kakaocdn.net/dn/cFaSt8/btsI1GcadFW/0xl74jGc9mGbu7VapzjSM1/img.png&quot; srcset=&quot;https://img1.daumcdn.net/thumb/R1280x0/?scode=mtistory2&amp;fname=https%3A%2F%2Fblog.kakaocdn.net%2Fdn%2FcFaSt8%2FbtsI1GcadFW%2F0xl74jGc9mGbu7VapzjSM1%2Fimg.png&quot; onerror=&quot;this.onerror=null; this.src='//t1.daumcdn.net/tistory_admin/static/images/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;116&quot; data-origin-width=&quot;1603&quot; data-origin-height=&quot;231&quot;/&gt;&lt;/span&gt;&lt;/figure&gt;
&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;이러한 문제점을 해결하기 위해 Release Template 문법이 필요하다. 사용 방법은 &lt;code&gt;values.yaml&lt;/code&gt;을 사용했을 때와 거의 동일하다. 동적으로 변경하고자 하는 부분에 문법을 적용시키면 된다.&lt;/p&gt;
&lt;pre class=&quot;yaml&quot;&gt;&lt;code&gt;# deployment.yaml
apiVersion: apps/v1
kind: Deployment
metadata:
  name: {{ .Release.Name }}
  labels:
    app: {{ .Values.app-name }}
spec:
  selector:
    matchLabels:
      app: {{ .Values.app-name }}
  replicas: 2
  template:
    metadata:
      labels:
        app: {{ .Values.app-name }}
    spec:
      containers:
      - name: nginx
        image: {{ .Values.image }}
        ports:
        - containerPort: 

---
# service.yaml
apiVersion: v1
kind: Service
metadata:
  name: {{ .Release.Name }}
  labels:
    run: {{ .Values.app-name }}
spec:
  ports:
  - port: 80
    protocol: TCP
  selector:
    run: {{ .Values.app-name }}&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;code&gt;metadata.name&lt;/code&gt;을 동일하게 설정했고 &lt;code&gt;nginx-for-helm&lt;/code&gt;을 추출하여 &lt;code&gt;values.yaml&lt;/code&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;Release Template을 사용한 방식으로 &lt;code&gt;release&lt;/code&gt;를 배포하게 되면 &lt;code&gt;first-nginx&lt;/code&gt;와 &lt;code&gt;second-nginx&lt;/code&gt; 모두 잘 배포가 되는 것을 확인할 수 있다.&lt;/p&gt;
&lt;p&gt;&lt;figure class=&quot;imageblock alignCenter&quot; data-ke-mobileStyle=&quot;widthOrigin&quot; data-origin-width=&quot;883&quot; data-origin-height=&quot;320&quot;&gt;&lt;span data-url=&quot;https://blog.kakaocdn.net/dn/DIWQn/btsIZJ2YpEU/9PKW9YRb98UdlCVy3NQ6mK/img.png&quot; data-phocus=&quot;https://blog.kakaocdn.net/dn/DIWQn/btsIZJ2YpEU/9PKW9YRb98UdlCVy3NQ6mK/img.png&quot;&gt;&lt;img src=&quot;https://blog.kakaocdn.net/dn/DIWQn/btsIZJ2YpEU/9PKW9YRb98UdlCVy3NQ6mK/img.png&quot; srcset=&quot;https://img1.daumcdn.net/thumb/R1280x0/?scode=mtistory2&amp;fname=https%3A%2F%2Fblog.kakaocdn.net%2Fdn%2FDIWQn%2FbtsIZJ2YpEU%2F9PKW9YRb98UdlCVy3NQ6mK%2Fimg.png&quot; onerror=&quot;this.onerror=null; this.src='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png'; this.srcset='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png';&quot; loading=&quot;lazy&quot; width=&quot;883&quot; height=&quot;320&quot; data-origin-width=&quot;883&quot; data-origin-height=&quot;320&quot;/&gt;&lt;/span&gt;&lt;/figure&gt;
&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;code&gt;Deployment&lt;/code&gt;와 &lt;code&gt;Service&lt;/code&gt;, &lt;code&gt;Pod&lt;/code&gt; 또한 성공적으로 생성됐다. 배포된 리소스의 이름 또한 &lt;code&gt;{{ .Release.Name }}&lt;/code&gt;을 통해 지정한대로 변경되어 실행되는 것을 확인해볼 수 있다.&lt;/p&gt;
&lt;p&gt;&lt;figure class=&quot;imageblock alignCenter&quot; data-ke-mobileStyle=&quot;widthOrigin&quot; data-origin-width=&quot;1309&quot; data-origin-height=&quot;327&quot;&gt;&lt;span data-url=&quot;https://blog.kakaocdn.net/dn/NfAmA/btsI0f1sT3U/k7SZe3bBbulwWzG5rRgH11/img.png&quot; data-phocus=&quot;https://blog.kakaocdn.net/dn/NfAmA/btsI0f1sT3U/k7SZe3bBbulwWzG5rRgH11/img.png&quot;&gt;&lt;img src=&quot;https://blog.kakaocdn.net/dn/NfAmA/btsI0f1sT3U/k7SZe3bBbulwWzG5rRgH11/img.png&quot; srcset=&quot;https://img1.daumcdn.net/thumb/R1280x0/?scode=mtistory2&amp;fname=https%3A%2F%2Fblog.kakaocdn.net%2Fdn%2FNfAmA%2FbtsI0f1sT3U%2Fk7SZe3bBbulwWzG5rRgH11%2Fimg.png&quot; onerror=&quot;this.onerror=null; this.src='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png'; this.srcset='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png';&quot; loading=&quot;lazy&quot; width=&quot;836&quot; height=&quot;209&quot; data-origin-width=&quot;1309&quot; data-origin-height=&quot;327&quot;/&gt;&lt;/span&gt;&lt;/figure&gt;
&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;Release Template 문법은 이외에도 여러가지 기능이 많이 존재하는데 대표적으로 특정 Namespace에 배포할 수 있는 &lt;code&gt;{{ .Release.Namespace }}&lt;/code&gt;가 존재한다. 사용하기 위해서는 템플릿 파일에 &lt;code&gt;metadata.namespace&lt;/code&gt; 항목을 추가하고 &lt;code&gt;{{ .Release.Namespace }}&lt;/code&gt;을 적용해주면 된다.&lt;/p&gt;
&lt;pre class=&quot;yaml&quot;&gt;&lt;code&gt;# deployment.yaml
apiVersion: apps/v1
kind: Deployment
metadata:
  name: {{ .Release.Name }}
  namespace: {{ .Release.Namespace }}
  labels:
    app: {{ .Values.app-name }}
spec:
  selector:
    matchLabels:
      app: {{ .Values.app-name }}
  replicas: 2
  template:
    metadata:
      labels:
        app: {{ .Values.app-name }}
    spec:
      containers:
      - name: nginx
        image: {{ .Values.image }}
        ports:
        - containerPort: 

---
# service.yaml
apiVersion: v1
kind: Service
metadata:
  name: {{ .Release.Name }}
  namespace: {{ .Release.Namespace }}
  labels:
    run: {{ .Values.app-name }}
spec:
  ports:
  - port: 80
    protocol: TCP
  selector:
    run: {{ .Values.app-name }}&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;code&gt;{{ .Release.Namespace }}&lt;/code&gt;을 사용하기 위해서는 Namespace가 사전에 생성되어 있어야 한다. 만약 Helm을 실행하는 동시에 Namespace를 생성하고자 한다면 &lt;code&gt;--create-namespace -n&lt;/code&gt; 옵션을 사용하면 되지만, 이는 언제 누가 Namespace를 생성했는지 추적하기가 어렵기 때문에 무분별한 사용은 지양하는 편이 좋다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;기존에 생성되어 있던 Namespace인 helmprac에 배포하고자 한다면, &lt;code&gt;helm install -n helmprac first-nginx .&lt;/code&gt; 명령어를 사용하면 된다.&lt;/p&gt;
&lt;h2 data-ke-size=&quot;size26&quot;&gt;Values.yaml override&lt;/h2&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;Helm Chart의 기존 &lt;code&gt;values.yaml&lt;/code&gt;파일의 일부 값을 변경하고자 할 때 사용할 수 있는 방법이다. 예를 들어 기존에 설정해둔 &lt;code&gt;image: nginx:latest&lt;/code&gt;를 &lt;code&gt;image: nginx:stable&lt;/code&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;code&gt;--set&lt;/code&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;code&gt;helm install --set image=nginx:stable first-nginx .&lt;/code&gt; 명령어를 통해 배포되는 Nginx의 이미지의 버전을 stable로 변경할 수 있다.&lt;/p&gt;
&lt;p&gt;&lt;figure class=&quot;imageblock alignCenter&quot; data-ke-mobileStyle=&quot;widthOrigin&quot; data-origin-width=&quot;1100&quot; data-origin-height=&quot;239&quot;&gt;&lt;span data-url=&quot;https://blog.kakaocdn.net/dn/Wiha0/btsI06oQ55e/kc9fa1kXaIJSFpXFKKLpm0/img.png&quot; data-phocus=&quot;https://blog.kakaocdn.net/dn/Wiha0/btsI06oQ55e/kc9fa1kXaIJSFpXFKKLpm0/img.png&quot;&gt;&lt;img src=&quot;https://blog.kakaocdn.net/dn/Wiha0/btsI06oQ55e/kc9fa1kXaIJSFpXFKKLpm0/img.png&quot; srcset=&quot;https://img1.daumcdn.net/thumb/R1280x0/?scode=mtistory2&amp;fname=https%3A%2F%2Fblog.kakaocdn.net%2Fdn%2FWiha0%2FbtsI06oQ55e%2Fkc9fa1kXaIJSFpXFKKLpm0%2Fimg.png&quot; onerror=&quot;this.onerror=null; this.src='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png'; this.srcset='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png';&quot; loading=&quot;lazy&quot; width=&quot;782&quot; height=&quot;170&quot; data-origin-width=&quot;1100&quot; data-origin-height=&quot;239&quot;/&gt;&lt;/span&gt;&lt;/figure&gt;
&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;하지만 &lt;code&gt;--set&lt;/code&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;code&gt;helm install -f &amp;lt;file-path&amp;gt; &amp;lt;release-name&amp;gt; &amp;lt;chart-name&amp;gt;&lt;/code&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;code&gt;my-values.yaml&lt;/code&gt; 파일을 생성하고 동적으로 변경하고자 하는 값인 Nginx의 이미지를 입력하자. &lt;code&gt;values.yaml&lt;/code&gt;을 작성할 때와 동일하게 &lt;code&gt;image: nginx:stable&lt;/code&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;code&gt;helm install -f my-values.yaml first-nginx .&lt;/code&gt; 명령어를 실행시키면, 다음과 같이 Nginx의 이미지 버전이 stable로 변경되어 배포되는 것을 확인할 수 있다.&lt;/p&gt;
&lt;p&gt;&lt;figure class=&quot;imageblock alignCenter&quot; data-ke-mobileStyle=&quot;widthOrigin&quot; data-origin-width=&quot;1036&quot; data-origin-height=&quot;304&quot;&gt;&lt;span data-url=&quot;https://blog.kakaocdn.net/dn/bpkk3w/btsI04q4pFy/3dWg6rSFWzJBKIxes1XWr1/img.png&quot; data-phocus=&quot;https://blog.kakaocdn.net/dn/bpkk3w/btsI04q4pFy/3dWg6rSFWzJBKIxes1XWr1/img.png&quot;&gt;&lt;img src=&quot;https://blog.kakaocdn.net/dn/bpkk3w/btsI04q4pFy/3dWg6rSFWzJBKIxes1XWr1/img.png&quot; srcset=&quot;https://img1.daumcdn.net/thumb/R1280x0/?scode=mtistory2&amp;fname=https%3A%2F%2Fblog.kakaocdn.net%2Fdn%2Fbpkk3w%2FbtsI04q4pFy%2F3dWg6rSFWzJBKIxes1XWr1%2Fimg.png&quot; onerror=&quot;this.onerror=null; this.src='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png'; this.srcset='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png';&quot; loading=&quot;lazy&quot; width=&quot;816&quot; height=&quot;239&quot; data-origin-width=&quot;1036&quot; data-origin-height=&quot;304&quot;/&gt;&lt;/span&gt;&lt;/figure&gt;
&lt;/p&gt;</description>
      <category>DevOps</category>
      <category>DevOps</category>
      <category>helm</category>
      <category>k8s</category>
      <category>인프라</category>
      <category>쿠버네티스</category>
      <category>헬름</category>
      <author>겨울바람_</author>
      <guid isPermaLink="true">https://breeze-winter.tistory.com/36</guid>
      <comments>https://breeze-winter.tistory.com/36#entry36comment</comments>
      <pubDate>Sat, 10 Aug 2024 19:43:35 +0900</pubDate>
    </item>
    <item>
      <title>[Terraform] 테라폼 반복문</title>
      <link>https://breeze-winter.tistory.com/35</link>
      <description>&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;603&quot;&gt;&lt;span data-url=&quot;https://blog.kakaocdn.net/dn/WcQn2/btsIZrVCcif/WW2FcPmYKKn2B2pM3uLvCk/img.png&quot; data-phocus=&quot;https://blog.kakaocdn.net/dn/WcQn2/btsIZrVCcif/WW2FcPmYKKn2B2pM3uLvCk/img.png&quot;&gt;&lt;img src=&quot;https://blog.kakaocdn.net/dn/WcQn2/btsIZrVCcif/WW2FcPmYKKn2B2pM3uLvCk/img.png&quot; srcset=&quot;https://img1.daumcdn.net/thumb/R1280x0/?scode=mtistory2&amp;fname=https%3A%2F%2Fblog.kakaocdn.net%2Fdn%2FWcQn2%2FbtsIZrVCcif%2FWW2FcPmYKKn2B2pM3uLvCk%2Fimg.png&quot; onerror=&quot;this.onerror=null; this.src='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png'; this.srcset='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png';&quot; loading=&quot;lazy&quot; width=&quot;522&quot; height=&quot;246&quot; data-origin-width=&quot;1280&quot; data-origin-height=&quot;603&quot;/&gt;&lt;/span&gt;&lt;/figure&gt;
&lt;/p&gt;
&lt;h2 data-ke-size=&quot;size26&quot;&gt;Terraform 반복문&lt;/h2&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;list 형태의 값 목록이나 Key-Value 형태의 문자열 집합인 데이터가 있는 경우 반복문을 통해 관리할 수 있다. 또한, 단순한 자료구조뿐만 아니라 리소스 내에 생성된 블록에 대한 것도 반복문을 통해 코드의 중복 없이 동적으로 생성이 가능하다.&lt;/p&gt;
&lt;h3 data-ke-size=&quot;size23&quot;&gt;count&lt;/h3&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;리소스 또는 모듈 블록에 &lt;code&gt;count&lt;/code&gt;값이 정수인 인수가 포함된 경우 선언된 정수 값만큼 리소스나 모듈을 생성하게 된다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;code&gt;count&lt;/code&gt;에서 생성되는 참조값은 &lt;code&gt;count.index&lt;/code&gt;이며, 반복하는 경우 0부터 1씩 증가해 인덱스가 부여된다. &lt;code&gt;main.tf&lt;/code&gt;를 아래와 같이 작성해보자.&lt;/p&gt;
&lt;pre class=&quot;nix&quot;&gt;&lt;code&gt;resource &quot;local_file&quot; &quot;abc&quot; {
    count = 5
    content = &quot;abc&quot;
    filename = &quot;${path.module}/abc.txt&quot;
}&lt;/code&gt;&lt;/pre&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;위 코드를 실행시켰을 때 목표로 하는 결과는 다섯 개의 파일이 생성되는 것이지만, 파일명에는 변화가 없기 때문에 하나의 파일만 존재하게 된다. 본래의 의도대로 실행되도록 하려면 다음과 같이 &lt;code&gt;count.index&lt;/code&gt; 값을 추가하면 된다.&lt;/p&gt;
&lt;pre class=&quot;nix&quot;&gt;&lt;code&gt;resource &quot;local_file&quot; &quot;abc&quot; {
    count = 5
    content = &quot;abc&quot;
    filename = &quot;${path.module}/abc${count.index}.txt&quot;
}&lt;/code&gt;&lt;/pre&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;br /&gt;&lt;code&gt;count&lt;/code&gt;를 프로그래밍 언어의 반복문 인덱스처럼 활용하는 방법은 다음과 같다.&lt;/p&gt;
&lt;pre class=&quot;routeros&quot;&gt;&lt;code&gt;variable &quot;names&quot; {
    type = list(string)
    default = [&quot;a&quot;, &quot;b&quot;, &quot;c&quot;]
}

resource &quot;local_file&quot; &quot;abc&quot; {
    count = length(var.names)
    content = &quot;abc&quot;
    filename = &quot;${path.module}/abc-${var.names[count.index]}.txt&quot;
}

resource &quot;local_file&quot; &quot;def&quot; {
    count = length(var.names)
    content = local_file.abc[count.index].content
    filename = &quot;${path.module}/def-${element(var.names, count.index)}.txt&quot;
}&lt;/code&gt;&lt;/pre&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;code&gt;local_file.abc&lt;/code&gt;와 &lt;code&gt;local_file.def&lt;/code&gt;는 &lt;code&gt;var.names&lt;/code&gt;에 선언되는 값에 영향을 받아 동일한 개수만큼 생성하게 된다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;code&gt;count&lt;/code&gt;로 생성되는 리소스의 경우 &lt;code&gt;&quot;&amp;lt;리소스 타입&amp;gt;.&amp;lt;이름&amp;gt;[&amp;lt;인덱스 번호&amp;gt;]&lt;/code&gt;, 모듈의 경우 &lt;code&gt;module.&amp;lt;모듈 이름&amp;gt;[&amp;lt;인덱스 번호&amp;gt;]&lt;/code&gt;로 해당 리소스의 값을 참조한다.&lt;/p&gt;
&lt;h3 data-ke-size=&quot;size23&quot;&gt;for_each&lt;/h3&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;리소스 또는 모듈 블록에서 &lt;code&gt;for_each&lt;/code&gt;에 입력된 데이터 형태가 &lt;code&gt;map&lt;/code&gt;또는 &lt;code&gt;set&lt;/code&gt;이면, 선언된 key 값 개수만큼 리소스를 생성하게 된다.&lt;/p&gt;
&lt;pre class=&quot;nix&quot;&gt;&lt;code&gt;resource &quot;local_file&quot; &quot;abc&quot; {
    for_each {
        a = &quot;content a&quot;
        b = &quot;content b&quot;
    }
    content = each.value
    filename = &quot;${path.module}/${each.key}.txt&quot;
}&lt;/code&gt;&lt;/pre&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;생성되는 리소스의 경우 &lt;code&gt;&amp;lt;리소스 타입&amp;gt;.&amp;lt;이름&amp;gt;[&amp;lt;key&amp;gt;]&lt;/code&gt;, 모듈의 경우 &lt;code&gt;module.&amp;lt;모듈 이름&amp;gt;[&amp;lt;key&amp;gt;]&lt;/code&gt;로 해당 리소스의 값을 참조한다. 이 참조 방식을 통해 리소스 간 종속성을 정의하기도 하고 변수로 다른 리소스에서 사용하거나 출력을 위한 결과 값으로 사용한다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;code&gt;for_each&lt;/code&gt;를 활용한 반복 참조 예시는 다음과 같다.&lt;/p&gt;
&lt;pre class=&quot;routeros&quot;&gt;&lt;code&gt;variable &quot;names&quot; {
    default = {
        a = &quot;content a&quot;
        b = &quot;content b&quot;
        c = &quot;content c&quot;
    }
}

resource &quot;local_file&quot; &quot;abc&quot; {
    for_each = var.names
    content = each.value
    filename = &quot;${path.module}/abc-${each.key}.txt&quot;
}

resource &quot;local_file&quot; &quot;def&quot; {
    for_each = local_file.abc
    content = each.value.content
    filename = &quot;${path.module}/def-${each.key}.txt&quot;
}&lt;/code&gt;&lt;/pre&gt;
&lt;h3 data-ke-size=&quot;size23&quot;&gt;for&lt;/h3&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;code&gt;for&lt;/code&gt;문은 복합 형식 값의 형태를 변환하는데 사용된다. 예를 들어 &lt;code&gt;list&lt;/code&gt; 값의 포맷을 변경하거나 특정 접두사를 추가할 수도 있고, &lt;code&gt;output&lt;/code&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;code&gt;list&lt;/code&gt;의 경우 값 또는 인덱스와 값을 반환한다.&lt;/li&gt;
&lt;li&gt;&lt;code&gt;map&lt;/code&gt;의 경우 키 또는 키와 값을 반환한다.&lt;/li&gt;
&lt;li&gt;&lt;code&gt;set&lt;/code&gt;의 경우 키 값을 반환한다.&lt;/li&gt;
&lt;/ul&gt;
&lt;pre class=&quot;routeros&quot;&gt;&lt;code&gt;variable &quot;names&quot; {
    default = [&quot;a&quot;, &quot;b&quot;, &quot;c&quot;]
}

resource &quot;local_file&quot; &quot;abc&quot; {
    content = jsonencode([for s in var.names: upper(s)]) # result: [&quot;A&quot;, &quot;B&quot;, &quot;C&quot;]
    filename = &quot;${path.module}/abc.txt&quot;
}&lt;/code&gt;&lt;/pre&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;code&gt;for&lt;/code&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;code&gt;list&lt;/code&gt; 타입의 경우 반환 받는 값이 하나로 되어 있으면 값을, 두 개인 경우 앞의 인수가 인덱스를 반환하고 두 번째 인수가 값을 반환한다.&lt;/li&gt;
&lt;li&gt;&lt;code&gt;map&lt;/code&gt; 타입의 경우 반환 받는 값이 하나로 되어 있으면 키를, 두 개인 경우 앞의 인수가 키를 반환하고 두 번째 인수가 값을 반환한다.&lt;/li&gt;
&lt;li&gt;&lt;code&gt;for&lt;/code&gt;문의 결과값은 &lt;code&gt;for&lt;/code&gt;문을 감싸고 있는 괄호의 종류에 따라 다르게 결정된다.
&lt;ul style=&quot;list-style-type: disc;&quot; data-ke-list-type=&quot;disc&quot;&gt;
&lt;li&gt;&lt;code&gt;[ ]&lt;/code&gt; 형태로 감싸고 있을 경우 &lt;code&gt;tuple&lt;/code&gt;로 반환된다.&lt;/li&gt;
&lt;li&gt;&lt;code&gt;{ }&lt;/code&gt; 형태로 감싸고 있을 경우 &lt;code&gt;object&lt;/code&gt; 형태로 반환된다.
&lt;ul style=&quot;list-style-type: disc;&quot; data-ke-list-type=&quot;disc&quot;&gt;
&lt;li&gt;&lt;code&gt;object&lt;/code&gt; 형태의 경우 키와 값을 =&amp;gt; 기호로 구분한다.&lt;/li&gt;
&lt;/ul&gt;
&lt;/li&gt;
&lt;/ul&gt;
&lt;/li&gt;
&lt;/ul&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;code&gt;list&lt;/code&gt; 타입에 적용할 수 있는 규칙을 간단한 예제를 통해서 확인해보자.&lt;/p&gt;
&lt;pre class=&quot;routeros&quot;&gt;&lt;code&gt;variable &quot;names&quot; {
  type = list(string)
  default = [&quot;a&quot;, &quot;b&quot;]
}

output &quot;tuple_upper_value&quot; {
  value = [for v in var.names: upper(v)]
} 

output &quot;tuple_index_value&quot; {
  value = [for i, v in var.names: &quot;${i}: ${v}&quot;]
}

output &quot;object_upper_value&quot; {
  value = {for v in var.names: v =&amp;gt; upper(v)}
}

output &quot;tuple_with_if&quot; {
  value = [for v in var.names: upper(v) if v != &quot;a&quot;]
}&lt;/code&gt;&lt;/pre&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;위의 HCL을 &lt;code&gt;terraform apply&lt;/code&gt; 명령어를 통해 실행할 경우 아래와 같은 &lt;code&gt;output&lt;/code&gt; 결과가 나오게 된다. 작성된 순서와 무관하게 결과가 표시되기 때문에 &lt;code&gt;output&lt;/code&gt;의 이름을 잘 확인해보자.&lt;/p&gt;
&lt;p&gt;&lt;figure class=&quot;imageblock alignCenter&quot; data-ke-mobileStyle=&quot;widthOrigin&quot; data-origin-width=&quot;269&quot; data-origin-height=&quot;404&quot;&gt;&lt;span data-url=&quot;https://blog.kakaocdn.net/dn/uPj6d/btsI0vvX0KA/UwXKzSVXnoAuzNDnVwRMyk/img.png&quot; data-phocus=&quot;https://blog.kakaocdn.net/dn/uPj6d/btsI0vvX0KA/UwXKzSVXnoAuzNDnVwRMyk/img.png&quot;&gt;&lt;img src=&quot;https://blog.kakaocdn.net/dn/uPj6d/btsI0vvX0KA/UwXKzSVXnoAuzNDnVwRMyk/img.png&quot; srcset=&quot;https://img1.daumcdn.net/thumb/R1280x0/?scode=mtistory2&amp;fname=https%3A%2F%2Fblog.kakaocdn.net%2Fdn%2FuPj6d%2FbtsI0vvX0KA%2FUwXKzSVXnoAuzNDnVwRMyk%2Fimg.png&quot; onerror=&quot;this.onerror=null; this.src='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png'; this.srcset='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png';&quot; loading=&quot;lazy&quot; width=&quot;221&quot; height=&quot;332&quot; data-origin-width=&quot;269&quot; data-origin-height=&quot;404&quot;/&gt;&lt;/span&gt;&lt;/figure&gt;
&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;이번에는 좀 더 복잡한 예제를 통해 확인해보자.&lt;/p&gt;
&lt;pre class=&quot;routeros&quot;&gt;&lt;code&gt;variable &quot;members&quot; {
  type = map(object({
    role = string
    group = string
  }))

  default = {
    &quot;memberA&quot; = {
      role = &quot;member&quot;,
      group = &quot;dev&quot;
    }
    &quot;adminB&quot; = {
      role = &quot;admin&quot;,
      group = &quot;dev&quot;
    }
    &quot;devopsC&quot; = {
      role = &quot;member&quot;,
      group = &quot;devops&quot;
    }
  }
}

output &quot;get_all_group&quot; {
  value = [for k, v in var.members: &quot;${k} is a ${v.group}&quot;]
} 

output &quot;get_only_admin&quot; {
  value = {
    for name, user in var.members: name =&amp;gt; user.role
    if user.role == &quot;admin&quot;
  }
}

output &quot;C&quot; {
  value = {
    for name, user in var.members: user.role =&amp;gt; name...
  }
}&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;위의 HCL을 &lt;code&gt;terraform apply&lt;/code&gt; 명령어를 통해 실행할 경우 아래와 같은 &lt;code&gt;output&lt;/code&gt; 결과가 나오게 된다. 작성된 순서와 무관하게 결과가 표시되기 때문에 &lt;code&gt;output&lt;/code&gt;의 이름을 잘 확인해보자.&lt;/p&gt;
&lt;p&gt;&lt;figure class=&quot;imageblock alignCenter&quot; data-ke-mobileStyle=&quot;widthOrigin&quot; data-origin-width=&quot;322&quot; data-origin-height=&quot;448&quot;&gt;&lt;span data-url=&quot;https://blog.kakaocdn.net/dn/xSX1S/btsI1ly5rjY/5GaKzYEUfZIXVKc98kAY41/img.png&quot; data-phocus=&quot;https://blog.kakaocdn.net/dn/xSX1S/btsI1ly5rjY/5GaKzYEUfZIXVKc98kAY41/img.png&quot;&gt;&lt;img src=&quot;https://blog.kakaocdn.net/dn/xSX1S/btsI1ly5rjY/5GaKzYEUfZIXVKc98kAY41/img.png&quot; srcset=&quot;https://img1.daumcdn.net/thumb/R1280x0/?scode=mtistory2&amp;fname=https%3A%2F%2Fblog.kakaocdn.net%2Fdn%2FxSX1S%2FbtsI1ly5rjY%2F5GaKzYEUfZIXVKc98kAY41%2Fimg.png&quot; onerror=&quot;this.onerror=null; this.src='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png'; this.srcset='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png';&quot; loading=&quot;lazy&quot; width=&quot;276&quot; height=&quot;384&quot; data-origin-width=&quot;322&quot; data-origin-height=&quot;448&quot;/&gt;&lt;/span&gt;&lt;/figure&gt;
&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;특히 주목해서 살펴봐야 할 &lt;code&gt;output&lt;/code&gt;은 &lt;code&gt;get_member_group_by_role&lt;/code&gt;인데, &lt;code&gt;name&lt;/code&gt; 뒤에 &lt;code&gt;...&lt;/code&gt;이 붙은 것을 확인할 수 있다. 이는 &lt;code&gt;object&lt;/code&gt; 타입으로 결과를 반환하는 경우 키 값은 고유해야 하기 때문에 값 뒤에 그룹화 모드 심볼인 &lt;code&gt;...&lt;/code&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;code&gt;...&lt;/code&gt;는 SQL의 &lt;code&gt;group by&lt;/code&gt;문과 동일한 역할을 한다.&lt;/p&gt;
&lt;h3 data-ke-size=&quot;size23&quot;&gt;dynamic&lt;/h3&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;테라폼을 통해 리소스를 구성하다 보면 &lt;code&gt;count&lt;/code&gt;나 &lt;code&gt;for_each&lt;/code&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;code&gt;dynamic&lt;/code&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;code&gt;dynamic&lt;/code&gt; 블록을 작성하려면, 기존 블록의 속성 이름을 &lt;code&gt;dynamic&lt;/code&gt; 블록의 이름으로 선언하고 기존 블록 속성에 정의되는 내용을 &lt;code&gt;content&lt;/code&gt; 블록에 작성한다. 반복 선언에 사용되는 반복문 구문은 &lt;code&gt;for_each&lt;/code&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;code&gt;for_each&lt;/code&gt;문 사용시 각 속성에 &lt;code&gt;key&lt;/code&gt;, &lt;code&gt;value&lt;/code&gt;가 할당되었다면, &lt;code&gt;dynamic&lt;/code&gt;에서는 &lt;code&gt;dynamic&lt;/code&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;code&gt;dynamic&lt;/code&gt;의 사용법에 대해 알아보자.&lt;/p&gt;
&lt;pre class=&quot;routeros&quot;&gt;&lt;code&gt;data &quot;archive_file&quot; &quot;dotfiles&quot; {
  type = &quot;zip&quot;
  output_path = &quot;${path.module}/dotfile.zip&quot;

  source {
    content = &quot;hello a&quot;
    filename = &quot;${path.module}/a.txt&quot;
  }

  source {
    content = &quot;hello b&quot;
    filename = &quot;${path.module}/b.txt&quot;
  }

  source {
    content = &quot;hello c&quot;
    filename = &quot;${path.module}/c.txt&quot;
  }
}&lt;/code&gt;&lt;/pre&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;위의 &lt;code&gt;HCL&lt;/code&gt;을 &lt;code&gt;terraform apply&lt;/code&gt;를 통해 실행시키게 되면 경로에 &lt;code&gt;dotfiles.zip&lt;/code&gt; 이라는 압축 파일이 생성되고 해당 압축 파일을 풀어보면 리소스 내에 정의된 &lt;code&gt;source&lt;/code&gt; 블록에 맞게 텍스트 파일이 생성된 것을 확인할 수 있다&lt;/p&gt;
&lt;p&gt;&lt;figure class=&quot;imageblock alignCenter&quot; data-ke-mobileStyle=&quot;widthOrigin&quot; data-origin-width=&quot;352&quot; data-origin-height=&quot;196&quot;&gt;&lt;span data-url=&quot;https://blog.kakaocdn.net/dn/bhT3Lr/btsIZZxtOPf/mV5cYkf71wk2COoWtNguh0/img.png&quot; data-phocus=&quot;https://blog.kakaocdn.net/dn/bhT3Lr/btsIZZxtOPf/mV5cYkf71wk2COoWtNguh0/img.png&quot;&gt;&lt;img src=&quot;https://blog.kakaocdn.net/dn/bhT3Lr/btsIZZxtOPf/mV5cYkf71wk2COoWtNguh0/img.png&quot; srcset=&quot;https://img1.daumcdn.net/thumb/R1280x0/?scode=mtistory2&amp;fname=https%3A%2F%2Fblog.kakaocdn.net%2Fdn%2FbhT3Lr%2FbtsIZZxtOPf%2FmV5cYkf71wk2COoWtNguh0%2Fimg.png&quot; onerror=&quot;this.onerror=null; this.src='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png'; this.srcset='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png';&quot; loading=&quot;lazy&quot; width=&quot;338&quot; height=&quot;188&quot; data-origin-width=&quot;352&quot; data-origin-height=&quot;196&quot;/&gt;&lt;/span&gt;&lt;/figure&gt;
&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;참고로 처음 &lt;code&gt;archive&lt;/code&gt; 프로바이더를 사용하는 경우에는 잊지 말고 &lt;code&gt;terraform init&lt;/code&gt;을 실행하자.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;이제 &lt;code&gt;data&lt;/code&gt; 리소스 내에 반복적으로 정의되어 있는 &lt;code&gt;source&lt;/code&gt; 블록을 &lt;code&gt;dynamic&lt;/code&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;code&gt;content&lt;/code&gt;와 &lt;code&gt;filename&lt;/code&gt; 부분을 추출하여 &lt;code&gt;variable&lt;/code&gt; 리소스로 만든다.&lt;/p&gt;
&lt;pre class=&quot;routeros&quot;&gt;&lt;code&gt;variable &quot;names&quot; {
    default = {
        a = &quot;hello a&quot;
        b = &quot;hello b&quot;
        c = &quot;hello c&quot;
    }
}

data &quot;archive_file&quot; &quot;dotfiles&quot; {
  type = &quot;zip&quot;
  output_path = &quot;${path.module}/dotfile.zip&quot;

  source {
    content = [ ]
    filename = &quot;${path.module}/[ ]&quot;
  }
}&lt;/code&gt;&lt;/pre&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;이제 반복적으로 생성되는 &lt;code&gt;source&lt;/code&gt; 리소스에 &lt;code&gt;dynamic&lt;/code&gt;을 붙이고 &lt;code&gt;for_each&lt;/code&gt;로 &lt;code&gt;variable&lt;/code&gt; 리소스에서 반복 구문을 추출한다.&lt;/p&gt;
&lt;pre class=&quot;routeros&quot;&gt;&lt;code&gt;variable &quot;names&quot; {
    default = {
        a = &quot;hello a&quot;
        b = &quot;hello b&quot;
        c = &quot;hello c&quot;
    }
}

data &quot;archive_file&quot; &quot;dotfiles&quot; {
  type = &quot;zip&quot;
  output_path = &quot;${path.module}/dotfile.zip&quot;

  dynamic &quot;source&quot; {
    for_each = var.names
    content = {
        content = source.value
        filename = &quot;${path.module}/${source.key}.txt&quot;        
    }
  }
}&lt;/code&gt;&lt;/pre&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;해당 코드를 실행시켜보면 이전에 &lt;code&gt;dynamic&lt;/code&gt;없이 작성했던 코드와 동일하게 동작하는 것을 확인할 수 있다. 이러한 반복문을 적절히 사용하는 것으로 테라폼 사용 시 중복되는 코드를 줄여 유지 보수 및 확장에 용이한 코드 작성이 가능해진다.&lt;/p&gt;</description>
      <category>DevOps</category>
      <category>AWS</category>
      <category>DevOps</category>
      <category>IAC</category>
      <category>terraform</category>
      <category>데브옵스</category>
      <category>반복문</category>
      <category>인프라</category>
      <category>클라우드</category>
      <category>테라폼</category>
      <author>겨울바람_</author>
      <guid isPermaLink="true">https://breeze-winter.tistory.com/35</guid>
      <comments>https://breeze-winter.tistory.com/35#entry35comment</comments>
      <pubDate>Fri, 9 Aug 2024 21:34:36 +0900</pubDate>
    </item>
    <item>
      <title>[Terraform] 테라폼 기초 (1)</title>
      <link>https://breeze-winter.tistory.com/34</link>
      <description>&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;603&quot;&gt;&lt;span data-url=&quot;https://blog.kakaocdn.net/dn/udYOw/btsISstwQ3v/JUyTOQONx102PrA9K7kJCK/img.png&quot; data-phocus=&quot;https://blog.kakaocdn.net/dn/udYOw/btsISstwQ3v/JUyTOQONx102PrA9K7kJCK/img.png&quot;&gt;&lt;img src=&quot;https://blog.kakaocdn.net/dn/udYOw/btsISstwQ3v/JUyTOQONx102PrA9K7kJCK/img.png&quot; srcset=&quot;https://img1.daumcdn.net/thumb/R1280x0/?scode=mtistory2&amp;fname=https%3A%2F%2Fblog.kakaocdn.net%2Fdn%2FudYOw%2FbtsISstwQ3v%2FJUyTOQONx102PrA9K7kJCK%2Fimg.png&quot; onerror=&quot;this.onerror=null; this.src='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png'; this.srcset='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png';&quot; loading=&quot;lazy&quot; width=&quot;522&quot; height=&quot;246&quot; data-origin-width=&quot;1280&quot; data-origin-height=&quot;603&quot;/&gt;&lt;/span&gt;&lt;/figure&gt;
&lt;/p&gt;
&lt;h2 data-ke-size=&quot;size26&quot;&gt;Terraform init&lt;/h2&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;code&gt;terraform init&lt;/code&gt; 명령어는 테라폼 구성 파일이 있는 작업 디렉토리를 초기화하는데 사용한다. 이 작업을 실행하는 디렉토리를&lt;/p&gt;
&lt;p&gt;&lt;mark style=&quot;background: #D2B3FFA6;&quot;&gt;루트 모듈&lt;/mark&gt;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;이라고 부른다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;b&gt;&lt;span style=&quot;letter-spacing: 0px;&quot;&gt;테라폼에서의 모듈이란?&lt;/span&gt;&lt;/b&gt;&lt;/p&gt;
&lt;blockquote data-ke-style=&quot;style2&quot;&gt;테라폼이 실행되는 디렉토리를 모듈이라고 부른다. 모듈에는 테라폼 코드 정의를 위한 tf 또는 tf.json 확장자의 파일과 tfvars 같은 변수를 정의하는 파일이 포함된다. 일반적으로 기본 작업 디렉토리의 정의된 파일 집합을 루트 모듈이라고 부른다. 루트 모듈은 다른 모듈을 호출해 해당 루트 모듈이 구성하는 리소스 구성을 포함시킬 수 있고, 이렇게 호출되는 모듈을 자식 모듈이라고 한다.&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;테라폼을 시작하는데 사용되는 첫 번째 명령어로 테라폼에서 사용되는 프로바이더, 모듈 등의 지정된 버전에 맞춰 루트 모듈을 구성한다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;자바의 pom.xml 이나 노드의 package.json 등과 같이 의존성 정의를 읽고, 최초 실행 시 실행에 필요한 아티팩트나 라이브러리를 다운로드하고 준비시키는 역할과 비슷하다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;code&gt;main.tf&lt;/code&gt; 파일이 존재하는 위치에서 &lt;code&gt;terraform init&lt;/code&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;803&quot; data-origin-height=&quot;525&quot;&gt;&lt;span data-url=&quot;https://blog.kakaocdn.net/dn/q3SHv/btsITmswpAt/dhDJl0SOHLtt6GMqU87NO1/img.png&quot; data-phocus=&quot;https://blog.kakaocdn.net/dn/q3SHv/btsITmswpAt/dhDJl0SOHLtt6GMqU87NO1/img.png&quot;&gt;&lt;img src=&quot;https://blog.kakaocdn.net/dn/q3SHv/btsITmswpAt/dhDJl0SOHLtt6GMqU87NO1/img.png&quot; srcset=&quot;https://img1.daumcdn.net/thumb/R1280x0/?scode=mtistory2&amp;fname=https%3A%2F%2Fblog.kakaocdn.net%2Fdn%2Fq3SHv%2FbtsITmswpAt%2FdhDJl0SOHLtt6GMqU87NO1%2Fimg.png&quot; onerror=&quot;this.onerror=null; this.src='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png'; this.srcset='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png';&quot; loading=&quot;lazy&quot; width=&quot;569&quot; height=&quot;372&quot; data-origin-width=&quot;803&quot; data-origin-height=&quot;525&quot;/&gt;&lt;/span&gt;&lt;/figure&gt;
&lt;/p&gt;
&lt;h3 data-ke-size=&quot;size23&quot;&gt;-upgrade&lt;/h3&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;0.14 버전 이후부터 프로바이더 종속성을 고정시키는 &lt;code&gt;.terraform.lock.hcl&lt;/code&gt;이 추가됐다. 작업자가 로컬에서 &lt;code&gt;init&lt;/code&gt;으로 받은 프로바이더, 모듈 버전으로 수행한 이후 다른 작업자나 리모트 환경에서 &lt;code&gt;init&lt;/code&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;code&gt;.terraform.lock.hcl&lt;/code&gt; 파일이 있으면 해당 파일에 명시된 버전으로 &lt;code&gt;init&lt;/code&gt;을 수행한다. 이후 작업자가 의도적으로 버전을 변경하거나 코드에 명시한 다른 버전으로 변경하려면 &lt;code&gt;terraform init -upgrade&lt;/code&gt;를 수행해야 한다.&lt;/p&gt;
&lt;h2 data-ke-size=&quot;size26&quot;&gt;Terraform validate&lt;/h2&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;단어의 의미 그대로 디렉토리에 있는 테라폼 구성 파일의 유효성을 확인한다. 서비스나 인프라의 상태를 확인하기 위한 원격 작업 혹은 API는 발생하지 않으며, 코드적인 유효성만 검토한다.&lt;/p&gt;
&lt;h2 data-ke-size=&quot;size26&quot;&gt;Terraform plan &amp;amp; apply&lt;/h2&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;code&gt;terraform plan&lt;/code&gt; 명령어는 테라폼으로 적용할 인프라의 변경 사항에 관한 실행 계획을 생성한다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;이를 통해 출력되는 결과를 확인하여 어떤 변경이 적용될지 사용자가 미리 검토하고 이해하는데 도움을 준다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;code&gt;terraform apply&lt;/code&gt;는 &lt;code&gt;plan&lt;/code&gt; 명령어를 통해 작성된 적용 내용을 토대로 작업을 실행한다. 만약 &lt;code&gt;plan&lt;/code&gt; 명령어로 생성된 실행 계획이 없다면, 자동으로 계획을 생성하고 해당 계획을 승인할 것인지 묻는 메시지가 출력된다.&lt;/p&gt;
&lt;h3 data-ke-size=&quot;size23&quot;&gt;-detailed-exitcode&lt;/h3&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;실행 계획 생성 명령과 함께 사용하기 좋은 추가 옵션으로, 파이프라인 설계에서 활용할 수 있다. exitcode 또는 errorlevel은 동작의 결과를 숫자 코드로 제공한다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;각 숫자 코드는 자동화된 동작을 설계할 때 그 뒤 작업을 수행할지, 사용자에게 알릴지, 정지할지 등을 나타낸다.&lt;/p&gt;
&lt;ul style=&quot;list-style-type: disc;&quot; data-ke-list-type=&quot;disc&quot;&gt;
&lt;li&gt;0 : 변경사항이 없는 성공&lt;/li&gt;
&lt;li&gt;1 : 오류가 있음&lt;/li&gt;
&lt;li&gt;2 : 변경사항이 있는 성공-out&lt;code&gt;terraform plan -out=tfplan&lt;/code&gt; 명령어는 실행 계획을 바이너리 파일 형태로 추출하는 옵션이다.&lt;/li&gt;
&lt;/ul&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;code&gt;-out=&amp;lt;파일명&amp;gt;&lt;/code&gt; 형태다. 해당 명령어로 생성된 &lt;code&gt;tfplan&lt;/code&gt; 바이너리 파일을 &lt;code&gt;apply&lt;/code&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;실행 계획이 이미 존재하기 때문에 해당 과정이 생략된 것이다. 만약 바이너리 파일이 생성된 이후 원본 tf 파일이 수정되었다면 &lt;code&gt;terrafrom apply tfplan&lt;/code&gt; 명령어는 실행할 수 없다.&lt;/p&gt;
&lt;p&gt;&lt;figure class=&quot;imageblock alignCenter&quot; data-ke-mobileStyle=&quot;widthOrigin&quot; data-origin-width=&quot;1252&quot; data-origin-height=&quot;148&quot;&gt;&lt;span data-url=&quot;https://blog.kakaocdn.net/dn/Fa51l/btsIUMwQ5fd/ZEGnCDwhUyNLRyN9yA2kBk/img.png&quot; data-phocus=&quot;https://blog.kakaocdn.net/dn/Fa51l/btsIUMwQ5fd/ZEGnCDwhUyNLRyN9yA2kBk/img.png&quot;&gt;&lt;img src=&quot;https://blog.kakaocdn.net/dn/Fa51l/btsIUMwQ5fd/ZEGnCDwhUyNLRyN9yA2kBk/img.png&quot; srcset=&quot;https://img1.daumcdn.net/thumb/R1280x0/?scode=mtistory2&amp;fname=https%3A%2F%2Fblog.kakaocdn.net%2Fdn%2FFa51l%2FbtsIUMwQ5fd%2FZEGnCDwhUyNLRyN9yA2kBk%2Fimg.png&quot; onerror=&quot;this.onerror=null; this.src='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png'; this.srcset='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png';&quot; loading=&quot;lazy&quot; width=&quot;1252&quot; height=&quot;148&quot; data-origin-width=&quot;1252&quot; data-origin-height=&quot;148&quot;/&gt;&lt;/span&gt;&lt;/figure&gt;
&lt;/p&gt;
&lt;h3 data-ke-size=&quot;size23&quot;&gt;-replace&lt;/h3&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;프로비저닝이 완료된 이후 &lt;code&gt;terraform plan&lt;/code&gt;과 &lt;code&gt;terraform apply&lt;/code&gt; 실행 시 코드 변경이 없다면 실행 계획에 프로비저닝할 대상이 없지만, 사용자가 필요에 의해 특정 리소스를 다시 생성해야 하는 경우 &lt;code&gt;-replace&lt;/code&gt; 옵션으로 대상 리소스 주소를 지정하면 대상을 삭제 후 생성하는 실행 계획이 발생한다.&lt;/p&gt;
&lt;p&gt;&lt;figure class=&quot;imageblock alignCenter&quot; data-ke-mobileStyle=&quot;widthOrigin&quot; data-origin-width=&quot;1420&quot; data-origin-height=&quot;832&quot;&gt;&lt;span data-url=&quot;https://blog.kakaocdn.net/dn/Fr16L/btsIUx0Y7yv/cvxAK34bmzhzB7YpKuJG81/img.png&quot; data-phocus=&quot;https://blog.kakaocdn.net/dn/Fr16L/btsIUx0Y7yv/cvxAK34bmzhzB7YpKuJG81/img.png&quot;&gt;&lt;img src=&quot;https://blog.kakaocdn.net/dn/Fr16L/btsIUx0Y7yv/cvxAK34bmzhzB7YpKuJG81/img.png&quot; srcset=&quot;https://img1.daumcdn.net/thumb/R1280x0/?scode=mtistory2&amp;fname=https%3A%2F%2Fblog.kakaocdn.net%2Fdn%2FFr16L%2FbtsIUx0Y7yv%2FcvxAK34bmzhzB7YpKuJG81%2Fimg.png&quot; onerror=&quot;this.onerror=null; this.src='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png'; this.srcset='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png';&quot; loading=&quot;lazy&quot; width=&quot;712&quot; height=&quot;417&quot; data-origin-width=&quot;1420&quot; data-origin-height=&quot;832&quot;/&gt;&lt;/span&gt;&lt;/figure&gt;
&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;code&gt;terraform plan&lt;/code&gt;과 &lt;code&gt;terraform apply&lt;/code&gt; 모두 적용이 가능하다.&lt;/p&gt;
&lt;h2 data-ke-size=&quot;size26&quot;&gt;Terraform destroy&lt;/h2&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;code&gt;terraform destory&lt;/code&gt; 명령어는 테라폼 구성에서 관리하는 모든 객체를 제거하는 명령어다. 테라폼 코드로 구성된 리소스의 일부만 제거하기 위해서는 테라폼의 선언적 특성에 따라 삭제하려는 항목을 코드에서 제거하고, 다시 &lt;code&gt;terraform apply&lt;/code&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;code&gt;terraform destory&lt;/code&gt; 명령어를 사용하면 된다. &lt;code&gt;terraform destory&lt;/code&gt; 명령어 또한 &lt;code&gt;apply&lt;/code&gt;처럼 실행 계획을 필요로 하며 실행 계획 파일을 생성한 후 &lt;code&gt;terraform destory&lt;/code&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;code&gt;terraform plan -destroy -out=&amp;lt;파일명&amp;gt;&lt;/code&gt;로 실행 계획을 만들고 &lt;code&gt;terraform apply&lt;/code&gt;로 해당 실행 계획을 실행하는 방식으로 단계를 나누어 사용한다.&lt;/p&gt;
&lt;h3 data-ke-size=&quot;size23&quot;&gt;-auto-approve&lt;/h3&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;code&gt;apply&lt;/code&gt;, &lt;code&gt;destroy&lt;/code&gt; 명령어처럼 사용자의 승인이 필요할 경우 해당 승인 요청을 자동으로 승낙하는 옵션이다. 당연한 이야기지만 사용에 주의해야 한다.&lt;/p&gt;
&lt;h2 data-ke-size=&quot;size26&quot;&gt;Terraform fmt&lt;/h2&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;code&gt;terraform fmt&lt;/code&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;code&gt;-recursive&lt;/code&gt; 옵션으로 하위 디렉토리의 테라폼 구성 파일을 모두 포함해 적용시킬 수 있다.&lt;/p&gt;
&lt;hr data-ke-style=&quot;style1&quot; /&gt;
&lt;h2 data-ke-size=&quot;size26&quot;&gt;HCL&lt;/h2&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;Terraform Block&lt;/li&gt;
&lt;li&gt;Resource Block&lt;/li&gt;
&lt;li&gt;Data Block&lt;/li&gt;
&lt;li&gt;Variable Block&lt;/li&gt;
&lt;li&gt;Local Block&lt;/li&gt;
&lt;li&gt;Output Block&lt;/li&gt;
&lt;/ul&gt;
&lt;h3 data-ke-size=&quot;size23&quot;&gt;Terraform Block&lt;/h3&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;테라폼의 구성을 명시하는데 사용된다. 테라폼 버전이나 프로바이더 버전과 같은 값들을 명시적으로 선언하고 필요한 조건들을 입력하여 실행 오류를 최소화하기 위해 사용한다.&lt;/p&gt;
&lt;pre class=&quot;nginx&quot;&gt;&lt;code&gt;terraform {
    required_version = &quot;~&amp;gt; 1.9.3&quot;  # 테라폼 버전

    required_providers {
        random = {
            version = &quot;&amp;gt;= 3.0.0, &amp;lt; 3.1.0&quot;  # 프로바이더 버전 나열
        }
        aws = {
            version = &quot;4.2.0&quot;
        }
    }

    cloud {  # Cloud/Enterprise 같은 원격 실행을 위한 정보
      organization = &quot;&amp;lt;MY_ORG_NAME&amp;gt;&quot;
      workspaces {
        name = &quot;my-first-workspace&quot;
      }
    }

    backend &quot;local&quot; {  # state를 보관하는 위치를 지정
        path = &quot;relative/path/to/terraform.tfstate&quot;
    }
}&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;787&quot; data-origin-height=&quot;306&quot;&gt;&lt;span data-url=&quot;https://blog.kakaocdn.net/dn/B43SN/btsIUDfOr19/xo3Spb83SfUsc2OfOZZ2V0/img.png&quot; data-phocus=&quot;https://blog.kakaocdn.net/dn/B43SN/btsIUDfOr19/xo3Spb83SfUsc2OfOZZ2V0/img.png&quot;&gt;&lt;img src=&quot;https://blog.kakaocdn.net/dn/B43SN/btsIUDfOr19/xo3Spb83SfUsc2OfOZZ2V0/img.png&quot; srcset=&quot;https://img1.daumcdn.net/thumb/R1280x0/?scode=mtistory2&amp;fname=https%3A%2F%2Fblog.kakaocdn.net%2Fdn%2FB43SN%2FbtsIUDfOr19%2Fxo3Spb83SfUsc2OfOZZ2V0%2Fimg.png&quot; onerror=&quot;this.onerror=null; this.src='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png'; this.srcset='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png';&quot; loading=&quot;lazy&quot; width=&quot;715&quot; height=&quot;278&quot; data-origin-width=&quot;787&quot; data-origin-height=&quot;306&quot;/&gt;&lt;/span&gt;&lt;/figure&gt;
&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;백엔드 블록의 구성은 테라폼 실행 시 저장되는 &lt;code&gt;state&lt;/code&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;code&gt;state&lt;/code&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;code&gt;state&lt;/code&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;code&gt;local&lt;/code&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;code&gt;state&lt;/code&gt;가 관리되면 테라폼이 실행되는 동안 &lt;code&gt;.terraform.tfstate.lock.info&lt;/code&gt; 파일이 생성되면서 해당 &lt;code&gt;state&lt;/code&gt;를 동시에 사용하지 못하도록 잠금 처리를 한다.&lt;/p&gt;
&lt;h3 data-ke-size=&quot;size23&quot;&gt;Resource Block&lt;/h3&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;리소스는 테라폼이 프로비저닝 도구라는 측면에서 가장 중요한 요소다. 리소스 블록은 선언된 항목을 생성하는 동작을 수행한다.&lt;/p&gt;
&lt;pre class=&quot;xml&quot;&gt;&lt;code&gt;resource &quot;&amp;lt;리소스 유형&amp;gt;&quot; &quot;&amp;lt;이름&amp;gt;&quot; {
    &amp;lt;인수&amp;gt; = &amp;lt;값&amp;gt;
}&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;code&gt;local_file&lt;/code&gt;의 경우 &lt;code&gt;local&lt;/code&gt; 프로바이더에 속한 리소스 유형이다. 따라서 해당 리소스의 유형이 어떤 프로바이더가 제공하는 것인지는 언더바 앞쪽의 이름을 보고 확인할 수 있다.&lt;/li&gt;
&lt;li&gt;이름의 경우 동일한 유형에 대해서 식별자 역할을 하기 때문에 유형이 같은 경우 동일한 이름을 사용할 수 없다.&lt;/li&gt;
&lt;/ul&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;code&gt;lifecycle&lt;/code&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;code&gt;create_before_destroy (bool)&lt;/code&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;생성되는 리소스가 기존 리소스로 인해 생성이 실패되거나 삭제 시 함께 삭제될 위험 또한 존재한다.&lt;/li&gt;
&lt;/ul&gt;
&lt;/li&gt;
&lt;li&gt;&lt;code&gt;prevent_destroy (bool)&lt;/code&gt;
&lt;ul style=&quot;list-style-type: disc;&quot; data-ke-list-type=&quot;disc&quot;&gt;
&lt;li&gt;해당 리소스를 &lt;code&gt;Destroy&lt;/code&gt;하려고 할 때 명시적으로 거부한다.&lt;/li&gt;
&lt;/ul&gt;
&lt;/li&gt;
&lt;li&gt;&lt;code&gt;ignore_changes (list)&lt;/code&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;/ul&gt;
&lt;/li&gt;
&lt;li&gt;&lt;code&gt;precondition&lt;/code&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;/ul&gt;
&lt;/li&gt;
&lt;li&gt;&lt;code&gt;postcondition&lt;/code&gt;
&lt;ul style=&quot;list-style-type: disc;&quot; data-ke-list-type=&quot;disc&quot;&gt;
&lt;li&gt;&lt;code&gt;plan&lt;/code&gt;과 &lt;code&gt;apply&lt;/code&gt; 이후의 결과를 속성 값으로 검증한다.Data Block데이터 소스는 테라폼으로 정의되지 않은 외부 리소스 또는 저장된 정보를 테라폼 내에서 참조할 때 사용한다.&lt;/li&gt;
&lt;/ul&gt;
&lt;/li&gt;
&lt;/ul&gt;
&lt;pre class=&quot;dts&quot;&gt;&lt;code&gt;data &quot;&amp;lt;리소스 유형&amp;gt;&quot; &quot;&amp;lt;이름&amp;gt;&quot; {
    &amp;lt;인수&amp;gt; = &amp;lt;값&amp;gt;
}

# 데이터 소스 참조
data.&amp;lt;리소스 유형&amp;gt;.&amp;lt;이름&amp;gt;.&amp;lt;속성&amp;gt;&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;code&gt;local_file&lt;/code&gt;의 경우 &lt;code&gt;local&lt;/code&gt; 프로바이더에 속한 리소스 유형이다.&lt;br /&gt;따라서 해당 리소스의 유형이 어떤 프로바이더가 제공하는 것인지는 언더바 앞쪽의 이름을 보고 확인할 수 있다.&lt;/li&gt;
&lt;li&gt;이름의 경우 동일한 유형에 대해서 식별자 역할을 하기 때문에 유형이 같은 경우 동일한 이름을 사용할 수 없다.&lt;/li&gt;
&lt;/ul&gt;
&lt;pre class=&quot;routeros&quot;&gt;&lt;code&gt;resource &quot;local_file&quot; &quot;abc&quot; {
    content = &quot;123!&quot;
    filename = &quot;${path.module}/abc.txt&quot;
}

data &quot;local_file&quot; &quot;abc&quot; {
    filename = local_file.abc.filename
}

resource &quot;local_file&quot; &quot;def&quot; {
    content = data.local_file.abc.content
    filename = &quot;${path.module}/def.txt&quot;
}&lt;/code&gt;&lt;/pre&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;데이터 소스인 &lt;code&gt;data.local_file.abc&lt;/code&gt;는 리소스 &lt;code&gt;local_file.abc&lt;/code&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;code&gt;local_file&lt;/code&gt;은 읽어온 파일의 내용을 &lt;code&gt;content&lt;/code&gt; 속성으로 참조할 수 있으므로 리소스 &lt;code&gt;local_file.def&lt;/code&gt;에서는 &lt;code&gt;data.local_file.abc.content&lt;/code&gt;로 읽혀진 데이터를 참조한다.&lt;/p&gt;
&lt;h3 data-ke-size=&quot;size23&quot;&gt;Variable Block&lt;/h3&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;입력 변수는 인프라를 구성하는 데 필요한 속성 값을 정의하여 코드의 변경 없이 여러 인프라를 생성하는데 목적을 둔다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;테라폼에서는 이를 &lt;code&gt;Input Variable&lt;/code&gt; 즉 입력 변수로 정의한다. 입력이라는 수식어가 붙는 이유는, 테라폼이 &lt;code&gt;plan&lt;/code&gt; 실행 시 값을 입력한다는 점 때문이다.&lt;/p&gt;
&lt;pre class=&quot;xml&quot;&gt;&lt;code&gt;variable &quot;&amp;lt;이름&amp;gt;&quot; {
    &amp;lt;인수&amp;gt; = &amp;lt;값&amp;gt;
}&lt;/code&gt;&lt;/pre&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;code&gt;default&lt;/code&gt; : 변수에 할당되는 기본값 정의&lt;/li&gt;
&lt;li&gt;&lt;code&gt;type&lt;/code&gt; : 변수에 허용되는 값 유형 정의&lt;/li&gt;
&lt;li&gt;&lt;code&gt;description&lt;/code&gt; : 입력 변수의 설명&lt;/li&gt;
&lt;li&gt;&lt;code&gt;validation&lt;/code&gt; : 변수 선언의 제약조건을 추가해 유효성 검사 규칙 정의&lt;/li&gt;
&lt;li&gt;&lt;code&gt;sensitive&lt;/code&gt; : 민감한 변수 값임을 알리고 테라폼의 출력문에서 값 노출을 제한&lt;/li&gt;
&lt;li&gt;&lt;code&gt;nullable&lt;/code&gt; : 변수에 값이 없어도 됨을 지정&lt;/li&gt;
&lt;/ul&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;지원되는 변수의 유형 또한 여러가지가 존재한다. 사용 예시는 아래를 참고&lt;/p&gt;
&lt;pre class=&quot;routeros&quot;&gt;&lt;code&gt;variable &quot;string&quot; {
    type = string
    description = &quot;var String&quot;
    default = &quot;myString&quot;
}

variable &quot;number&quot; {
    type = number
    default = 123
}

variable &quot;boolean&quot; {
    default = true
}

variable &quot;list&quot; {
    default = [
        &quot;meta&quot;,
        &quot;nvidia&quot;,
        &quot;apple&quot;,
        &quot;amazon&quot;,
        &quot;google&quot;,
        &quot;tesla&quot;,
        &quot;microsoft&quot;
    ]
}

output &quot;list_index_0&quot; {
    value = var.list.0
}

output &quot;list_all&quot; {
    value = [
        for name in var.list :
            upper(name)
    ]
}

variable &quot;set&quot; {
    type = set(string)
    default = [
        &quot;google&quot;,
        &quot;vmware&quot;,
        &quot;amazon&quot;,
        &quot;microsoft&quot;
    ]
}

variable &quot;object&quot; {
    type = object({name=string, age=number})
    default = {
        name = &quot;abc&quot;
        age = 12
    }
}

variable &quot;tuple&quot; {
    type = tuple([string, number, bool])
    default = [&quot;abc&quot;, 123, true]
}

variable &quot;ingress_rules&quot; {
    type = list(object({
        port = number,
        description = optional(string),
        protocol = optional(string, &quot;tcp),
    }))
    default = [
        { port = 80, description = &quot;web&quot; }
        { port = 53, protocol = &quot;udp&quot; }
    ]    
}&lt;/code&gt;&lt;/pre&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;변수에 대한 유효성 검사는 &lt;code&gt;validation&lt;/code&gt; 메타인수를 사용해 진행할 수 있다. 예시는 다음과 같다.&lt;/p&gt;
&lt;pre class=&quot;routeros&quot;&gt;&lt;code&gt;variable &quot;image_id&quot; {
    type = string
    description = &quot;The id of the machine image (AMI) to use for the server&quot;

    validation {
        condition = length(var.image_id) &amp;gt; 4
        error_message = &quot;The image_id value must exceed 4.&quot;
    }
}&lt;/code&gt;&lt;/pre&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;변수에 대한 참조는 코드 내에서 &lt;code&gt;var.&amp;lt;이름&amp;gt;&lt;/code&gt; 형태로 할 수 있다. 선언된 &lt;code&gt;variable&lt;/code&gt;에 정의된 값이 없으면 &lt;code&gt;terraform plan&lt;/code&gt; 혹은 &lt;code&gt;apply&lt;/code&gt; 실행 후 변수 값을 입력하라는 항목을 확인할 수 있다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;변수 값을 입력하면 입력한 값으로 실행 계획을 생성하고 수행한다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;민감한 입력 변수를 사용해야 할 경우 다음과 같이 &lt;code&gt;sensitive&lt;/code&gt; 메타인수를 사용하여 민감 여부를 선언할 수 있다.&lt;/p&gt;
&lt;pre class=&quot;routeros&quot;&gt;&lt;code&gt;variable &quot;my_password&quot; {
    default = &quot;password&quot;
    sensitive = true
}

resource &quot;local_file&quot; &quot;abc&quot; {
    content = var.my_password
    filename = &quot;${path.module}/abc.txt&quot;
}&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;1422&quot; data-origin-height=&quot;421&quot;&gt;&lt;span data-url=&quot;https://blog.kakaocdn.net/dn/x4705/btsITucRtm3/he2Au1TCUmd3lg8H9uAOTK/img.png&quot; data-phocus=&quot;https://blog.kakaocdn.net/dn/x4705/btsITucRtm3/he2Au1TCUmd3lg8H9uAOTK/img.png&quot;&gt;&lt;img src=&quot;https://blog.kakaocdn.net/dn/x4705/btsITucRtm3/he2Au1TCUmd3lg8H9uAOTK/img.png&quot; srcset=&quot;https://img1.daumcdn.net/thumb/R1280x0/?scode=mtistory2&amp;fname=https%3A%2F%2Fblog.kakaocdn.net%2Fdn%2Fx4705%2FbtsITucRtm3%2Fhe2Au1TCUmd3lg8H9uAOTK%2Fimg.png&quot; onerror=&quot;this.onerror=null; this.src='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png'; this.srcset='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png';&quot; loading=&quot;lazy&quot; width=&quot;766&quot; height=&quot;227&quot; data-origin-width=&quot;1422&quot; data-origin-height=&quot;421&quot;/&gt;&lt;/span&gt;&lt;/figure&gt;
&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;프로비저닝을 완료할 경우 출력에서는 값이 표현되지 않았지만 실제 생성되는 리소스 결과물에서는 지정한 값이 입력된 것을 확인할 수 있다.&lt;/p&gt;
&lt;p&gt;&lt;figure class=&quot;imageblock alignCenter&quot; data-ke-mobileStyle=&quot;widthOrigin&quot; data-origin-width=&quot;441&quot; data-origin-height=&quot;123&quot;&gt;&lt;span data-url=&quot;https://blog.kakaocdn.net/dn/R1Kwg/btsIUjhA7Xy/qwI5Ok1oQ4KejmwVusSV8K/img.png&quot; data-phocus=&quot;https://blog.kakaocdn.net/dn/R1Kwg/btsIUjhA7Xy/qwI5Ok1oQ4KejmwVusSV8K/img.png&quot;&gt;&lt;img src=&quot;https://blog.kakaocdn.net/dn/R1Kwg/btsIUjhA7Xy/qwI5Ok1oQ4KejmwVusSV8K/img.png&quot; srcset=&quot;https://img1.daumcdn.net/thumb/R1280x0/?scode=mtistory2&amp;fname=https%3A%2F%2Fblog.kakaocdn.net%2Fdn%2FR1Kwg%2FbtsIUjhA7Xy%2FqwI5Ok1oQ4KejmwVusSV8K%2Fimg.png&quot; onerror=&quot;this.onerror=null; this.src='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png'; this.srcset='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png';&quot; loading=&quot;lazy&quot; width=&quot;384&quot; height=&quot;107&quot; data-origin-width=&quot;441&quot; data-origin-height=&quot;123&quot;/&gt;&lt;/span&gt;&lt;/figure&gt;
&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;code&gt;sensitive&lt;/code&gt;를 사용하여 민감한 변수로 지정하더라도 &lt;code&gt;terraform.tfstate&lt;/code&gt; 파일에는 결과문이 평문으로 기록되기 때문에 &lt;code&gt;state&lt;/code&gt; 파일의 보안에는 유의해야 한다.&lt;/p&gt;
&lt;h3 data-ke-size=&quot;size23&quot;&gt;Local Block&lt;/h3&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;코드 내에서 사용자가 지정한 값 또는 속성 값을 가공해 참조 가능한 &lt;code&gt;local&lt;/code&gt;은 외부에서 입력되지 않고, 코드 내에서만 가공되어 동작하는 값을 선언한다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;선언된 모듈 내에서만 접근이 가능하고, 변수처럼 실행 시에 입력받을 수 없다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;code&gt;locals&lt;/code&gt;로 선언할 수 있으며, &lt;code&gt;locals&lt;/code&gt; 내에 선언한 로컬 변수의 이름은 전체 루트 모듈 내에서 유일해야 한다.&lt;/p&gt;
&lt;pre class=&quot;routeros&quot;&gt;&lt;code&gt;variable &quot;prefix&quot; {
    default = &quot;hello&quot;
}

locals {
    name = &quot;terraform&quot;
    content = &quot;${var.prefix} ${local.name}&quot;
    my_info = {
        age = 20
        region = &quot;KR&quot;
    }
    my_nums = [1, 2, 3, 4, 5]
}

locals {
    content = &quot;duplicated content&quot; # 중복 선언되었으므로 에러가 발생
}&lt;/code&gt;&lt;/pre&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;선언된 로컬 변수는 &lt;code&gt;local.&amp;lt;이름&amp;gt;&lt;/code&gt;으로 참조할 수 있다. 테라폼 구성 파일을 여러 개 생성해 작업하는 경우 서로 다른 파일에 선언되어 있더라도 다른 파일에서 참조할 수 있다.&lt;/p&gt;
&lt;h3 data-ke-size=&quot;size23&quot;&gt;Output Block&lt;/h3&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;출력 값은 주로 테라폼 코드의 프로비저닝 수행 후의 결과 속성 값을 확인하는 용도로 사용된다. 또한 테라폼 모듈 간, 워크스페이스 간 데이터 접근 요소로도 활용할 수 있다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;예를 들어 자바의 &lt;code&gt;getter&lt;/code&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;서로 다른 루트 모듈의 결과를 원격으로 읽기 위한 접근 요소로 활용할 수 있도록 한다.&lt;/li&gt;
&lt;/ul&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;출력 값을 작성하면 단순 디버깅을 넘어 속성 값을 노출하고 접근할 수 있다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;모듈 내에서 생성되는 속성 값들은 &lt;code&gt;output block&lt;/code&gt;에 정의된다.&lt;/p&gt;
&lt;pre class=&quot;nginx&quot;&gt;&lt;code&gt;output &quot;instance_ip_addr&quot; {
    value = &quot;http://${aws_instance.server.private.ip}&quot;
}&lt;/code&gt;&lt;/pre&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;출력되는 값은 &lt;code&gt;value&lt;/code&gt;의 값이며 테라폼이 제공하는 조합과 프로그래밍적인 기능들에 의해 원하는 값을 출력할 수 있다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;주의할 점은 &lt;code&gt;output&lt;/code&gt; 결과에서 리소스 생성 후 결정되는 속성 값은 프로비저닝이 완료되어야 최종적으로 결과를 확인할 수 있고 &lt;code&gt;terraform plan&lt;/code&gt; 단계에서는 적용될 값을 출력하지 않는다는 것이다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;사용할 수 있는 메타인수는 다음과 같다.&lt;/p&gt;
&lt;ul style=&quot;list-style-type: disc;&quot; data-ke-list-type=&quot;disc&quot;&gt;
&lt;li&gt;&lt;code&gt;description&lt;/code&gt; : 출력 값 설명&lt;/li&gt;
&lt;li&gt;&lt;code&gt;sensitive&lt;/code&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;/ul&gt;
&lt;/li&gt;
&lt;li&gt;&lt;code&gt;depends_on&lt;/code&gt; : &lt;code&gt;value&lt;/code&gt;에 담길 값이 특정 구성에 종속성이 있는 경우 생성되는 순서를 임의로 조정한다.&lt;/li&gt;
&lt;li&gt;&lt;code&gt;precondition&lt;/code&gt; : 출력 전에 지정된 조건을 검증한다.&lt;/li&gt;
&lt;/ul&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;사용 방법은 다음과 같다.&lt;/p&gt;
&lt;pre class=&quot;nginx&quot;&gt;&lt;code&gt;resource &quot;local_file&quot; &quot;abc&quot; {
  content = &quot;abc123&quot;
  filename = &quot;${path.module}/abc.txt&quot;
}

output &quot;file_id&quot; {
  value = local_file.abc.id
}

output &quot;file_abspath&quot; {
  value = local_file.abc.filename
}&lt;/code&gt;&lt;/pre&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;code&gt;terraform plan&lt;/code&gt;을 사용할 경우 &lt;code&gt;file_id&lt;/code&gt;의 경우 실행 계획 단계에서는 알 수 없기 때문에 &lt;code&gt;apply&lt;/code&gt; 실행 이후 확인할 수 있다고 출력된다.&lt;/p&gt;
&lt;p&gt;&lt;figure class=&quot;imageblock alignCenter&quot; data-ke-mobileStyle=&quot;widthOrigin&quot; data-origin-width=&quot;428&quot; data-origin-height=&quot;147&quot;&gt;&lt;span data-url=&quot;https://blog.kakaocdn.net/dn/bEC15i/btsITwhnE3m/B2DmUM4lwFIq5YX8esXvLK/img.png&quot; data-phocus=&quot;https://blog.kakaocdn.net/dn/bEC15i/btsITwhnE3m/B2DmUM4lwFIq5YX8esXvLK/img.png&quot;&gt;&lt;img src=&quot;https://blog.kakaocdn.net/dn/bEC15i/btsITwhnE3m/B2DmUM4lwFIq5YX8esXvLK/img.png&quot; srcset=&quot;https://img1.daumcdn.net/thumb/R1280x0/?scode=mtistory2&amp;fname=https%3A%2F%2Fblog.kakaocdn.net%2Fdn%2FbEC15i%2FbtsITwhnE3m%2FB2DmUM4lwFIq5YX8esXvLK%2Fimg.png&quot; onerror=&quot;this.onerror=null; this.src='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png'; this.srcset='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png';&quot; loading=&quot;lazy&quot; width=&quot;346&quot; height=&quot;119&quot; data-origin-width=&quot;428&quot; data-origin-height=&quot;147&quot;/&gt;&lt;/span&gt;&lt;/figure&gt;
&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;code&gt;apply&lt;/code&gt; 명령어를 실행하면 프로비저닝 이후 해당 값이 잘 출력되는 모습을 확인할 수 있다.&lt;/p&gt;
&lt;p&gt;&lt;figure class=&quot;imageblock alignCenter&quot; data-ke-mobileStyle=&quot;widthOrigin&quot; data-origin-width=&quot;592&quot; data-origin-height=&quot;161&quot;&gt;&lt;span data-url=&quot;https://blog.kakaocdn.net/dn/cu1xzn/btsISMrEDWC/z0s2HETKT8r7v8zKKrR4OK/img.png&quot; data-phocus=&quot;https://blog.kakaocdn.net/dn/cu1xzn/btsISMrEDWC/z0s2HETKT8r7v8zKKrR4OK/img.png&quot;&gt;&lt;img src=&quot;https://blog.kakaocdn.net/dn/cu1xzn/btsISMrEDWC/z0s2HETKT8r7v8zKKrR4OK/img.png&quot; srcset=&quot;https://img1.daumcdn.net/thumb/R1280x0/?scode=mtistory2&amp;fname=https%3A%2F%2Fblog.kakaocdn.net%2Fdn%2Fcu1xzn%2FbtsISMrEDWC%2Fz0s2HETKT8r7v8zKKrR4OK%2Fimg.png&quot; onerror=&quot;this.onerror=null; this.src='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png'; this.srcset='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png';&quot; loading=&quot;lazy&quot; width=&quot;522&quot; height=&quot;142&quot; data-origin-width=&quot;592&quot; data-origin-height=&quot;161&quot;/&gt;&lt;/span&gt;&lt;/figure&gt;
&lt;/p&gt;</description>
      <category>DevOps</category>
      <category>AWS</category>
      <category>IAC</category>
      <category>terraform</category>
      <category>데브옵스</category>
      <category>우분투</category>
      <category>인프라</category>
      <category>클라우드</category>
      <category>테라폼</category>
      <author>겨울바람_</author>
      <guid isPermaLink="true">https://breeze-winter.tistory.com/34</guid>
      <comments>https://breeze-winter.tistory.com/34#entry34comment</comments>
      <pubDate>Sat, 3 Aug 2024 20:22:05 +0900</pubDate>
    </item>
    <item>
      <title>[k8s] K8s Storage with OpenEBS</title>
      <link>https://breeze-winter.tistory.com/33</link>
      <description>&lt;h3 data-ke-size=&quot;size23&quot;&gt;k8s Storage&lt;/h3&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;컨테이너 환경에서는 별도 설정을 하지 않으면 데이터는 호스트 노드의 임시 디스크에 보관된다. 컨테이너를 삭제하면 임시 디스크에 있는 데이터는 저장되지 않고 컨테이너와 함께 삭제된다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;이러한 문제를 쿠버네티스에서는 Pod와 데이터를 분리해서 영구 볼륨이라는 별도의 추상화된 리소스로 해결한다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;쿠버네티스 볼륨을 구성하는 주요 리소스로 &lt;code&gt;Persistent Volume (영구볼륨)&lt;/code&gt;, &lt;code&gt;PVC (Persistent VolumeClaim, 영구볼륨 요청자)&lt;/code&gt;, &lt;code&gt;Storage Class&lt;/code&gt;가 있다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;클러스터 관리자와 사용자(개발자)의 역할에 따라 쿠버네티스 볼륨에 관련된 작업이 서로 다르다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;클러스터 관리자는 클라우드 서비스 제공업자, 상용 혹은 오픈소스 솔루션 중에서 원하는 성능과 기&lt;br /&gt;능을 제공하는 스토리지를 선택하여 개발자에게 적절한 솔루션을 제공하는 역할을 한다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;게빌지는 애플리케이션에서 필요한 스토리지 용량과 특성(ReadWriteOnce/ReadWriteMany)을 고려해 애플리케이션에 연결하는 작업을 진행한다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;이번 예제에서는 오픈소스 OpenEBS 로컬 호스트패스를 이용해 스토리지 클래스를 만들고 스토리지 관련 설정을 YAML 파일로 작성하는 방법에 대해 알아보려고 한다.&lt;/p&gt;
&lt;h3 data-ke-size=&quot;size23&quot;&gt;PersistentVolume&lt;code&gt;&lt;/code&gt;&lt;/h3&gt;
&lt;p&gt;&lt;figure class=&quot;imageblock alignCenter&quot; data-ke-mobileStyle=&quot;widthOrigin&quot; data-origin-width=&quot;981&quot; data-origin-height=&quot;463&quot;&gt;&lt;span data-url=&quot;https://blog.kakaocdn.net/dn/Zy1Zt/btsIUDGH44P/7MyESDszmhVkPsCFSfQrO1/img.png&quot; data-phocus=&quot;https://blog.kakaocdn.net/dn/Zy1Zt/btsIUDGH44P/7MyESDszmhVkPsCFSfQrO1/img.png&quot;&gt;&lt;img src=&quot;https://blog.kakaocdn.net/dn/Zy1Zt/btsIUDGH44P/7MyESDszmhVkPsCFSfQrO1/img.png&quot; srcset=&quot;https://img1.daumcdn.net/thumb/R1280x0/?scode=mtistory2&amp;fname=https%3A%2F%2Fblog.kakaocdn.net%2Fdn%2FZy1Zt%2FbtsIUDGH44P%2F7MyESDszmhVkPsCFSfQrO1%2Fimg.png&quot; onerror=&quot;this.onerror=null; this.src='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png'; this.srcset='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png';&quot; loading=&quot;lazy&quot; width=&quot;981&quot; height=&quot;463&quot; data-origin-width=&quot;981&quot; data-origin-height=&quot;463&quot;/&gt;&lt;/span&gt;&lt;/figure&gt;
&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;code&gt;PV (Persistent Volume) 영구볼륨&lt;/code&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;code&gt;PVC (Persistent Volume Claim) 영구볼륨 요청자&lt;/code&gt;는 실제 데이터가 저장돠는 영구볼륨과 분리해서 영구볼륨의 스토리지 용량과 액세스 모드 등 영구볼륨과 관련된 설정만 별도로 분리한 쿠버네티스 리소스다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;관리자는 PV, 스토리지 클래스를 만들고 개발자는 영구볼륨의 상세한 설정 내역을 몰라도 PVC를 이용해 볼륨을 사용할 수 있다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;스토리지 클래스는 스토리지 솔루션 또는 클라우드 서비스 제공업체에서 제공하는 여러 가지 스토리지 중 동일한 속성 (IOPS, 레이턴시, 백업정책 등)을 가지는 스토리지의 집합 리소스다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;br /&gt;사용자는 원하는 스토리지 클래스를 지정해 PVC에서 요청하면 스토리지 클래스에서 해당 볼륨을 동적으로 할당한다.&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;br /&gt;사용자는 PVC로 볼륨 할당을 요청하면 해당 스토리지 클래스에서 동적으로 PV가 할당된다.&lt;/p&gt;
&lt;h3 data-ke-size=&quot;size23&quot;&gt;OpenEBS 로컬 호스트패스 설치&lt;/h3&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;쿠버네티스 자체에서는 스토리지 클래스를 제공하지 않고 별도의 솔루션을 설치해야 사용할 수 있다. 일반적으로 퍼블릭 클라우드 서비스 업체는 스토리지 클래스를 기본으로 제공하고 온프레미스 환경은 별도 스토리지 솔루션으로 구현한다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;현업에서 많이 사용하는 오픈소스 스토리지 솔루션으로 &lt;code&gt;Ceph&lt;/code&gt;와 &lt;code&gt;GlusterFS&lt;/code&gt;, &lt;code&gt;OpenEBS&lt;/code&gt; 등이 있다. 이번 포스팅의 예제에서는 속도가 빠르고 설치 및 사용이 쉬운 &lt;code&gt;OpenEBS 호스트패스&lt;/code&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;code&gt;OpenEBS&lt;/code&gt;는 Pod가 실행되는 호스트 노드의 특정 디렉터리(호스트패스)를 Pod의 볼륨으로 할당한다. &lt;code&gt;OpenEBS&lt;/code&gt; 솔루션 없이 호스트 노드의 원하는 경로를 직접 Pod의 볼륨으로 할당할 수 있지만 스토리지 클래스를 이용하지 않으므로 필요할 때마다 영구볼륨을 생성하고 다시 수동으로 삭제해야 하는 정적인 구성으로 굉장히 번거롭다.&lt;/p&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;code&gt;OpenEBS&lt;/code&gt;를 사용하면 스토리지 클래스를 이용해 동적으로 볼륨을 생성하고 삭제할 수 있다. 또한 볼륨 관련 구성은 공통 속성을 사용하므로 기존에 사용하는 애플리케이션 YAML 파일을 수정하지 않고도 &lt;code&gt;OpenEBS&lt;/code&gt; 환경에 그대로 사용할 수 있다는 장점이 있다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;외부 네트워크 지연과 분산 파일 시스템을 사용하지 않고 노드의 로컬 디스크에 직접 데이터를 읽고 쓰므로 다른 스토리지 솔루션에 비해 성능이 뛰어나다.&lt;/p&gt;
&lt;pre class=&quot;bash&quot; data-ke-language=&quot;bash&quot;&gt;&lt;code&gt;k create ns openbs
k ns openbs

k apply -f https://openebs.github.io/charts/openebs-operator-lite.yaml
k apply -f https://openebs.github.io/charts/openebs-lite-sc.yaml

k get sc&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;code&gt;openebs-device&lt;/code&gt;와 &lt;code&gt;openebs-hostpath&lt;/code&gt;라는 2가지 스토리지 클래스가 생성된다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;code&gt;openebs-device&lt;/code&gt;는 노드에서 마운트하지 않은 별도의 디스크 디바이스에 Pod의 데이터를 저장하고 &lt;code&gt;openebs-hostpath&lt;/code&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;code&gt;/var/openebs/local&lt;/code&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;code&gt;OpenEBS&lt;/code&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;code&gt;openebs-hostpath&lt;/code&gt; 스토리지 클래스를 이용해 PVC를 생성하고 Pod에 볼륨을 할당한다.&lt;/p&gt;
&lt;h3 data-ke-size=&quot;size23&quot;&gt;스토리지 클래스를 이용한 PVC 및 영구볼륨 사용&lt;/h3&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;YAML 파일을 이용해 PVC를 생성하고 애플리케이션 내 영구 볼륨을 마운트하는 실습을 진행하려 한다. 개발자는 해당 작업만 가능하면 쿠버네티스 스토리지를 사용하는 데 별다른 문제가 없다.&lt;/p&gt;
&lt;pre class=&quot;bash&quot; data-ke-language=&quot;bash&quot;&gt;&lt;code&gt;apiVersion: v1
kind: PersistentVolumeClaim
metadata:
  name: default-pvc
  namespace: default
spec:
  accessModes:
  - ReadWriteOnce
  volumeMode: FileSystem
  resources:
    requests: 
      storage: 1Gi
storageClassName: &quot;openebs-hostpath&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;code&gt;kind: PersistentVolumeClaim&lt;/code&gt;
&lt;ul style=&quot;list-style-type: disc;&quot; data-ke-list-type=&quot;disc&quot;&gt;
&lt;li&gt;쿠버네티스는 영구볼륨을 요청하는 PersistentVolumeClaim을 별도의 리소스로 지정한다&lt;/li&gt;
&lt;/ul&gt;
&lt;/li&gt;
&lt;li&gt;&lt;code&gt;namespace: default&lt;/code&gt;
&lt;ul style=&quot;list-style-type: disc;&quot; data-ke-list-type=&quot;disc&quot;&gt;
&lt;li&gt;PVC는 네임스페이스 단위로 생성하고 구분된다&lt;/li&gt;
&lt;li&gt;영구볼륨은 특정 네임스페이스가 아닌 전체 클러스터 단위로 할당된다&lt;/li&gt;
&lt;/ul&gt;
&lt;/li&gt;
&lt;li&gt;&lt;code&gt;spec.accessModes: ReadWriteOnce&lt;/code&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;/ul&gt;
&lt;/li&gt;
&lt;li&gt;&lt;code&gt;spec.volumeMode: Filesystem&lt;/code&gt;
&lt;ul style=&quot;list-style-type: disc;&quot; data-ke-list-type=&quot;disc&quot;&gt;
&lt;li&gt;Filesystem과 Block의 두 가지 모드를 선택할 수 있다&lt;/li&gt;
&lt;/ul&gt;
&lt;/li&gt;
&lt;li&gt;&lt;code&gt;spec.resources.requests.storage: 1Gi&lt;/code&gt;
&lt;ul style=&quot;list-style-type: disc;&quot; data-ke-list-type=&quot;disc&quot;&gt;
&lt;li&gt;영구볼륨이 사용하는 용량을 지정한다. Pod가 마운트한 볼륨은 해당 용량을 초과해서 사용할 수 없다&lt;/li&gt;
&lt;/ul&gt;
&lt;/li&gt;
&lt;li&gt;&lt;code&gt;spec.storageClassName: &quot;openebs-hostpath&quot;&lt;/code&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;/ul&gt;
&lt;/li&gt;
&lt;/ul&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;개발자는 위와 같은 기본 템플릿 PVC 파일에서 용량과 스토리지 클래스 이름 등 몇 가지 변수만 변경해서 새로운 PVC를 반복 생성할 수 있다.&lt;/p&gt;
&lt;pre class=&quot;routeros&quot;&gt;&lt;code&gt;k ns default
k apply -f date-pvc.yaml
k get pvc&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;PVC를 생성하면 정상적으로 &lt;code&gt;default-pvc&lt;/code&gt;가 생성된다. PVC 또한 리소스이므로 k get 명령어로 PVC 목록을 확인할 수 있다. 하지만, 상태가 정상이지 않고 &lt;code&gt;Pending&lt;/code&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;code&gt;k describe pvc default-pvc&lt;/code&gt; 명령어를 통해 확인해보면, 첫 번째 사용자(Pod)가 볼륨을 연결하기를 기다린다. 라는 의미의 메시지를 확인할 수 있다.&lt;/p&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;code&gt;OpenEBS&lt;/code&gt; 스토리지 클래스의 특징으로 PVC는 해당 PVC를 사용하는 Pod가 먼저 생성되고 다음으로 볼륨이 연결된다. Pod가 생성되고 해당 PVC를 마운트하면 상태가 &lt;code&gt;Pending&lt;/code&gt;에서 &lt;code&gt;Bound&lt;/code&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;이처럼 사용자는 먼저 Pod가 사용할 볼륨을 PVC로 생성한다. 이후 헤당 PVC를 Pod YAML 파일의 &lt;code&gt;volumeMounts&lt;/code&gt;와 &lt;code&gt;volumes&lt;/code&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;아래는 실제 애플리케이션에서 PVC를 사용하는 &lt;code&gt;Deployment&lt;/code&gt; YAML 파일의 예제다&lt;/p&gt;
&lt;pre class=&quot;less&quot;&gt;&lt;code&gt;apiVersion: apps/v1
kind: Deployment
metadata:
  name: data-pod
  namespace: default
  labels:
    app: date
spec:
  replicas: 1
  selector:
    matchLabels:
      app: date
  template:
    metadata:
      labels:
        app: date
    spec:
      containers:
      - name: date-pod
        image: busybox
        command:
        - &quot;/bin/sh&quot;
        - &quot;-c&quot;
        - &quot;while true; do date &amp;gt;&amp;gt; /data/pod-out.txt; cd /data; sync; sync; sleep 30; done&quot;
        volumeMounts:
        - name: date-vol
          mountPath: /data
    volumes:
    - name: date-vol
      persistentVolumeClaim:
        claimName: default-pvc&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;code&gt;spec.template.spec.containers.volumeMounts&lt;/code&gt;
&lt;ul style=&quot;list-style-type: disc;&quot; data-ke-list-type=&quot;disc&quot;&gt;
&lt;li&gt;컨테이너에서 사용할 볼륨의 정보를 지정한다. Pod는 볼륨을 사용하기 위해 volumeMounts와 volumes의 2가지를 사용한다&lt;/li&gt;
&lt;/ul&gt;
&lt;/li&gt;
&lt;li&gt;&lt;code&gt;spec.template.spec.containers.volumeMounts.name&lt;/code&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;/ul&gt;
&lt;/li&gt;
&lt;li&gt;&lt;code&gt;spec.template.spec.containers.volumeMounts.mountPath&lt;/code&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;/ul&gt;
&lt;/li&gt;
&lt;li&gt;&lt;code&gt;spec.template.spec.volumes.name&lt;/code&gt;
&lt;ul style=&quot;list-style-type: disc;&quot; data-ke-list-type=&quot;disc&quot;&gt;
&lt;li&gt;컨테이너 마운트 포인트 이름과 동일하게 매핑한다. YAML 파일 내에 볼륨 마운트가 여러 개 있으면 해당하는 볼륨 마운트 이름과 일치시킨다&lt;/li&gt;
&lt;/ul&gt;
&lt;/li&gt;
&lt;li&gt;&lt;code&gt;spec.template.spec.volumes.persistentVolumeClaim.claimName&lt;/code&gt;
&lt;ul style=&quot;list-style-type: disc;&quot; data-ke-list-type=&quot;disc&quot;&gt;
&lt;li&gt;볼륨에 사용할 PVC 이름을 지정한다. PVC는 Pod와 동일한 네임스페이스에 있어야 한다&lt;/li&gt;
&lt;/ul&gt;
&lt;/li&gt;
&lt;/ul&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;이전 Pod 예제와 동일하게 date 명령의 출력 결과를 파일에 저장하는 &lt;code&gt;Deployment&lt;/code&gt; YAML파일이다. 이전과 다르게 PVC를 사용해 데이터가 사라지지 않고 영구볼륨에 저장된다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;PVC 볼륨을 사용하는 &lt;code&gt;Deployment&lt;/code&gt; YAML 파일은 기존의 &lt;code&gt;Deployment&lt;/code&gt; YAML과 동일하지만 &lt;code&gt;volumeMounts&lt;/code&gt;와 &lt;code&gt;volumes&lt;/code&gt; 부분만 기존 &lt;code&gt;Deployment&lt;/code&gt; YAML에 추가됐다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;위의 YAML 파일을 통해 &lt;code&gt;Deployment&lt;/code&gt;를 생성한다.&lt;/p&gt;
&lt;pre class=&quot;routeros&quot;&gt;&lt;code&gt;k apply -f date-pvc-deploy.yaml
k get pod -o wide
k get pvc&lt;/code&gt;&lt;/pre&gt;
&lt;h3 data-ke-size=&quot;size23&quot;&gt;PV/PVC 삭제&lt;/h3&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;영구볼륨은 Pod에서 볼륨으로 사용하고 있으므로 영구볼륨을 삭제하려면 먼저 볼륨을 사용하고 있는 Pod를 삭제해야 한다. Pod를 삭제하지 않으면, 영구볼륨이 삭제되지 않는다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;PVC를 삭제할 때는 영구볼륨의 삭제와 관련된 정책은 해당 스토리지 클래스의 정책을 따르며 &lt;code&gt;k get pv&lt;/code&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;code&gt;ReclaimPolicy(재요구정책)&lt;/code&gt;에는 &lt;code&gt;Delete&lt;/code&gt;와 &lt;code&gt;Retain&lt;/code&gt;옵션이 있다. &lt;code&gt;Delete&lt;/code&gt;는 PVC를 삭제하면 영구볼륨도 함께 삭제되고, &lt;code&gt;Retain&lt;/code&gt;은 PVC는 삭제돼도 영구볼륨은 삭제되지 않고 유지하는 정책이다.&lt;/p&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;code&gt;Retain&lt;/code&gt; 옵션을 사용하면 PVC를 삭제해도 PV는 삭제되지 않으므로 안전하지만, 삭제 작업은 수동으로 진행해야 한다. 만약 지워지지 않은 영구볼륨이 있다면 향후 해당 영구볼륨을 이용해 수동으로 새로운 PVC를 생성할 수 있다.&lt;/p&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;code&gt;OpenEBS&lt;/code&gt; 스토리지 클래스의 기본 정책은 &lt;code&gt;Delete&lt;/code&gt;다.&lt;/p&gt;
&lt;h3 data-ke-size=&quot;size23&quot;&gt;사용자 스토리지 클래스를 지정해 헬름 차트 MySQL 설치하기&lt;/h3&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;쿠버네티스 환경에서 애플리케이션을 설치하는 데는 주로 헬름을 이용한다. 헬름 차트는 템플릿 파일로 개별 상황에 맞게 설치 옵션을 지정할 수 있다. 스토리지 클래스도 템플릿 파일을 이용해 지정할 수 있다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;대부분의 헬름 파일에 스토리지 클래스만 지정하면 PVC가 자동으로 생성된다. 이번 예제에서는 &lt;code&gt;OpenEBS&lt;/code&gt; 호스트패스를 스토리지 클래스로 지정해 헬름 차트로 MySQL 애플리케이션을 설치한다.&lt;/p&gt;
&lt;pre class=&quot;angelscript&quot;&gt;&lt;code&gt;helm search repo mysql
helm pull bitnami/mysql
tar xvfz mysql-9.1.0.tgz
mv mysql mysql-9.1.0
cd mysql-9.1.0/
cp values.yaml my-values.yaml&lt;/code&gt;&lt;/pre&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;이후 YAML 파일의 내용 중 아래 부분을 변경한다.&lt;/p&gt;
&lt;ul style=&quot;list-style-type: disc;&quot; data-ke-list-type=&quot;disc&quot;&gt;
&lt;li&gt;&lt;code&gt;architecture: replication&lt;/code&gt;
&lt;ul style=&quot;list-style-type: disc;&quot; data-ke-list-type=&quot;disc&quot;&gt;
&lt;li&gt;MySQL 애플리케이션 구조를 복제 구성으로 변경한다&lt;/li&gt;
&lt;/ul&gt;
&lt;/li&gt;
&lt;li&gt;&lt;code&gt;persistence.storageClass&lt;/code&gt;
&lt;ul style=&quot;list-style-type: disc;&quot; data-ke-list-type=&quot;disc&quot;&gt;
&lt;li&gt;&lt;code&gt;primary.persistence.storageClass&lt;/code&gt;와 &lt;code&gt;secondary.persistence.storageClass&lt;/code&gt;의 스토리지 클래스를 &lt;code&gt;openebs-hostpath&lt;/code&gt;로 변경한다
&lt;pre class=&quot;properties&quot;&gt;&lt;code&gt;k create ns mysql
k ns mysql
helm install mysql -f my-values.yaml .&lt;/code&gt;&lt;/pre&gt;
&lt;/li&gt;
&lt;/ul&gt;
&lt;/li&gt;
&lt;/ul&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;헬름 차트를 이용해 애플리케이션을 설치할 때 헬름의 스토리지 클래스를 새롭게 생성한 &lt;code&gt;openebs-hostpath&lt;/code&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;헬름 차트 삭제시, 데이터 보호를 위해 기본적으로 PVC는 삭제하지 않는다. 따라서 헬름 차트가 업그레이드되거나 변경돼도 데이터는 그대로 유지해서 기존 데이터의 변경 없이 그대로 사용할 수 있다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;PVC를 삭제하려면 다음과 같이 수동으로 삭제해야 한다.&lt;/p&gt;
&lt;pre class=&quot;livecodeserver&quot;&gt;&lt;code&gt;k delete pvc --all&lt;/code&gt;&lt;/pre&gt;
&lt;h3 data-ke-size=&quot;size23&quot;&gt;로컬 호스트패스 스토리지 클래스의 장점 및 제약 사항&lt;/h3&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;code&gt;OpenEBS&lt;/code&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;또한, 분산 파일 시스템을 사용하지 않고 로컬 노드의 디스크를 바로 사용하므로 다른 스토리지 솔루션에 비해 성능이 월등하다. 하지만 해당 노드에서만 Pod가 실행되므로 노드가 다운되면 Pod 역시 함께 사용하지 못한다는 제약 사항이 있다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;고가용성 측면에서의 제약 사항은 애플리케이션 단에서 다른 노드의 Pod와 데이터를 동기화하여 해결할 수 있다. 데이터베이스 자체에서 제공하는 동기화 기능을 사용하면 이러한 제약 사항이 없기 때문에 로컬 호스트패스를 실제 운영 환경의 데이터베이스 스토리지 클래스로 사용하는 경우도 있다.&lt;/p&gt;
&lt;h3 data-ke-size=&quot;size23&quot;&gt;스토리지 고가용성 구성 제약&lt;/h3&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;로컬 호스트패스 스토리지 클래스는 Pod가 실행 중인 노드의 특정 디렉터리를 Pod의 볼륨으로 할당한다. 따라서 Pod의 데이터는 해당 노드에만 존재해서 다른 노드로 Pod를 이동하는 것이 불가능하다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;볼륨을 가지지 않는 일반 파드(Stateless POD)는 특정 노드에 장애가 발생해도 다른 노드로 이전이 가능하지만, 로컬 호스트패스를 스토리지 클래스로 가진 Pod는 다른 노드로 이동이 불가능하다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;PVC가 특정 노드만 실행 중인 Pod는 해당 노드에서만 실행 가능하다. 해당 노드의 문제가 발생하면 Pod가 실행되지 않아 서비스 장애가 발생한다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;호스트패스를 사용하는 Pod는 애플리케이션 단에서 서로 다른 노드의 Pod와 서로 데이터를 복제하도록 설정할 필요가 있다. 볼륨 복제를 지원하면 기존 PVC를 삭제했을 때 새로운 노드에서 기존 데이터를 처음부터 복제해서 Pod 실행이 가능하다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;다음 포스팅에서는 EKS 환경에서의 Storage 설정에 대해 작성해보고자 한다.&lt;/p&gt;</description>
      <category>Infra</category>
      <category>k8s</category>
      <category>k8s storage</category>
      <category>openebs</category>
      <category>PV</category>
      <category>pvc</category>
      <category>storage</category>
      <category>Volume</category>
      <category>볼륨 마운트</category>
      <author>겨울바람_</author>
      <guid isPermaLink="true">https://breeze-winter.tistory.com/33</guid>
      <comments>https://breeze-winter.tistory.com/33#entry33comment</comments>
      <pubDate>Fri, 2 Aug 2024 19:45:50 +0900</pubDate>
    </item>
    <item>
      <title>[EKS] AWS Load Balancer Controller on EKS</title>
      <link>https://breeze-winter.tistory.com/32</link>
      <description>&lt;h3 data-ke-size=&quot;size23&quot;&gt;AWS Load Balancer Controller?&lt;/h3&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;AWS Load Balancer Controller는 Kubernetes 클러스터를 위한 AWS Elastic Load Balancer를 관리하는 add-on이다. 따라서 별도로 AWS Load Balancer Controller를 추가 설치하여 로드 밸런서를 관리하는 것이다.&lt;/p&gt;
&lt;p&gt;&lt;figure class=&quot;imageblock alignCenter&quot; data-ke-mobileStyle=&quot;widthOrigin&quot; data-origin-width=&quot;511&quot; data-origin-height=&quot;615&quot;&gt;&lt;span data-url=&quot;https://blog.kakaocdn.net/dn/ctWAo8/btsIPwbHj46/OXTIcWYJqt2qr1skVmP1k0/img.png&quot; data-phocus=&quot;https://blog.kakaocdn.net/dn/ctWAo8/btsIPwbHj46/OXTIcWYJqt2qr1skVmP1k0/img.png&quot;&gt;&lt;img src=&quot;https://blog.kakaocdn.net/dn/ctWAo8/btsIPwbHj46/OXTIcWYJqt2qr1skVmP1k0/img.png&quot; srcset=&quot;https://img1.daumcdn.net/thumb/R1280x0/?scode=mtistory2&amp;fname=https%3A%2F%2Fblog.kakaocdn.net%2Fdn%2FctWAo8%2FbtsIPwbHj46%2FOXTIcWYJqt2qr1skVmP1k0%2Fimg.png&quot; onerror=&quot;this.onerror=null; this.src='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png'; this.srcset='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png';&quot; loading=&quot;lazy&quot; width=&quot;511&quot; height=&quot;615&quot; data-origin-width=&quot;511&quot; data-origin-height=&quot;615&quot;/&gt;&lt;/span&gt;&lt;/figure&gt;
&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;그림과 같이 클러스터 내부에 다수의 Pod가 존재하고 연결을 위해 AWS ELB를 구성했다고 가정해보자. 이 상황에서 AWS Load Balancer Controller는 Control Plane과 상호 작용하여 대상 Pod의 정보를 확인하고 이벤트를 모니터링 하는 것으로 클러스터 내부의 Pod 정보를 취득할 수 있다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;그리고 AWS ELB로 해당 Pod 정보를 프로비저닝하여 타겟 그룹 바인딩을 할 수 있다. 즉, 대상 그룹에 Pod 정보를 추가하거나 삭제하는 관리자 역할을 수행할 수 있다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;이렇듯 AWS Load Balancer Controller가 해당 권한을 수행하기 위해서는 &lt;code&gt;IRSA&lt;/code&gt;라는 보안 작업을 선행해야 한다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;code&gt;IRSA&lt;/code&gt;는 &lt;code&gt;IAM Role for Service Accounts&lt;/code&gt;의 약자로 AWS Load Balancer Controller의 인증 절차를 통해 권한을 위임한다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;AWS Load Balancer Controller가 AWS ELB와 EKS Cluster Control Plane과 지속적인 상호작용을 통해 동작하는 것으로 미루어 봤을 때, Kubernetes 서비스 어카운트와 AWS IAM을 연동하여 인증 절차를 진행하는 것으로 볼 수 있다.&lt;/p&gt;
&lt;p&gt;&lt;figure class=&quot;imageblock alignCenter&quot; data-ke-mobileStyle=&quot;widthOrigin&quot; data-origin-width=&quot;959&quot; data-origin-height=&quot;319&quot;&gt;&lt;span data-url=&quot;https://blog.kakaocdn.net/dn/b8rrfB/btsIRMjCKm2/dI5ZkoKB1LfGrcEUquOwl1/img.png&quot; data-phocus=&quot;https://blog.kakaocdn.net/dn/b8rrfB/btsIRMjCKm2/dI5ZkoKB1LfGrcEUquOwl1/img.png&quot;&gt;&lt;img src=&quot;https://blog.kakaocdn.net/dn/b8rrfB/btsIRMjCKm2/dI5ZkoKB1LfGrcEUquOwl1/img.png&quot; srcset=&quot;https://img1.daumcdn.net/thumb/R1280x0/?scode=mtistory2&amp;fname=https%3A%2F%2Fblog.kakaocdn.net%2Fdn%2Fb8rrfB%2FbtsIRMjCKm2%2FdI5ZkoKB1LfGrcEUquOwl1%2Fimg.png&quot; onerror=&quot;this.onerror=null; this.src='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png'; this.srcset='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png';&quot; loading=&quot;lazy&quot; width=&quot;959&quot; height=&quot;319&quot; data-origin-width=&quot;959&quot; data-origin-height=&quot;319&quot;/&gt;&lt;/span&gt;&lt;/figure&gt;
&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;만약 위 그림처럼 AWS Load Balancer Controller가 존재하지 않는다면, ELB에서 노드의 IP와 Port로 Node Port와 Cluster IP를 거쳐 iptables 분산 룰로 Pod에 전달될 것이다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;AWS Load Balancer Controller가 구성된다면 노드에 Pod 형태로 구성이 된다. AWS Load Balancer Controller는 Control Plane과 상호 작용하여 ELB로 프로비저닝 하기 때문에 ELB는 대상 그룹에 Pod가 매핑되어 Node Port, Cluster IP를 거치지 않고 직접적으로 Pod와 통신을 수행한다.&lt;/p&gt;
&lt;p&gt;&lt;figure class=&quot;imageblock alignCenter&quot; data-ke-mobileStyle=&quot;widthOrigin&quot; data-origin-width=&quot;959&quot; data-origin-height=&quot;319&quot;&gt;&lt;span data-url=&quot;https://blog.kakaocdn.net/dn/bLP4QE/btsIQn57H5a/frlCa9vwSQh5wOwwFT0aa0/img.png&quot; data-phocus=&quot;https://blog.kakaocdn.net/dn/bLP4QE/btsIQn57H5a/frlCa9vwSQh5wOwwFT0aa0/img.png&quot;&gt;&lt;img src=&quot;https://blog.kakaocdn.net/dn/bLP4QE/btsIQn57H5a/frlCa9vwSQh5wOwwFT0aa0/img.png&quot; srcset=&quot;https://img1.daumcdn.net/thumb/R1280x0/?scode=mtistory2&amp;fname=https%3A%2F%2Fblog.kakaocdn.net%2Fdn%2FbLP4QE%2FbtsIQn57H5a%2FfrlCa9vwSQh5wOwwFT0aa0%2Fimg.png&quot; onerror=&quot;this.onerror=null; this.src='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png'; this.srcset='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png';&quot; loading=&quot;lazy&quot; width=&quot;959&quot; height=&quot;319&quot; data-origin-width=&quot;959&quot; data-origin-height=&quot;319&quot;/&gt;&lt;/span&gt;&lt;/figure&gt;
&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;그로 인해 리눅스 커널의 conntrack과 iptables 분산 룰을 확인하는 과정이 생략되어 효율적인 통신이 가능하다. 이러한 동작이 가능한 이유는 Amazon VPC CNI 환경에 따라 노드와 Pod가 동일한 IP 대역인 것이 가장 크다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;ELB의 대표적인 세 가지 중 NLB, CLB와 같이 L4 영역의 부하 분산으로 Kubernetes의 Service의 Load Balancer가 구성될 수 있고, ALB와 같은 L7 영역의 부하 분산으로 Ingress의 Load Balancer가 구성될 수 있다.&lt;/p&gt;</description>
      <category>Public Cloud</category>
      <category>AWS</category>
      <category>EKS</category>
      <category>ELB</category>
      <category>lb</category>
      <category>로드밸런서</category>
      <author>겨울바람_</author>
      <guid isPermaLink="true">https://breeze-winter.tistory.com/32</guid>
      <comments>https://breeze-winter.tistory.com/32#entry32comment</comments>
      <pubDate>Mon, 29 Jul 2024 21:00:33 +0900</pubDate>
    </item>
    <item>
      <title>[EKS] Amazon VPC CNI</title>
      <link>https://breeze-winter.tistory.com/31</link>
      <description>&lt;h3 data-ke-size=&quot;size23&quot;&gt;Amazon VPC CNI 소개&lt;/h3&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;Amazon EKS는 Amazon VPC CNI 플러그인을 통해 클러스터 네트워킹 환경을 구성한다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;CNI는 Container Network Interface의 약자로 컨테이너 간 네트워킹을 제어하는 표준이라는 의미를 가진다. 참고로 Kubernetes의 경우 kubenet이라는 자체 CNI가 존재하지만 기능적 측면에서 제약이 많아 외부 플러그인을 주로 사용한다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;Amazon VPC CNI는 AWS VPC와 자연스럽게 연계되어 네트워킹 환경을 구성할 수 있다. 이로 인해 다양한 VPC 서비스와 통합이 가능하다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;Pod와 Node의 IP를 동일한 대역으로 할당할 수 있으며 이로 인해 통신에 대한 오버헤드가 거의 발생하지 않는다.&lt;/p&gt;
&lt;h3 data-ke-size=&quot;size23&quot;&gt;Assign IP Address on Pod&lt;/h3&gt;
&lt;p&gt;&lt;figure class=&quot;imageblock alignCenter&quot; data-ke-mobileStyle=&quot;widthOrigin&quot; data-origin-width=&quot;1161&quot; data-origin-height=&quot;529&quot;&gt;&lt;span data-url=&quot;https://blog.kakaocdn.net/dn/zzr2f/btsIMi5INKo/th1eKSeEqN7QKtpuGeumB1/img.png&quot; data-phocus=&quot;https://blog.kakaocdn.net/dn/zzr2f/btsIMi5INKo/th1eKSeEqN7QKtpuGeumB1/img.png&quot;&gt;&lt;img src=&quot;https://blog.kakaocdn.net/dn/zzr2f/btsIMi5INKo/th1eKSeEqN7QKtpuGeumB1/img.png&quot; srcset=&quot;https://img1.daumcdn.net/thumb/R1280x0/?scode=mtistory2&amp;fname=https%3A%2F%2Fblog.kakaocdn.net%2Fdn%2Fzzr2f%2FbtsIMi5INKo%2Fth1eKSeEqN7QKtpuGeumB1%2Fimg.png&quot; onerror=&quot;this.onerror=null; this.src='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png'; this.srcset='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png';&quot; loading=&quot;lazy&quot; width=&quot;1161&quot; height=&quot;529&quot; data-origin-width=&quot;1161&quot; data-origin-height=&quot;529&quot;/&gt;&lt;/span&gt;&lt;/figure&gt;
&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;br /&gt;노드에 Amazon VPC CLI 플러그인을 설치하면 &lt;code&gt;aws-node&lt;/code&gt;라는 이름의 &lt;code&gt;DaemonSet&lt;/code&gt;이 생성된다. &lt;code&gt;aws-node&lt;/code&gt;는 Local IP Address Manager의 약자인 L-IPAM과 CNI 플러그인으로 구성되어 있다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;VPC CNI와 L-IPAM은 gRPC를 통해 통신하여 IP주소를 삭제하거나 추가하는 구조이며, L-IPAM은 인스턴스에 대한 메타데이터를 확인하여 사용 가능한 ENI와 Secondary IP 주소를 파악한다.&lt;/p&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;code&gt;Warm-Pool&lt;/code&gt;이라는 사용 가능한 Secondary IP 대역을 구성하는데 &lt;code&gt;Warm-Pool&lt;/code&gt;에 구성된 IP 주소를 Pod가 생성될 때마다 하나씩 할당해준다. &lt;code&gt;Warm Pool&lt;/code&gt;에 적재 가능한 IP 주소는 인스턴스 유형에 따라 가용 수량이 정해져 있다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;L-IPAM의 &lt;code&gt;Warm-Pool&lt;/code&gt;과 인스턴스의 Primary ENI는 밀접한 연관을 지니고 있다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;우선 인스턴스에 구성되는 Primary ENI는 다수의 Priavte IP 주소를 포함하고 있을 수 있다. Primary ENI에 구성된 IP 주소는 Slot이라는 영역에 매칭된다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;위의 그림을 보면 총 3개의 Slot으로 구성되어 있으며, 이를 통해 해당 ENI에서는 3개의 Private IP 주소를 사용할 수 있다는 것을 알 수 있다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;이때 첫 번째 Slot에 매핑되는 IP 주소를 &lt;code&gt;Primary IP&lt;/code&gt; 주소라고 하며 그 이외의 주소를 &lt;code&gt;Secondary IP&lt;/code&gt; 주소라고 한다. 이 &lt;code&gt;Secondary IP&lt;/code&gt; 주소가 &lt;code&gt;Warm-Pool&lt;/code&gt;에 할당되는 것이다.&lt;/p&gt;
&lt;p&gt;&lt;figure class=&quot;imageblock alignCenter&quot; data-ke-mobileStyle=&quot;widthOrigin&quot; data-origin-width=&quot;967&quot; data-origin-height=&quot;576&quot;&gt;&lt;span data-url=&quot;https://blog.kakaocdn.net/dn/dXFqxi/btsIMCpmQZg/6DMd2KdUKg1owetDks2jXk/img.png&quot; data-phocus=&quot;https://blog.kakaocdn.net/dn/dXFqxi/btsIMCpmQZg/6DMd2KdUKg1owetDks2jXk/img.png&quot;&gt;&lt;img src=&quot;https://blog.kakaocdn.net/dn/dXFqxi/btsIMCpmQZg/6DMd2KdUKg1owetDks2jXk/img.png&quot; srcset=&quot;https://img1.daumcdn.net/thumb/R1280x0/?scode=mtistory2&amp;fname=https%3A%2F%2Fblog.kakaocdn.net%2Fdn%2FdXFqxi%2FbtsIMCpmQZg%2F6DMd2KdUKg1owetDks2jXk%2Fimg.png&quot; onerror=&quot;this.onerror=null; this.src='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png'; this.srcset='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png';&quot; loading=&quot;lazy&quot; width=&quot;967&quot; height=&quot;576&quot; data-origin-width=&quot;967&quot; data-origin-height=&quot;576&quot;/&gt;&lt;/span&gt;&lt;/figure&gt;
&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;Pod가 생성되면 &lt;code&gt;Warm-Pool&lt;/code&gt; 대역의 IP 주소를 제공하는데 이때 ENI에 &lt;code&gt;Secondary IP&lt;/code&gt; 주소를 추가하는 개념이다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;만약 API Server로부터 Kubelet으로 신규 Pod를 추가하라는 명령이 내려오면 Kubelet은 Amazon VPC CNI로 추가 명령을 전달한다. VPC CNI는 L-IPAM에게 gRPC로 IP 주소 추가 명령을 전달하는 것이다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;L-IPAM은 &lt;code&gt;Warm-Pool&lt;/code&gt; 대역의 IP 주소를 사용하도록 알리고 ENI의 빈 Slot에 Secondary IP 주소를 매핑한다. 최종적으로는 kubelet이 Pod에 ENI의 &lt;code&gt;Secondary IP&lt;/code&gt;를 할당한다. 여기서 Node의 IP 주소는 &lt;code&gt;Primary IP&lt;/code&gt; 주소이고 Pod의 IP 주소는 ENI의 &lt;code&gt;Secondary IP&lt;/code&gt; 주소로 VPC 내의 동일한 IP 대역으로 유지된다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;만약 Primary ENI에 사용 가능한 Slot이 가득 찼을 때 새로운 Pod 생성 요청이 발생하면 어떻게 될까?&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;추가적인 ENI를 구성하여 해당 ENI의 Slot에 IP 주소를 매핑하고 해당 IP 주소를 Pod에게 할당한다. ENI의 경우 인스턴스의 유형에 따라 생성 한계가 정해져 있기 때문에 무한정 IP 주소를 할당할 수는 없다.&lt;/p&gt;
&lt;h3 data-ke-size=&quot;size23&quot;&gt;VPC CNI Communication&lt;/h3&gt;
&lt;p&gt;&lt;figure class=&quot;imageblock alignCenter&quot; data-ke-mobileStyle=&quot;widthOrigin&quot; data-origin-width=&quot;1017&quot; data-origin-height=&quot;545&quot;&gt;&lt;span data-url=&quot;https://blog.kakaocdn.net/dn/cSncaa/btsINdiiNAv/7j8r6waOSVDBP0iXQZLAI0/img.png&quot; data-phocus=&quot;https://blog.kakaocdn.net/dn/cSncaa/btsINdiiNAv/7j8r6waOSVDBP0iXQZLAI0/img.png&quot;&gt;&lt;img src=&quot;https://blog.kakaocdn.net/dn/cSncaa/btsINdiiNAv/7j8r6waOSVDBP0iXQZLAI0/img.png&quot; srcset=&quot;https://img1.daumcdn.net/thumb/R1280x0/?scode=mtistory2&amp;fname=https%3A%2F%2Fblog.kakaocdn.net%2Fdn%2FcSncaa%2FbtsINdiiNAv%2F7j8r6waOSVDBP0iXQZLAI0%2Fimg.png&quot; onerror=&quot;this.onerror=null; this.src='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png'; this.srcset='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png';&quot; loading=&quot;lazy&quot; width=&quot;1017&quot; height=&quot;545&quot; data-origin-width=&quot;1017&quot; data-origin-height=&quot;545&quot;/&gt;&lt;/span&gt;&lt;/figure&gt;
&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;ENI로 연결된 구역은 VPC 연결 구간으로 AWS Networking을 통해 통신이 가능하다. 각각의 Pod는 통신을 위해 &lt;code&gt;veth0&lt;/code&gt;로 구성되고 이를 연결하기 위해 가상의 ENI와 1:1 매칭이 된다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;그리고 이들을 서로 연결하여 통신이 가능하게 하는 Virtual Router가 각각의 가상의 ENI와 연결되는 Linux Networking 공간에 구성된다. 아래의 그림에서 Pod와 Node의 대역대가 동일한 것을 확인할 수 있는데 이는 VPC IP 대역을 활용하기 위해서다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;위의 그림은 Pod A에서 Pod C로 통신하는 상황을 예시로 그려둔 것이다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;Pod A에서 발생한 Packet은 Linux Networking을 통해 Node A의 ENI까지 전달되고 VPC 구간의 AWS Networking을 통해 Node B로 전달된다. Node B에 들어온 Packet이 Pod C로 전달될 때는 동일한 IP 대역이기 때문에 별도의 작업 없이 전달된다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;이러한 측면에서 오버헤드의 발생이 적어 빠른 통신이 가능하다.&lt;/p&gt;
&lt;h3 data-ke-size=&quot;size23&quot;&gt;EKS VPC CNI vs Kubernetes Calico&lt;/h3&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;Kubernetes 환경에서 보편적으로 많이 사용하고 있는 CNI 플러그인 중 하나인 Calico와 VPC CNI를 비교해보자. 참고로 필자의 홈 서버에 구축된 쿠버네티스의 CNI 또한 Calico를 사용하고 있다.&lt;/p&gt;
&lt;p&gt;&lt;figure class=&quot;imageblock alignCenter&quot; data-ke-mobileStyle=&quot;widthOrigin&quot; data-origin-width=&quot;1016&quot; data-origin-height=&quot;558&quot;&gt;&lt;span data-url=&quot;https://blog.kakaocdn.net/dn/budEfZ/btsIMHcU2tf/khJZg7QuXfyc2CSjtR5ltK/img.png&quot; data-phocus=&quot;https://blog.kakaocdn.net/dn/budEfZ/btsIMHcU2tf/khJZg7QuXfyc2CSjtR5ltK/img.png&quot;&gt;&lt;img src=&quot;https://blog.kakaocdn.net/dn/budEfZ/btsIMHcU2tf/khJZg7QuXfyc2CSjtR5ltK/img.png&quot; srcset=&quot;https://img1.daumcdn.net/thumb/R1280x0/?scode=mtistory2&amp;fname=https%3A%2F%2Fblog.kakaocdn.net%2Fdn%2FbudEfZ%2FbtsIMHcU2tf%2FkhJZg7QuXfyc2CSjtR5ltK%2Fimg.png&quot; onerror=&quot;this.onerror=null; this.src='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png'; this.srcset='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png';&quot; loading=&quot;lazy&quot; width=&quot;1016&quot; height=&quot;558&quot; data-origin-width=&quot;1016&quot; data-origin-height=&quot;558&quot;/&gt;&lt;/span&gt;&lt;/figure&gt;
&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;우선 가장 다른 점은 Node와 Pod가 서로 다른 대역대로 IP 주소가 할당된다는 것이다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;VPC CNI의 예제와 동일하게 Pod A와 Pod C가 통신한다고 가정해보자. Pod A에서 목적지 주소를 Pod C의 IP 주소인 10.0.2.1로 할당한 Packet을 Linux Networking을 통해 Node A의 ENI까지 전달할 것이다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;하지만 Node 간 연결되는 네트워크 구간은 10.0.2.1의 IP 대역을 알지 못한다. 즉, 통신이 불가능하다는 것이다. 이러한 문제점을 해결하기 위해 VXLAN이나 IPIP같은 &lt;code&gt;오버레이 네트워크&lt;/code&gt;를 구성하여 동작한다.&lt;/p&gt;
&lt;p&gt;&lt;figure class=&quot;imageblock alignCenter&quot; data-ke-mobileStyle=&quot;widthOrigin&quot; data-origin-width=&quot;986&quot; data-origin-height=&quot;544&quot;&gt;&lt;span data-url=&quot;https://blog.kakaocdn.net/dn/c6SVwk/btsIMjKiwKI/4lTQTkXKwJMP4pOcgmKLg0/img.png&quot; data-phocus=&quot;https://blog.kakaocdn.net/dn/c6SVwk/btsIMjKiwKI/4lTQTkXKwJMP4pOcgmKLg0/img.png&quot;&gt;&lt;img src=&quot;https://blog.kakaocdn.net/dn/c6SVwk/btsIMjKiwKI/4lTQTkXKwJMP4pOcgmKLg0/img.png&quot; srcset=&quot;https://img1.daumcdn.net/thumb/R1280x0/?scode=mtistory2&amp;fname=https%3A%2F%2Fblog.kakaocdn.net%2Fdn%2Fc6SVwk%2FbtsIMjKiwKI%2F4lTQTkXKwJMP4pOcgmKLg0%2Fimg.png&quot; onerror=&quot;this.onerror=null; this.src='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png'; this.srcset='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png';&quot; loading=&quot;lazy&quot; width=&quot;986&quot; height=&quot;544&quot; data-origin-width=&quot;986&quot; data-origin-height=&quot;544&quot;/&gt;&lt;/span&gt;&lt;/figure&gt;
&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;code&gt;오버레이 네트워크&lt;/code&gt;는 Packet의 IP 헤더에 추가적인 IP 헤더를 덧붙여 전달하는 방법이다. Pod A가 전달하는 Packet을 VXLAN이나 IPIP로 ENI끼리 통신할 수 있는 IP 헤더를 캡슐화하여 전달한다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;그렇게 되면 ENI 구간은 추가된 IP 헤더를 사용하여 Node B까지 통신이 가능해진다. Packet을 Pod C로 전달할 때는 앞에서 덧붙였던 IP 헤더를 역캡슐화하여 원본 Packet을 Pod C에 전달한다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;code&gt;오버레이 네트워크&lt;/code&gt;를 통해 Packet을 캡슐화 혹은 역캡슐화 하는 과정에서 VPC CNI에서는 발생하지 않았던 오버헤드가 발생하기 때문에 VPC CNI 보다는 비효율적인 통신이라고 생각할 수 있다.&lt;/p&gt;
&lt;h3 data-ke-size=&quot;size23&quot;&gt;Amazon VPC CNI Maximum Pod Quantity&lt;/h3&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;Amazon VPC CNI를 통해 Pod를 생성할 경우 인스턴스 유형에 따라 최대 Pod 생성 수량에 제한이 존재한다.&lt;/p&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;code&gt;Secondary IPv4 Address&lt;/code&gt; 방식의 경우 최대 Pod 생성 개수는 &lt;code&gt;ENI 수 x (ENI 당 지원하는 IPv4 수 - 1) + 2&lt;/code&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;ENI 별로 첫 번째 IP 주소는 &lt;code&gt;Primary IP&lt;/code&gt;이기 때문에 제외하고 Node 별로 구성되는 aws-node와 kube-proxy Pod 2대를 포함하여 산정한다. 해당 Pod들은 Node의 &lt;code&gt;Primary IP&lt;/code&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;이러한 최대 Pod 생성 수량에 대한 제약을 늘리기 위해 2021년 8월에 IP Prefix Delegation 방식이 추가되었다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;해당 방식의 원리는 ENI의 Slot에 하나의 IP가 아닌 /28의 IP Prefix 대역으로 구성하는 것이다. /28의 IP Prefix는 2의 4승으로 총 16개의 IP를 사용할 수 있는 대역이다. 그로 인해 계산식이 다음과 같이 변경되었다.&lt;/p&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;code&gt;ENI 수 x (ENI 당 지원하는 IPv4 수 - 1) x 16&lt;/code&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;하지만 IP Prefix Delegation 방식은 Nitro System 계열의 인스턴스 유형에서만 사용이 가능하다. 또한 vCPU 30 코어 미만은 110개로 제한, 그 외에는 250개로 최대치가 정해져있다. 해당 방식을 사용하기 위해서는 kubectl 명령을 통해 활성화해야 한다.&lt;/p&gt;</description>
      <category>Public Cloud</category>
      <category>AWS</category>
      <category>Calico</category>
      <category>EKS</category>
      <category>network</category>
      <category>pod간 통신</category>
      <category>VPC</category>
      <category>vpc cni</category>
      <author>겨울바람_</author>
      <guid isPermaLink="true">https://breeze-winter.tistory.com/31</guid>
      <comments>https://breeze-winter.tistory.com/31#entry31comment</comments>
      <pubDate>Fri, 26 Jul 2024 00:29:25 +0900</pubDate>
    </item>
    <item>
      <title>[Network] TCP 프로토콜에 대한 이해</title>
      <link>https://breeze-winter.tistory.com/30</link>
      <description>&lt;h3 data-ke-size=&quot;size23&quot;&gt;OSI 7 Layer&lt;/h3&gt;
&lt;p&gt;&lt;figure class=&quot;imageblock alignCenter&quot; data-ke-mobileStyle=&quot;widthOrigin&quot; data-origin-width=&quot;1691&quot; data-origin-height=&quot;836&quot;&gt;&lt;span data-url=&quot;https://blog.kakaocdn.net/dn/MPXfZ/btsIKXsaMHl/FCKD2aWuCn8BxwouTtBIQk/img.png&quot; data-phocus=&quot;https://blog.kakaocdn.net/dn/MPXfZ/btsIKXsaMHl/FCKD2aWuCn8BxwouTtBIQk/img.png&quot;&gt;&lt;img src=&quot;https://blog.kakaocdn.net/dn/MPXfZ/btsIKXsaMHl/FCKD2aWuCn8BxwouTtBIQk/img.png&quot; srcset=&quot;https://img1.daumcdn.net/thumb/R1280x0/?scode=mtistory2&amp;fname=https%3A%2F%2Fblog.kakaocdn.net%2Fdn%2FMPXfZ%2FbtsIKXsaMHl%2FFCKD2aWuCn8BxwouTtBIQk%2Fimg.png&quot; onerror=&quot;this.onerror=null; this.src='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png'; this.srcset='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png';&quot; loading=&quot;lazy&quot; width=&quot;1691&quot; height=&quot;836&quot; data-origin-width=&quot;1691&quot; data-origin-height=&quot;836&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;User Mode 레벨에서의 OSI 7계층은 각각 Application Layer, Presentation Layer, Session Layer가 존재한다. User Mode 레벨에서의 계층의 전송 단위는 Data로 표현되며, 호스트(PC 혹은 노트북)가 주요 장비라고 볼 수 있다.&lt;/p&gt;
&lt;ul style=&quot;list-style-type: disc;&quot; data-ke-list-type=&quot;disc&quot;&gt;
&lt;li&gt;Application Layer
&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;Protocol
&lt;ul style=&quot;list-style-type: disc;&quot; data-ke-list-type=&quot;disc&quot;&gt;
&lt;li&gt;HTTP&lt;/li&gt;
&lt;li&gt;FTP&lt;/li&gt;
&lt;/ul&gt;
&lt;/li&gt;
&lt;/ul&gt;
&lt;/li&gt;
&lt;li&gt;Presentation Layer
&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;송신자에게서 온 데이터를 해석하기 위한 Application Layer 데이터 부호화&lt;/li&gt;
&lt;li&gt;수신자 측에서 데이터의 압축을 풀수 있는 방식으로 데이터 압축&lt;/li&gt;
&lt;li&gt;데이터의 암호화 및 복호화&lt;/li&gt;
&lt;li&gt;Protocol
&lt;ul style=&quot;list-style-type: disc;&quot; data-ke-list-type=&quot;disc&quot;&gt;
&lt;li&gt;JPEG&lt;/li&gt;
&lt;li&gt;MPEG&lt;/li&gt;
&lt;/ul&gt;
&lt;/li&gt;
&lt;/ul&gt;
&lt;/li&gt;
&lt;li&gt;Session Layer
&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;Protocol
&lt;ul style=&quot;list-style-type: disc;&quot; data-ke-list-type=&quot;disc&quot;&gt;
&lt;li&gt;RPC&lt;/li&gt;
&lt;/ul&gt;
&lt;/li&gt;
&lt;/ul&gt;
&lt;/li&gt;
&lt;/ul&gt;
&lt;hr data-ke-style=&quot;style1&quot; /&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;Kernel Mode 레벨에서의 OSI 7계층은 각각 Tranport Layer와 Network Layer가 존재한다. Transport Layer의 전송 단위는 Segment이며, Network Layer의 전송 단위는 Packet이다. 각각 L4 스위치와 라우터가 주요 장비다.&lt;/p&gt;
&lt;ul style=&quot;list-style-type: disc;&quot; data-ke-list-type=&quot;disc&quot;&gt;
&lt;li&gt;Transport Layer
&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;데이터 전송을 위해 Port 번호를 사용한다&lt;/li&gt;
&lt;li&gt;주요 데이터 전송 단위는 Segment다&lt;/li&gt;
&lt;li&gt;Protocol
&lt;ul style=&quot;list-style-type: disc;&quot; data-ke-list-type=&quot;disc&quot;&gt;
&lt;li&gt;TCP&lt;/li&gt;
&lt;li&gt;UDP&lt;/li&gt;
&lt;/ul&gt;
&lt;/li&gt;
&lt;/ul&gt;
&lt;/li&gt;
&lt;li&gt;Network Layer
&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;주요 데이터 전송 단위는 Packet이다&lt;/li&gt;
&lt;li&gt;Protocol
&lt;ul style=&quot;list-style-type: disc;&quot; data-ke-list-type=&quot;disc&quot;&gt;
&lt;li&gt;IP&lt;/li&gt;
&lt;li&gt;ICMP&lt;/li&gt;
&lt;/ul&gt;
&lt;/li&gt;
&lt;/ul&gt;
&lt;/li&gt;
&lt;/ul&gt;
&lt;hr data-ke-style=&quot;style1&quot; /&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;H/W 레벨에서의 OSI 7계층은 각각 Data Link Layer와 Physical Layer가 존재한다. Data Link Layer의 전송 단위는 Frame이다. 주요 장비로는 Data Link Layer는 브릿지와 스위치, Physical Layer는 허브, 리피터가 있다.&lt;/p&gt;
&lt;ul style=&quot;list-style-type: disc;&quot; data-ke-list-type=&quot;disc&quot;&gt;
&lt;li&gt;Data Link Layer
&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;MAC 주소를 통해서 통신&lt;/li&gt;
&lt;li&gt;오류 제어, 흐름 제어, 회선 제어를 담당&lt;/li&gt;
&lt;li&gt;주요 데이터 전송 단위는 Frame이다&lt;/li&gt;
&lt;li&gt;Protocol
&lt;ul style=&quot;list-style-type: disc;&quot; data-ke-list-type=&quot;disc&quot;&gt;
&lt;li&gt;HDLC&lt;/li&gt;
&lt;li&gt;PPP&lt;/li&gt;
&lt;/ul&gt;
&lt;/li&gt;
&lt;/ul&gt;
&lt;/li&gt;
&lt;li&gt;Physical Layer
&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;데이터는 0과 1의 비트열, 즉 On, Off의 전기적 신호 상태로 이루어져 해당 계층은 단지 데이터를 전달&lt;/li&gt;
&lt;li&gt;단지 데이터 전달의 역할을 할 뿐이라 알고리즘, 오류제어 기능이 없음&lt;/li&gt;
&lt;li&gt;Protocol
&lt;ul style=&quot;list-style-type: disc;&quot; data-ke-list-type=&quot;disc&quot;&gt;
&lt;li&gt;RS-232CSocketSession Layer에서 Transport Layer로 데이터가 전달될 때 Socket이라는 네트워크 통신 방식을 통해 전달된다.&lt;/li&gt;
&lt;/ul&gt;
&lt;/li&gt;
&lt;/ul&gt;
&lt;/li&gt;
&lt;/ul&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;Socket은 TCP와 같은 프로토콜을 User Mode Application Process에서 접근할 수 있도록 파일 형식으로 추상화한 인터페이스다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;Socket의 종류는 대표적으로 Stream Socket과 Datagram Socket 두 가지가 존재하는데, 이번 포스팅에서는 웹 서버에서 주로 사용하는 Stream Socket을 기반으로 설명하려고 한다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;참고로 Stream Socket은 양방향 통신으로 송신한 데이터가 실제로 도착했는지 즉각적으로 확인할 수 있기 때문에 높은 신뢰성을 보장한다. TCP 프로토콜을 사용하며 주로 웹 서버와 메일 서버 등에서 사용된다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;Datagram Socket은 단방향이며 수신 측에서 데이터를 순서대로 전달받을 수 있는 보장이 없고 패킷 손실이 발생할 수 있기 때문에 신뢰할 수 없다. 하지만 부하가 적고 가볍기 때문에 네트워크 게임 혹은 미디어 스트리밍에 주로 사용된다. Datagram Socket은 UDP 프로토콜을 사용한다.&lt;/p&gt;
&lt;h3 data-ke-size=&quot;size23&quot;&gt;Segmentation&lt;/h3&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;Transport Layer는 Session Layer에서 전달된 Stream Socket을 일정 길이 단위로 분할하여 Transport Layer의 전송 단위인 Segment로 만드는데, 이러한 데이터 변형 과정을 Segmentation 이라고 한다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;Stream Socket의 경우 명확한 용량이 정해져 있지 않지만, Segment는 MSS(Maximum Segment Size)가 정해져있기 때문에 MSS 단위로 Stream을 분할한다. MSS의 경우 다음 계층인 Network Layer의 전송 단위인 Packet의 최대 전송 용량인 MTU(Maximum Transport Unit)의 영향을 받아 할당되는데, 일반적으로 1460bytes가 할당된다.&lt;/p&gt;
&lt;h3 data-ke-size=&quot;size23&quot;&gt;Packet&lt;/h3&gt;
&lt;p&gt;&lt;figure class=&quot;imageblock alignCenter&quot; data-ke-mobileStyle=&quot;widthOrigin&quot; data-origin-width=&quot;1364&quot; data-origin-height=&quot;575&quot;&gt;&lt;span data-url=&quot;https://blog.kakaocdn.net/dn/pE5UH/btsILwOtrs2/M41sJqcexnJe5xxuSU6Rt1/img.png&quot; data-phocus=&quot;https://blog.kakaocdn.net/dn/pE5UH/btsILwOtrs2/M41sJqcexnJe5xxuSU6Rt1/img.png&quot;&gt;&lt;img src=&quot;https://blog.kakaocdn.net/dn/pE5UH/btsILwOtrs2/M41sJqcexnJe5xxuSU6Rt1/img.png&quot; srcset=&quot;https://img1.daumcdn.net/thumb/R1280x0/?scode=mtistory2&amp;fname=https%3A%2F%2Fblog.kakaocdn.net%2Fdn%2FpE5UH%2FbtsILwOtrs2%2FM41sJqcexnJe5xxuSU6Rt1%2Fimg.png&quot; onerror=&quot;this.onerror=null; this.src='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png'; this.srcset='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png';&quot; loading=&quot;lazy&quot; width=&quot;1364&quot; height=&quot;575&quot; data-origin-width=&quot;1364&quot; data-origin-height=&quot;575&quot;/&gt;&lt;/span&gt;&lt;/figure&gt;
&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;Packet은 Header와 Payload를 가진 구조로 되어있는데 특별한 이유로 변경하지 않는 이상 1500bytes로 고정되어 있다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;Packet의 Header에는 IP 헤더와 TCP 헤더가 각각 20bytes 씩 할당된다. 즉 헤더의 크기는 총 40bytes이며, 남은 Payload 부분이 1460bytes를 차지한다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;Transport Layer의 전송 단위인 Segment가 IP 헤더와 TCP 헤더로 Encapsulation된 형태를 Packet이라 지칭하기 때문에 위에서 언급했던 것처럼 Segment의 최대 크기인 MSS는 Packet의 Payload의 최대 용량인 1460bytes로 할당되는 것이다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;만약 Packet의 MTU 용량이 변화하여 Payload의 용량 또한 변한다면 Segment의 MSS 또한 Payload의 용량에 맞춰 변화한다.&lt;/p&gt;
&lt;h3 data-ke-size=&quot;size23&quot;&gt;TCP Transport&lt;/h3&gt;
&lt;p&gt;&lt;figure class=&quot;imageblock alignCenter&quot; data-ke-mobileStyle=&quot;widthOrigin&quot; data-origin-width=&quot;2221&quot; data-origin-height=&quot;942&quot;&gt;&lt;span data-url=&quot;https://blog.kakaocdn.net/dn/bs3irN/btsIJF7dkJl/EgXmzxc7VtIND2wR4eyzGk/img.png&quot; data-phocus=&quot;https://blog.kakaocdn.net/dn/bs3irN/btsIJF7dkJl/EgXmzxc7VtIND2wR4eyzGk/img.png&quot;&gt;&lt;img src=&quot;https://blog.kakaocdn.net/dn/bs3irN/btsIJF7dkJl/EgXmzxc7VtIND2wR4eyzGk/img.png&quot; srcset=&quot;https://img1.daumcdn.net/thumb/R1280x0/?scode=mtistory2&amp;fname=https%3A%2F%2Fblog.kakaocdn.net%2Fdn%2Fbs3irN%2FbtsIJF7dkJl%2FEgXmzxc7VtIND2wR4eyzGk%2Fimg.png&quot; onerror=&quot;this.onerror=null; this.src='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png'; this.srcset='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png';&quot; loading=&quot;lazy&quot; width=&quot;2221&quot; height=&quot;942&quot; data-origin-width=&quot;2221&quot; data-origin-height=&quot;942&quot;/&gt;&lt;/span&gt;&lt;/figure&gt;
&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;Packet은 다시 Encapsulation 되어 Frame에 감싸진채 수신 측으로 전달된다. TCP 프로토콜의 경우 이 송수신 과정에서 ACK를 통해 현재 송수신 진행 상태를 수신 측에서 송신 측으로 전달하게 되는데, ACK에는 Window Size라는 것이 포함되어 있다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;Window Size라는 것은 TCP Buffer의 현재 남아있는 용량을 의미한다. TCP Buffer에는 송신 측으로부터 전달받은 데이터가 Segment 단위로 저장되어 있다. 한 파일의 용량이 MSS보다 클 경우 여러 번에 걸쳐 데이터를 전송하게 되는데 이때 송신 측으로 전달받은 데이터를 임시로 보관하게 되는 곳이 TCP Buffer다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;TCP 프로토콜의 속도가 UDP에 비해 비교적 느린 이유가 이 ACK 때문이다. 수신 측 ACK에 담긴 Window Size가 만약 송신 측에서 전달하는 Segment의 MSS보다 작으면 Wait이 발생하게 되고 이로 인해 송수신이 지연되기 때문이다.&lt;/p&gt;</description>
      <category>Infra</category>
      <category>network</category>
      <category>OSI</category>
      <category>OSI 7 layer</category>
      <category>Packet</category>
      <category>SEGMENT</category>
      <category>Segmentation</category>
      <category>socket</category>
      <category>TCP</category>
      <author>겨울바람_</author>
      <guid isPermaLink="true">https://breeze-winter.tistory.com/30</guid>
      <comments>https://breeze-winter.tistory.com/30#entry30comment</comments>
      <pubDate>Tue, 23 Jul 2024 21:30:43 +0900</pubDate>
    </item>
    <item>
      <title>그림으로 이해하는 객체 지향 설계 5원칙 [SOLID]</title>
      <link>https://breeze-winter.tistory.com/29</link>
      <description>&lt;h3 data-ke-size=&quot;size23&quot;&gt;SRP (&lt;span style=&quot;background-color: #ffffff; color: #4d5156; text-align: left;&quot;&gt;Single Responsibility Principle): 단일 책임 원칙&lt;/span&gt;&lt;/h3&gt;
&lt;blockquote data-ke-style=&quot;style1&quot;&gt;&lt;span style=&quot;font-family: 'Noto Serif KR';&quot;&gt;&quot;어떤 클래스를 변경해야 하는 이유는 오직 하나뿐이어야 한다.&quot;&lt;/span&gt;&lt;br /&gt;&lt;span style=&quot;font-family: 'Noto Serif KR';&quot;&gt;- 로버트 C. 마틴&lt;/span&gt;&lt;br /&gt;&lt;br /&gt;&lt;/blockquote&gt;
&lt;p&gt;&lt;figure class=&quot;imageblock alignCenter&quot; data-ke-mobileStyle=&quot;widthOrigin&quot; data-origin-width=&quot;754&quot; data-origin-height=&quot;567&quot;&gt;&lt;span data-url=&quot;https://blog.kakaocdn.net/dn/cj9XRy/btsIIAb5tnM/T5dwUcmEmahdzibBkl5T3k/img.png&quot; data-phocus=&quot;https://blog.kakaocdn.net/dn/cj9XRy/btsIIAb5tnM/T5dwUcmEmahdzibBkl5T3k/img.png&quot;&gt;&lt;img src=&quot;https://blog.kakaocdn.net/dn/cj9XRy/btsIIAb5tnM/T5dwUcmEmahdzibBkl5T3k/img.png&quot; srcset=&quot;https://img1.daumcdn.net/thumb/R1280x0/?scode=mtistory2&amp;fname=https%3A%2F%2Fblog.kakaocdn.net%2Fdn%2Fcj9XRy%2FbtsIIAb5tnM%2FT5dwUcmEmahdzibBkl5T3k%2Fimg.png&quot; onerror=&quot;this.onerror=null; this.src='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png'; this.srcset='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png';&quot; loading=&quot;lazy&quot; width=&quot;544&quot; height=&quot;409&quot; data-origin-width=&quot;754&quot; data-origin-height=&quot;567&quot;/&gt;&lt;/span&gt;&lt;/figure&gt;
&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;위의 그림과 같이 Man 클래스에 의존하고 있는 다양한 클래스들이 존재한다고 생각해 보자. 그림에서부터 확인이 가능하듯 Man 클래스에 부과된 역할과 책임이 너무 많기 때문에 남성의 표정이 그리 밝지 않다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;어느 날, 여자친구와 헤어질 경우 Man 클래스는 챙길 일 없는 기념일과 사랑한다고 말할 대상이 사라져 힘들어하게 된다. 거기다 여자친구와 결별한 영향이 부모님과 직장 상사에게 까지 영향이 미칠 수 있다. 이렇듯 한 클래스에 너무 많은 책임이 집중될 경우 발생할 수 있는 악영향과 관리의 어려움을 예방하기 위해 책임을 분리하라는 것이 단일 책임 원칙이다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;위의 그림에서 Man 클래스에 집중되어 있는 책임을 아래의 그림처럼 분리해보자.&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;707&quot; data-origin-height=&quot;742&quot;&gt;&lt;span data-url=&quot;https://blog.kakaocdn.net/dn/dpsiYb/btsIIBIPXIA/BDgkIfIZlf8C7RcKPZCApk/img.png&quot; data-phocus=&quot;https://blog.kakaocdn.net/dn/dpsiYb/btsIIBIPXIA/BDgkIfIZlf8C7RcKPZCApk/img.png&quot;&gt;&lt;img src=&quot;https://blog.kakaocdn.net/dn/dpsiYb/btsIIBIPXIA/BDgkIfIZlf8C7RcKPZCApk/img.png&quot; srcset=&quot;https://img1.daumcdn.net/thumb/R1280x0/?scode=mtistory2&amp;fname=https%3A%2F%2Fblog.kakaocdn.net%2Fdn%2FdpsiYb%2FbtsIIBIPXIA%2FBDgkIfIZlf8C7RcKPZCApk%2Fimg.png&quot; onerror=&quot;this.onerror=null; this.src='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png'; this.srcset='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png';&quot; loading=&quot;lazy&quot; width=&quot;548&quot; height=&quot;575&quot; data-origin-width=&quot;707&quot; data-origin-height=&quot;742&quot;/&gt;&lt;/span&gt;&lt;/figure&gt;
&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;Man 이라는 하나의 클래스가 역할과 책임에 따라 세 개의 클래스로 나누어진 것을 볼 수 있다. 만일 여자친구와 헤어져도 남자친구의 역할을 요구하지 않는 부모님과 직장 상사에게는 어떤 악영향도 주지 않는 구조가 만들어진 것이다.&lt;/p&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 style=&quot;color: #000000; text-align: start;&quot; data-ke-size=&quot;size23&quot;&gt;OCP (Open Closed &lt;span style=&quot;background-color: #ffffff; color: #4d5156; text-align: left;&quot;&gt;Principle): 개방 폐쇄 원칙&lt;/span&gt;&lt;/h3&gt;
&lt;blockquote data-ke-style=&quot;style1&quot;&gt;&lt;span style=&quot;font-family: 'Noto Serif KR';&quot;&gt;&quot;소프트웨어 엔티티는 확장에 대해서는 열려 있어야 하지만 변경에 대해서는 닫혀 있어야 한다.&quot;&lt;br /&gt;- 로버트 C. 마틴 &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;585&quot; data-origin-height=&quot;386&quot;&gt;&lt;span data-url=&quot;https://blog.kakaocdn.net/dn/ceT6yn/btsIHnLtkTC/qx1BE8pSH7LIbqEM9LHB6k/img.png&quot; data-phocus=&quot;https://blog.kakaocdn.net/dn/ceT6yn/btsIHnLtkTC/qx1BE8pSH7LIbqEM9LHB6k/img.png&quot;&gt;&lt;img src=&quot;https://blog.kakaocdn.net/dn/ceT6yn/btsIHnLtkTC/qx1BE8pSH7LIbqEM9LHB6k/img.png&quot; srcset=&quot;https://img1.daumcdn.net/thumb/R1280x0/?scode=mtistory2&amp;fname=https%3A%2F%2Fblog.kakaocdn.net%2Fdn%2FceT6yn%2FbtsIHnLtkTC%2Fqx1BE8pSH7LIbqEM9LHB6k%2Fimg.png&quot; onerror=&quot;this.onerror=null; this.src='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png'; this.srcset='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png';&quot; loading=&quot;lazy&quot; width=&quot;518&quot; height=&quot;342&quot; data-origin-width=&quot;585&quot; data-origin-height=&quot;386&quot;/&gt;&lt;/span&gt;&lt;/figure&gt;
&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;어느 날 한 운전자가 제네시스 G80을 구입했다. 수년 동안 제네시스 G80을 운전한 운전자는 돈을 더 모아 닷지 챌린저 SRT를 구입하게 된다. 창문과 기어 조작이 자동이던 제네시스 G80에서 창문과 기어 조작이 수동인 닷지 챌린저를 운전하려고 하니 운전자의 행동에 변화가 생긴다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;제네시스 G80을 운전할 때는 해당 차량 인스턴스의 창문자동개방() 메소드를 사용했는데 닷지 챌린저로 차종을 변경하자 닷지 챌린저 인스턴스의 창문수동개방() 메소드를 사용하게 된다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;현실 세계라면 차의 기종에 맞게 운전자가 행동을 다르게 하는 것이 올바른 방법이겠지만 객체 지향 원칙이 적용되는 프로그래밍 세계에서는 굳이 운전자가 기종 변경의 영향을 받을 필요는 없다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p&gt;&lt;figure class=&quot;imageblock alignCenter&quot; data-ke-mobileStyle=&quot;widthOrigin&quot; data-origin-width=&quot;669&quot; data-origin-height=&quot;594&quot;&gt;&lt;span data-url=&quot;https://blog.kakaocdn.net/dn/bVIqkz/btsIGa7Fk1N/hySuIKEvmTmkqgXUNn5F00/img.png&quot; data-phocus=&quot;https://blog.kakaocdn.net/dn/bVIqkz/btsIGa7Fk1N/hySuIKEvmTmkqgXUNn5F00/img.png&quot;&gt;&lt;img src=&quot;https://blog.kakaocdn.net/dn/bVIqkz/btsIGa7Fk1N/hySuIKEvmTmkqgXUNn5F00/img.png&quot; srcset=&quot;https://img1.daumcdn.net/thumb/R1280x0/?scode=mtistory2&amp;fname=https%3A%2F%2Fblog.kakaocdn.net%2Fdn%2FbVIqkz%2FbtsIGa7Fk1N%2FhySuIKEvmTmkqgXUNn5F00%2Fimg.png&quot; onerror=&quot;this.onerror=null; this.src='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png'; this.srcset='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png';&quot; loading=&quot;lazy&quot; width=&quot;612&quot; height=&quot;543&quot; data-origin-width=&quot;669&quot; data-origin-height=&quot;594&quot;/&gt;&lt;/span&gt;&lt;/figure&gt;
&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;위의 그림과 같이 상위 클래스 혹은 인터페이스를 중간에 두는 것으로 다양한 자동차가 생긴다고 해도 객체 지향 세계의 운전자는 영향을 받지 않게 된다.&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;여기서 다양한 자동차가 생긴다는 것을 자동차 입장에서는 자신의 확장에는 개방돼 있는 것이고, 운전자의 입장에서는 변화에 폐쇄돼 있는 것이다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;개방 폐쇄 원칙이 적용된 것들 중 자바 개발자가 자주 접했을 대표적인 예시가 바로 JDBC다. JDBC를 사용하는 클라이언트는 데이터베이스가 오라클에서 MySQL로 변경되더라도 Connection을 설정하는 부분 이외에는 수정할 필요가 없다.&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;br /&gt;JDBC뿐만 아니라 Hibernate, MyBatis, iBatis 등 데이터베이스 프로그래밍을 지원하는 다른 라이브러리와 프레임워크에도 개방 폐쇄 원칙이 적용되어 있다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;사실 개방 폐쇄 원칙을 따르지 않는다고 해서 객체 지향 프로그램을 구현하는 것이 불가능한 것은 아니다. 하지만 개방 폐쇄 원칙을 무시하고 프로그램을 작성하면 객체 지향 프로그래밍의 가장 큰 장점인 유연성, 재사용성, 유지보수성을 얻을 수 없다.&lt;/p&gt;
&lt;h3 style=&quot;color: #000000; text-align: start;&quot; data-ke-size=&quot;size23&quot;&gt;LSP (&lt;span style=&quot;background-color: #ffffff; color: #202122; text-align: left;&quot;&gt;Liskov substitution&lt;/span&gt; &lt;span style=&quot;background-color: #ffffff; color: #4d5156; text-align: left;&quot;&gt;Principle): 리스코프 치환 원칙&lt;/span&gt;&lt;/h3&gt;
&lt;blockquote data-ke-style=&quot;style1&quot;&gt;&lt;span style=&quot;font-family: 'Noto Serif KR';&quot;&gt;&quot;서브 타입은 언제나 자신의 기반 타입으로 교체할 수 있어야 한다.&quot;&lt;br /&gt;&lt;span style=&quot;background-color: #ffffff; color: #4d5156; text-align: left;&quot;&gt;- 로버트 C. 마틴&lt;/span&gt; &lt;br /&gt;&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;p data-ke-size=&quot;size16&quot;&gt;1. 하위 클래스 is a kind of 상위 클래스&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;2. 구현 클래스 is able to 인터페이스&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;위 두 개의 문장대로 구현된 프로그램이라면 이미 리스코프 치환 원칙을 잘 지키고 있는 것이라 할 수 있다. 위의 문장대로 구현되지 않은 상속이 존재할 수 있을까?&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;만약 상속 관계를 조직도 혹은 계층도 형태로 구현할 경우 위 문장대로 구현되지 않은 상속이 발생하게 된다. 아래의 예시를 한 번 살펴보자.&lt;/p&gt;
&lt;p&gt;&lt;figure class=&quot;imageblock alignCenter&quot; data-ke-mobileStyle=&quot;widthOrigin&quot; data-origin-width=&quot;441&quot; data-origin-height=&quot;296&quot;&gt;&lt;span data-url=&quot;https://blog.kakaocdn.net/dn/9iZ1g/btsIHHwax60/C9vBh3wvPDkpuXLMyfAHfk/img.png&quot; data-phocus=&quot;https://blog.kakaocdn.net/dn/9iZ1g/btsIHHwax60/C9vBh3wvPDkpuXLMyfAHfk/img.png&quot;&gt;&lt;img src=&quot;https://blog.kakaocdn.net/dn/9iZ1g/btsIHHwax60/C9vBh3wvPDkpuXLMyfAHfk/img.png&quot; srcset=&quot;https://img1.daumcdn.net/thumb/R1280x0/?scode=mtistory2&amp;fname=https%3A%2F%2Fblog.kakaocdn.net%2Fdn%2F9iZ1g%2FbtsIHHwax60%2FC9vBh3wvPDkpuXLMyfAHfk%2Fimg.png&quot; onerror=&quot;this.onerror=null; this.src='//t1.daumcdn.net/tistory_admin/static/images/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;266&quot; data-origin-width=&quot;441&quot; data-origin-height=&quot;296&quot;/&gt;&lt;/span&gt;&lt;/figure&gt;
&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;위와 같은 가계도를 상속 관계라고 생각해보자. 최상위 클래스인 GrandFather 클래스가 존재하고 해당 클래스를 상속받는 Father와 Uncle 클래스가 존재한다.&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;이러한 형태의 상속 관계는 어째서 리스코프 치환 원칙을 잘 지키지 못하는 것일까?&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;상위 클래스의 객체 참조 변수에는 하위 클래스의 인스턴스를 할당할 수 있다. 즉, 상위 클래스인 Father 객체 참조 변수에는 하위 클래스인 Son 혹은 Daughter의 인스턴스를 할당할 수 있어야 한다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;간단한 코드로 표현하자면 다음과 같다.&amp;nbsp;&lt;/p&gt;
&lt;pre id=&quot;code_1721468522117&quot; class=&quot;java&quot; data-ke-language=&quot;java&quot; data-ke-type=&quot;codeblock&quot;&gt;&lt;code&gt;Father 보영 = new Daughter();&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;굉장히 이상한 코드라는 생각이 든다. 보영이는 Father 클래스를 상속받고 있기 때문에 Father 객체가 가진 메소드를 가지고 있어야 한다. 그렇다면 어떤 경우가 올바른 형태의 상속 관계를 가져 리스코프 치환 원칙을 만족할 수 있을까?&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;바로 분류도 형태의 상속 관계가 이에 해당한다. 아래의 예시를 살펴보자.&lt;/p&gt;
&lt;p&gt;&lt;figure class=&quot;imageblock alignCenter&quot; data-ke-mobileStyle=&quot;widthOrigin&quot; data-origin-width=&quot;677&quot; data-origin-height=&quot;296&quot;&gt;&lt;span data-url=&quot;https://blog.kakaocdn.net/dn/cga7A0/btsIHKzFW8q/7cmwcTpvGbgNNI526No5lK/img.png&quot; data-phocus=&quot;https://blog.kakaocdn.net/dn/cga7A0/btsIHKzFW8q/7cmwcTpvGbgNNI526No5lK/img.png&quot;&gt;&lt;img src=&quot;https://blog.kakaocdn.net/dn/cga7A0/btsIHKzFW8q/7cmwcTpvGbgNNI526No5lK/img.png&quot; srcset=&quot;https://img1.daumcdn.net/thumb/R1280x0/?scode=mtistory2&amp;fname=https%3A%2F%2Fblog.kakaocdn.net%2Fdn%2Fcga7A0%2FbtsIHKzFW8q%2F7cmwcTpvGbgNNI526No5lK%2Fimg.png&quot; onerror=&quot;this.onerror=null; this.src='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png'; this.srcset='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png';&quot; loading=&quot;lazy&quot; width=&quot;579&quot; height=&quot;253&quot; data-origin-width=&quot;677&quot; data-origin-height=&quot;296&quot;/&gt;&lt;/span&gt;&lt;/figure&gt;
&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;최상위 Animal 클래스를 상속받는 Mammailia 클래스와 Birds 클래스가 존재한다. 보영이 때처럼 간단히 코드를 사용해 표현해보면 다음과 같다.&lt;/p&gt;
&lt;pre id=&quot;code_1721468862809&quot; class=&quot;java&quot; data-ke-language=&quot;java&quot; data-ke-type=&quot;codeblock&quot;&gt;&lt;code&gt;Mammaila 고래 = new Whale()&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;span style=&quot;color: #333333; text-align: start;&quot;&gt;Mammailia&lt;span&gt; 클래스의 &lt;/span&gt;&lt;/span&gt;메소드를 그대로 상속받아 사용해도 이상하지 않다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;결론적으로 계층도/조직도 형태의 구조는 리스코프 치환 원칙을 위배하고 있는 것이며, 분류도 형태의 구조는 리스코프 치환 원칙을 만족하는 것이다.&lt;/p&gt;
&lt;h3 style=&quot;color: #000000; text-align: start;&quot; data-ke-size=&quot;size23&quot;&gt;ISP (&lt;span style=&quot;background-color: #ffffff; color: #202122; text-align: left;&quot;&gt;Interface Segregation&lt;/span&gt;&lt;span&gt;&amp;nbsp;&lt;/span&gt;&lt;span style=&quot;background-color: #ffffff; color: #4d5156; text-align: left;&quot;&gt;Principle): 인터페이스 분리 원칙&lt;/span&gt;&lt;/h3&gt;
&lt;blockquote data-ke-style=&quot;style1&quot;&gt;&lt;span style=&quot;font-family: 'Noto Serif KR';&quot;&gt;&quot;클라이언트는 자신이 사용하지 않는 메소드에 의존 관계를 맺으면 안 된다.&quot;&lt;br /&gt;- 로버트 C. 마틴&lt;br /&gt;&lt;br /&gt;&lt;/span&gt;&lt;/blockquote&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;ISP에 대한 설명 이전에, 단일 책임 원칙의 예제를 다시 기억해보자. 단일 책임 원칙을 적용하기 전 Man 클래스는 여러 책임과 역할을 가지고 있었지만, 단일 책임 원칙을 적용한 이후 각기 하나의 책임과 역할을 갖는 클래스로 나뉘었다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;하지만 Man 클래스를 나누지 않는 방법이 있다면 어떨까?&lt;/p&gt;
&lt;p&gt;&lt;figure class=&quot;imageblock alignCenter&quot; data-ke-mobileStyle=&quot;widthOrigin&quot; data-origin-width=&quot;1326&quot; data-origin-height=&quot;723&quot;&gt;&lt;span data-url=&quot;https://blog.kakaocdn.net/dn/bly7xI/btsIGGd0fkO/SHvkH9ZS1g0TrFTwXkKfLk/img.png&quot; data-phocus=&quot;https://blog.kakaocdn.net/dn/bly7xI/btsIGGd0fkO/SHvkH9ZS1g0TrFTwXkKfLk/img.png&quot;&gt;&lt;img src=&quot;https://blog.kakaocdn.net/dn/bly7xI/btsIGGd0fkO/SHvkH9ZS1g0TrFTwXkKfLk/img.png&quot; srcset=&quot;https://img1.daumcdn.net/thumb/R1280x0/?scode=mtistory2&amp;fname=https%3A%2F%2Fblog.kakaocdn.net%2Fdn%2Fbly7xI%2FbtsIGGd0fkO%2FSHvkH9ZS1g0TrFTwXkKfLk%2Fimg.png&quot; onerror=&quot;this.onerror=null; this.src='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png'; this.srcset='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png';&quot; loading=&quot;lazy&quot; width=&quot;1326&quot; height=&quot;723&quot; data-origin-width=&quot;1326&quot; data-origin-height=&quot;723&quot;/&gt;&lt;/span&gt;&lt;/figure&gt;
&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;여자친구를 만날 때는 남자친구 역할만 할 수 있게 인터페이스로 제한하고, 부모님과 있을 때는 아들 역할만 할 수 있게 인터페이스로 제한하고, 직장 상사 앞에서는 사원 인터페이스로 제한하는 것이 바로 인터페이스 분리 원칙의 핵심이다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;결론적으로 SRP와 ISP는 같은 문제에 대한 두 가지 다른 해결책이라고 볼 수 있다. 프로젝트 요구사항과 설계자의 취향에 따라 단일 책임 원칙이나 인터페이스 분할 원칙 중 하나를 선택해서 설계할 수 있다. 하지만 특별한 경우가 아니라면 단일 책임 원칙을 적용하는 것이 더 좋은 해결책이라고 할 수 있다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;인터페이스 분할 원칙을 이야기할 때 항상 함께 등장하는 원칙 중 하나로 인터페이스 최소주의 원칙이라는 것이 있다. 인터페이스를 통해 메소드를 외부에 제공할 때는 그 역할에 충실한 최소한의 메소드만 제공하라는 것이다.&lt;/p&gt;
&lt;h3 style=&quot;color: #000000; text-align: start;&quot; data-ke-size=&quot;size23&quot;&gt;DIP (Dependency Inversion &lt;span style=&quot;background-color: #ffffff; color: #4d5156; text-align: left;&quot;&gt;Principle): 의존 역전 원칙&lt;/span&gt;&lt;/h3&gt;
&lt;blockquote data-ke-style=&quot;style1&quot;&gt;&lt;span style=&quot;font-family: 'Noto Serif KR';&quot;&gt;&quot;고차원 모듈은 저차원 모듈에 의존하면 안된다. 두 모듈 모두 다른 추상화된 것에 의존해야 한다.&quot;&lt;br /&gt;&quot;추상화된 것은 구체적인 것에 의존하면 안된다. 구체적인 것이 추상화된 것에 의존해야 한다.&quot;&lt;br /&gt;&quot;자주 변경되는 구체 클래스에 의존하지 마라.&quot;&lt;br /&gt;- 로버트 C. 마틴&lt;br /&gt;&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;이번 DIP 또한 예제를 통해 설명해보려고 한다. 우선 개방 폐쇄 원칙의 예시에서 사용한 자동차를 기억해낼 필요가 있다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;눈이 매우 많이 오는 날에 안전하게 운전을 하려면 스노우 타이어가 필요하다. 자동차가 스노우 타이어에 의존하는 것이다.&amp;nbsp;&lt;/p&gt;
&lt;p&gt;&lt;figure class=&quot;imageblock alignCenter&quot; data-ke-mobileStyle=&quot;widthOrigin&quot; data-origin-width=&quot;711&quot; data-origin-height=&quot;248&quot;&gt;&lt;span data-url=&quot;https://blog.kakaocdn.net/dn/tAQ3j/btsIGbyKQ41/8C9YBUuklLQbsvMIyuaF5K/img.png&quot; data-phocus=&quot;https://blog.kakaocdn.net/dn/tAQ3j/btsIGbyKQ41/8C9YBUuklLQbsvMIyuaF5K/img.png&quot;&gt;&lt;img src=&quot;https://blog.kakaocdn.net/dn/tAQ3j/btsIGbyKQ41/8C9YBUuklLQbsvMIyuaF5K/img.png&quot; srcset=&quot;https://img1.daumcdn.net/thumb/R1280x0/?scode=mtistory2&amp;fname=https%3A%2F%2Fblog.kakaocdn.net%2Fdn%2FtAQ3j%2FbtsIGbyKQ41%2F8C9YBUuklLQbsvMIyuaF5K%2Fimg.png&quot; onerror=&quot;this.onerror=null; this.src='//t1.daumcdn.net/tistory_admin/static/images/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;216&quot; data-origin-width=&quot;711&quot; data-origin-height=&quot;248&quot;/&gt;&lt;/span&gt;&lt;/figure&gt;
&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;하지만 스노우타이어는 계절이 바뀌면 일반 타이어로 교체해야 한다. 스노우 타이어를 일반 타이어로 교체할 때 자동차는 보다 변경이 더 자주 발생하는 스노우 타이어라는 객체에 의존하고 있기 때문에 의도치 않게 영향을 받게 된다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;이 구조를 개선하려면 다음과 같이 자동차가 구체적인 타이어의 종류에 의존하는 것이 아닌 추상화된 타이어의 인터페이스에만 의존하게 하는 방식으로 구조를 변경해야 한다.&lt;/p&gt;
&lt;p&gt;&lt;figure class=&quot;imageblock alignCenter&quot; data-ke-mobileStyle=&quot;widthOrigin&quot; data-origin-width=&quot;999&quot; data-origin-height=&quot;552&quot;&gt;&lt;span data-url=&quot;https://blog.kakaocdn.net/dn/JfzGn/btsIIADbtHx/x2oN4Ov74kIt5DOKZ5Mip0/img.png&quot; data-phocus=&quot;https://blog.kakaocdn.net/dn/JfzGn/btsIIADbtHx/x2oN4Ov74kIt5DOKZ5Mip0/img.png&quot;&gt;&lt;img src=&quot;https://blog.kakaocdn.net/dn/JfzGn/btsIIADbtHx/x2oN4Ov74kIt5DOKZ5Mip0/img.png&quot; srcset=&quot;https://img1.daumcdn.net/thumb/R1280x0/?scode=mtistory2&amp;fname=https%3A%2F%2Fblog.kakaocdn.net%2Fdn%2FJfzGn%2FbtsIIADbtHx%2Fx2oN4Ov74kIt5DOKZ5Mip0%2Fimg.png&quot; onerror=&quot;this.onerror=null; this.src='//t1.daumcdn.net/tistory_admin/static/images/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;385&quot; data-origin-width=&quot;999&quot; data-origin-height=&quot;552&quot;/&gt;&lt;/span&gt;&lt;/figure&gt;
&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;해당 구조는 스노우 타이어에서 일반 타이어로, 또는 다른 구체적인 타이어로 변경돼도 자동차는 그 영향을 받지 않는 형태로 구성된다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;위의 그림의 형태가 OCP에서 예시로 든 자동차의 구조도와 굉장히 유사하다는 것을 느낄 수 있다. OCP와 DIP는 객체 지향 4개 특성 중 상속과 다형성을 통해 구현되기 때문에 비슷한 형태를 띄는 것이다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;구조도가 변경되기 전 스노우 타이어와 변경된 후의 스노우 타이어에 큰 변화가 생겼다. 바로 스노우 타이어가 그 무엇에도 의존하지 않는 클래스였는데, 구조도가 변경된 이후의 스노우 타이어는 추상적인 타이어 인터페이스에 의존하게 됐다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;그리고 자동차는 변경되기 쉬운 스노우 타이어에 의존하던 관계를 추상화된 타이어 인터페이스를 추가해 두고 의존 관계를 역전시키고 있다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;이처럼 자신보다 변하기 쉬운 것에 의존하던 것을 추상화된 인터페이스나 상위 클래스를 두어 변화로 인한 영향을 받지 않게 하는 것이 의존 관계 역전 원칙이다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;상위 클래스, 인터페이스, 추상 클래스일수록 변하지 않을 가능성이 높기에 하위 클래스나 구체 클래스가 아닌 상위 클래스, 인터페이스, 추상 클래스를 통해 의존하는 것이다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;</description>
      <category>Dev</category>
      <category>DIP</category>
      <category>ISP</category>
      <category>Java</category>
      <category>lsp</category>
      <category>OCP</category>
      <category>SOLID</category>
      <category>spring</category>
      <category>SRP</category>
      <category>객체 지향 원칙</category>
      <category>객체지향</category>
      <author>겨울바람_</author>
      <guid isPermaLink="true">https://breeze-winter.tistory.com/29</guid>
      <comments>https://breeze-winter.tistory.com/29#entry29comment</comments>
      <pubDate>Sat, 20 Jul 2024 20:06:29 +0900</pubDate>
    </item>
    <item>
      <title>[Jenkins] CI/CD 파이프라인 구축 중 .gitIgnore 처리된 Config 파일 관리</title>
      <link>https://breeze-winter.tistory.com/28</link>
      <description>&lt;h3 data-ke-size=&quot;size23&quot;&gt;CI/CD Pipeline Architecture&lt;/h3&gt;
&lt;p&gt;&lt;figure class=&quot;imageblock alignCenter&quot; data-ke-mobileStyle=&quot;widthOrigin&quot; data-origin-width=&quot;772&quot; data-origin-height=&quot;564&quot;&gt;&lt;span data-url=&quot;https://blog.kakaocdn.net/dn/no1Ky/btsIGL0gXI3/B0e7cKhlGv9AHbGXwPskEk/img.png&quot; data-phocus=&quot;https://blog.kakaocdn.net/dn/no1Ky/btsIGL0gXI3/B0e7cKhlGv9AHbGXwPskEk/img.png&quot;&gt;&lt;img src=&quot;https://blog.kakaocdn.net/dn/no1Ky/btsIGL0gXI3/B0e7cKhlGv9AHbGXwPskEk/img.png&quot; srcset=&quot;https://img1.daumcdn.net/thumb/R1280x0/?scode=mtistory2&amp;fname=https%3A%2F%2Fblog.kakaocdn.net%2Fdn%2Fno1Ky%2FbtsIGL0gXI3%2FB0e7cKhlGv9AHbGXwPskEk%2Fimg.png&quot; onerror=&quot;this.onerror=null; this.src='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png'; this.srcset='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png';&quot; loading=&quot;lazy&quot; width=&quot;582&quot; height=&quot;425&quot; data-origin-width=&quot;772&quot; data-origin-height=&quot;564&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;개발자가 Github에 변경된 코드를 Push 하게 되면, 홈서버에 위치한 Jenkins로 HTTP POST 요청이 전달된다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;Jenkins에서는 해당 요청을 트리거로 지정된 Job을 수행하게 된다. 이번 프로젝트에서 설정한 Jenkins의 파이프라인은 Github의 Repository를 클론하여 해당 프로젝트의 이미지를 build 한 후 해당 이미지를 DockerHub로 Push 하는 것이다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;사용한 백엔드 프레임워크는 Spring Boot로 Gradle을 통해 bootJar된 jar 파일을 이미지화 시켜 DockerHub에 Push 하는 것이 주된 목표다.&lt;/p&gt;
&lt;h3 data-ke-size=&quot;size23&quot;&gt;Spring Boot &amp;amp; yaml file&lt;/h3&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;Spring Boot의 경우 데이터베이스에 대한 정보, OAuth 사용을 위한 Key 값과 같이 노출을 피해야 하는 정보들을 &lt;code&gt;application.yaml&lt;/code&gt; 혹은 &lt;code&gt;application.properties&lt;/code&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;개발자들은 노출되서는 안되는 해당 파일들이 Github 같은 공용 저장소에 올라가지 않도록 주의해야 하는데, Git을 사용할 경우 &lt;code&gt;.gitIgnore&lt;/code&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;code&gt;.gitIgnore&lt;/code&gt; 를 통해 &lt;code&gt;application-local.yaml&lt;/code&gt;, &lt;code&gt;application-oauth.yaml&lt;/code&gt; 파일을 관리하고 있었다. &lt;code&gt;.gitIgnore&lt;/code&gt;에 지정한 파일들은 형상 관리의 범위에서 제외되기 때문에 Github에도 업로드 되지 않는다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;하지만 필자가 작성한 CI/CD 파이프라인은 Github의 리포지토리를 클론하여 동작하기 때문에 해당 파일들이 없다면 Spring Boot가 정상적으로 구동이 되지 않아 파이프라인이 실패하게 된다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;어떻게 해당 파일들을 공개된 저장소에 올리지 않으면서도 CI/CD 시에 적용할 수 있을까?&lt;/p&gt;
&lt;h3 data-ke-size=&quot;size23&quot;&gt;Jenkins Credentials&lt;/h3&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;필자는 Jenkins에서 제공하는 Credentials에 각 파일을 등록하여, 파이프라인 스크립트 작성 시 변수로 불러와 사용하는 방법을 사용했다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;[Jenkins 관리] -&amp;gt; [Credentials] -&amp;gt; [Add Credentials] 를 통해 Credential을 생성할 수 있는 폼으로 이동할 수 있다.&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;1606&quot; data-origin-height=&quot;762&quot;&gt;&lt;span data-url=&quot;https://blog.kakaocdn.net/dn/bm0Dmx/btsIHG4X10J/Z2MK9h6V4tqRhp1Whkolg0/img.png&quot; data-phocus=&quot;https://blog.kakaocdn.net/dn/bm0Dmx/btsIHG4X10J/Z2MK9h6V4tqRhp1Whkolg0/img.png&quot;&gt;&lt;img src=&quot;https://blog.kakaocdn.net/dn/bm0Dmx/btsIHG4X10J/Z2MK9h6V4tqRhp1Whkolg0/img.png&quot; srcset=&quot;https://img1.daumcdn.net/thumb/R1280x0/?scode=mtistory2&amp;fname=https%3A%2F%2Fblog.kakaocdn.net%2Fdn%2Fbm0Dmx%2FbtsIHG4X10J%2FZ2MK9h6V4tqRhp1Whkolg0%2Fimg.png&quot; onerror=&quot;this.onerror=null; this.src='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png'; this.srcset='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png';&quot; loading=&quot;lazy&quot; width=&quot;1606&quot; height=&quot;762&quot; data-origin-width=&quot;1606&quot; data-origin-height=&quot;762&quot;/&gt;&lt;/span&gt;&lt;/figure&gt;
&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;위의 사진과 같이 파일 자체를 Credential로 등록할 때는 Kind를 Secret file로 선택하고 등록하고자 하는 파일을 지정하면 된다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;ID의 경우 파이프라인 스크립트에서 사용되기 때문에 명확하게 구분이 가능한 이름으로 지어주고, Description은 해당 Credential에 대한 간단한 설명글을 작성해두면 된다. 성공적으로 등록했다면 아래와 같이 Credentials 목록에서 확인이 가능하다.&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;1461&quot; data-origin-height=&quot;88&quot;&gt;&lt;span data-url=&quot;https://blog.kakaocdn.net/dn/bzNIwn/btsIHAKqhJN/mQHpJfuJLbh9TavGiW7AWK/img.png&quot; data-phocus=&quot;https://blog.kakaocdn.net/dn/bzNIwn/btsIHAKqhJN/mQHpJfuJLbh9TavGiW7AWK/img.png&quot;&gt;&lt;img src=&quot;https://blog.kakaocdn.net/dn/bzNIwn/btsIHAKqhJN/mQHpJfuJLbh9TavGiW7AWK/img.png&quot; srcset=&quot;https://img1.daumcdn.net/thumb/R1280x0/?scode=mtistory2&amp;fname=https%3A%2F%2Fblog.kakaocdn.net%2Fdn%2FbzNIwn%2FbtsIHAKqhJN%2FmQHpJfuJLbh9TavGiW7AWK%2Fimg.png&quot; onerror=&quot;this.onerror=null; this.src='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png'; this.srcset='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png';&quot; loading=&quot;lazy&quot; width=&quot;1461&quot; height=&quot;88&quot; data-origin-width=&quot;1461&quot; data-origin-height=&quot;88&quot;/&gt;&lt;/span&gt;&lt;/figure&gt;
&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;이렇게 등록된 Credentials는 Jenkins의 파이프라인 스크립트를 통해 불러올 수 있다.&lt;/p&gt;
&lt;h3 data-ke-size=&quot;size23&quot;&gt;CI/CD Pipeline Script&lt;/h3&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;이번 포스팅의 주요 목적은 Credentials로 등록된 config 파일을 파이프라인 스크립트에서 사용하는 것이기 때문에 이외의 스크립트에 대한 추가적인 설명은 진행하지 않는다.&lt;/p&gt;
&lt;pre class=&quot;routeros&quot;&gt;&lt;code&gt;...

stage('copy yaml') {
    steps {
        withCredentials([file(credentialsId: 'Animealth-Application-Local', variable: 'LOCAL_YAML'),
                        file(credentialsId: 'Animealth-Application-Oauth', variable: 'OAUTH_YAML')]) {
            script {
                sh 'cp $LOCAL_YAML /var/jenkins_home/workspace/Animealth_Backend/src/main/resources/application-local.yml'
                sh 'cp $OAUTH_YAML /var/jenkins_home/workspace/Animealth_Backend/src/main/resources/application-oauth.yml'
            }
        }

        echo 'copy yaml success!'
    }
}

...&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;위의 스크립트는 Docker 이미지를 빌드하기 전에 Credentials로 등록된 파일을 클론한 프로젝트 내부의 경로로 복사하는 스크립트다.&lt;/p&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;code&gt;CredentialsId&lt;/code&gt;에는 아까전 등록한 Credential의 ID를 입력하고, &lt;code&gt;variable&lt;/code&gt;에는 해당 스크립트에서 사용하게 될 Credential을 담게 될 변수명을 원하는대로 입력하면 된다.&lt;/p&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;code&gt;sh&lt;/code&gt; 명령어는 &lt;code&gt;$LOCAL_YAML&lt;/code&gt; 이라는 변수로 등록된 &lt;code&gt;Animealth-Application-Local&lt;/code&gt; Credential을 &lt;code&gt;/var/jenkins_home/workspace/Animealth_Backend/src/main/resources&lt;/code&gt; 경로에 위치한 &lt;code&gt;application-local.yaml&lt;/code&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;code&gt;sh&lt;/code&gt; 명령어 또한 동일한 의미의 명령어로 &lt;code&gt;application-oauth.yaml&lt;/code&gt;에 대한 내용을 담고 있다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;성공적으로 &lt;code&gt;copy yaml&lt;/code&gt; 스테이지가 종료되면 copy yaml success! 라는 문구를 콘솔에 출력한다.&lt;/p&gt;
&lt;h3 data-ke-size=&quot;size23&quot;&gt;Result&lt;/h3&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;작성된 파이프라인을 통해 CI/CD를 진행하면 Jenkins의 Item Console Output을 통해 결과를 실시간으로 확인할 수 있다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;yaml 파일에 대한 Credentials 등록과 파이프라인 스크립트 작성이 잘 이루어졌다면, 아래와 같이 copy yaml success! 라는 문구가 콘솔에 출력되는 것을 확인할 수 있다.&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;895&quot; data-origin-height=&quot;472&quot;&gt;&lt;span data-url=&quot;https://blog.kakaocdn.net/dn/lilT9/btsIGGETrjx/XKQKCOkxHKXaUroFNWhHNK/img.png&quot; data-phocus=&quot;https://blog.kakaocdn.net/dn/lilT9/btsIGGETrjx/XKQKCOkxHKXaUroFNWhHNK/img.png&quot;&gt;&lt;img src=&quot;https://blog.kakaocdn.net/dn/lilT9/btsIGGETrjx/XKQKCOkxHKXaUroFNWhHNK/img.png&quot; srcset=&quot;https://img1.daumcdn.net/thumb/R1280x0/?scode=mtistory2&amp;fname=https%3A%2F%2Fblog.kakaocdn.net%2Fdn%2FlilT9%2FbtsIGGETrjx%2FXKQKCOkxHKXaUroFNWhHNK%2Fimg.png&quot; onerror=&quot;this.onerror=null; this.src='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png'; this.srcset='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png';&quot; loading=&quot;lazy&quot; width=&quot;895&quot; height=&quot;472&quot; data-origin-width=&quot;895&quot; data-origin-height=&quot;472&quot;/&gt;&lt;/span&gt;&lt;/figure&gt;
&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;Jenkins에서 제공하는 GUI를 통해 전체적인 파이프라인 공정이 성공적으로 수행된 것을 볼 수 있다.&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;1105&quot; data-origin-height=&quot;195&quot;&gt;&lt;span data-url=&quot;https://blog.kakaocdn.net/dn/dufKln/btsIF98zMUJ/q8CftQgb5kvAT1P6v1wFck/img.png&quot; data-phocus=&quot;https://blog.kakaocdn.net/dn/dufKln/btsIF98zMUJ/q8CftQgb5kvAT1P6v1wFck/img.png&quot;&gt;&lt;img src=&quot;https://blog.kakaocdn.net/dn/dufKln/btsIF98zMUJ/q8CftQgb5kvAT1P6v1wFck/img.png&quot; srcset=&quot;https://img1.daumcdn.net/thumb/R1280x0/?scode=mtistory2&amp;fname=https%3A%2F%2Fblog.kakaocdn.net%2Fdn%2FdufKln%2FbtsIF98zMUJ%2Fq8CftQgb5kvAT1P6v1wFck%2Fimg.png&quot; onerror=&quot;this.onerror=null; this.src='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png'; this.srcset='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png';&quot; loading=&quot;lazy&quot; width=&quot;1105&quot; height=&quot;195&quot; data-origin-width=&quot;1105&quot; data-origin-height=&quot;195&quot;/&gt;&lt;/span&gt;&lt;/figure&gt;
&lt;/p&gt;</description>
      <category>DevOps</category>
      <category>Docker</category>
      <category>Dockerhub</category>
      <category>gitignore</category>
      <category>jenkins</category>
      <category>spring boot</category>
      <category>YAML</category>
      <author>겨울바람_</author>
      <guid isPermaLink="true">https://breeze-winter.tistory.com/28</guid>
      <comments>https://breeze-winter.tistory.com/28#entry28comment</comments>
      <pubDate>Fri, 19 Jul 2024 23:17:13 +0900</pubDate>
    </item>
    <item>
      <title>[AWS] Elastic Block Storage (EBS)</title>
      <link>https://breeze-winter.tistory.com/27</link>
      <description>&lt;h3 data-ke-size=&quot;size23&quot;&gt;Elastic Block Store&lt;/h3&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;EC2 인스턴스에 사용할 영구 블록 스토리지 볼륨을 제공한다. 각 Amazon EBS 볼륨은 EC2와 동일한 가용 영역 내에 자동으로 복제되어 구성요소 장애로부터 부호해주고, 고가용성 및 내구성을 제공한다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p&gt;&lt;figure class=&quot;imageblock alignCenter&quot; data-ke-mobileStyle=&quot;widthOrigin&quot; data-origin-width=&quot;900&quot; data-origin-height=&quot;434&quot;&gt;&lt;span data-url=&quot;https://blog.kakaocdn.net/dn/beQfsZ/btsICX6nN5r/otWwfisYtdCtn5j2I5K391/img.png&quot; data-phocus=&quot;https://blog.kakaocdn.net/dn/beQfsZ/btsICX6nN5r/otWwfisYtdCtn5j2I5K391/img.png&quot;&gt;&lt;img src=&quot;https://blog.kakaocdn.net/dn/beQfsZ/btsICX6nN5r/otWwfisYtdCtn5j2I5K391/img.png&quot; srcset=&quot;https://img1.daumcdn.net/thumb/R1280x0/?scode=mtistory2&amp;fname=https%3A%2F%2Fblog.kakaocdn.net%2Fdn%2FbeQfsZ%2FbtsICX6nN5r%2FotWwfisYtdCtn5j2I5K391%2Fimg.png&quot; onerror=&quot;this.onerror=null; this.src='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png'; this.srcset='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png';&quot; loading=&quot;lazy&quot; width=&quot;746&quot; height=&quot;360&quot; data-origin-width=&quot;900&quot; data-origin-height=&quot;434&quot;/&gt;&lt;/span&gt;&lt;/figure&gt;
&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;Amazon EBS 볼륨은 워크로드 실행에 필요한 지연 시간이 짧고 일관된 성능을 제공한다. Amazon EBS를 사용하면 몇 분 내에 사용량을 많게 또는 적게 확장할 수 있으며, 프로비저닝한 부분에 대해서만 저렴한 비용을 지불한다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;일종의 가상 하드디스크로 EC2와 EBS는 네트워크로 연결되어 있기 때문에 EC2 인스턴스가 종료되어도 계속 유지가 가능하다. EC2 스케일업 시에도 EBS를 종료하지 않고 EC2만 스케일업할 수 있다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p&gt;&lt;figure class=&quot;imageblock alignCenter&quot; data-ke-mobileStyle=&quot;widthOrigin&quot; data-origin-width=&quot;492&quot; data-origin-height=&quot;342&quot;&gt;&lt;span data-url=&quot;https://blog.kakaocdn.net/dn/q1m8Z/btsICda4wsn/gggGrKsGENB5bPVbWVcfhK/img.png&quot; data-phocus=&quot;https://blog.kakaocdn.net/dn/q1m8Z/btsICda4wsn/gggGrKsGENB5bPVbWVcfhK/img.png&quot;&gt;&lt;img src=&quot;https://blog.kakaocdn.net/dn/q1m8Z/btsICda4wsn/gggGrKsGENB5bPVbWVcfhK/img.png&quot; srcset=&quot;https://img1.daumcdn.net/thumb/R1280x0/?scode=mtistory2&amp;fname=https%3A%2F%2Fblog.kakaocdn.net%2Fdn%2Fq1m8Z%2FbtsICda4wsn%2FgggGrKsGENB5bPVbWVcfhK%2Fimg.png&quot; onerror=&quot;this.onerror=null; this.src='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png'; this.srcset='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png';&quot; loading=&quot;lazy&quot; width=&quot;492&quot; height=&quot;342&quot; data-origin-width=&quot;492&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;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;루트 볼륨으로 사용시에는 EC2가 종료될 때 함께 삭제된다. 단 설정을 통해 EBS만 따로 존속 가능하다.&lt;/p&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가지 타입을 제공한다.&lt;/p&gt;
&lt;ul style=&quot;list-style-type: disc;&quot; data-ke-list-type=&quot;disc&quot;&gt;
&lt;li&gt;범용 : SSD&lt;/li&gt;
&lt;li&gt;프로비저닝 된 IOPS : SSD&lt;/li&gt;
&lt;li&gt;Throughput 최적화&lt;/li&gt;
&lt;li&gt;콜드 HDD&lt;/li&gt;
&lt;li&gt;마그네틱&lt;/li&gt;
&lt;/ul&gt;
&lt;p&gt;&lt;figure class=&quot;imageblock alignCenter&quot; data-ke-mobileStyle=&quot;widthOrigin&quot; data-origin-width=&quot;1880&quot; data-origin-height=&quot;800&quot;&gt;&lt;span data-url=&quot;https://blog.kakaocdn.net/dn/cUsRRm/btsICfmpo0a/nGe6ZTwnKqao0H0kjgZu5k/img.png&quot; data-phocus=&quot;https://blog.kakaocdn.net/dn/cUsRRm/btsICfmpo0a/nGe6ZTwnKqao0H0kjgZu5k/img.png&quot;&gt;&lt;img src=&quot;https://blog.kakaocdn.net/dn/cUsRRm/btsICfmpo0a/nGe6ZTwnKqao0H0kjgZu5k/img.png&quot; srcset=&quot;https://img1.daumcdn.net/thumb/R1280x0/?scode=mtistory2&amp;fname=https%3A%2F%2Fblog.kakaocdn.net%2Fdn%2FcUsRRm%2FbtsICfmpo0a%2FnGe6ZTwnKqao0H0kjgZu5k%2Fimg.png&quot; onerror=&quot;this.onerror=null; this.src='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png'; this.srcset='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png';&quot; loading=&quot;lazy&quot; width=&quot;1880&quot; height=&quot;800&quot; data-origin-width=&quot;1880&quot; data-origin-height=&quot;800&quot;/&gt;&lt;/span&gt;&lt;/figure&gt;
&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;IOPS 수치가 높을수록 데이터 통신이 빠르다.&lt;/p&gt;
&lt;h3 data-ke-size=&quot;size23&quot;&gt;Snapshot&lt;/h3&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;특정 시간대의 EBS 상태 저장본으로 필요시 스냅샷을 통해 특정 시간의 EBS를 복구할 수 있다. S3에 변화된 부분만을 저장하는 증분식으로 저장한다.&lt;/p&gt;
&lt;p&gt;&lt;figure class=&quot;imageblock alignCenter&quot; data-ke-mobileStyle=&quot;widthOrigin&quot; data-origin-width=&quot;1739&quot; data-origin-height=&quot;669&quot;&gt;&lt;span data-url=&quot;https://blog.kakaocdn.net/dn/pc8Xn/btsIA9tW7yh/POpyMXi9oSzxk44oWNCink/img.png&quot; data-phocus=&quot;https://blog.kakaocdn.net/dn/pc8Xn/btsIA9tW7yh/POpyMXi9oSzxk44oWNCink/img.png&quot;&gt;&lt;img src=&quot;https://blog.kakaocdn.net/dn/pc8Xn/btsIA9tW7yh/POpyMXi9oSzxk44oWNCink/img.png&quot; srcset=&quot;https://img1.daumcdn.net/thumb/R1280x0/?scode=mtistory2&amp;fname=https%3A%2F%2Fblog.kakaocdn.net%2Fdn%2Fpc8Xn%2FbtsIA9tW7yh%2FPOpyMXi9oSzxk44oWNCink%2Fimg.png&quot; onerror=&quot;this.onerror=null; this.src='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png'; this.srcset='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png';&quot; loading=&quot;lazy&quot; width=&quot;1739&quot; height=&quot;669&quot; data-origin-width=&quot;1739&quot; data-origin-height=&quot;669&quot;/&gt;&lt;/span&gt;&lt;/figure&gt;
&lt;/p&gt;
&lt;h3 data-ke-size=&quot;size23&quot;&gt;AMI&lt;/h3&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;EC2 인스턴스를 실행하기 위해 필요한 정보를 모아둔 단위로 OS, 아키텍처 타입, 저장공간 용량 등이 AMI를 통해 구성된다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;스냅샷을 통하여 AMI 구성이 가능하기 때문에 AMI를 사용하여 EC2를 복제하거나 다른 리전, 계정으로 전달이 가능하다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;AMI는 총 두 가지 타입이 존재한다.&lt;/p&gt;
&lt;p&gt;&lt;figure class=&quot;imageblock alignCenter&quot; data-ke-mobileStyle=&quot;widthOrigin&quot; data-filename=&quot;blob&quot; data-origin-width=&quot;1383&quot; data-origin-height=&quot;888&quot;&gt;&lt;span data-url=&quot;https://blog.kakaocdn.net/dn/R37nd/btsIBaTVmbw/xkSwVkZejQOXrhVev8LgrK/img.png&quot; data-phocus=&quot;https://blog.kakaocdn.net/dn/R37nd/btsIBaTVmbw/xkSwVkZejQOXrhVev8LgrK/img.png&quot;&gt;&lt;img src=&quot;https://blog.kakaocdn.net/dn/R37nd/btsIBaTVmbw/xkSwVkZejQOXrhVev8LgrK/img.png&quot; srcset=&quot;https://img1.daumcdn.net/thumb/R1280x0/?scode=mtistory2&amp;fname=https%3A%2F%2Fblog.kakaocdn.net%2Fdn%2FR37nd%2FbtsIBaTVmbw%2FxkSwVkZejQOXrhVev8LgrK%2Fimg.png&quot; onerror=&quot;this.onerror=null; this.src='//t1.daumcdn.net/tistory_admin/static/images/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;455&quot; data-filename=&quot;blob&quot; data-origin-width=&quot;1383&quot; data-origin-height=&quot;888&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;인스턴스 저장 기반
&lt;ul style=&quot;list-style-type: disc;&quot; data-ke-list-type=&quot;disc&quot;&gt;
&lt;li&gt;EC2안에 storage가 들어있음 (네트워크 연결X)&lt;/li&gt;
&lt;li&gt;속도가 빠름&lt;/li&gt;
&lt;li&gt;안에 들어있는 형태이니, 인스턴스가 삭제되면 스토리지도 같이 삭제 됨&lt;/li&gt;
&lt;li&gt;EBS처럼 스토어를 분리해서 다른 인스턴스에 연결 불가능&lt;/li&gt;
&lt;li&gt;보통 영구적이지 않은 데이터를 저장&lt;br /&gt;ex) 캐시 데이터&lt;/li&gt;
&lt;li&gt;S3에 저장된 템플릿을 기반으로 생성&lt;/li&gt;
&lt;/ul&gt;
&lt;/li&gt;
&lt;li&gt;EBS 기반
&lt;ul style=&quot;list-style-type: disc;&quot; data-ke-list-type=&quot;disc&quot;&gt;
&lt;li&gt;EC2가 EBS와 네트워크로 연결&lt;/li&gt;
&lt;li&gt;속도가 느림&lt;/li&gt;
&lt;li&gt;인스턴스가 삭제되더라도 EBS는 남아있음&lt;/li&gt;
&lt;li&gt;하나의 인스턴스에 연결한 EBS 볼륨을 따로 분리해서 다른 인스턴스에 연결 가능&lt;/li&gt;
&lt;li&gt;스냅샷을 기반으로 루트 디바이스 생성&lt;/li&gt;
&lt;/ul&gt;
&lt;/li&gt;
&lt;/ul&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;AMI 생성 방법&lt;/p&gt;
&lt;p&gt;&lt;figure class=&quot;imageblock alignCenter&quot; data-ke-mobileStyle=&quot;widthOrigin&quot; data-origin-width=&quot;957&quot; data-origin-height=&quot;437&quot;&gt;&lt;span data-url=&quot;https://blog.kakaocdn.net/dn/ebyhEN/btsIBjJQLrD/OPGtT6dp6VWgjHUIX0HdK0/img.png&quot; data-phocus=&quot;https://blog.kakaocdn.net/dn/ebyhEN/btsIBjJQLrD/OPGtT6dp6VWgjHUIX0HdK0/img.png&quot;&gt;&lt;img src=&quot;https://blog.kakaocdn.net/dn/ebyhEN/btsIBjJQLrD/OPGtT6dp6VWgjHUIX0HdK0/img.png&quot; srcset=&quot;https://img1.daumcdn.net/thumb/R1280x0/?scode=mtistory2&amp;fname=https%3A%2F%2Fblog.kakaocdn.net%2Fdn%2FebyhEN%2FbtsIBjJQLrD%2FOPGtT6dp6VWgjHUIX0HdK0%2Fimg.png&quot; onerror=&quot;this.onerror=null; this.src='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png'; this.srcset='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png';&quot; loading=&quot;lazy&quot; width=&quot;957&quot; height=&quot;437&quot; data-origin-width=&quot;957&quot; data-origin-height=&quot;437&quot;/&gt;&lt;/span&gt;&lt;/figure&gt;
&lt;/p&gt;</description>
      <category>Public Cloud</category>
      <category>Ami</category>
      <category>AWS</category>
      <category>EBS</category>
      <category>EC2</category>
      <category>elastic block storage</category>
      <category>snapshot</category>
      <author>겨울바람_</author>
      <guid isPermaLink="true">https://breeze-winter.tistory.com/27</guid>
      <comments>https://breeze-winter.tistory.com/27#entry27comment</comments>
      <pubDate>Tue, 16 Jul 2024 21:15:47 +0900</pubDate>
    </item>
    <item>
      <title>[AWS] Slack을 사용해서 EC2 인스턴스 조작하기</title>
      <link>https://breeze-winter.tistory.com/26</link>
      <description>&lt;p data-ke-size=&quot;size16&quot;&gt;이번 포스팅에서는 AWS Chatbot을 활용하여 Slack을 통해 EC2 인스턴스의 상태를 중지/실행으로 변경해고자 한다.&lt;/p&gt;
&lt;h2 data-ke-size=&quot;size26&quot;&gt;AWS Chatbot&lt;/h2&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;AWS Chatbot은 Microsoft Teams 및 Slack 채널을 통해 AWS 리소스를 쉽게 모니터링하고 상호 작용하도록 지원하는 대화형 에이전트다. AWS Chatbot을 활용하면 알림을 수신하고 명령을 통해 진단 정보를 반환하고, AWS Lambda 함수를 호출하고 AWS Support 케이스를 생성하여 팀이 더 빠르게 협업하고 이벤트에 대응할 수 있다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;AWS Chatbot은 별도의 요금이 존재하지 않는다. 대신 AWS Chatbot 없이 사용하는 것과 동일한 방식으로 사용하는 기본 서비스(예: Amazon Simple Notification Service, Amazon CloudWatch, Amazon GuardDuty, AWS Security Hub)에 대해서만 비용을 지불한다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;본 포스팅에서 사용되는 AWS 리소스는 AWS Chatbot과 EC2, IAM이기 때문에 프리티어 사용 시 발생 비용은 존재하지 않는다.&lt;/p&gt;
&lt;h2 data-ke-size=&quot;size26&quot;&gt;EC2 생성&lt;/h2&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;우선 이번 포스팅에서 사용할 EC2 인스턴스를 생성한다. 본 포스팅에서 EC2가 메인이 아니고, EC2에서 추가적으로 수행해야 하는 사항은 없기 때문에 EC2 생성 부분은 각자가 원하는 방식대로 자유롭게 생성하면 된다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;마찬가지로 OS 또한 본 포스팅에서 크게 중요한 부분이 아니기 때문에 아무 OS나 택해도 크게 관계없다. 필자는 가장 친숙한 Ubuntu AMI를 선택했다.&lt;/p&gt;
&lt;p&gt;&lt;figure class=&quot;imageblock alignCenter&quot; data-ke-mobileStyle=&quot;widthOrigin&quot; data-origin-width=&quot;1012&quot; data-origin-height=&quot;757&quot;&gt;&lt;span data-url=&quot;https://blog.kakaocdn.net/dn/cBEeAM/btsIAwhm2Ju/eULKw1kKPJQsap3igRHXI1/img.png&quot; data-phocus=&quot;https://blog.kakaocdn.net/dn/cBEeAM/btsIAwhm2Ju/eULKw1kKPJQsap3igRHXI1/img.png&quot;&gt;&lt;img src=&quot;https://blog.kakaocdn.net/dn/cBEeAM/btsIAwhm2Ju/eULKw1kKPJQsap3igRHXI1/img.png&quot; srcset=&quot;https://img1.daumcdn.net/thumb/R1280x0/?scode=mtistory2&amp;fname=https%3A%2F%2Fblog.kakaocdn.net%2Fdn%2FcBEeAM%2FbtsIAwhm2Ju%2FeULKw1kKPJQsap3igRHXI1%2Fimg.png&quot; onerror=&quot;this.onerror=null; this.src='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png'; this.srcset='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png';&quot; loading=&quot;lazy&quot; width=&quot;1012&quot; height=&quot;757&quot; data-origin-width=&quot;1012&quot; data-origin-height=&quot;757&quot;/&gt;&lt;/span&gt;&lt;/figure&gt;
&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;실행 및 중지 테스트를 위한 EC2 인스턴스기 때문에, 보안 그룹은 내 IP에서만 접속을 허용해준다. 인스턴스 유형과 스토리지 또한 프리티어를 사용한다.&lt;/p&gt;
&lt;p&gt;&lt;figure class=&quot;imageblock alignCenter&quot; data-ke-mobileStyle=&quot;widthOrigin&quot; data-origin-width=&quot;948&quot; data-origin-height=&quot;257&quot;&gt;&lt;span data-url=&quot;https://blog.kakaocdn.net/dn/bjXc9u/btsIBnDVFNs/rqJ7x8j6qTzjAxd8OQeHck/img.png&quot; data-phocus=&quot;https://blog.kakaocdn.net/dn/bjXc9u/btsIBnDVFNs/rqJ7x8j6qTzjAxd8OQeHck/img.png&quot;&gt;&lt;img src=&quot;https://blog.kakaocdn.net/dn/bjXc9u/btsIBnDVFNs/rqJ7x8j6qTzjAxd8OQeHck/img.png&quot; srcset=&quot;https://img1.daumcdn.net/thumb/R1280x0/?scode=mtistory2&amp;fname=https%3A%2F%2Fblog.kakaocdn.net%2Fdn%2FbjXc9u%2FbtsIBnDVFNs%2FrqJ7x8j6qTzjAxd8OQeHck%2Fimg.png&quot; onerror=&quot;this.onerror=null; this.src='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png'; this.srcset='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png';&quot; loading=&quot;lazy&quot; width=&quot;948&quot; height=&quot;257&quot; data-origin-width=&quot;948&quot; data-origin-height=&quot;257&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;806&quot; data-origin-height=&quot;40&quot;&gt;&lt;span data-url=&quot;https://blog.kakaocdn.net/dn/stZLS/btsIy1JD85o/kg5s7CILwrAXVK1YgosxjK/img.png&quot; data-phocus=&quot;https://blog.kakaocdn.net/dn/stZLS/btsIy1JD85o/kg5s7CILwrAXVK1YgosxjK/img.png&quot;&gt;&lt;img src=&quot;https://blog.kakaocdn.net/dn/stZLS/btsIy1JD85o/kg5s7CILwrAXVK1YgosxjK/img.png&quot; srcset=&quot;https://img1.daumcdn.net/thumb/R1280x0/?scode=mtistory2&amp;fname=https%3A%2F%2Fblog.kakaocdn.net%2Fdn%2FstZLS%2FbtsIy1JD85o%2Fkg5s7CILwrAXVK1YgosxjK%2Fimg.png&quot; onerror=&quot;this.onerror=null; this.src='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png'; this.srcset='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png';&quot; loading=&quot;lazy&quot; width=&quot;806&quot; height=&quot;40&quot; data-origin-width=&quot;806&quot; data-origin-height=&quot;40&quot;/&gt;&lt;/span&gt;&lt;/figure&gt;
&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;EC2가 정상적으로 동작하는 것을 확인한 후, Slack과 AWS를 연동하기 위해 AWS Chatbot을 생성한다.&lt;/p&gt;
&lt;p&gt;&lt;figure class=&quot;imageblock alignCenter&quot; data-ke-mobileStyle=&quot;widthOrigin&quot; data-origin-width=&quot;1108&quot; data-origin-height=&quot;298&quot;&gt;&lt;span data-url=&quot;https://blog.kakaocdn.net/dn/cgkPMf/btsIAu4RMFR/FK8VXK0jVEGongndnyoaMK/img.png&quot; data-phocus=&quot;https://blog.kakaocdn.net/dn/cgkPMf/btsIAu4RMFR/FK8VXK0jVEGongndnyoaMK/img.png&quot;&gt;&lt;img src=&quot;https://blog.kakaocdn.net/dn/cgkPMf/btsIAu4RMFR/FK8VXK0jVEGongndnyoaMK/img.png&quot; srcset=&quot;https://img1.daumcdn.net/thumb/R1280x0/?scode=mtistory2&amp;fname=https%3A%2F%2Fblog.kakaocdn.net%2Fdn%2FcgkPMf%2FbtsIAu4RMFR%2FFK8VXK0jVEGongndnyoaMK%2Fimg.png&quot; onerror=&quot;this.onerror=null; this.src='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png'; this.srcset='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png';&quot; loading=&quot;lazy&quot; width=&quot;1108&quot; height=&quot;298&quot; data-origin-width=&quot;1108&quot; data-origin-height=&quot;298&quot;/&gt;&lt;/span&gt;&lt;/figure&gt;
&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;AWS Chatbot과 연결할 클라이언트를 지정해주자. 필자는 이번 포스팅에서는 Slack과 연동하여 사용할 예정이기 때문에 Slack으로 지정했다.&lt;/p&gt;
&lt;p&gt;&lt;figure class=&quot;imageblock alignCenter&quot; data-ke-mobileStyle=&quot;widthOrigin&quot; data-origin-width=&quot;1292&quot; data-origin-height=&quot;523&quot;&gt;&lt;span data-url=&quot;https://blog.kakaocdn.net/dn/bSynVE/btsIzevcB8Y/CuNDVIdmodpBIhNDCWJpqk/img.png&quot; data-phocus=&quot;https://blog.kakaocdn.net/dn/bSynVE/btsIzevcB8Y/CuNDVIdmodpBIhNDCWJpqk/img.png&quot;&gt;&lt;img src=&quot;https://blog.kakaocdn.net/dn/bSynVE/btsIzevcB8Y/CuNDVIdmodpBIhNDCWJpqk/img.png&quot; srcset=&quot;https://img1.daumcdn.net/thumb/R1280x0/?scode=mtistory2&amp;fname=https%3A%2F%2Fblog.kakaocdn.net%2Fdn%2FbSynVE%2FbtsIzevcB8Y%2FCuNDVIdmodpBIhNDCWJpqk%2Fimg.png&quot; onerror=&quot;this.onerror=null; this.src='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png'; this.srcset='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png';&quot; loading=&quot;lazy&quot; width=&quot;1292&quot; height=&quot;523&quot; data-origin-width=&quot;1292&quot; data-origin-height=&quot;523&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;클라이언트 구성 버튼을 누르면, Slack의 워크스페이스와 연동할 수 있는 페이지가 보인다. 만약 연동 가능한 워크스페이스가 존재하지 않다면 새로운 Slack 워크스페이스를 생성하자.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;필자는 AWS 관리를 위한 워크스페이스를 새롭게 생성했다.&lt;/p&gt;
&lt;p&gt;&lt;figure class=&quot;imageblock alignCenter&quot; data-ke-mobileStyle=&quot;widthOrigin&quot; data-origin-width=&quot;1811&quot; data-origin-height=&quot;772&quot;&gt;&lt;span data-url=&quot;https://blog.kakaocdn.net/dn/ctyI8n/btsIAccnasl/HulKKK9XcQRYvGFXOpTdhK/img.png&quot; data-phocus=&quot;https://blog.kakaocdn.net/dn/ctyI8n/btsIAccnasl/HulKKK9XcQRYvGFXOpTdhK/img.png&quot;&gt;&lt;img src=&quot;https://blog.kakaocdn.net/dn/ctyI8n/btsIAccnasl/HulKKK9XcQRYvGFXOpTdhK/img.png&quot; srcset=&quot;https://img1.daumcdn.net/thumb/R1280x0/?scode=mtistory2&amp;fname=https%3A%2F%2Fblog.kakaocdn.net%2Fdn%2FctyI8n%2FbtsIAccnasl%2FHulKKK9XcQRYvGFXOpTdhK%2Fimg.png&quot; onerror=&quot;this.onerror=null; this.src='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png'; this.srcset='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png';&quot; loading=&quot;lazy&quot; width=&quot;1811&quot; height=&quot;772&quot; data-origin-width=&quot;1811&quot; data-origin-height=&quot;772&quot;/&gt;&lt;/span&gt;&lt;/figure&gt;
&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;워크스페이스를 새롭게 만든 후, 다시 AWS Chatbot의 Slack 연동 페이지를 살펴보면 새롭게 만들어진 워크스페이스와 연동이 가능해진 것을 확인할 수 있다.&lt;/p&gt;
&lt;p&gt;&lt;figure class=&quot;imageblock alignCenter&quot; data-ke-mobileStyle=&quot;widthOrigin&quot; data-origin-width=&quot;721&quot; data-origin-height=&quot;653&quot;&gt;&lt;span data-url=&quot;https://blog.kakaocdn.net/dn/cL03cp/btsIAWmvXHr/dKOiOjLe1hwzdS9jgnKfik/img.png&quot; data-phocus=&quot;https://blog.kakaocdn.net/dn/cL03cp/btsIAWmvXHr/dKOiOjLe1hwzdS9jgnKfik/img.png&quot;&gt;&lt;img src=&quot;https://blog.kakaocdn.net/dn/cL03cp/btsIAWmvXHr/dKOiOjLe1hwzdS9jgnKfik/img.png&quot; srcset=&quot;https://img1.daumcdn.net/thumb/R1280x0/?scode=mtistory2&amp;fname=https%3A%2F%2Fblog.kakaocdn.net%2Fdn%2FcL03cp%2FbtsIAWmvXHr%2FdKOiOjLe1hwzdS9jgnKfik%2Fimg.png&quot; onerror=&quot;this.onerror=null; this.src='//t1.daumcdn.net/tistory_admin/static/images/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;447&quot; data-origin-width=&quot;721&quot; data-origin-height=&quot;653&quot;/&gt;&lt;/span&gt;&lt;/figure&gt;
&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;허용 버튼을 누르면 다음과 같이 해당 워크스페이스와 연동된 AWS Chatbot의 화면을 확인할 수 있다.&lt;/p&gt;
&lt;p&gt;&lt;figure class=&quot;imageblock alignCenter&quot; data-ke-mobileStyle=&quot;widthOrigin&quot; data-origin-width=&quot;1460&quot; data-origin-height=&quot;648&quot;&gt;&lt;span data-url=&quot;https://blog.kakaocdn.net/dn/IFA0c/btsIA0iaOsV/SjsLEqiZqJFT47Cgt0rYqK/img.png&quot; data-phocus=&quot;https://blog.kakaocdn.net/dn/IFA0c/btsIA0iaOsV/SjsLEqiZqJFT47Cgt0rYqK/img.png&quot;&gt;&lt;img src=&quot;https://blog.kakaocdn.net/dn/IFA0c/btsIA0iaOsV/SjsLEqiZqJFT47Cgt0rYqK/img.png&quot; srcset=&quot;https://img1.daumcdn.net/thumb/R1280x0/?scode=mtistory2&amp;fname=https%3A%2F%2Fblog.kakaocdn.net%2Fdn%2FIFA0c%2FbtsIA0iaOsV%2FSjsLEqiZqJFT47Cgt0rYqK%2Fimg.png&quot; onerror=&quot;this.onerror=null; this.src='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png'; this.srcset='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png';&quot; loading=&quot;lazy&quot; width=&quot;1460&quot; height=&quot;648&quot; data-origin-width=&quot;1460&quot; data-origin-height=&quot;648&quot;/&gt;&lt;/span&gt;&lt;/figure&gt;
&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;다음은 EC2에 접근할 수 있는 IAM을 만들어 챗봇에 부여해보자. 우선 챗봇에 부여할 역할을 생성한다. 역할 생성 버튼을 누르고 AWS 서비스 중 AWS Chatbot에 적용되는 사용 사례를 선택한다.&lt;/p&gt;
&lt;p&gt;&lt;figure class=&quot;imageblock alignCenter&quot; data-ke-mobileStyle=&quot;widthOrigin&quot; data-origin-width=&quot;1006&quot; data-origin-height=&quot;801&quot;&gt;&lt;span data-url=&quot;https://blog.kakaocdn.net/dn/cshjbN/btsIBle8A7X/apjNkscbR5ekTMR6pgtggK/img.png&quot; data-phocus=&quot;https://blog.kakaocdn.net/dn/cshjbN/btsIBle8A7X/apjNkscbR5ekTMR6pgtggK/img.png&quot;&gt;&lt;img src=&quot;https://blog.kakaocdn.net/dn/cshjbN/btsIBle8A7X/apjNkscbR5ekTMR6pgtggK/img.png&quot; srcset=&quot;https://img1.daumcdn.net/thumb/R1280x0/?scode=mtistory2&amp;fname=https%3A%2F%2Fblog.kakaocdn.net%2Fdn%2FcshjbN%2FbtsIBle8A7X%2FapjNkscbR5ekTMR6pgtggK%2Fimg.png&quot; onerror=&quot;this.onerror=null; this.src='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png'; this.srcset='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png';&quot; loading=&quot;lazy&quot; width=&quot;1006&quot; height=&quot;801&quot; data-origin-width=&quot;1006&quot; data-origin-height=&quot;801&quot;/&gt;&lt;/span&gt;&lt;/figure&gt;
&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;이번 포스팅의 목적은 챗봇을 활용하여 EC2 인스턴스를 관리하는 것이기 때문에, Amazon EC2 FullAccess 권한을 챗봇에 부여한다.&lt;/p&gt;
&lt;p&gt;&lt;figure class=&quot;imageblock alignCenter&quot; data-ke-mobileStyle=&quot;widthOrigin&quot; data-origin-width=&quot;1386&quot; data-origin-height=&quot;508&quot;&gt;&lt;span data-url=&quot;https://blog.kakaocdn.net/dn/FvMRC/btsIAVuocra/oY6ge2QrnLH0gVOxX3oYTk/img.png&quot; data-phocus=&quot;https://blog.kakaocdn.net/dn/FvMRC/btsIAVuocra/oY6ge2QrnLH0gVOxX3oYTk/img.png&quot;&gt;&lt;img src=&quot;https://blog.kakaocdn.net/dn/FvMRC/btsIAVuocra/oY6ge2QrnLH0gVOxX3oYTk/img.png&quot; srcset=&quot;https://img1.daumcdn.net/thumb/R1280x0/?scode=mtistory2&amp;fname=https%3A%2F%2Fblog.kakaocdn.net%2Fdn%2FFvMRC%2FbtsIAVuocra%2FoY6ge2QrnLH0gVOxX3oYTk%2Fimg.png&quot; onerror=&quot;this.onerror=null; this.src='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png'; this.srcset='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png';&quot; loading=&quot;lazy&quot; width=&quot;1386&quot; height=&quot;508&quot; data-origin-width=&quot;1386&quot; data-origin-height=&quot;508&quot;/&gt;&lt;/span&gt;&lt;/figure&gt;
&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;다음 버튼을 클릭한 후 해당 역할에 이름과 설명을 적절하게 부여하자. 태그의 경우 선택사항이기 때문에 이번 포스팅에서는 추가하지 않았다.&lt;/p&gt;
&lt;p&gt;&lt;figure class=&quot;imageblock alignCenter&quot; data-ke-mobileStyle=&quot;widthOrigin&quot; data-origin-width=&quot;1403&quot; data-origin-height=&quot;532&quot;&gt;&lt;span data-url=&quot;https://blog.kakaocdn.net/dn/ddZvag/btsIAVnDe7G/XOQhwV0O5adQ9qyBISKmk1/img.png&quot; data-phocus=&quot;https://blog.kakaocdn.net/dn/ddZvag/btsIAVnDe7G/XOQhwV0O5adQ9qyBISKmk1/img.png&quot;&gt;&lt;img src=&quot;https://blog.kakaocdn.net/dn/ddZvag/btsIAVnDe7G/XOQhwV0O5adQ9qyBISKmk1/img.png&quot; srcset=&quot;https://img1.daumcdn.net/thumb/R1280x0/?scode=mtistory2&amp;fname=https%3A%2F%2Fblog.kakaocdn.net%2Fdn%2FddZvag%2FbtsIAVnDe7G%2FXOQhwV0O5adQ9qyBISKmk1%2Fimg.png&quot; onerror=&quot;this.onerror=null; this.src='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png'; this.srcset='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png';&quot; loading=&quot;lazy&quot; width=&quot;1403&quot; height=&quot;532&quot; data-origin-width=&quot;1403&quot; data-origin-height=&quot;532&quot;/&gt;&lt;/span&gt;&lt;/figure&gt;
&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;역할 이름과 설명을 부여한 후 생성 버튼을 클릭하면 다음과 같이 역할이 잘 생성된 것을 확인할 수 있을 것이다.&lt;/p&gt;
&lt;p&gt;&lt;figure class=&quot;imageblock alignCenter&quot; data-ke-mobileStyle=&quot;widthOrigin&quot; data-origin-width=&quot;961&quot; data-origin-height=&quot;46&quot;&gt;&lt;span data-url=&quot;https://blog.kakaocdn.net/dn/E9kBe/btsIyyt8XeD/iRYYrdEKfep1YrAkp91nlK/img.png&quot; data-phocus=&quot;https://blog.kakaocdn.net/dn/E9kBe/btsIyyt8XeD/iRYYrdEKfep1YrAkp91nlK/img.png&quot;&gt;&lt;img src=&quot;https://blog.kakaocdn.net/dn/E9kBe/btsIyyt8XeD/iRYYrdEKfep1YrAkp91nlK/img.png&quot; srcset=&quot;https://img1.daumcdn.net/thumb/R1280x0/?scode=mtistory2&amp;fname=https%3A%2F%2Fblog.kakaocdn.net%2Fdn%2FE9kBe%2FbtsIyyt8XeD%2FiRYYrdEKfep1YrAkp91nlK%2Fimg.png&quot; onerror=&quot;this.onerror=null; this.src='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png'; this.srcset='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png';&quot; loading=&quot;lazy&quot; width=&quot;961&quot; height=&quot;46&quot; data-origin-width=&quot;961&quot; data-origin-height=&quot;46&quot;/&gt;&lt;/span&gt;&lt;/figure&gt;
&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;다음은 가드레일을 위한 정책을 부여할 차례다. 정책 생성 버튼을 누른 뒤, 서비스로는 EC2를 선택한다. 본 포스팅의 목적에 부합하는 EC2 인스턴스를 시작, 정지할 수 있는 정책만 허용한다.&lt;/p&gt;
&lt;p&gt;&lt;figure class=&quot;imageblock alignCenter&quot; data-ke-mobileStyle=&quot;widthOrigin&quot; data-origin-width=&quot;1333&quot; data-origin-height=&quot;522&quot;&gt;&lt;span data-url=&quot;https://blog.kakaocdn.net/dn/OaMNF/btsIAyM4CDZ/nCbN8QPWMtEzpzmYUPFwIK/img.png&quot; data-phocus=&quot;https://blog.kakaocdn.net/dn/OaMNF/btsIAyM4CDZ/nCbN8QPWMtEzpzmYUPFwIK/img.png&quot;&gt;&lt;img src=&quot;https://blog.kakaocdn.net/dn/OaMNF/btsIAyM4CDZ/nCbN8QPWMtEzpzmYUPFwIK/img.png&quot; srcset=&quot;https://img1.daumcdn.net/thumb/R1280x0/?scode=mtistory2&amp;fname=https%3A%2F%2Fblog.kakaocdn.net%2Fdn%2FOaMNF%2FbtsIAyM4CDZ%2FnCbN8QPWMtEzpzmYUPFwIK%2Fimg.png&quot; onerror=&quot;this.onerror=null; this.src='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png'; this.srcset='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png';&quot; loading=&quot;lazy&quot; width=&quot;1333&quot; height=&quot;522&quot; data-origin-width=&quot;1333&quot; data-origin-height=&quot;522&quot;/&gt;&lt;/span&gt;&lt;/figure&gt;
&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;이후 다음 버튼을 누르고 마찬가지로 이름과 설명을 적절히 부여하면 성공적으로 가드레일 정책이 생성된다.&lt;/p&gt;
&lt;p&gt;&lt;figure class=&quot;imageblock alignCenter&quot; data-ke-mobileStyle=&quot;widthOrigin&quot; data-origin-width=&quot;1021&quot; data-origin-height=&quot;552&quot;&gt;&lt;span data-url=&quot;https://blog.kakaocdn.net/dn/CdzlQ/btsIATJ7vvO/3nw3yKrK0Gja8dozKlSoDk/img.png&quot; data-phocus=&quot;https://blog.kakaocdn.net/dn/CdzlQ/btsIATJ7vvO/3nw3yKrK0Gja8dozKlSoDk/img.png&quot;&gt;&lt;img src=&quot;https://blog.kakaocdn.net/dn/CdzlQ/btsIATJ7vvO/3nw3yKrK0Gja8dozKlSoDk/img.png&quot; srcset=&quot;https://img1.daumcdn.net/thumb/R1280x0/?scode=mtistory2&amp;fname=https%3A%2F%2Fblog.kakaocdn.net%2Fdn%2FCdzlQ%2FbtsIATJ7vvO%2F3nw3yKrK0Gja8dozKlSoDk%2Fimg.png&quot; onerror=&quot;this.onerror=null; this.src='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png'; this.srcset='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png';&quot; loading=&quot;lazy&quot; width=&quot;1021&quot; height=&quot;552&quot; data-origin-width=&quot;1021&quot; data-origin-height=&quot;552&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;1425&quot; data-origin-height=&quot;313&quot;&gt;&lt;span data-url=&quot;https://blog.kakaocdn.net/dn/w0FTh/btsIy4szLsR/XEJLSH2acO3simZq5KBrP1/img.png&quot; data-phocus=&quot;https://blog.kakaocdn.net/dn/w0FTh/btsIy4szLsR/XEJLSH2acO3simZq5KBrP1/img.png&quot;&gt;&lt;img src=&quot;https://blog.kakaocdn.net/dn/w0FTh/btsIy4szLsR/XEJLSH2acO3simZq5KBrP1/img.png&quot; srcset=&quot;https://img1.daumcdn.net/thumb/R1280x0/?scode=mtistory2&amp;fname=https%3A%2F%2Fblog.kakaocdn.net%2Fdn%2Fw0FTh%2FbtsIy4szLsR%2FXEJLSH2acO3simZq5KBrP1%2Fimg.png&quot; onerror=&quot;this.onerror=null; this.src='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png'; this.srcset='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png';&quot; loading=&quot;lazy&quot; width=&quot;1425&quot; height=&quot;313&quot; data-origin-width=&quot;1425&quot; data-origin-height=&quot;313&quot;/&gt;&lt;/span&gt;&lt;/figure&gt;
&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;이제 만들어진 역할과 정책을 부여한 챗봇 채널을 생성해보자. 채널 생성 과정은 AWS Chatbot과 Slack 워크스페이스의 채널을 연결하는 작업이다. 다시 AWS Chatbot 페이지로 돌아가 새 채널 생성 버튼을 클릭하자.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;AWS Chatbot 또한 CloudWatch와의 연동을 통해 로그를 관리할 수 있다. 필요에 따라 해당 옵션을 활성화하면 된다.&lt;/p&gt;
&lt;p&gt;&lt;figure class=&quot;imageblock alignCenter&quot; data-ke-mobileStyle=&quot;widthOrigin&quot; data-origin-width=&quot;1017&quot; data-origin-height=&quot;430&quot;&gt;&lt;span data-url=&quot;https://blog.kakaocdn.net/dn/cd8X50/btsIAcwNIxa/2VtTeRzYb8Y5OFYYiAt6Q1/img.png&quot; data-phocus=&quot;https://blog.kakaocdn.net/dn/cd8X50/btsIAcwNIxa/2VtTeRzYb8Y5OFYYiAt6Q1/img.png&quot;&gt;&lt;img src=&quot;https://blog.kakaocdn.net/dn/cd8X50/btsIAcwNIxa/2VtTeRzYb8Y5OFYYiAt6Q1/img.png&quot; srcset=&quot;https://img1.daumcdn.net/thumb/R1280x0/?scode=mtistory2&amp;fname=https%3A%2F%2Fblog.kakaocdn.net%2Fdn%2Fcd8X50%2FbtsIAcwNIxa%2F2VtTeRzYb8Y5OFYYiAt6Q1%2Fimg.png&quot; onerror=&quot;this.onerror=null; this.src='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png'; this.srcset='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png';&quot; loading=&quot;lazy&quot; width=&quot;1017&quot; height=&quot;430&quot; data-origin-width=&quot;1017&quot; data-origin-height=&quot;430&quot;/&gt;&lt;/span&gt;&lt;/figure&gt;
&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;채널에 대한 이름을 정해주고 스크롤을 아래로 내리면 연동하고자 하는 Slack 채널에 대한 ID를 입력하는 폼이 나온다. 각자 생성한 혹은 이미 존재하고 있는 워크스페이스의 채널 ID와 채널 유형을 각각 입력하면 된다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;참고로 채널 ID의 경우, 브라우저 사용 시에는 URL 맨 뒷자리에서 확인이 가능하며, 데스크톱 앱을 사용할 경우 채널의 설정 부분에서 확인할 수 있다.&lt;/p&gt;
&lt;p&gt;&lt;figure class=&quot;imageblock alignCenter&quot; data-ke-mobileStyle=&quot;widthOrigin&quot; data-origin-width=&quot;475&quot; data-origin-height=&quot;50&quot;&gt;&lt;span data-url=&quot;https://blog.kakaocdn.net/dn/8tuJ7/btsIzfgCWg7/8ABFfAnypBK5vLRLWmq7T1/img.png&quot; data-phocus=&quot;https://blog.kakaocdn.net/dn/8tuJ7/btsIzfgCWg7/8ABFfAnypBK5vLRLWmq7T1/img.png&quot; data-alt=&quot;슬랙 주소의 맨 끝부분이 해당 채널의 ID다&quot;&gt;&lt;img src=&quot;https://blog.kakaocdn.net/dn/8tuJ7/btsIzfgCWg7/8ABFfAnypBK5vLRLWmq7T1/img.png&quot; srcset=&quot;https://img1.daumcdn.net/thumb/R1280x0/?scode=mtistory2&amp;fname=https%3A%2F%2Fblog.kakaocdn.net%2Fdn%2F8tuJ7%2FbtsIzfgCWg7%2F8ABFfAnypBK5vLRLWmq7T1%2Fimg.png&quot; onerror=&quot;this.onerror=null; this.src='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png'; this.srcset='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png';&quot; loading=&quot;lazy&quot; width=&quot;456&quot; height=&quot;48&quot; data-origin-width=&quot;475&quot; data-origin-height=&quot;50&quot;/&gt;&lt;/span&gt;&lt;figcaption&gt;슬랙 주소의 맨 끝부분이 해당 채널의 ID다&lt;/figcaption&gt;
&lt;/figure&gt;
&lt;figure class=&quot;imageblock alignCenter&quot; data-ke-mobileStyle=&quot;widthOrigin&quot; data-origin-width=&quot;1118&quot; data-origin-height=&quot;876&quot;&gt;&lt;span data-url=&quot;https://blog.kakaocdn.net/dn/cgNLfl/btsIBiJwTTj/tiOpq870GrTLqWXxRkqJU0/img.png&quot; data-phocus=&quot;https://blog.kakaocdn.net/dn/cgNLfl/btsIBiJwTTj/tiOpq870GrTLqWXxRkqJU0/img.png&quot;&gt;&lt;img src=&quot;https://blog.kakaocdn.net/dn/cgNLfl/btsIBiJwTTj/tiOpq870GrTLqWXxRkqJU0/img.png&quot; srcset=&quot;https://img1.daumcdn.net/thumb/R1280x0/?scode=mtistory2&amp;fname=https%3A%2F%2Fblog.kakaocdn.net%2Fdn%2FcgNLfl%2FbtsIBiJwTTj%2FtiOpq870GrTLqWXxRkqJU0%2Fimg.png&quot; onerror=&quot;this.onerror=null; this.src='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png'; this.srcset='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png';&quot; loading=&quot;lazy&quot; width=&quot;1118&quot; height=&quot;876&quot; data-origin-width=&quot;1118&quot; data-origin-height=&quot;876&quot;/&gt;&lt;/span&gt;&lt;/figure&gt;
&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;다음은 채널에 역할과 정책을 부여할 차례다. 위에서 생성한 역할과 가드레일 정책을 부여하자.&lt;/p&gt;
&lt;p&gt;&lt;figure class=&quot;imageblock alignCenter&quot; data-ke-mobileStyle=&quot;widthOrigin&quot; data-origin-width=&quot;988&quot; data-origin-height=&quot;803&quot;&gt;&lt;span data-url=&quot;https://blog.kakaocdn.net/dn/bEn5QB/btsIAbECoAJ/jtGrR4udKjVlJdyB11bBMk/img.png&quot; data-phocus=&quot;https://blog.kakaocdn.net/dn/bEn5QB/btsIAbECoAJ/jtGrR4udKjVlJdyB11bBMk/img.png&quot;&gt;&lt;img src=&quot;https://blog.kakaocdn.net/dn/bEn5QB/btsIAbECoAJ/jtGrR4udKjVlJdyB11bBMk/img.png&quot; srcset=&quot;https://img1.daumcdn.net/thumb/R1280x0/?scode=mtistory2&amp;fname=https%3A%2F%2Fblog.kakaocdn.net%2Fdn%2FbEn5QB%2FbtsIAbECoAJ%2FjtGrR4udKjVlJdyB11bBMk%2Fimg.png&quot; onerror=&quot;this.onerror=null; this.src='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png'; this.srcset='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png';&quot; loading=&quot;lazy&quot; width=&quot;988&quot; height=&quot;803&quot; data-origin-width=&quot;988&quot; data-origin-height=&quot;803&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;알림의 경우 특정 상황에서 발생되는 여러가지 이벤트를 Slack 챗봇을 통해 받고 싶을 때 사용하면 된다. 여기까지 설정이 완료됐다면 구성 버튼을 눌러 채널 생성을 완료하면 된다.&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;1377&quot; data-origin-height=&quot;90&quot;&gt;&lt;span data-url=&quot;https://blog.kakaocdn.net/dn/Ilxcx/btsIzWnnOnb/nQTzZq4kLWxCJPmki8fEJK/img.png&quot; data-phocus=&quot;https://blog.kakaocdn.net/dn/Ilxcx/btsIzWnnOnb/nQTzZq4kLWxCJPmki8fEJK/img.png&quot;&gt;&lt;img src=&quot;https://blog.kakaocdn.net/dn/Ilxcx/btsIzWnnOnb/nQTzZq4kLWxCJPmki8fEJK/img.png&quot; srcset=&quot;https://img1.daumcdn.net/thumb/R1280x0/?scode=mtistory2&amp;fname=https%3A%2F%2Fblog.kakaocdn.net%2Fdn%2FIlxcx%2FbtsIzWnnOnb%2FnQTzZq4kLWxCJPmki8fEJK%2Fimg.png&quot; onerror=&quot;this.onerror=null; this.src='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png'; this.srcset='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png';&quot; loading=&quot;lazy&quot; width=&quot;1377&quot; height=&quot;90&quot; data-origin-width=&quot;1377&quot; data-origin-height=&quot;90&quot;/&gt;&lt;/span&gt;&lt;/figure&gt;
&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;채널을 생성했다고 곧장 AWS와 Slack을 연동해서 사용할 수 있는 것은 아니다. AWS 기능을 Slack을 통해 사용하기 위해서는 챗봇과 연동한 Slack 채널에 AWS Chatbot을 초대해야 한다.&lt;/p&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;code&gt;/invite @aws&lt;/code&gt; 라는 메시지를 입력한 후 전송하는 것으로 AWS Chatbot을 해당 채널에 초대할 수 있다.&lt;/p&gt;
&lt;p&gt;&lt;figure class=&quot;imageblock alignCenter&quot; data-ke-mobileStyle=&quot;widthOrigin&quot; data-origin-width=&quot;880&quot; data-origin-height=&quot;610&quot;&gt;&lt;span data-url=&quot;https://blog.kakaocdn.net/dn/FlzuN/btsIzWOs6A8/K8RWRUnYpV0quEDvKS8ffk/img.png&quot; data-phocus=&quot;https://blog.kakaocdn.net/dn/FlzuN/btsIzWOs6A8/K8RWRUnYpV0quEDvKS8ffk/img.png&quot;&gt;&lt;img src=&quot;https://blog.kakaocdn.net/dn/FlzuN/btsIzWOs6A8/K8RWRUnYpV0quEDvKS8ffk/img.png&quot; srcset=&quot;https://img1.daumcdn.net/thumb/R1280x0/?scode=mtistory2&amp;fname=https%3A%2F%2Fblog.kakaocdn.net%2Fdn%2FFlzuN%2FbtsIzWOs6A8%2FK8RWRUnYpV0quEDvKS8ffk%2Fimg.png&quot; onerror=&quot;this.onerror=null; this.src='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png'; this.srcset='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png';&quot; loading=&quot;lazy&quot; width=&quot;880&quot; height=&quot;610&quot; data-origin-width=&quot;880&quot; data-origin-height=&quot;610&quot;/&gt;&lt;/span&gt;&lt;/figure&gt;
&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;AWS SNS를 통해 알림 설정을 등록했다면 테스트 메시지 전송을 통해 AWS Chatbot이 잘 등록됐는지 확인할 수 있다. 만약 설정하지 않았더라도 챗봇을 사용하는데 큰 문제는 없다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;이제 Slack에 등록된 AWS Chatbot을 통해 처음 만들어두었던 EC2 인스턴스를 중지시켜보자. 우선 생성해둔 EC2 인스턴스의 ID를 복사한 뒤, Slack의 메시지 입력란에 다음과 같이 입력해보자.&lt;/p&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;code&gt;@aws ec2 stop-instances --instance-ids {EC2 Instance ID} --region {Region}&lt;/code&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;이때 주의할 점은 인스턴스 ID를 링크 상태로 복사하면 안된다. 순수한 문자열인 인스턴스 ID를 복사하도록 하자. 인스턴스 상세 페이지에서 복사 버튼을 클릭하는 방법을 권장한다.&lt;/p&gt;
&lt;p&gt;&lt;figure class=&quot;imageblock alignCenter&quot; data-ke-mobileStyle=&quot;widthOrigin&quot; data-origin-width=&quot;1201&quot; data-origin-height=&quot;360&quot;&gt;&lt;span data-url=&quot;https://blog.kakaocdn.net/dn/B6iGn/btsIzrgLjmr/aoAIgsNQeGt5JYinFgHLPk/img.png&quot; data-phocus=&quot;https://blog.kakaocdn.net/dn/B6iGn/btsIzrgLjmr/aoAIgsNQeGt5JYinFgHLPk/img.png&quot;&gt;&lt;img src=&quot;https://blog.kakaocdn.net/dn/B6iGn/btsIzrgLjmr/aoAIgsNQeGt5JYinFgHLPk/img.png&quot; srcset=&quot;https://img1.daumcdn.net/thumb/R1280x0/?scode=mtistory2&amp;fname=https%3A%2F%2Fblog.kakaocdn.net%2Fdn%2FB6iGn%2FbtsIzrgLjmr%2FaoAIgsNQeGt5JYinFgHLPk%2Fimg.png&quot; onerror=&quot;this.onerror=null; this.src='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png'; this.srcset='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png';&quot; loading=&quot;lazy&quot; width=&quot;1201&quot; height=&quot;360&quot; data-origin-width=&quot;1201&quot; data-origin-height=&quot;360&quot;/&gt;&lt;/span&gt;&lt;/figure&gt;
&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;해당 메시지를 전송하게 되면 아래의 사진과 같이 스레드가 생성되고 AWS Chatbot이 성공적으로 명령어를 인식했다는 Reply를 확인할 수 있다. 해당 Reply는 명령어를 실행했다는 Reply가 아닌 &lt;b&gt;명령 수행 전 최종 확인을 위한 것이다.&lt;/b&gt;&lt;/p&gt;
&lt;p&gt;&lt;figure class=&quot;imageblock alignCenter&quot; data-ke-mobileStyle=&quot;widthOrigin&quot; data-origin-width=&quot;452&quot; data-origin-height=&quot;613&quot;&gt;&lt;span data-url=&quot;https://blog.kakaocdn.net/dn/cDAV8u/btsIAvCMUZ8/QZkCFN0LqiRQKjPeyYVFB0/img.png&quot; data-phocus=&quot;https://blog.kakaocdn.net/dn/cDAV8u/btsIAvCMUZ8/QZkCFN0LqiRQKjPeyYVFB0/img.png&quot;&gt;&lt;img src=&quot;https://blog.kakaocdn.net/dn/cDAV8u/btsIAvCMUZ8/QZkCFN0LqiRQKjPeyYVFB0/img.png&quot; srcset=&quot;https://img1.daumcdn.net/thumb/R1280x0/?scode=mtistory2&amp;fname=https%3A%2F%2Fblog.kakaocdn.net%2Fdn%2FcDAV8u%2FbtsIAvCMUZ8%2FQZkCFN0LqiRQKjPeyYVFB0%2Fimg.png&quot; onerror=&quot;this.onerror=null; this.src='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png'; this.srcset='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png';&quot; loading=&quot;lazy&quot; width=&quot;363&quot; height=&quot;492&quot; data-origin-width=&quot;452&quot; data-origin-height=&quot;613&quot;/&gt;&lt;/span&gt;&lt;/figure&gt;
&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;Reply의 [Run] command 를 클릭해야 AWS Chatbot이 입력한 명령어를 수행한다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;성공적으로 수행했다는 챗봇의 Reply를 다음과 같이 확인할 수 있다.&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;458&quot; data-origin-height=&quot;372&quot;&gt;&lt;span data-url=&quot;https://blog.kakaocdn.net/dn/bHQCJv/btsIzqhTURx/xThOHeQSyttKcMOESPwNt0/img.png&quot; data-phocus=&quot;https://blog.kakaocdn.net/dn/bHQCJv/btsIzqhTURx/xThOHeQSyttKcMOESPwNt0/img.png&quot;&gt;&lt;img src=&quot;https://blog.kakaocdn.net/dn/bHQCJv/btsIzqhTURx/xThOHeQSyttKcMOESPwNt0/img.png&quot; srcset=&quot;https://img1.daumcdn.net/thumb/R1280x0/?scode=mtistory2&amp;fname=https%3A%2F%2Fblog.kakaocdn.net%2Fdn%2FbHQCJv%2FbtsIzqhTURx%2FxThOHeQSyttKcMOESPwNt0%2Fimg.png&quot; onerror=&quot;this.onerror=null; this.src='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png'; this.srcset='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png';&quot; loading=&quot;lazy&quot; width=&quot;366&quot; height=&quot;297&quot; data-origin-width=&quot;458&quot; data-origin-height=&quot;372&quot;/&gt;&lt;/span&gt;&lt;/figure&gt;
&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;AWS EC2 페이지에 들어가 직접 EC2의 상태를 확인해보면 실행에서 중지 중 상태로 변환된 것을 확인할 수 있다.&lt;/p&gt;
&lt;p&gt;&lt;figure class=&quot;imageblock alignCenter&quot; data-ke-mobileStyle=&quot;widthOrigin&quot; data-origin-width=&quot;1478&quot; data-origin-height=&quot;317&quot;&gt;&lt;span data-url=&quot;https://blog.kakaocdn.net/dn/LYEFW/btsIyZFaByK/pwbgX4At2CI3Ow99bavmh1/img.png&quot; data-phocus=&quot;https://blog.kakaocdn.net/dn/LYEFW/btsIyZFaByK/pwbgX4At2CI3Ow99bavmh1/img.png&quot;&gt;&lt;img src=&quot;https://blog.kakaocdn.net/dn/LYEFW/btsIyZFaByK/pwbgX4At2CI3Ow99bavmh1/img.png&quot; srcset=&quot;https://img1.daumcdn.net/thumb/R1280x0/?scode=mtistory2&amp;fname=https%3A%2F%2Fblog.kakaocdn.net%2Fdn%2FLYEFW%2FbtsIyZFaByK%2FpwbgX4At2CI3Ow99bavmh1%2Fimg.png&quot; onerror=&quot;this.onerror=null; this.src='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png'; this.srcset='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png';&quot; loading=&quot;lazy&quot; width=&quot;1478&quot; height=&quot;317&quot; data-origin-width=&quot;1478&quot; data-origin-height=&quot;317&quot;/&gt;&lt;/span&gt;&lt;/figure&gt;
&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;중지된 인스턴스를 다시 실행시키고 싶다면&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;code&gt;@aws ec2 start-instances --instance-ids {EC2 Instance ID} --region {Region}&lt;/code&gt;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;명령어를 통해 인스턴스를 실행 상태로 변경할 수 있다.&lt;/p&gt;
&lt;p&gt;&lt;figure class=&quot;imageblock alignCenter&quot; data-ke-mobileStyle=&quot;widthOrigin&quot; data-origin-width=&quot;1462&quot; data-origin-height=&quot;327&quot;&gt;&lt;span data-url=&quot;https://blog.kakaocdn.net/dn/bd5zUY/btsIAcjf1l8/1ql77R9kUmFaGCdMHliycK/img.png&quot; data-phocus=&quot;https://blog.kakaocdn.net/dn/bd5zUY/btsIAcjf1l8/1ql77R9kUmFaGCdMHliycK/img.png&quot;&gt;&lt;img src=&quot;https://blog.kakaocdn.net/dn/bd5zUY/btsIAcjf1l8/1ql77R9kUmFaGCdMHliycK/img.png&quot; srcset=&quot;https://img1.daumcdn.net/thumb/R1280x0/?scode=mtistory2&amp;fname=https%3A%2F%2Fblog.kakaocdn.net%2Fdn%2Fbd5zUY%2FbtsIAcjf1l8%2F1ql77R9kUmFaGCdMHliycK%2Fimg.png&quot; onerror=&quot;this.onerror=null; this.src='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png'; this.srcset='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png';&quot; loading=&quot;lazy&quot; width=&quot;1462&quot; height=&quot;327&quot; data-origin-width=&quot;1462&quot; data-origin-height=&quot;327&quot;/&gt;&lt;/span&gt;&lt;/figure&gt;
&lt;/p&gt;</description>
      <category>Public Cloud</category>
      <category>AWS</category>
      <category>AWS Chatbot</category>
      <category>aws cli</category>
      <category>Chatbot</category>
      <category>slack</category>
      <author>겨울바람_</author>
      <guid isPermaLink="true">https://breeze-winter.tistory.com/26</guid>
      <comments>https://breeze-winter.tistory.com/26#entry26comment</comments>
      <pubDate>Mon, 15 Jul 2024 18:38:45 +0900</pubDate>
    </item>
    <item>
      <title>[AWS] Elastic Network Interface (ENI)</title>
      <link>https://breeze-winter.tistory.com/25</link>
      <description>&lt;h3 data-ke-size=&quot;size23&quot;&gt;Elastic Network Interface&lt;/h3&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;탄련적 네트워크 인터페이스는 VPC에서 가상 네트워크 카드를 나타내는 논리적 네트워킹 구성 요소다. EC2의 IP주소와 Mac 주소를 보유하고 있다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;EC2는 반드시 하나 이상의 ENI가 연결되어 있으며, EC2 최초 생성시 Primary ENI가 생성되어 연결된다. 사용자의 필요에 따라 한 개의 EC2에 여러 개의 ENI를 연결시킬 수 있다. 추가된 ENI의 경우 EC2와 동일한 가용영역이라면 다른 서브넷에도 설정이 가능하다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;하나의 ENI에 Private IP와 하나의 Public IP로 구성되어 있는데, Public IP의 경우 사용자의 선택에 따라 설정하지 않을 수도 있다. 또한 필요에 따라서 한 개 이상의 Private IP를 부여할 수 있다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;실질적으로 EC2의 서브넷 위치, 보안그룹 등 외부와 관련된 연결은 ENI 단위에서 결정된다. 사용자 입장에서는 서브넷 내부에 EC2를 위치시킨다고 생각할수도 있지만, 사실 EC2는 가용영역 단위에 존재하며 해당 EC2에 연결된 Primary ENI가 서브넷에 위치하는 것이다.&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;921&quot; data-origin-height=&quot;541&quot;&gt;&lt;span data-url=&quot;https://blog.kakaocdn.net/dn/b6h3SM/btsIy5KJ90A/ZxMaUxikiZD8x9qPvQCxW0/img.png&quot; data-phocus=&quot;https://blog.kakaocdn.net/dn/b6h3SM/btsIy5KJ90A/ZxMaUxikiZD8x9qPvQCxW0/img.png&quot;&gt;&lt;img src=&quot;https://blog.kakaocdn.net/dn/b6h3SM/btsIy5KJ90A/ZxMaUxikiZD8x9qPvQCxW0/img.png&quot; srcset=&quot;https://img1.daumcdn.net/thumb/R1280x0/?scode=mtistory2&amp;fname=https%3A%2F%2Fblog.kakaocdn.net%2Fdn%2Fb6h3SM%2FbtsIy5KJ90A%2FZxMaUxikiZD8x9qPvQCxW0%2Fimg.png&quot; onerror=&quot;this.onerror=null; this.src='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png'; this.srcset='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png';&quot; loading=&quot;lazy&quot; width=&quot;921&quot; height=&quot;541&quot; data-origin-width=&quot;921&quot; data-origin-height=&quot;541&quot;/&gt;&lt;/span&gt;&lt;/figure&gt;
&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;EC2 자체가 가용영역 단위이기 때문에, 동일한 가용영역에 위치한 서브넷에는 ENI를 여러개 연결할 수 있지만, 다른 가용영역은 불가능하다.&lt;/p&gt;
&lt;h4 data-ke-size=&quot;size20&quot;&gt;ENI &amp;amp; Security Group&lt;/h4&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;보안그룹 적용은 ENI 단위기 때문에 하나의 EC2 인스턴스에 다양한 보안그룹으로 구성된 경로를 적용할 수 있다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;예를 들어 서브넷 A에서는 80번 포트만 허용하여 고객으로 하여금 웹서버를 통해 접근이 가능하도록 할 수 있고 서브넷 B에서는 22번 포트만 허용하여 SSH 연결로 인스턴스를 관리할 수 있도록 설정이 가능하다는 것이다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;참고로 NACL의 경우 서브넷 단위이기 때문에 보안그룹과는 상이하다.&lt;/p&gt;
&lt;h4 data-ke-size=&quot;size20&quot;&gt;EC2의 Public IP&lt;/h4&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;EC2의 Public IP는 ENI가 아닌 Public IP와 Private IP를 중계하는 가상의 라우팅 테이블을 통해 관리된다. 예를 들어 특정 Public IP로 요청이 들어왔을 때 가상 라우팅 테이블을 통해 매핑되어 있는 Private IP 주소의 ENI로 요청을 전달하는 것이다.&lt;/p&gt;
&lt;p&gt;&lt;figure class=&quot;imageblock alignCenter&quot; data-ke-mobileStyle=&quot;widthOrigin&quot; data-origin-width=&quot;1016&quot; data-origin-height=&quot;586&quot;&gt;&lt;span data-url=&quot;https://blog.kakaocdn.net/dn/5E6Vp/btsIzcbY0Du/0JJsKkJd8txR993Jo50XkK/img.png&quot; data-phocus=&quot;https://blog.kakaocdn.net/dn/5E6Vp/btsIzcbY0Du/0JJsKkJd8txR993Jo50XkK/img.png&quot;&gt;&lt;img src=&quot;https://blog.kakaocdn.net/dn/5E6Vp/btsIzcbY0Du/0JJsKkJd8txR993Jo50XkK/img.png&quot; srcset=&quot;https://img1.daumcdn.net/thumb/R1280x0/?scode=mtistory2&amp;fname=https%3A%2F%2Fblog.kakaocdn.net%2Fdn%2F5E6Vp%2FbtsIzcbY0Du%2F0JJsKkJd8txR993Jo50XkK%2Fimg.png&quot; onerror=&quot;this.onerror=null; this.src='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png'; this.srcset='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png';&quot; loading=&quot;lazy&quot; width=&quot;1016&quot; height=&quot;586&quot; data-origin-width=&quot;1016&quot; data-origin-height=&quot;586&quot;/&gt;&lt;/span&gt;&lt;/figure&gt;
&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;해당 Public IP, Private IP 매핑 레코드는 Elastic IP를 사용해 고정하지 않는 이상 영구적인 레코드가 아니다. 이 때문에 EC2 중지 혹은 재부팅 시 Public IP가 변경되는 반면, Private IP는 변경되지 않는 것이다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;인터넷에서 Public IP로 요청이 전달되면 IGW가 해당 라우팅 테이블을 통해 Private IP로 변환 후 전달한다. 이 때문에 EC2 인스턴스의 OS에서는 할당된 Public IP를 절대 알 수 없다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;EC2 생성 시점에 만들어지는 Primary ENI가 아닌 별도의 ENI에 Public IP를 부여하기 위해서는 Elastic IP가 필요하다.&lt;/p&gt;
&lt;h4 data-ke-size=&quot;size20&quot;&gt;Source / Destination Check&lt;/h4&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;ENI는 기본적으로 자신이 발생시켰거나, 대상이 아닌 트래픽은 무시한다. 단, 설정에 따라서 해제가 가능하다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;NAT 인스턴스 등 자신을 위한 트래픽이 아닌 다른 대상에게 중계해주는 경우 해당 설정을 해제할 필요가 있다.&lt;/p&gt;</description>
      <category>Public Cloud</category>
      <category>AWS</category>
      <category>Cloud</category>
      <category>EC2</category>
      <category>elastic network interface</category>
      <category>Eni</category>
      <category>network</category>
      <author>겨울바람_</author>
      <guid isPermaLink="true">https://breeze-winter.tistory.com/25</guid>
      <comments>https://breeze-winter.tistory.com/25#entry25comment</comments>
      <pubDate>Sat, 13 Jul 2024 22:24:43 +0900</pubDate>
    </item>
    <item>
      <title>[k8s] etcd &amp;amp; Raft Alogorithm (2)</title>
      <link>https://breeze-winter.tistory.com/24</link>
      <description>&lt;p&gt;&lt;figure class=&quot;imageblock alignCenter&quot; data-ke-mobileStyle=&quot;widthOrigin&quot; data-origin-width=&quot;750&quot; data-origin-height=&quot;250&quot;&gt;&lt;span data-url=&quot;https://blog.kakaocdn.net/dn/cePCgq/dJMcahDGIH1/il87IM81SxuGFySOP8kXl0/img.png&quot; data-phocus=&quot;https://blog.kakaocdn.net/dn/cePCgq/dJMcahDGIH1/il87IM81SxuGFySOP8kXl0/img.png&quot;&gt;&lt;img src=&quot;https://blog.kakaocdn.net/dn/cePCgq/dJMcahDGIH1/il87IM81SxuGFySOP8kXl0/img.png&quot; srcset=&quot;https://img1.daumcdn.net/thumb/R1280x0/?scode=mtistory2&amp;fname=https%3A%2F%2Fblog.kakaocdn.net%2Fdn%2FcePCgq%2FdJMcahDGIH1%2Fil87IM81SxuGFySOP8kXl0%2Fimg.png&quot; onerror=&quot;this.onerror=null; this.src='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png'; this.srcset='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png';&quot; loading=&quot;lazy&quot; width=&quot;492&quot; height=&quot;164&quot; data-origin-width=&quot;750&quot; data-origin-height=&quot;250&quot;/&gt;&lt;/span&gt;&lt;/figure&gt;
&lt;/p&gt;
&lt;h3 data-ke-size=&quot;size23&quot;&gt;Runtime Reconfiguration&lt;/h3&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;etcd 클러스터가 동작 중일 때, etcd 서버를 추가/삭제하는 것을 런타임 재구성이라고 한다.&lt;/p&gt;
&lt;h4 data-ke-size=&quot;size20&quot;&gt;Member Add&lt;/h4&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;3개의 서버로 구성된 etcd 클러스터의 lastIndex가 10101이라고 가정해보자. etcd에는 Snapshot 이라는 개념이 있는데, etcd 서버가 현재까지 받아들인 모든 log를 Entry에서만 관리하는 것이 아니라 파일 시스템에 백업해두는 것을 의미한다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;Snapshot 실행 빈도는 etcd 클러스터를 생성하면서 옵션으로 설정할 수 있고, 디폴트 값은 100,000이다. 이러한 상황에서 4번째 서버를 추가하는 요청이 발생했다고 가정해보자.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;etcd는 이러한 구성의 변경을 log append와 동일한 메커니즘으로 처리한다. Leader는 사용자로부터 전달받은 재구성 요청에 따라 새로운 구성에 해당하는 &lt;code&gt;Cnew&lt;/code&gt;를 log Entry에 추가하고 Append RPC call을 이용해 다른 서버에 전파한다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;일반적인 Log Replication과 다른 점이 있다면, 이 구성에 대한 요청은 commit 된 이후에 효력을 발휘하는 것이 아니라 Entry에 write 되자마자 곧장 효력을 발휘한다는 것이다. 만약 구성과 관련된 로그가 Entry 내에 여러 개 존재한다면 가장 최신의 로그를 사용하게 된다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;또한, Log Entry에 write 되자마자 곧장 효력을 발휘하기 때문에 Log Entry에 존재하는 Cnew를 commit 하기 위해 필요한 정족수는 3 (4/2 + 1)으로 증거된다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;서버 추가를 요청한 클라이언트에게 응답을 한 이후, Leader는 추가된 멤버가 가능한 빨리 동일한 log를 보유할 수 있도록 Snapshot을 보내준다. 해당 Snapshot은 파일 시스템에 저장되어 있는 것들 중 가장 최신의 것과 Leader의 현재 Log Entry를 합쳐 생성된다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;새롭게 추가된 서버는 해당 Snapshot을 이용해 DB를 만드는 것으로 Append RPC call을 정상적으로 받아들일 수 있게 된다.&lt;/p&gt;
&lt;h4 data-ke-size=&quot;size20&quot;&gt;Member Add Risk&lt;/h4&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;만약 새롭게 추가된 4번째 서버가 새롭게 구성되고 있을 때, 새로운 서버 추가 요청이 한 번 더 들어온다면 어떻게 될까?&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;우선, 새롭게 서버 추가 요청이 Leader의 Log Entry에 저장된다. 정족수는 3 (5 / 2 + 1) 에서 증가되지 않지만, 만약 다른 서버에서 해당 요청 로그를 복제받는 것에 지연이 생긴다면 새롭게 들어온 서버 추가 요청이 지연이 commit 되는 것 또한 지연된다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;이러한 상황에서 추가적인 write 요청이 들어오게 된다면, 해당 요청 또한 block이 되고 클라이언트의 Timeout 시간 동안 해당 요청을 처리하지 못하면 가용성에 문제가 생기게 된다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;Raft 알고리즘은 이러한 문제를 해결하기 위해 Leader, Follower, Candidate 외에 Learner 상태가 존재한다. Learner는 etcd 클러스터의 멤버이지만, 정족수 카운트에서는 제외되는 특별한 상태로 etcd 3.4.0부터 구현됐다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;Learner는 etcd의 promote API를 사용하여 일반적인 Follower로 변경할 수 있다. Learner가 아직 log를 모두 따라잡지 못한 경우에는 거절된다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;Kubeadm, Kubespray와 같이 널리 쓰이는 Kubernetes 설치 자동화 도구는 etcd 클러스터 생성 시 Learner의 개념을 활용하고 있지는 않다. Kubernetes에서 사용하는 etcd 클러스터의 Snapshot은 평균적으로 크기가 수십 Kb에 불과하기 때문에 이러한 이슈가 발생할 가능성은 낮다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;또한 이러한 일을 방지하기 위해 Raft 알고리즘에서는 런타임 재구성은 한 번에 하나씩 처리할 것을 원칙으로 하고 있으며, etcd에서는 restriction을 설정하여 Leader의 log에 commit 되지 않은 구성 요청이 있다면 새로운 구성 요청을 받지 않는다.&lt;/p&gt;
&lt;h4 data-ke-size=&quot;size20&quot;&gt;Remove Leader&lt;/h4&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;etcd 클러스터에서 특정 서버를 삭제하라는 요청 또한 etcd 서버 추가와 동일한 방식으로 진행된다. 하지만 삭제 대상이 Leader일 경우, Leader는 Leader 삭제에 대한 구성 요청을 commit 시키는데 필요한 로그 복제에 대한 정족수를 셈할 때 자기 자신을 제외한다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;Leader를 제외한만큼의 정족수가 확보되면 Leader 삭제에 대한 내용이 존재하는 로그를 commit 하고 해당 요청을 보낸 클라이언트에게 응답을 보낸다. commit 이후 Leader 상태에서 빠져나오게 되고 더 이상 다른 서버에 Heartbeat를 보내지 않게 된다. 이것을 Raft와 etcd에서는 step down 이라고 표현한다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;Leader가 클라이언트의 삭제 요청을 처리할 때 정족수에서 자기 자신을 제외하였기 때문에, 새로운 Leader의 선출이 가능해진다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;만약 step down 이 발생하기 전에 write 요청이 들어온다면 어떻게 될까?&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;구성 요청은 Entry에 들어가자마자 효력을 발휘하지만, Leader 삭제 요청의 경우 Entry에 존재하는 가장 최신의 구성 로그에 자신이 없더라도 Leader 역할을 수행한다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;예를 들어, 아직 삭제 요청이 commit 되기 이전에 write 요청이 들어온다면 Leader는 자기 자신을 제외한 정족수만큼 로그 복제를 수행한다.&lt;/p&gt;
&lt;h4 data-ke-size=&quot;size20&quot;&gt;Member Remove Restriction&lt;/h4&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;Leader는 정족수와 현재 정상 동작 중인 서버의 수를 비교하여 현재 정상 동작 중인 서버 수가 정족수보다 낮아질 것으로 예상되면 클라이언트의 멤버 삭제 요청을 거절한다.&lt;/p&gt;</description>
      <category>Infra</category>
      <category>ETCD</category>
      <category>k8s</category>
      <category>kubernetes</category>
      <category>raft</category>
      <author>겨울바람_</author>
      <guid isPermaLink="true">https://breeze-winter.tistory.com/24</guid>
      <comments>https://breeze-winter.tistory.com/24#entry24comment</comments>
      <pubDate>Fri, 12 Jul 2024 18:09:30 +0900</pubDate>
    </item>
    <item>
      <title>[AWS] Elastic Load Balancer</title>
      <link>https://breeze-winter.tistory.com/23</link>
      <description>&lt;h3 data-ke-size=&quot;size23&quot;&gt;ELB?&lt;/h3&gt;
&lt;p&gt;&lt;figure class=&quot;imageblock alignCenter&quot; data-ke-mobileStyle=&quot;widthOrigin&quot; data-origin-width=&quot;757&quot; data-origin-height=&quot;502&quot;&gt;&lt;span data-url=&quot;https://blog.kakaocdn.net/dn/OeENw/btsIxEseao1/jm90WXg4kcKDXHWabvILXK/img.png&quot; data-phocus=&quot;https://blog.kakaocdn.net/dn/OeENw/btsIxEseao1/jm90WXg4kcKDXHWabvILXK/img.png&quot;&gt;&lt;img src=&quot;https://blog.kakaocdn.net/dn/OeENw/btsIxEseao1/jm90WXg4kcKDXHWabvILXK/img.png&quot; srcset=&quot;https://img1.daumcdn.net/thumb/R1280x0/?scode=mtistory2&amp;fname=https%3A%2F%2Fblog.kakaocdn.net%2Fdn%2FOeENw%2FbtsIxEseao1%2Fjm90WXg4kcKDXHWabvILXK%2Fimg.png&quot; onerror=&quot;this.onerror=null; this.src='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png'; this.srcset='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png';&quot; loading=&quot;lazy&quot; width=&quot;618&quot; height=&quot;410&quot; data-origin-width=&quot;757&quot; data-origin-height=&quot;502&quot;/&gt;&lt;/span&gt;&lt;/figure&gt;
&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;다수의 서비스에 트래픽을 분산시켜주는 서비스이며, 직접 트래픽을 발생시켜 인스턴스가 살아있는지 체크한다. 여러 가용영역에 분산이 가능하기 때문에 애플리케이션 가용성을 향상시키고 HTTP, HTTPS, TCP, SSL, gRPC 등 다양한 프로토콜을 지원한다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;또한, AWS의 CloudWatch 기능을 이용하여 로그와 메트릭을 모니터링할 수 있으며, Auto Scaling 기능과 결합하여 트래픽이 증가할 때 자동으로 인스턴스를 추가하거나 제거하면서 애플리케이션 가용성을 유지한다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;지속적으로 IP가 변경되기 때문에 도메인 기반으로 사용하는 것이 편리하다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;대표적으로 4가지 종류가 존재한다.&lt;/p&gt;
&lt;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;Application Load Balancer (ALB)&lt;/li&gt;
&lt;li&gt;Network Load Balancer (NLB)&lt;/li&gt;
&lt;li&gt;&lt;del&gt;Classic Load Balancer (CLB)&lt;/del&gt; 현재는 잘 사용되지 않는다&lt;/li&gt;
&lt;li&gt;Gateway Load Balancer (GWLB)&lt;/li&gt;
&lt;/ol&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;각 CLB를 제외한 각각의 ELB를 살펴보기 전에 AWS ELB의 구성 요소에 대해 먼저 알아보자&lt;/p&gt;
&lt;h4 data-ke-size=&quot;size20&quot;&gt;ELB 구성 요소&lt;/h4&gt;
&lt;h4 data-ke-size=&quot;size20&quot;&gt;Load Balancer&lt;/h4&gt;
&lt;p data-ke-size=&quot;size14&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;여러 대의 EC2 인스턴스, IP주소, 람다 등을 사용하여 트래픽을 대상 그룹에 있는 인스턴스로 분산시켜 애플리케이션의 가용성을 유지하는 역할을 한다. 로드 밸런서는 사용자 요청을 받아 애플리케이션 서버로 전달하고, 애플리케이션 서버의 응답을 사용자에게 반환한다.&lt;/p&gt;
&lt;h4 data-ke-size=&quot;size20&quot;&gt;Listener&lt;/h4&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;로드 밸런서에서 사용할 포트와 프로토콜을 설정하는 구성 요소로, 로드 m 밸런 서에서 클라이언트 요청을 수신하고, 해당 요청을 처리할 대상 그룹을 선택하는 역할을 한다.&lt;/p&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;h4 data-ke-size=&quot;size20&quot;&gt;Target Group&lt;/h4&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;로드 밸런서에서 분산할 대상의 잡합을 정의하는 구성 요소. 대상 그룹의 인스턴스에 대해 정적 또는 동적으로 구성할 수 있으며, 라우팅 규칙에 따라 요청을 받아들일 대상 그룹을 선택한다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;로드 밸런서는 대상 그룹에 포함된 대상들의 상태를 정기적으로 확인하여 장애 발생 대상을 자동으로 제외하고, 정상적으로 동작하는 대상에만 요청을 전달한다.&lt;/p&gt;
&lt;h4 data-ke-size=&quot;size20&quot;&gt;ELB의 동작 방식&lt;/h4&gt;
&lt;p&gt;&lt;figure class=&quot;imageblock alignCenter&quot; data-ke-mobileStyle=&quot;widthOrigin&quot; data-origin-width=&quot;1184&quot; data-origin-height=&quot;814&quot;&gt;&lt;span data-url=&quot;https://blog.kakaocdn.net/dn/l4zfp/btsIvRs2Sl3/tJeSf9hZKUwALQWgHNRQwk/img.png&quot; data-phocus=&quot;https://blog.kakaocdn.net/dn/l4zfp/btsIvRs2Sl3/tJeSf9hZKUwALQWgHNRQwk/img.png&quot;&gt;&lt;img src=&quot;https://blog.kakaocdn.net/dn/l4zfp/btsIvRs2Sl3/tJeSf9hZKUwALQWgHNRQwk/img.png&quot; srcset=&quot;https://img1.daumcdn.net/thumb/R1280x0/?scode=mtistory2&amp;fname=https%3A%2F%2Fblog.kakaocdn.net%2Fdn%2Fl4zfp%2FbtsIvRs2Sl3%2FtJeSf9hZKUwALQWgHNRQwk%2Fimg.png&quot; onerror=&quot;this.onerror=null; this.src='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png'; this.srcset='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png';&quot; loading=&quot;lazy&quot; width=&quot;642&quot; height=&quot;441&quot; data-origin-width=&quot;1184&quot; data-origin-height=&quot;814&quot;/&gt;&lt;/span&gt;&lt;/figure&gt;
&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;로드 밸런서에서 클라이언트 요청을 수신하면, 로드 밸런서는 클라이언트와 연결을 유지하며, 요청을 수신하기 위해 리스너를 등록한다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;수신한 클라이언트 요청을 처리할 대상 그룹을 선택한다. 대상 그룹은 인스턴스, IP 주소, 람다, ALB 등 여러 유형의 대상으로 구성된다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;선택된 대상 그룹에서 요청을 처리할 대상을 선택하고 해당 대상으로 요청을 분산한다. 이때 로드 밸런서는 각 대상의 가용성 상태를 모니터링하고 가용하지 않은 대상을 제외한다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;분산된 요청을 대상에서 처리하고 클라이언트에 응답을 반환한다. 이때 응답은 로드 밸런서에서 수신한 것으로 반한된다.&lt;/p&gt;
&lt;h4 data-ke-size=&quot;size20&quot;&gt;Cross-zone Load Balancing&lt;/h4&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;ELB는 여러 가용 영역에서 로드 밸런서 노드를 실행하며, 각 노드는 가용 영역 내 대상 그룹으로 요청을 분산한다. 이때 대상 그룹에 등록된 대상이 여러 가용 영역에 걸쳐 있다면 기본적으로 로드 밸랜서는 동일한 비중으로 가용 영역 내에 있는 대상으로 트래픽을 분산한다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;하지만 가용 영역 내 인스턴스 수량이 불균형할 때는 일부 인스턴스로 트래픽이 몰리고 다른 인스턴스는 유휴 상태가 되는 불균형 문제가 발생할 수 있다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;이런 불균형 문제를 해결하는데 ELB 교차 영역 로드 밸런싱을 활용할 수 있다. 교차 영역 로드 밸런싱은 여러 가용 영역에 걸쳐 있는 EC2 인스턴스나 컨테이너 등 대상을 더 효과적으로 로드 밸런싱하는 기능이다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;이런 교차 영역 로드 밸런싱은 가용 영역별로 인스턴스 수량이 불균형하게 위치할 때 트래픽 비중을 보정할 수 있으며, 트래픽을 분산하는 기준이 가용 영역이 아닌 대상 그룹에 속한 자원을 기준으로 균일한 비중의 로드 밸런싱을 수행할 수 있다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;ALB 사용 시 기본적으로 활성화되어 있으나, NLB는 비활성화되어 있다. 또한, 교차 영역 로드 밸런싱은 대상 그룹 수준에서 구성할 수 있으므로 필요에 따라 각 대상 그룹 별로 별도로 구성할 수 있다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;단, 사용할 때 가용 영역 간 통신 비용이 발생할 수 있다.&lt;/p&gt;
&lt;h3 data-ke-size=&quot;size23&quot;&gt;ALB&lt;/h3&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;ALB는 AWS에서 제공하는 L7 로드 밸런서로, HTTP/HTTPS 같은 웹 애플리케이션 프로토콜을 지원한다. ALB는 대상 그룹 단위로 트래픽을 분산하며, 각 대상 그룹은 ALB가 요청을 전달할 대상으로 라우팅하는 기능을 제공한다.&lt;/p&gt;
&lt;h4 data-ke-size=&quot;size20&quot;&gt;ALB 특징&lt;/h4&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;- HTTP 헤더를 확인하여 다양한 라우팅 기능을 제공한다&lt;br /&gt;- 경로 기반 라우팅&lt;br /&gt;- 호스트 기반 라우팅&lt;br /&gt;- 쿼리 문자열 기반 라우팅&lt;br /&gt;- Auto Scailing과 함께 사용하여 확장성 있는 애플리케이션을 구성할 수 있다&lt;br /&gt;- 대상 그룹 내 인스턴스에 대해 상태 검사를 수행하고, 문제가 발생하면 자동으로 장애 조치를 취할 수 있다&lt;br /&gt;- Amazon CloudWatch와 통합되어 로그 및 지표 데이터를 수집하고 모니터링 및 분석이 가능하다&lt;/p&gt;
&lt;h3 data-ke-size=&quot;size23&quot;&gt;NLB&lt;/h3&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;NLB는 AWS에서 제공하는 L4 로드 밸런서로, TCP/UDP/TLS 프로토콜을 지원한다. ALB와는 달리, 클라이언트와 로드 밸런서 간 연결을 TCP 레벨에서 유지하므로 대규모 트래픽을 처리할 수 있다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;동일한 IP 주소에서 여러 대상 그룹을 지원할 수 있다.&lt;/p&gt;
&lt;h4 data-ke-size=&quot;size20&quot;&gt;NLB 특징&lt;/h4&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;- 높은 처리량&lt;br /&gt;- 빠른 응답 시간&lt;br /&gt;- 높은 가용성 :&amp;nbsp;여러 가용 영역에서 인스턴스를 실행하고 매우 빠른 인스턴스 검색을 수행하여 신속한 장애 복구가 가능&lt;br /&gt;- IP 주소 보존 : 클라이언트 IP 주소를 원래 IP 주소로 보존할 수 있다. 이는 클라이언트 IP 주소를 유지하며 로드 밸런싱을 수행할 수 있다는 것을 의미한다&lt;br /&gt;- 모니터링&lt;/p&gt;
&lt;h3 data-ke-size=&quot;size23&quot;&gt;GWLB&lt;/h3&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;주로 가상 어플라이언스 배포/확장 관리를 위해 사용된다. 통상적으로 네트워크 트래픽을 서드 파티의 방화벽/어플라이언스 장비로 부하분산 처리하는 로드 밸런서다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;GWLB는 VPC 내에서 실행되는 애플리케이션의 가용성과 확장성을 향상시키는데 사용되며, TCP 및 UDP 프로토콜을 지원하여 다양한 유형의 애플리케이션에 유연하게 적용할 수 있다.&lt;/p&gt;</description>
      <category>Public Cloud</category>
      <author>겨울바람_</author>
      <guid isPermaLink="true">https://breeze-winter.tistory.com/23</guid>
      <comments>https://breeze-winter.tistory.com/23#entry23comment</comments>
      <pubDate>Thu, 11 Jul 2024 22:57:15 +0900</pubDate>
    </item>
    <item>
      <title>[k8s] etcd &amp;amp; Raft Algorithm (1)</title>
      <link>https://breeze-winter.tistory.com/22</link>
      <description>&lt;p&gt;&lt;figure class=&quot;imageblock alignCenter&quot; data-ke-mobileStyle=&quot;widthOrigin&quot; data-origin-width=&quot;750&quot; data-origin-height=&quot;250&quot;&gt;&lt;span data-url=&quot;https://blog.kakaocdn.net/dn/cePCgq/dJMcahDGIH1/il87IM81SxuGFySOP8kXl0/img.png&quot; data-phocus=&quot;https://blog.kakaocdn.net/dn/cePCgq/dJMcahDGIH1/il87IM81SxuGFySOP8kXl0/img.png&quot;&gt;&lt;img src=&quot;https://blog.kakaocdn.net/dn/cePCgq/dJMcahDGIH1/il87IM81SxuGFySOP8kXl0/img.png&quot; srcset=&quot;https://img1.daumcdn.net/thumb/R1280x0/?scode=mtistory2&amp;fname=https%3A%2F%2Fblog.kakaocdn.net%2Fdn%2FcePCgq%2FdJMcahDGIH1%2Fil87IM81SxuGFySOP8kXl0%2Fimg.png&quot; onerror=&quot;this.onerror=null; this.src='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png'; this.srcset='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png';&quot; loading=&quot;lazy&quot; width=&quot;492&quot; height=&quot;164&quot; data-origin-width=&quot;750&quot; data-origin-height=&quot;250&quot;/&gt;&lt;/span&gt;&lt;/figure&gt;
&lt;/p&gt;
&lt;h3 data-ke-size=&quot;size23&quot;&gt;etcd&lt;/h3&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;쿠버네티스는 기반 스토리지(backing storage)로 etcd를 사용하고 있고, 모든 데이터가 etcd에 보관된다. 예를 들어, &lt;b&gt;쿠버네티스 클러스터에 노드가 몇 개 있고, 어떤 Pod가 어떤 노드에서 동작하고 있는지 등이 etcd에 기록된다.&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;etcd는 위와 이러한 정보를 key-value 형태로 저장한다. 만약 etcd가 다운되게 되면 쿠버네티스 클러스터는 제대로 동작하지 못하게 되기 때문에 높은 신뢰성을 제공해야 한다.&lt;/p&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;etcd는 Replicated State Machine(RSM)이다.&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;RSM은 서버들을 복사하고 복제된 서버와 클라이언트 간의 상호적용을 조율하는 것으로 장애 허용 서비스를 구현하는 일반적인 방법이다. &lt;b&gt;RSM은 여러 노드에 동일한 상태 머신(State Machine)을 복제하여, 클라이언트 요청이 어떤 노드로 전달되든 동일한 결과를 얻게 한다. 이를 통해 시스템의 가용성과 내결함성을 높일 수 있다.&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;RSM은 command가 들어있는 log 단위로 데이터를 처리한다. 데이터의 &lt;code&gt;write&lt;/code&gt;를 &lt;code&gt;log append&lt;/code&gt;라고 부르며, Machine은 받은 log를 순서대로 처리한다. 하지만, 동일한 데이터를 여러 서버에 복제하기 때문에 발생하는 문제점 또한 존재한다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;안정적인 RSM을 구축하기 위해서는 데이터 복제 과정에서 발생할 수 있는 여러 가지 문제점을 해결하기 위해 컨센서스를 확보해야 한다. 컨센서스를 확보한다는 것은 RSM이 아래의 네 가지 속성을 만족한다는 것이다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;table data-ke-align=&quot;alignLeft&quot;&gt;
&lt;thead&gt;
&lt;tr&gt;
&lt;th&gt;속성&lt;/th&gt;
&lt;th&gt;설명&lt;/th&gt;
&lt;/tr&gt;
&lt;/thead&gt;
&lt;tbody&gt;
&lt;tr&gt;
&lt;td&gt;Safety&lt;/td&gt;
&lt;td&gt;항상 올바른 결과를 리턴해야 합니다.&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;Available&lt;/td&gt;
&lt;td&gt;서버가 몇 대 다운되더라도 항상 응답해야 합니다.&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;Independent from timing&lt;/td&gt;
&lt;td&gt;네트워크 지연이 발생해도 로그의 일관성이 깨져서는 안됩니다.&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;Reactivity&lt;/td&gt;
&lt;td&gt;모든 서버에 복제되지 않았더라도 조건을 만족하면 빠르게 요청에 응답해야 합니다.&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;&amp;nbsp;&lt;/td&gt;
&lt;td&gt;&amp;nbsp;&lt;/td&gt;
&lt;/tr&gt;
&lt;/tbody&gt;
&lt;/table&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;span style=&quot;color: #333333; text-align: start;&quot;&gt;쿠버네티스의 Control Plane Component인 etcd는 컨센서스를 확보하기 위해&lt;span&gt;&amp;nbsp;&lt;/span&gt;&lt;/span&gt;&lt;b&gt;Raft 알고리즘을 사용했다. &lt;/b&gt;&lt;/p&gt;
&lt;h3 data-ke-size=&quot;size23&quot;&gt;Raft Algorithm&lt;/h3&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;Raft 알고리즘은 합의 알고리즘의 일종으로 여러 노드가 네트워크 지연, 노드 실패 등의 상황에서도 일관된 결정을 내릴 수 있도록 한다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;Raft 알고리즘을 설명하기 전에 기본적인 용어 3가지를 알아야 한다.&lt;/p&gt;
&lt;h6&gt;Quorum&lt;/h6&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;Quorum이란 정족수, 라는 의미를 가지며 의사결정에 필요한 최소한의 서버 수를 의미한다. 예를 들어 RSM을 구성하는 서버의 숫자가 3대인 경우 Quorum 값은 2가 된다. etcd는 &lt;code&gt;write&lt;/code&gt; 요청을 받았을 때, Quorum 숫자만큼의 서버에 데이터 복제가 발생하면 작업이 완료된 것으로 간주하고 다음 작업을 받아들일 수 있는 상태가 된다.&lt;/p&gt;
&lt;h6&gt;State&lt;/h6&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;Etcd를 구성하는 서버는 State를 가지며 이는 Leader, Follower, Candidate 중 하나가 된다.&lt;/p&gt;
&lt;h6&gt;Timer&lt;/h6&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;etcd의 Leader 서버는 다른 모든 서버에게 Heartbeat를 주기적으로 전송하여, Leader가 존재함을 알린다. 만약 Leader가 아닌 서버들이 일정 시간(Election Timeout) 동안 Heartbeat를 받지 못하게 되면 Leader가 없어졌다고 간주하고 다음 리더를 선출한다.&lt;/p&gt;
&lt;h4 data-ke-size=&quot;size20&quot;&gt;Leader Election&lt;/h4&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;3개의 서버를 이용하여 etcd 클러스터를 구성했을 때, 각 서버는 Follower 상태이며 Term 값을 0으로 설정하고 동작을 시작한다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;최초로 초기화된 etcd 클러스터는 Leader가 존재하지 않기 때문에 누구도 Heartbeat를 보내지 않는다. 따라서 etcd 서버 중 한대에서 Election Timeout이 발생하게 되고, Timeout이 발생한 etcd 서버가 자신의 상태를 Candidate로 변경하고 Term을 1 증가 시킨다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;Candidate가 된 etcd 서버는 클러스터 내에 존재하는 다른 서버에 &lt;code&gt;RequestVote RPC call&lt;/code&gt;을 보낸다. &lt;code&gt;RequestVote RPC call&lt;/code&gt;을 받은 각 서버는 자신이 가진 Term 정보와 log를 비교하여 Candidate보다 자신의 것이 크다면 거절한다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;하지만 초기에는 Candidate의 Term 정보가 더 크기 때문에 해당 요청을 수락한다. Candidate 서버는 자기 자신을 포함하여 다른 서버로부터 받은 수락 응답이 Quorum과 동일하면 Leader로 선출된다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;Leader가 된 서버는 클러스터의 다른 서버에게 Heartbeat를 보내는데, 이 Append RPC call에는 Leader의 Term과 보유한 log index 정보가 들어있다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;Leader가 아닌 서버는 Append RPC call을 받았을 때 자신의 Term보다 높은 Term을 가진 서버의 call 인지 확인하고, 자신의 Term을 받은 Term 값으로 업데이트한다.&lt;/p&gt;
&lt;h4 data-ke-size=&quot;size20&quot;&gt;Log Replication&lt;/h4&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;각 서버는 자신이 가지고 있는 log의 lastIndex 값을 가지고 있으며 Leader는 여기에 추가로 Follower가 새로운 log를 써야 할 nextIndex를 알고 있다. 사용자로부터 log append 요청을 받은 Leader는 자신의 lastIndex 다음 위치에 로그를 기록하고 lastIndex 값을 증가시킨다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;그리고 다음 Heartbeat Interval이 도래하면 모든 서버에게 AppendEntry RPC call을 보내면서, Follower의 nextIndex에 해당하는 log를 함께 보낸다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;첫 번째 Follower(이하 F1)는 자신의 Entry(메모리)에 Leader로부터 받은 log를 기록했고 두 번째 Follower(이하 F2)는 아직 기록하지 못했다고 가정해보자.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;F1은 log를 잘 받았다는 응답을 Leader에게 보내주게 되고, Leader는 F1의 nextIndex를 업데이트한다. F2가 아직 응답을 주지 않았지만, 자기 자신을 포함해서 Quorum 숫자만큼의 Log Replication이 일어났기 때문에 commit을 수행한다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;commit이란, log entry에 먼저 기록된 데이터를 서버마다 가지고 있는 db(파일시스템)에 write 하는 것을 의미한다. 만약 commit된 데이터가 x = 1이라는 데이터일 경우, 사용자가 etcd 클러스터에서 x를 read하면 1이 return 된다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;Follower들은 Leader가 특정 로그를 commit한 사실을 알게 되면, 각 서버의 Entry에 보관 중이던 log를 commit 합니다.&lt;/p&gt;
&lt;h4 data-ke-size=&quot;size20&quot;&gt;Leader Down&lt;/h4&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;사용자의 write 요청에 따라 Quorum 숫자만큼 log의 복제와 commit이 완료된 이후, Leader가 다운된다면 etcd는 어떻게 동작할지 알아보자. Log Replication에서 다룬 상황 이후에 Leader 서버의 etcd 프로세스가 알 수 없는 이유로 다운되었다고 가정한다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;Follower들은 Leader로부터 Election Timeout 동안 Heartbeat를 받지 못하고, F2가 Timeout 됐다고 가정해보자. F2는 자신을 Candidate로 바꾸고 Term을 증가시킨 다음 RequestVote RPC call을 모든 서버에게 보낸다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;RequestVote RPC call에는 요청을 받은 Candidate가 Leader가 되어도 문제가 없는지 Follower가 판단할 수 있도록 Term과 index 정보가 들어있다. RequestVote를 받은 F1은 F2가 보내온 RequestVote RPC 데이터를 살펴본다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;일단 Term의 숫자는 하나 높긴 한데, F1이 가진 log가 F2보다 더 최신이기 때문에 F1는 F2의 RequestVote에 거절 응답을 보내고, F2는 leader로 선출되지 못한다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;이후 F1에서 다시 Election Timeout이 발생하면 F1은 F2와 Term이 같으며 더 최신의 log를 보유하고 있으므로 F2는 F1의 RequestVote RPC call에 OK 응답을 하고 F1은 새로운 Leader로 선출된다. F1이 Leader가 된 이후 보낸 첫 번째 Heartbeat(Append RPC)를 수신한 F2는 자신의 상태를 Follower로 변경한다&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;F1이 새로운 Leader가 된 상태에서 write 요청을 받게 되더라도 쿼럼 숫자만큼의 etcd 서버가 Running 중이므로 etcd 클러스터는 정상 동작한다.&lt;/p&gt;
&lt;h4 data-ke-size=&quot;size20&quot;&gt;Leader Back&lt;/h4&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;F1이 새로운 Leader가 된 상태에서 구 Leader가 복구되면, F1에게 Heartbeat를 보내거나 F1으로부터 Heartbeat를 받을 텐데 lastIndex 값이 더 작고 Term도 낮으므로 자기 자신을 follower로 변경하고 Term 숫자를 높은 쪽에 맞춘다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;여기까지가 대략적인 etcd에 대한 기본 개념과 etcd에 적용된 Raft 알고리즘에 대한 설명이다. 다음 포스트에서는 &lt;span style=&quot;background-color: #ffffff; color: #374151; text-align: start;&quot;&gt;etcd 클러스터가 동작 중인 가운데 etcd 서버를 추가/삭제 발생할 때 어떤 식으로 Raft 알고리즘이 적용되는지 알아보자.&lt;/span&gt;&lt;/p&gt;</description>
      <category>Infra</category>
      <category>control plane</category>
      <category>ETCD</category>
      <category>Infra</category>
      <category>k8s</category>
      <category>kubernetes</category>
      <category>쿠버네티스</category>
      <author>겨울바람_</author>
      <guid isPermaLink="true">https://breeze-winter.tistory.com/22</guid>
      <comments>https://breeze-winter.tistory.com/22#entry22comment</comments>
      <pubDate>Wed, 10 Jul 2024 22:58:00 +0900</pubDate>
    </item>
    <item>
      <title>[K8s] k8s Pod 생성 시 벌어지는 일</title>
      <link>https://breeze-winter.tistory.com/21</link>
      <description>&lt;h3 data-ke-size=&quot;size23&quot;&gt;Pod&lt;/h3&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;K8s의 Pod는 쿠버네티스에서 생성하고 관리할 수 있는 배포 가능한 가장 작은 컴퓨팅 단위이다. 하나 이상의 &lt;a href=&quot;https://kubernetes.io/ko/docs/concepts/containers/&quot;&gt;컨테이너&lt;/a&gt;의 그룹이며, 이 그룹은 스토리지 및 네트워크를 공유하고, 해당 컨테이너를 구동하는 방식에 대한 명세를 갖는다. 파드의 콘텐츠는 항상 함께 배치되고, 함께 스케줄되며, 공유 콘텍스트에서 실행된다.&lt;/p&gt;
&lt;p&gt;&lt;figure class=&quot;imageblock alignCenter&quot; data-ke-mobileStyle=&quot;widthOrigin&quot; data-origin-width=&quot;277&quot; data-origin-height=&quot;295&quot;&gt;&lt;span data-url=&quot;https://blog.kakaocdn.net/dn/bIs6uf/btsIrrVMKYj/2fkPWJRlvNAcmKxMDaHQPK/img.png&quot; data-phocus=&quot;https://blog.kakaocdn.net/dn/bIs6uf/btsIrrVMKYj/2fkPWJRlvNAcmKxMDaHQPK/img.png&quot;&gt;&lt;img src=&quot;https://blog.kakaocdn.net/dn/bIs6uf/btsIrrVMKYj/2fkPWJRlvNAcmKxMDaHQPK/img.png&quot; srcset=&quot;https://img1.daumcdn.net/thumb/R1280x0/?scode=mtistory2&amp;fname=https%3A%2F%2Fblog.kakaocdn.net%2Fdn%2FbIs6uf%2FbtsIrrVMKYj%2F2fkPWJRlvNAcmKxMDaHQPK%2Fimg.png&quot; onerror=&quot;this.onerror=null; this.src='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png'; this.srcset='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png';&quot; loading=&quot;lazy&quot; width=&quot;262&quot; height=&quot;279&quot; data-origin-width=&quot;277&quot; data-origin-height=&quot;295&quot;/&gt;&lt;/span&gt;&lt;/figure&gt;
&lt;/p&gt;
&lt;h3 data-ke-size=&quot;size23&quot;&gt;ReplicaSet&lt;/h3&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;yaml 파일 혹은 &lt;code&gt;kubectl run&lt;/code&gt; 명령어를 통해 Pod를 생성할 수 있다. 하지만 Pod만 생성할 경우 Pod에 문제가 생겨 해당 Pod가 다운되거나, 종료되었을 때 쿠버네티스가 해당 Pod를 다시 생성할 수 없다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;Pod에 문제가 생겨 종료되었을 때, 해당 Pod를 복구할 수 있는 쿠버네티스의 기능을 Self-Healing 이라고 한다. Self-Healing 기능을 위해서는 ReplicaSet이 필요하다.&lt;/p&gt;
&lt;p&gt;&lt;figure class=&quot;imageblock alignCenter&quot; data-ke-mobileStyle=&quot;widthOrigin&quot; data-origin-width=&quot;277&quot; data-origin-height=&quot;484&quot;&gt;&lt;span data-url=&quot;https://blog.kakaocdn.net/dn/oCSbu/btsIrLNerHi/7DN10qJOM1dTNk2daCgEjk/img.png&quot; data-phocus=&quot;https://blog.kakaocdn.net/dn/oCSbu/btsIrLNerHi/7DN10qJOM1dTNk2daCgEjk/img.png&quot;&gt;&lt;img src=&quot;https://blog.kakaocdn.net/dn/oCSbu/btsIrLNerHi/7DN10qJOM1dTNk2daCgEjk/img.png&quot; srcset=&quot;https://img1.daumcdn.net/thumb/R1280x0/?scode=mtistory2&amp;fname=https%3A%2F%2Fblog.kakaocdn.net%2Fdn%2FoCSbu%2FbtsIrLNerHi%2F7DN10qJOM1dTNk2daCgEjk%2Fimg.png&quot; onerror=&quot;this.onerror=null; this.src='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png'; this.srcset='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png';&quot; loading=&quot;lazy&quot; width=&quot;196&quot; height=&quot;342&quot; data-origin-width=&quot;277&quot; data-origin-height=&quot;484&quot;/&gt;&lt;/span&gt;&lt;/figure&gt;
&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;br /&gt;ReplicaSet은 주기적으로 Pod를 감시하며, 사용자가 설정한 수만큼 Pod가 실행되고 있는지를 확인한다.&lt;/p&gt;
&lt;h3 data-ke-size=&quot;size23&quot;&gt;Deployment&lt;/h3&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;Deployment는 ReplicaSet을 관리하는 controller다. 또한, ReplicaSet에서는 지원하지 않았던 배포 관한 전까지도 지원하며, 문제가 생겼을 때 롤백을 어떤 식으로 진행할지에 대한 설정도 가능하다.&lt;/p&gt;
&lt;p&gt;&lt;figure class=&quot;imageblock alignCenter&quot; data-ke-mobileStyle=&quot;widthOrigin&quot; data-origin-width=&quot;277&quot; data-origin-height=&quot;672&quot;&gt;&lt;span data-url=&quot;https://blog.kakaocdn.net/dn/cjeqsJ/btsIswB9jZA/cGlsF1egTVqJqP0Kz8xBA0/img.png&quot; data-phocus=&quot;https://blog.kakaocdn.net/dn/cjeqsJ/btsIswB9jZA/cGlsF1egTVqJqP0Kz8xBA0/img.png&quot;&gt;&lt;img src=&quot;https://blog.kakaocdn.net/dn/cjeqsJ/btsIswB9jZA/cGlsF1egTVqJqP0Kz8xBA0/img.png&quot; srcset=&quot;https://img1.daumcdn.net/thumb/R1280x0/?scode=mtistory2&amp;fname=https%3A%2F%2Fblog.kakaocdn.net%2Fdn%2FcjeqsJ%2FbtsIswB9jZA%2FcGlsF1egTVqJqP0Kz8xBA0%2Fimg.png&quot; onerror=&quot;this.onerror=null; this.src='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png'; this.srcset='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png';&quot; loading=&quot;lazy&quot; width=&quot;185&quot; height=&quot;449&quot; data-origin-width=&quot;277&quot; data-origin-height=&quot;672&quot;/&gt;&lt;/span&gt;&lt;/figure&gt;
&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;Deployment를 사용하면 더욱 안정적으로 Pod들의 운영이 가능하기 때문에 Pod를 생성할 때 Pod만 생성하는 것이 아니라 Deployment 단위로 생성하게 된다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;Pod가 생성되는 순서를 &lt;code&gt;kubectl get events&lt;/code&gt; 명령어로 살펴보면, Deployment가 먼저 생성되고 Deployment에 의해 ReplicaSet이 만들어진다. 최종적으로 ReplicaSet 설정된 사용자의 설정에 따라 Pod가 생성된다.&lt;/p&gt;
&lt;h3 data-ke-size=&quot;size23&quot;&gt;k8s Cluster 내부&lt;/h3&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;쿠버네티스 클러스터 내부는 각각 마스터 노드와 워커 노드로 분리되어 있다.&lt;/p&gt;
&lt;p&gt;&lt;figure class=&quot;imageblock alignCenter&quot; data-ke-mobileStyle=&quot;widthOrigin&quot; data-origin-width=&quot;825&quot; data-origin-height=&quot;397&quot;&gt;&lt;span data-url=&quot;https://blog.kakaocdn.net/dn/bBzoEJ/btsIreiarrL/eNhsTpC9sOv8tkkI2sxukk/img.png&quot; data-phocus=&quot;https://blog.kakaocdn.net/dn/bBzoEJ/btsIreiarrL/eNhsTpC9sOv8tkkI2sxukk/img.png&quot;&gt;&lt;img src=&quot;https://blog.kakaocdn.net/dn/bBzoEJ/btsIreiarrL/eNhsTpC9sOv8tkkI2sxukk/img.png&quot; srcset=&quot;https://img1.daumcdn.net/thumb/R1280x0/?scode=mtistory2&amp;fname=https%3A%2F%2Fblog.kakaocdn.net%2Fdn%2FbBzoEJ%2FbtsIreiarrL%2FeNhsTpC9sOv8tkkI2sxukk%2Fimg.png&quot; onerror=&quot;this.onerror=null; this.src='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png'; this.srcset='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png';&quot; loading=&quot;lazy&quot; width=&quot;825&quot; height=&quot;397&quot; data-origin-width=&quot;825&quot; data-origin-height=&quot;397&quot;/&gt;&lt;/span&gt;&lt;/figure&gt;
&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;마스터 노드 내부에는 API Server, Scheduler, Controller Manager, etcd 와 같은 컴포넌트들이 존재한다. 워커 노드 내부에는 kubelet, kubeProxy 가 존재하며, &lt;code&gt;kubectl&lt;/code&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;code&gt;watch&lt;/code&gt; 라는 매커니즘을 가지고 있다. 예를 들어, API Server는 etcd를 지속적으로 watch 하며, API Server에 변경사항이 발생할 경우 해당 변경사항이 etcd로 업데이트 된다. etcd 는 API 변경사항을 새롭게 저장하게 되고, 그에 따라 etcd 에도 변경사항이 발생하게 된다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;etcd에 변경사항이 발생하게 될 경우, etcd를 &lt;code&gt;watch&lt;/code&gt;하고 있는 API Server에 &lt;code&gt;notify&lt;/code&gt;를 보내게 되고, API Server 또한 API Server를 &lt;code&gt;watch&lt;/code&gt; 하고 있는 Components에 &lt;code&gt;notify&lt;/code&gt;를 보내게 된다.&lt;/p&gt;
&lt;p&gt;&lt;figure class=&quot;imageblock alignCenter&quot; data-ke-mobileStyle=&quot;widthOrigin&quot; data-origin-width=&quot;709&quot; data-origin-height=&quot;541&quot;&gt;&lt;span data-url=&quot;https://blog.kakaocdn.net/dn/wsG6y/btsIqQ2Oxus/0emySX8zalSqkICALekVR0/img.png&quot; data-phocus=&quot;https://blog.kakaocdn.net/dn/wsG6y/btsIqQ2Oxus/0emySX8zalSqkICALekVR0/img.png&quot;&gt;&lt;img src=&quot;https://blog.kakaocdn.net/dn/wsG6y/btsIqQ2Oxus/0emySX8zalSqkICALekVR0/img.png&quot; srcset=&quot;https://img1.daumcdn.net/thumb/R1280x0/?scode=mtistory2&amp;fname=https%3A%2F%2Fblog.kakaocdn.net%2Fdn%2FwsG6y%2FbtsIqQ2Oxus%2F0emySX8zalSqkICALekVR0%2Fimg.png&quot; onerror=&quot;this.onerror=null; this.src='//t1.daumcdn.net/tistory_admin/static/images/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;372&quot; data-origin-width=&quot;709&quot; data-origin-height=&quot;541&quot;/&gt;&lt;/span&gt;&lt;/figure&gt;
&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;위와 같은 구조를 쿠버네티스 클러스터에 실제로 적용시키게 되면, 다음과 같은 흐름을 볼 수 있다.&lt;/p&gt;
&lt;p&gt;&lt;figure class=&quot;imageblock alignCenter&quot; data-ke-mobileStyle=&quot;widthOrigin&quot; data-origin-width=&quot;879&quot; data-origin-height=&quot;490&quot;&gt;&lt;span data-url=&quot;https://blog.kakaocdn.net/dn/l9e71/btsIs4kNEPh/TrACKwb9r9l1mMdsNpULtK/img.png&quot; data-phocus=&quot;https://blog.kakaocdn.net/dn/l9e71/btsIs4kNEPh/TrACKwb9r9l1mMdsNpULtK/img.png&quot;&gt;&lt;img src=&quot;https://blog.kakaocdn.net/dn/l9e71/btsIs4kNEPh/TrACKwb9r9l1mMdsNpULtK/img.png&quot; srcset=&quot;https://img1.daumcdn.net/thumb/R1280x0/?scode=mtistory2&amp;fname=https%3A%2F%2Fblog.kakaocdn.net%2Fdn%2Fl9e71%2FbtsIs4kNEPh%2FTrACKwb9r9l1mMdsNpULtK%2Fimg.png&quot; onerror=&quot;this.onerror=null; this.src='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png'; this.srcset='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png';&quot; loading=&quot;lazy&quot; width=&quot;879&quot; height=&quot;490&quot; data-origin-width=&quot;879&quot; data-origin-height=&quot;490&quot;/&gt;&lt;/span&gt;&lt;/figure&gt;
&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;Components 들은 API Server를 &lt;code&gt;watch&lt;/code&gt;하고 있고, API Server는 etcd를 &lt;code&gt;watch&lt;/code&gt;하고 있다. &lt;code&gt;kubectl run&lt;/code&gt; 명령어를 통해 Deployment 단위로 Pod를 생성하게 되면 결과적으로 아래와 같은 흐름에 따라 Pod가 생성된다.&lt;/p&gt;
&lt;p&gt;&lt;figure class=&quot;imageblock alignCenter&quot; data-ke-mobileStyle=&quot;widthOrigin&quot; data-origin-width=&quot;979&quot; data-origin-height=&quot;580&quot;&gt;&lt;span data-url=&quot;https://blog.kakaocdn.net/dn/vEwf3/btsIsKz8OhX/QFuKOiWsRBD9beBVRMAGL1/img.png&quot; data-phocus=&quot;https://blog.kakaocdn.net/dn/vEwf3/btsIsKz8OhX/QFuKOiWsRBD9beBVRMAGL1/img.png&quot;&gt;&lt;img src=&quot;https://blog.kakaocdn.net/dn/vEwf3/btsIsKz8OhX/QFuKOiWsRBD9beBVRMAGL1/img.png&quot; srcset=&quot;https://img1.daumcdn.net/thumb/R1280x0/?scode=mtistory2&amp;fname=https%3A%2F%2Fblog.kakaocdn.net%2Fdn%2FvEwf3%2FbtsIsKz8OhX%2FQFuKOiWsRBD9beBVRMAGL1%2Fimg.png&quot; onerror=&quot;this.onerror=null; this.src='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png'; this.srcset='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png';&quot; loading=&quot;lazy&quot; width=&quot;979&quot; height=&quot;580&quot; data-origin-width=&quot;979&quot; data-origin-height=&quot;580&quot;/&gt;&lt;/span&gt;&lt;/figure&gt;
&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;br /&gt;꽤 복잡해보이지만, 흐름을 정리하면 다음과 같다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;code&gt;kubectl run&lt;/code&gt; 명령 실행시 Deployment Resource에 변화가 생기고 해당 Resource 를 &lt;code&gt;watch&lt;/code&gt; 하고 있던 Controller 에 &lt;code&gt;notify&lt;/code&gt;가 응답으로 보내지게 된다. 해당 &lt;code&gt;notify&lt;/code&gt;에 따라 Controller는 Resource에 Resource 생성을 위한 요청을 Controller에 보내게 된다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;이 과정이 반복되고 최종적으로 Pod Resource에서 보내진 &lt;code&gt;notify&lt;/code&gt;가 Scheduler로 보내지고, 해당 Scheduler가 워커 노드에 Pod를 생성하게 된다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;워커 노드에 Pod가 생성되는 과정까지 상세히 살펴보자. 우선 Scheduler는 API Server에 수집되고 있는 워커 노드에 대한 자원 사용량과 &lt;code&gt;Affinity&lt;/code&gt;, &lt;code&gt;Taint&lt;/code&gt; 등의 정보를 통해 Pod를 생성하기에 최적인 워커 노드를 선발한다.&lt;/p&gt;
&lt;p&gt;&lt;figure class=&quot;imageblock alignCenter&quot; data-ke-mobileStyle=&quot;widthOrigin&quot; data-origin-width=&quot;739&quot; data-origin-height=&quot;670&quot;&gt;&lt;span data-url=&quot;https://blog.kakaocdn.net/dn/I4tqY/btsIscjyxAc/HPEqtELEi0EfSdWjz912rK/img.png&quot; data-phocus=&quot;https://blog.kakaocdn.net/dn/I4tqY/btsIscjyxAc/HPEqtELEi0EfSdWjz912rK/img.png&quot;&gt;&lt;img src=&quot;https://blog.kakaocdn.net/dn/I4tqY/btsIscjyxAc/HPEqtELEi0EfSdWjz912rK/img.png&quot; srcset=&quot;https://img1.daumcdn.net/thumb/R1280x0/?scode=mtistory2&amp;fname=https%3A%2F%2Fblog.kakaocdn.net%2Fdn%2FI4tqY%2FbtsIscjyxAc%2FHPEqtELEi0EfSdWjz912rK%2Fimg.png&quot; onerror=&quot;this.onerror=null; this.src='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png'; this.srcset='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png';&quot; loading=&quot;lazy&quot; width=&quot;553&quot; height=&quot;501&quot; data-origin-width=&quot;739&quot; data-origin-height=&quot;670&quot;/&gt;&lt;/span&gt;&lt;/figure&gt;
&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;최종적으로는 Pod Resource에 변경사항이 발생하고 해당 변경사항을 워커 노드의 kubelet이 감지하여 Pod를 생성한다.&lt;/p&gt;
&lt;p&gt;&lt;figure class=&quot;imageblock alignCenter&quot; data-ke-mobileStyle=&quot;widthOrigin&quot; data-origin-width=&quot;1338&quot; data-origin-height=&quot;579&quot;&gt;&lt;span data-url=&quot;https://blog.kakaocdn.net/dn/cFOrC6/btsIti4ezti/pcEHm15z6754F4Y87C6If0/img.png&quot; data-phocus=&quot;https://blog.kakaocdn.net/dn/cFOrC6/btsIti4ezti/pcEHm15z6754F4Y87C6If0/img.png&quot;&gt;&lt;img src=&quot;https://blog.kakaocdn.net/dn/cFOrC6/btsIti4ezti/pcEHm15z6754F4Y87C6If0/img.png&quot; srcset=&quot;https://img1.daumcdn.net/thumb/R1280x0/?scode=mtistory2&amp;fname=https%3A%2F%2Fblog.kakaocdn.net%2Fdn%2FcFOrC6%2FbtsIti4ezti%2FpcEHm15z6754F4Y87C6If0%2Fimg.png&quot; onerror=&quot;this.onerror=null; this.src='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png'; this.srcset='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png';&quot; loading=&quot;lazy&quot; width=&quot;1338&quot; height=&quot;579&quot; data-origin-width=&quot;1338&quot; data-origin-height=&quot;579&quot;/&gt;&lt;/span&gt;&lt;/figure&gt;
&lt;/p&gt;</description>
      <category>Infra</category>
      <category>deployment</category>
      <category>k8s</category>
      <category>kubernetes</category>
      <category>POD</category>
      <category>replicaset</category>
      <category>쿠버네티스</category>
      <category>파드</category>
      <author>겨울바람_</author>
      <guid isPermaLink="true">https://breeze-winter.tistory.com/21</guid>
      <comments>https://breeze-winter.tistory.com/21#entry21comment</comments>
      <pubDate>Tue, 9 Jul 2024 13:41:09 +0900</pubDate>
    </item>
    <item>
      <title>Traefik 이란?</title>
      <link>https://breeze-winter.tistory.com/20</link>
      <description>&lt;h3 data-ke-size=&quot;size23&quot;&gt;Traefik 이란?&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;877&quot;&gt;&lt;span data-url=&quot;https://blog.kakaocdn.net/dn/b9Bl7u/btsHGkcb8hH/SzorAKwW7kjkUbTlg36VZk/img.png&quot; data-phocus=&quot;https://blog.kakaocdn.net/dn/b9Bl7u/btsHGkcb8hH/SzorAKwW7kjkUbTlg36VZk/img.png&quot;&gt;&lt;img src=&quot;https://blog.kakaocdn.net/dn/b9Bl7u/btsHGkcb8hH/SzorAKwW7kjkUbTlg36VZk/img.png&quot; srcset=&quot;https://img1.daumcdn.net/thumb/R1280x0/?scode=mtistory2&amp;fname=https%3A%2F%2Fblog.kakaocdn.net%2Fdn%2Fb9Bl7u%2FbtsHGkcb8hH%2FSzorAKwW7kjkUbTlg36VZk%2Fimg.png&quot; onerror=&quot;this.onerror=null; this.src='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png'; this.srcset='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png';&quot; loading=&quot;lazy&quot; width=&quot;1280&quot; height=&quot;877&quot; data-origin-width=&quot;1280&quot; data-origin-height=&quot;877&quot;/&gt;&lt;/span&gt;&lt;/figure&gt;
&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;Traefik은 Nginx, Apache와 같이 웹 서버 앞에서 동작하며 클라이언트로부터의 요청을 웹 서버에 전달하는 역할을 수행하는 Reverse Proxy Server의 일종이다.&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;오픈소스 Edge Router라는 명칭으로도 불리며, config 파일을 수정해야 하는 번거로움을 가진 Nginx, Apache 등의 다른 툴과 달리 서비스를 배포할 때 서비스가 처리할 수 있는 요청의 특성을 Traefik에 알리는 정보를 첨부하는 것으로, 서비스가 배포될 때 실시간으로 Traefik의 라우팅 규칙을 업데이트 할 수 있다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;실제로 Docker 환경에서 사용할 때, docker.sock, label 속성을 통해 적절히 값을 설정해주면 서비스 컨테이너와 Traefik이 실시간으로 연결되는 것을 확인할 수 있다. Traefik 또한 Docker Container를 통한 손쉬운 배포가 가능하다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;현재 필자의 개인 서버 또한, Traefik을 통해 Docker Container로 배포된 서비스들을 관리하고 있다.&amp;nbsp;&lt;/p&gt;
&lt;h3 data-ke-size=&quot;size23&quot;&gt;Traefik Concept&lt;/h3&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;Traefik은 다음과 같은 구성 요소들로 이루어져있다.&lt;/p&gt;
&lt;h4 data-ke-size=&quot;size20&quot;&gt;EntryPoint&lt;/h4&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;b&gt;엔트리포인트는 Traefik으로 들어오는 외부 요청의 진입점으로, 패킷을 어떤 포트로 받을지 정의하고 통신 방식을 정의할 수 있다.&lt;/b&gt;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;span style=&quot;background-color: #ffffff; color: #000000; text-align: justify;&quot;&gt;EntryPoints are the network entry points into Traefik. They define the port which will receive the packets, and whether to listen for TCP or UDP.&amp;nbsp;&lt;/span&gt;&lt;/p&gt;
&lt;h4 data-ke-size=&quot;size20&quot;&gt;Router&lt;/h4&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;b&gt;라우터는 엔트리포인트를 통해 Traefik으로 들어온 요청을 서비스로 전달하는 역할을 한다.&amp;nbsp;&lt;/b&gt;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;span style=&quot;background-color: #ffffff; color: #000000; text-align: justify;&quot;&gt;A router is in charge of connecting incoming requests to the services that can handle them.&lt;/span&gt;&lt;/p&gt;
&lt;h4 style=&quot;text-align: justify;&quot; data-ke-size=&quot;size20&quot;&gt;&lt;span style=&quot;background-color: #ffffff;&quot;&gt;Middleware&lt;/span&gt;&lt;/h4&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;b&gt;&lt;span style=&quot;background-color: #ffffff;&quot;&gt;라우터와 함께 동작하며, 서비스로 요청 혹은 응답이 도착하기 전에 그것들을 수정할 수 있다. 예를 들어, http 요청을 https 요청으로 변환시키는 역할을 수행하는 역할을 담당할 수 있다.&lt;/span&gt;&lt;/b&gt;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;span style=&quot;background-color: #ffffff; color: #000000; text-align: justify;&quot;&gt;Attached to the routers, middlewares can modify the requests or responses before they are sent to your service&lt;/span&gt;&lt;/p&gt;
&lt;h4 data-ke-size=&quot;size20&quot;&gt;Service&lt;/h4&gt;
&lt;div&gt;
&lt;div&gt;
&lt;div&gt;
&lt;div&gt;
&lt;div&gt;
&lt;div data-scroll-anchor=&quot;true&quot; data-testid=&quot;conversation-turn-3&quot;&gt;
&lt;div&gt;
&lt;div&gt;
&lt;div&gt;
&lt;div&gt;
&lt;div&gt;
&lt;div data-message-id=&quot;83623f97-f6ac-478e-851d-0e866c7eacc8&quot; data-message-author-role=&quot;assistant&quot;&gt;
&lt;div&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;b&gt;서비스는 들어오는 요청을 처리할 실제 서비스에 어떻게 도달하게 할 것인지를 구성한다.&lt;/b&gt;&lt;/p&gt;
&lt;/div&gt;
&lt;/div&gt;
&lt;/div&gt;
&lt;/div&gt;
&lt;/div&gt;
&lt;/div&gt;
&lt;/div&gt;
&lt;/div&gt;
&lt;/div&gt;
&lt;/div&gt;
&lt;/div&gt;
&lt;/div&gt;
&lt;/div&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;span style=&quot;background-color: #ffffff; color: #000000; text-align: justify;&quot;&gt;Services are responsible for configuring how to reach the actual services that will eventually handle the incoming requests.&lt;/span&gt;&lt;/p&gt;
&lt;h2 id=&quot;auto-service-discovery&quot; style=&quot;background-color: #ffffff; color: #000000; text-align: start;&quot; data-ke-size=&quot;size26&quot;&gt;Auto Service Discovery&lt;/h2&gt;
&lt;p&gt;&lt;figure class=&quot;imageblock alignCenter&quot; data-ke-mobileStyle=&quot;widthOrigin&quot; data-origin-width=&quot;2305&quot; data-origin-height=&quot;1501&quot;&gt;&lt;span data-url=&quot;https://blog.kakaocdn.net/dn/orYsV/btsHHOwiTPD/n0lbxnKzaFrlmuloVIGZw0/img.webp&quot; data-phocus=&quot;https://blog.kakaocdn.net/dn/orYsV/btsHHOwiTPD/n0lbxnKzaFrlmuloVIGZw0/img.webp&quot;&gt;&lt;img src=&quot;https://blog.kakaocdn.net/dn/orYsV/btsHHOwiTPD/n0lbxnKzaFrlmuloVIGZw0/img.webp&quot; srcset=&quot;https://img1.daumcdn.net/thumb/R1280x0/?scode=mtistory2&amp;fname=https%3A%2F%2Fblog.kakaocdn.net%2Fdn%2ForYsV%2FbtsHHOwiTPD%2Fn0lbxnKzaFrlmuloVIGZw0%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;2305&quot; height=&quot;1501&quot; data-origin-width=&quot;2305&quot; data-origin-height=&quot;1501&quot;/&gt;&lt;/span&gt;&lt;/figure&gt;
&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;위에서 설명했듯이 Traefik은 전통적인 Reverse Proxy 도구인 Nginx 혹은 Apache 처럼 매번 서비스를 등록하기 위해서 config 파일을 수정해야 하는 번거로움이 없다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;이는 서비스가 배포됐을 때, Traefik이 배포된 서비스를 즉각 감지하고 라우팅 규칙을 실시간으로 업데이트 한다는 것을 의미한다. 비슷하게 서비스의 배포가 중단됐을 때, 해당하는 라우팅 규칙 또한 따라서 삭제된다.&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;즉, Traefik을 사용하는 개발자는 더 이상 배포되는 서비스와 IP주소와 라우팅 규칙 등을 config 파일에 작성하고 동기화 할 필요가 없는 것이다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;Traefik은 이와 같은 Auto Service Discovery를 구현하기 위해 Provider에게 API를 호출하여 E&lt;span style=&quot;background-color: #ffffff; color: #212529; text-align: start;&quot;&gt;ntry Point, Router, Service에 대한 정보를 받아온다. 이때 Provider는 오케스트레이터, 컨테이너 엔진, 클라우드 제공업체 또는 키-값 저장소와 같은 인프라 구성 요소가 될 수 있다. 대표적으로는 Docker, K8s 등이 해당된다.&lt;/span&gt;&lt;/p&gt;
&lt;h3 data-ke-size=&quot;size23&quot;&gt;Traefik with Docker Provider&lt;/h3&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;필자의 개인 서버는 언급했듯이 Docker를 Provider로 하여 Traefik을 운영 중이기 때문에 Docker 환경에서의 Traefik 설정 방법에 대해 이야기 해보고자 한다.&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;우선 Traefik의 설정이 어떤 방식으로 구성되는지 알아보자.&lt;/p&gt;
&lt;h4 data-ke-size=&quot;size20&quot;&gt;Static Configuration&lt;/h4&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;정적 설정은 설정 파일 작성, CLI 인자 작성, 환경 변수 설정 세 가지 방법 중 하나를 선택하여 Traefik에 대한 설정을 정의할 수 있다. 옵션에 대한 값을 정의하지 않을 경우 기본 값으로 해당 값이 설정된다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;Nginx와 Apache 등에서 이루어지는 설정 방식과 유사하며, Traefik의 설정 파일 작성 경로는 다음과 같다.&lt;/p&gt;
&lt;ul style=&quot;list-style-type: disc; background-color: #ffffff; color: #000000; text-align: start;&quot; data-ke-list-type=&quot;disc&quot;&gt;
&lt;li&gt;/etc/traefik/&lt;/li&gt;
&lt;li&gt;$XDG_CONFIG_HOME/&lt;/li&gt;
&lt;li&gt;$HOME/.config/&lt;/li&gt;
&lt;li&gt;.&lt;span&gt;&amp;nbsp;&lt;/span&gt;(the working directory)&lt;/li&gt;
&lt;/ul&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;Traefik은 실행 시, 해당 경로에 위치한 traefik.yml&lt;span style=&quot;background-color: #ffffff; color: #000000; text-align: justify;&quot;&gt;&lt;span&gt; 혹은&lt;/span&gt;&lt;span&gt;&amp;nbsp;&lt;/span&gt;&lt;/span&gt;traefik.yaml,&lt;span style=&quot;background-color: #ffffff; color: #000000; text-align: justify;&quot;&gt;&lt;span&gt;&amp;nbsp;&lt;/span&gt;&lt;/span&gt;traefik.toml 을 찾아 해당 설정 파일의 내용을 반영하여 실행된다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size18&quot;&gt;Dynamic Configuration&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;Docker를 Provider로 사용할 경우 동적 설정 방식은 labels를 사용하여 라우터, 서비스, 엔트리 포인트, 로드밸런서, 미들웨어 등의 설정을 정의할 수 있다.&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;예를 들어, Traefik의 관리 하에 두고싶은 서비스의 도커 컴포즈 파일을 다음과 같이 작성할 경우&lt;/p&gt;
&lt;pre id=&quot;code_1717044791102&quot; class=&quot;bash&quot; data-ke-language=&quot;bash&quot; data-ke-type=&quot;codeblock&quot;&gt;&lt;code&gt;  whoami1:
    image: traefik/whoami
    container_name: whoami1
    labels:
      - traefik.http.routers.whoami1.rule=Host(`example.com`) &amp;amp;&amp;amp;
        PathPrefix(`/1`)
      - traefik.http.routers.whoami1.entrypoints=websecure
      - traefik.http.routers.whoami1.tls.certresolver=myresolver
    networks:
      - proxy&lt;/code&gt;&lt;/pre&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;자동으로 엔트리 포인트를 연결해주고, Host 도메인과 PathPrefix 경로에 해당하는 라우터와 해당 라우터에 연결된 서비스를 생성하여 실제 실행되는 whoami 서비스와 연결시킨다.&lt;/p&gt;
&lt;h4 data-ke-size=&quot;size20&quot;&gt;Docker-Compose.yaml&lt;/h4&gt;
&lt;pre id=&quot;code_1717045090410&quot; class=&quot;bash&quot; data-ke-language=&quot;bash&quot; data-ke-type=&quot;codeblock&quot;&gt;&lt;code&gt;version: &quot;3.8&quot;
networks:
  proxy:
    name: proxy
services:
  traefik:
    image: traefik:v2.9
    container_name: traefik
    command:
      - --api.insecure=false
      - --api.dashboard=true
      - --providers.docker=true
      - --entrypoints.web.address=:80
      - --entrypoints.websecure.address=:443
      - --certificatesresolvers.myresolver.acme.httpchallenge=true
      - --certificatesresolvers.myresolver.acme.httpchallenge.entrypoint=web
      - --certificatesresolvers.myresolver.acme.email=dev_in_wonderland@naver.com
      - --certificatesresolvers.myresolver.acme.storage=/letsencrypt/acme.json
    labels:
      - traefik.enable=true
      - traefik.docker.network=proxy
      - traefik.http.routers.traefik.rule=Host(`web.dev-in-wonderland.pro`) &amp;amp;&amp;amp;
        (PathPrefix(`/api`) || PathPrefix(`/dashboard`))
      - traefik.http.routers.traefik.entrypoints=websecure
      - traefik.http.routers.traefik.tls.certresolver=myresolver
      - traefik.http.routers.traefik.service=api@internal
      - traefik.http.middlewares.redirect-to-https.redirectscheme.scheme=https
      - traefik.http.routers.redirs.rule=hostregexp(`{host:.+}`)
      - traefik.http.routers.redirs.entrypoints=web
      - traefik.http.routers.redirs.middlewares=redirect-to-https
      - traefik.http.routers.traefik.middlewares=auth
      - traefik.http.middlewares.auth.basicauth.users=[사용자]:[비밀번호]
    ports:
      - 80:80
      - 443:443
      - 8080:8080
    volumes:
      - /var/run/docker.sock:/var/run/docker.sock:ro
      - /home/cheshire-cat/traefik:/etc/traefik/
      - /home/cheshire-cat/letsencrypt:/letsencrypt
    networks:
      - proxy
  whoami1:
    image: traefik/whoami
    container_name: whoami1
    labels:
      - traefik.http.routers.whoami1.rule=Host(`web.dev-in-wonderland.pro`) &amp;amp;&amp;amp;
        PathPrefix(`/1`)
      - traefik.http.routers.whoami1.entrypoints=websecure
      - traefik.http.routers.whoami1.tls.certresolver=myresolver
    networks:
      - proxy&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;Traefik의 구성 요소인 EntryPoint, Router, Service, Middleware가 모두 잘 정의된 것을 확인할 수 있다. 주의해야 할 점은 Traefik의 관리하에 운영하고 싶은 서비스의 경우 Traefik과 동일한 네트워크를 사용해야 한다.&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;traefik에서 정의된 EntryPoint는 web(80), websecure(443) 두 개다. 아래의 whoami1 서비스의 경우 labels 속성에 동적으로 설정된&amp;nbsp; &lt;b&gt;traefik.http.routers.whoami1.entrypoints=websecure&lt;/b&gt; 을 보면, traefik에서 정의한 EntryPoint를 진입점으로 가지는 것을 확인할 수 있다.&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;traefik.http.routers.whoami1.rule=Host(`web.dev-in-wonderland.pro`) &amp;amp;&amp;amp; PathPrefix(`/1`)&amp;nbsp;&lt;/b&gt;Routing Rule 부분을 살펴보면 Host가 web.dev-in-wonderland.pro 서브 도메인으로 잡혀있고, 경로는 1로 지정되어 있다.&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;결과적으로 `web.dev-in-wonderland.pro:443/1` 경로로 들어오는 요청을 whoami1 서비스로 전달한다는 의미다.&amp;nbsp;&lt;/p&gt;
&lt;h3 data-ke-size=&quot;size23&quot;&gt;마무리&amp;nbsp;&lt;/h3&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;Traefik은 Reverse Proxy를 통해 구성할 수 있는 다양한 기능들을 굉장히 손쉽게 서비스에 적용할 수 있도록 해주는 굉장히 편리한 도구다. 해당 포스팅에서는 추가적으로 설명하지는 않았지만 로드 밸런서 기능 또한 동적 설정으로 손쉽게 적용할 수 있으며, HTTPS 사용을 위한 TLS 인증서 발급 또한 쉽게 가능하다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;현재 개인 서버를 운영하면서 다수의 서비스에 대한 관리를 Traefik을 통해 이어나가고 있는데, 사용하면 할 수록 굉장히 편리한 점들이 많아 간단하게 소개해볼 겸 해당 포스팅을 작성하게 됐다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;추후 Traefik에서 제공하는 다른 기능들, 로드밸런서, 미들웨어 등에 대한 포스팅도 기회가 되면 작성하려고 한다.&amp;nbsp;&lt;/p&gt;</description>
      <category>Infra</category>
      <author>겨울바람_</author>
      <guid isPermaLink="true">https://breeze-winter.tistory.com/20</guid>
      <comments>https://breeze-winter.tistory.com/20#entry20comment</comments>
      <pubDate>Thu, 30 May 2024 14:21:45 +0900</pubDate>
    </item>
    <item>
      <title>Ubuntu HomeServer - 2 : 외부 접속 허용하기</title>
      <link>https://breeze-winter.tistory.com/19</link>
      <description>&lt;p data-ke-size=&quot;size16&quot;&gt;들어가기 전에, 필자의 환경에 대해 설명하고자 한다. 내부의 인터넷 망 연결 상태에 따라 방법이 달라지기 때문에 필자의 환경과 본인의 환경이 완벽하게 일치하지 않는 이상 그대로 따라하기 보다는 참고 정도로만 생각하는 것이 좋다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;필자는 LG U+ 통신사를 사용하고 있으며 이중 공유기를 사용하고 있다. 조금 더 이해를 쉽게하기 위해 아래의 그림을 참고하도록 하자.&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;304&quot; data-origin-height=&quot;790&quot;&gt;&lt;span data-url=&quot;https://blog.kakaocdn.net/dn/Xkb1Z/btsHDxuidYz/YzKeQKQakQsjjAEYuIyLKK/img.png&quot; data-phocus=&quot;https://blog.kakaocdn.net/dn/Xkb1Z/btsHDxuidYz/YzKeQKQakQsjjAEYuIyLKK/img.png&quot;&gt;&lt;img src=&quot;https://blog.kakaocdn.net/dn/Xkb1Z/btsHDxuidYz/YzKeQKQakQsjjAEYuIyLKK/img.png&quot; srcset=&quot;https://img1.daumcdn.net/thumb/R1280x0/?scode=mtistory2&amp;fname=https%3A%2F%2Fblog.kakaocdn.net%2Fdn%2FXkb1Z%2FbtsHDxuidYz%2FYzKeQKQakQsjjAEYuIyLKK%2Fimg.png&quot; onerror=&quot;this.onerror=null; this.src='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png'; this.srcset='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png';&quot; loading=&quot;lazy&quot; width=&quot;207&quot; height=&quot;538&quot; data-origin-width=&quot;304&quot; data-origin-height=&quot;790&quot;/&gt;&lt;/span&gt;&lt;/figure&gt;
&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;LG U+의 경우 기가 인터넷 속도 증폭을 목적으로 홈 게이트웨이를 제공하는데, 해당 홈 게이트웨이를 한 번 거쳐서 공유기를 통해 노트북에 무선 인터넷이 연결되는 환경이었다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;외부 접속을 허용하기 위해서는 홈 게이트웨이와 공유기의 관리자 페이지에 접속해야 하는데, 이를 위해서는 각 기기의 관리자 페이지 접속을 위한 비밀번호를 알고 있어야 한다. 보통 기기 뒷면에 초기 비밀번호가 나와있으니 기기의 뒷면을 잘 확인해보자.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;참고로 홈 게이트웨이는 신발장 배선함 내부에 존재했고, 공유기는 벽걸이TV 뒤에 있었기 때문에 확인하는데 굉장히 어려움을 겪었다. 하지만 확인이 안되면 외부 접속 허용이 불가능하니 인내심을 가지고 잘 찾아보자.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;h4 data-ke-size=&quot;size20&quot;&gt;기기 관리자 페이지 접속&lt;/h4&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;LG U+의 경우 기본적으로 관리자 페이지에 접속하는 주소는 동일하다. 본인 로컬 환경에서 &lt;b&gt;192.168.219.1 &lt;/b&gt;을 주소창에 입력하면 관리자 페이지로 접속할 수 있다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p&gt;&lt;figure class=&quot;imageblock alignCenter&quot; data-ke-mobileStyle=&quot;widthOrigin&quot; data-origin-width=&quot;939&quot; data-origin-height=&quot;690&quot;&gt;&lt;span data-url=&quot;https://blog.kakaocdn.net/dn/b4hQbp/btsHCsUVWsG/pC4W2Tka6km4SvxZ5NTZ7K/img.png&quot; data-phocus=&quot;https://blog.kakaocdn.net/dn/b4hQbp/btsHCsUVWsG/pC4W2Tka6km4SvxZ5NTZ7K/img.png&quot;&gt;&lt;img src=&quot;https://blog.kakaocdn.net/dn/b4hQbp/btsHCsUVWsG/pC4W2Tka6km4SvxZ5NTZ7K/img.png&quot; srcset=&quot;https://img1.daumcdn.net/thumb/R1280x0/?scode=mtistory2&amp;fname=https%3A%2F%2Fblog.kakaocdn.net%2Fdn%2Fb4hQbp%2FbtsHCsUVWsG%2FpC4W2Tka6km4SvxZ5NTZ7K%2Fimg.png&quot; onerror=&quot;this.onerror=null; this.src='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png'; this.srcset='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png';&quot; loading=&quot;lazy&quot; width=&quot;939&quot; height=&quot;690&quot; data-origin-width=&quot;939&quot; data-origin-height=&quot;690&quot;/&gt;&lt;/span&gt;&lt;/figure&gt;
&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;필자의 경우 위에서 설명했듯이 홈 게이트웨이라는 거추장스러운 친구가 존재하기 때문에 두 개의 기기가 화면에 표시되는 것을 확인할 수 있다. 화면에 공유기 하나만 나타난다고 해서 잘못된 것이 아니다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;각 기기 그림을 클릭하게 되면 해당 기기의 관리자 페이지로 접속하기 위한 인가 페이지가 화면에 나타난다. 우선 하단에 위치한 공유기의 관리자 페이지에 접속해보자.&lt;/p&gt;
&lt;p&gt;&lt;figure class=&quot;imageblock alignCenter&quot; data-ke-mobileStyle=&quot;widthOrigin&quot; data-origin-width=&quot;526&quot; data-origin-height=&quot;451&quot;&gt;&lt;span data-url=&quot;https://blog.kakaocdn.net/dn/coYlmR/btsHBMfAc75/FKhu5C4txiq7R6eq38O1f0/img.png&quot; data-phocus=&quot;https://blog.kakaocdn.net/dn/coYlmR/btsHBMfAc75/FKhu5C4txiq7R6eq38O1f0/img.png&quot;&gt;&lt;img src=&quot;https://blog.kakaocdn.net/dn/coYlmR/btsHBMfAc75/FKhu5C4txiq7R6eq38O1f0/img.png&quot; srcset=&quot;https://img1.daumcdn.net/thumb/R1280x0/?scode=mtistory2&amp;fname=https%3A%2F%2Fblog.kakaocdn.net%2Fdn%2FcoYlmR%2FbtsHBMfAc75%2FFKhu5C4txiq7R6eq38O1f0%2Fimg.png&quot; onerror=&quot;this.onerror=null; this.src='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png'; this.srcset='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png';&quot; loading=&quot;lazy&quot; width=&quot;432&quot; height=&quot;370&quot; data-origin-width=&quot;526&quot; data-origin-height=&quot;451&quot;/&gt;&lt;/span&gt;&lt;/figure&gt;
&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;공유기 뒤편에서 확인한 관리자 페이지 접속 비밀번호와 보안코드를 입력하고 로그인을 누르면, 아래와 같이 관리자 페이지에 접속할 수 있다.&lt;/p&gt;
&lt;p&gt;&lt;figure class=&quot;imageblock alignCenter&quot; data-ke-mobileStyle=&quot;widthOrigin&quot; data-origin-width=&quot;909&quot; data-origin-height=&quot;951&quot;&gt;&lt;span data-url=&quot;https://blog.kakaocdn.net/dn/bLawr5/btsHCyHnQMt/csJdRmYGkbbOzWVdDbFEhk/img.png&quot; data-phocus=&quot;https://blog.kakaocdn.net/dn/bLawr5/btsHCyHnQMt/csJdRmYGkbbOzWVdDbFEhk/img.png&quot;&gt;&lt;img src=&quot;https://blog.kakaocdn.net/dn/bLawr5/btsHCyHnQMt/csJdRmYGkbbOzWVdDbFEhk/img.png&quot; srcset=&quot;https://img1.daumcdn.net/thumb/R1280x0/?scode=mtistory2&amp;fname=https%3A%2F%2Fblog.kakaocdn.net%2Fdn%2FbLawr5%2FbtsHCyHnQMt%2FcsJdRmYGkbbOzWVdDbFEhk%2Fimg.png&quot; onerror=&quot;this.onerror=null; this.src='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png'; this.srcset='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png';&quot; loading=&quot;lazy&quot; width=&quot;909&quot; height=&quot;951&quot; data-origin-width=&quot;909&quot; data-origin-height=&quot;951&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;여기서 네트워크 설정탭의 NAT 설정을 선택하면, 포트포워딩을 설정할 수 있는 설정창이 나오는데, 만약 본인의 환경이 단일 공유기만 존재한다면 해당 공유기를 포트포워딩 할 경우 바로 외부 접속이 허용된다.&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;981&quot; data-origin-height=&quot;647&quot;&gt;&lt;span data-url=&quot;https://blog.kakaocdn.net/dn/wrCBW/btsHCwpnhcF/0wpOqM5UBETPjZzzbP1tA0/img.png&quot; data-phocus=&quot;https://blog.kakaocdn.net/dn/wrCBW/btsHCwpnhcF/0wpOqM5UBETPjZzzbP1tA0/img.png&quot;&gt;&lt;img src=&quot;https://blog.kakaocdn.net/dn/wrCBW/btsHCwpnhcF/0wpOqM5UBETPjZzzbP1tA0/img.png&quot; srcset=&quot;https://img1.daumcdn.net/thumb/R1280x0/?scode=mtistory2&amp;fname=https%3A%2F%2Fblog.kakaocdn.net%2Fdn%2FwrCBW%2FbtsHCwpnhcF%2F0wpOqM5UBETPjZzzbP1tA0%2Fimg.png&quot; onerror=&quot;this.onerror=null; this.src='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png'; this.srcset='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png';&quot; loading=&quot;lazy&quot; width=&quot;981&quot; height=&quot;647&quot; data-origin-width=&quot;981&quot; data-origin-height=&quot;647&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;서비스 포트는 외부로 공개할 포트, 내부 포트의 경우 서버로 사용하고 있는 노트북과 연결할 포트라고 보면된다. 즉, 서비스 포트로 요청이 들어왔을 때 해당 요청은 설정한 내부 IP 주소의 내부 포트로 향하게 된다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;내부 IP 주소를 확인하는 방법은 Ubuntu 기준으로 &lt;b&gt;ifconfig &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;22번 포트의 경우 ssh 접속에 대한 기본 포트로 많이 알려져 있기 때문에 손쉽게 공격에 노출될 수 있기 때문에 별도의 포트 번호를 사용하는 것을 추천한다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;포트포워딩을 위한 정보 입력을 끝마쳤다면, 추가 버튼을 누르고 설정 적용을 버튼을 누르자. 참고로 LG U+ 환경에서는 설정 적용을 버튼을 클릭했을 때 공유기가 60초 가량 먹통이 되기 때문에 만약 가족과 함께 생활한다면 양해를 미리 구해두거나 가족들이 잠든 새벽에 진행하자.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;단일 공유기만을 사용한다면, 여기까지만 해도 외부 접속이 가능하다. 하지만 필자는 홈 게이트웨이가 존재하는 이중 공유기 환경이기 때문에 홈 게이트웨이에도 공유기와 동일하게 포트포워딩을 적용시켜줘야 한다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;이중 공유기 환경의 경우, 홈 게이트웨이의 서비스 포트가 실질적인 외부 인터넷과 연결되는 외부 포트 역할을 하고 홈 게이트웨이의 내부 포트가 공유기의 서비스 포트로 포트포워딩을 진행하게 된다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;풀어서 설명하자면, 홈 게이트웨이의 서비스 포트가 11, 내부 IP 주소는 공유기의 외부 IP 주소, 내부 포트 번호는 공유기에서 설정한 서비스 포트 번호가 될 것이다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;단일 공유기 환경과 다른 점이라고 하면 홈 게이트웨이를 한 번 더 거쳐서 요청이 들어온다는 것이다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;홈 게이트웨이 또한 공유기와 동일하게 포트포워딩을 진행하면 되는데, 필자의 홈 게이트웨이의 경우 IPv6 환경이었기에 NAT 설정이 불가능했고 이를 IPv4 only로 바꿔줘야 했다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;IPv4 only로 설정하게 되면 IPv6로만 운영되는 서비스에 접속이 불가능해진다. IPv6로만 운영되는 서비스를 사용하고 있다면 주의하도록 하자.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;성공적으로 홈 게이트웨이까지 포트포워딩에 성공했다면, 이제 포트포워딩을 통해 외부 접속을 허용한 포트가 실제로 외부 접속이 가능한지 확인해볼 차례다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;a href=&quot;https://www.yougetsignal.com/tools/open-ports/&quot; target=&quot;_blank&quot; rel=&quot;noopener&amp;nbsp;noreferrer&quot;&gt;https://www.yougetsignal.com/tools/open-ports/&lt;/a&gt;&lt;/p&gt;
&lt;figure id=&quot;og_1716718509871&quot; contenteditable=&quot;false&quot; data-ke-type=&quot;opengraph&quot; data-ke-align=&quot;alignCenter&quot; data-og-type=&quot;website&quot; data-og-title=&quot;Open Port Check Tool - Test Port Forwarding on Your Router&quot; data-og-description=&quot;&quot; data-og-host=&quot;www.yougetsignal.com&quot; data-og-source-url=&quot;https://www.yougetsignal.com/tools/open-ports/&quot; data-og-url=&quot;https://www.yougetsignal.com/tools/open-ports/&quot; data-og-image=&quot;&quot;&gt;&lt;a href=&quot;https://www.yougetsignal.com/tools/open-ports/&quot; target=&quot;_blank&quot; rel=&quot;noopener&quot; data-source-url=&quot;https://www.yougetsignal.com/tools/open-ports/&quot;&gt;
&lt;div class=&quot;og-image&quot; style=&quot;background-image: url();&quot;&gt;&amp;nbsp;&lt;/div&gt;
&lt;div class=&quot;og-text&quot;&gt;
&lt;p class=&quot;og-title&quot; data-ke-size=&quot;size16&quot;&gt;Open Port Check Tool - Test Port Forwarding on Your Router&lt;/p&gt;
&lt;p class=&quot;og-desc&quot; data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p class=&quot;og-host&quot; data-ke-size=&quot;size16&quot;&gt;www.yougetsignal.com&lt;/p&gt;
&lt;/div&gt;
&lt;/a&gt;&lt;/figure&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;해당 사이트에 들어가서 홈 게이트웨이의 외부 IP 주소와 포트 포워딩을 허용한 서비스 포트 번호를 입력하여 테스트해보자. 참고로 각 기기의 외부 IP 주소는 관리자 페이지의 홈에서 확인 가능하다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;별 다른 문제없이 포트포워딩에 성공했다면 아래와 같이 초록색 깃발이 표시된다. 만약 붉은 색 깃발이 표시된다면 포트포워딩에 실패했다는 것이니 내부 포트와 서비스 포트, IP 주소를 잘 살펴보도록 하자.&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;981&quot; data-origin-height=&quot;840&quot;&gt;&lt;span data-url=&quot;https://blog.kakaocdn.net/dn/bktOZz/btsHCPWviGp/WIq0mkXPMD20khjnb1GKb0/img.png&quot; data-phocus=&quot;https://blog.kakaocdn.net/dn/bktOZz/btsHCPWviGp/WIq0mkXPMD20khjnb1GKb0/img.png&quot;&gt;&lt;img src=&quot;https://blog.kakaocdn.net/dn/bktOZz/btsHCPWviGp/WIq0mkXPMD20khjnb1GKb0/img.png&quot; srcset=&quot;https://img1.daumcdn.net/thumb/R1280x0/?scode=mtistory2&amp;fname=https%3A%2F%2Fblog.kakaocdn.net%2Fdn%2FbktOZz%2FbtsHCPWviGp%2FWIq0mkXPMD20khjnb1GKb0%2Fimg.png&quot; onerror=&quot;this.onerror=null; this.src='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png'; this.srcset='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png';&quot; loading=&quot;lazy&quot; width=&quot;981&quot; height=&quot;840&quot; data-origin-width=&quot;981&quot; data-origin-height=&quot;840&quot;/&gt;&lt;/span&gt;&lt;/figure&gt;
&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;</description>
      <category>HomeServer</category>
      <author>겨울바람_</author>
      <guid isPermaLink="true">https://breeze-winter.tistory.com/19</guid>
      <comments>https://breeze-winter.tistory.com/19#entry19comment</comments>
      <pubDate>Sun, 26 May 2024 19:19:55 +0900</pubDate>
    </item>
    <item>
      <title>Ubuntu HomeServer - 1 : Window에서 Ubuntu로 변경하기</title>
      <link>https://breeze-winter.tistory.com/18</link>
      <description>&lt;p data-ke-size=&quot;size16&quot;&gt;사용하지 않은 채 방치되어 있던 노트북을 개인 서버 용도로 사용하기로 마음먹었다. 이번 게시글에서는 해당 노트북에 이미 설치되어 있던 Window 운영체제를 Ubuntu로 변경하는 과정을 설명하려고 한다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;h4 data-ke-size=&quot;size20&quot;&gt;Install Ubuntu ISO Image&lt;/h4&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;a href=&quot;https://ubuntu.com/download/desktop&quot; target=&quot;_blank&quot; rel=&quot;noopener&amp;nbsp;noreferrer&quot;&gt;https://ubuntu.com/download/desktop&lt;/a&gt;&lt;/p&gt;
&lt;figure id=&quot;og_1716711412178&quot; contenteditable=&quot;false&quot; data-ke-type=&quot;opengraph&quot; data-ke-align=&quot;alignCenter&quot; data-og-type=&quot;website&quot; data-og-title=&quot;https://ubuntu.com/download/desktop&quot; data-og-description=&quot;&quot; data-og-host=&quot;ubuntu.com&quot; data-og-source-url=&quot;https://ubuntu.com/download/desktop&quot; data-og-url=&quot;https://ubuntu.com/download/desktop&quot; data-og-image=&quot;&quot;&gt;&lt;a href=&quot;https://ubuntu.com/download/desktop&quot; target=&quot;_blank&quot; rel=&quot;noopener&quot; data-source-url=&quot;https://ubuntu.com/download/desktop&quot;&gt;
&lt;div class=&quot;og-image&quot; style=&quot;background-image: url();&quot;&gt;&amp;nbsp;&lt;/div&gt;
&lt;div class=&quot;og-text&quot;&gt;
&lt;p class=&quot;og-title&quot; data-ke-size=&quot;size16&quot;&gt;https://ubuntu.com/download/desktop&lt;/p&gt;
&lt;p class=&quot;og-desc&quot; data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p class=&quot;og-host&quot; data-ke-size=&quot;size16&quot;&gt;ubuntu.com&lt;/p&gt;
&lt;/div&gt;
&lt;/a&gt;&lt;/figure&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;본인이 사용하고 싶은 서버 환경에 따라 버전 및 데스크톱/서버 용도의 이미지를 잘 선택하여 다운로드하면 된다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;필자의 경우 최초에는 24.04 버전을 다운받았으나, 버전 충돌 문제 등으로 인해 22.04 이미지로 다시 내려받았다. 각 Ubuntu 버전마다 apt repository에서 기본적으로 제공되는 Python 등의 버전이 달라진다는 것을 유의하고 선택하길 바란다.&lt;/p&gt;
&lt;h4 data-ke-size=&quot;size20&quot;&gt;Create Bootable USB Drive&lt;/h4&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;a href=&quot;https://rufus.ie/ko/&quot; target=&quot;_blank&quot; rel=&quot;noopener&amp;nbsp;noreferrer&quot;&gt;https://rufus.ie/ko/&lt;/a&gt;&lt;/p&gt;
&lt;figure id=&quot;og_1716711785861&quot; contenteditable=&quot;false&quot; data-ke-type=&quot;opengraph&quot; data-ke-align=&quot;alignCenter&quot; data-og-type=&quot;website&quot; data-og-title=&quot;Rufus - 간편하게 부팅 가능한 USB 드라이브 만들기&quot; data-og-description=&quot;Rufus는 USB 메모리 및 플래시 드라이브를 포맷하고 부팅할 수 있도록 만드는 도구입니다. 이 페이지 아래에 나열된 ISO 이미지 이외에도 Rufus는 여러 종류의 ISO 이미지를 지원합니다. (1) Windows 8 이&quot; data-og-host=&quot;rufus.ie&quot; data-og-source-url=&quot;https://rufus.ie/ko/&quot; data-og-url=&quot;https://rufus.ie/ko/&quot; data-og-image=&quot;https://scrap.kakaocdn.net/dn/pg11k/hyWdfrRUJ0/eN3pn7fJVbyncmo8o6PHP0/img.png?width=1018&amp;amp;height=1234&amp;amp;face=0_0_1018_1234,https://scrap.kakaocdn.net/dn/bQYqeM/hyV9MZaAwa/X4zdgzKCqPDNBD3WHjRHkK/img.png?width=983&amp;amp;height=1235&amp;amp;face=0_0_983_1235,https://scrap.kakaocdn.net/dn/ZubAJ/hyWdfMa0i3/CGzkwGeZm7VHKWWhVyIA4K/img.png?width=950&amp;amp;height=1266&amp;amp;face=0_0_950_1266&quot;&gt;&lt;a href=&quot;https://rufus.ie/ko/&quot; target=&quot;_blank&quot; rel=&quot;noopener&quot; data-source-url=&quot;https://rufus.ie/ko/&quot;&gt;
&lt;div class=&quot;og-image&quot; style=&quot;background-image: url('https://scrap.kakaocdn.net/dn/pg11k/hyWdfrRUJ0/eN3pn7fJVbyncmo8o6PHP0/img.png?width=1018&amp;amp;height=1234&amp;amp;face=0_0_1018_1234,https://scrap.kakaocdn.net/dn/bQYqeM/hyV9MZaAwa/X4zdgzKCqPDNBD3WHjRHkK/img.png?width=983&amp;amp;height=1235&amp;amp;face=0_0_983_1235,https://scrap.kakaocdn.net/dn/ZubAJ/hyWdfMa0i3/CGzkwGeZm7VHKWWhVyIA4K/img.png?width=950&amp;amp;height=1266&amp;amp;face=0_0_950_1266');&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;Rufus - 간편하게 부팅 가능한 USB 드라이브 만들기&lt;/p&gt;
&lt;p class=&quot;og-desc&quot; data-ke-size=&quot;size16&quot;&gt;Rufus는 USB 메모리 및 플래시 드라이브를 포맷하고 부팅할 수 있도록 만드는 도구입니다. 이 페이지 아래에 나열된 ISO 이미지 이외에도 Rufus는 여러 종류의 ISO 이미지를 지원합니다. (1) Windows 8 이&lt;/p&gt;
&lt;p class=&quot;og-host&quot; data-ke-size=&quot;size16&quot;&gt;rufus.ie&lt;/p&gt;
&lt;/div&gt;
&lt;/a&gt;&lt;/figure&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;ISO 이미지 파일을 통해 BIOS에서 운영 체제를 다시 등록시켜야 하기 때문에, 부팅 가능한 USB 드라이브를 손쉽게 만들어주는 Rufus를 사용한다.&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;Rufus의 경우 Window 전용이기 때문에 별도의 OS를 사용하고 있다면, 다른 방법을 찾아봐야 한다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;Rufus를 사용할 경우 USB가 포맷되기 때문에, 포맷이 돼도 괜찮은 USB를 사용하는 것을 권장한다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;필자의 경우 Rufus Portable을 사용해 진행했다. 성공적으로 실행이 완료됐다면, 아래와 같은 창을 확인할 수 있을 것이다.&lt;/p&gt;
&lt;p&gt;&lt;figure class=&quot;imageblock alignCenter&quot; data-ke-mobileStyle=&quot;widthOrigin&quot; data-filename=&quot;2.jpg&quot; data-origin-width=&quot;418&quot; data-origin-height=&quot;538&quot;&gt;&lt;span data-url=&quot;https://blog.kakaocdn.net/dn/8uRad/btsHBzAALlg/kC4XOgWnlgixv0DEimEFDK/img.jpg&quot; data-phocus=&quot;https://blog.kakaocdn.net/dn/8uRad/btsHBzAALlg/kC4XOgWnlgixv0DEimEFDK/img.jpg&quot;&gt;&lt;img src=&quot;https://blog.kakaocdn.net/dn/8uRad/btsHBzAALlg/kC4XOgWnlgixv0DEimEFDK/img.jpg&quot; srcset=&quot;https://img1.daumcdn.net/thumb/R1280x0/?scode=mtistory2&amp;fname=https%3A%2F%2Fblog.kakaocdn.net%2Fdn%2F8uRad%2FbtsHBzAALlg%2FkC4XOgWnlgixv0DEimEFDK%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;405&quot; height=&quot;521&quot; data-filename=&quot;2.jpg&quot; data-origin-width=&quot;418&quot; data-origin-height=&quot;538&quot;/&gt;&lt;/span&gt;&lt;/figure&gt;
&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;선택 버튼을 클릭하여, 내려받았던 Ubuntu ISO 파일을 선택하고, 장치에 본인이 부팅 가능한 USB로 만들고 싶은 장치를 잘 선택하여 시작 버튼을 누르자.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;시작 버튼을 누르면 &quot;다운로드가 필요합니다.&quot; 라는 경고창이 보일텐데, '예' 를 클릭하고 다음으로 넘어가면 &quot;ISOHybrid 이미지가 감지되었습니다.&quot; 라는 팝업창을 확인할 수 있다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;해당 팝업창에는 두 가지 선택지가 존재하는데, 여기서 권장하는 방식인 ISO 이미지 모드로 쓰기 버튼을 클릭한다.&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;여기까지 진행하는데 별 다른 이상이 없었다면, 해당 장치의 데이터가 모든 삭제된다는 경고창이 화면에 나타날텐데, 다시 한 번 본인의 USB 드라이브가 포맷해도 되는 것인지 잘 생각해보고 확인 버튼을 눌러주자.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;확인 버튼을 누르면, 상태창 아래에서 초록색 바가 서서히 움직이는 것을 확인할 수 있는데, 해당 작업이 완료되는데 약간의 시간이 필요하다.&lt;/p&gt;
&lt;p&gt;&lt;figure class=&quot;imageblock alignCenter&quot; data-ke-mobileStyle=&quot;widthOrigin&quot; data-origin-width=&quot;914&quot; data-origin-height=&quot;1114&quot;&gt;&lt;span data-url=&quot;https://blog.kakaocdn.net/dn/MPO6A/btsHDVO8TAL/VEklz3QfTmmH8JXMiwLkx0/img.png&quot; data-phocus=&quot;https://blog.kakaocdn.net/dn/MPO6A/btsHDVO8TAL/VEklz3QfTmmH8JXMiwLkx0/img.png&quot; data-alt=&quot;우분투 공식 페이지에서 가져온 예시 이미지다&quot;&gt;&lt;img src=&quot;https://blog.kakaocdn.net/dn/MPO6A/btsHDVO8TAL/VEklz3QfTmmH8JXMiwLkx0/img.png&quot; srcset=&quot;https://img1.daumcdn.net/thumb/R1280x0/?scode=mtistory2&amp;fname=https%3A%2F%2Fblog.kakaocdn.net%2Fdn%2FMPO6A%2FbtsHDVO8TAL%2FVEklz3QfTmmH8JXMiwLkx0%2Fimg.png&quot; onerror=&quot;this.onerror=null; this.src='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png'; this.srcset='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png';&quot; loading=&quot;lazy&quot; width=&quot;427&quot; height=&quot;520&quot; data-origin-width=&quot;914&quot; data-origin-height=&quot;1114&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;p data-ke-size=&quot;size16&quot;&gt;위의 이미지처럼 초록색 바가 완전히 꽉 채워졌다면, 닫기 버튼을 누르자. 시작 버튼을 누르면 똑같은 작업을 반복하게 되니 주의하도록 하자.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;이제, 노트북이 부팅될 때 USB에 내려받은 Ubuntu 이미지 파일이 우선적으로 시행되도록 만들어줘야 한다. 해당 과정에 들어가기 앞서 본인의 노트북에서 BIOS로 들어갈 수 있는 방법을 알아보도록 하자.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;삼성 노트북의 경우 노트북이 켜지고 삼성 로고가 화면에 나타날 때 F12 버튼을 연타하면 BIOS로 이동할 수 있다. 본인이 사용하는 노트북 제품에 따라 방법이 다르기 때문에 미리 알아두자.&lt;/p&gt;
&lt;h4 data-ke-size=&quot;size20&quot;&gt;Change Boot Device Priority&lt;/h4&gt;
&lt;p&gt;&lt;figure class=&quot;imageblock alignCenter&quot; data-ke-mobileStyle=&quot;widthOrigin&quot; data-origin-width=&quot;800&quot; data-origin-height=&quot;448&quot;&gt;&lt;span data-url=&quot;https://blog.kakaocdn.net/dn/bASrXX/btsHDbSvQbn/4fz6e8h9ShxZattsqT1pj0/img.jpg&quot; data-phocus=&quot;https://blog.kakaocdn.net/dn/bASrXX/btsHDbSvQbn/4fz6e8h9ShxZattsqT1pj0/img.jpg&quot;&gt;&lt;img src=&quot;https://blog.kakaocdn.net/dn/bASrXX/btsHDbSvQbn/4fz6e8h9ShxZattsqT1pj0/img.jpg&quot; srcset=&quot;https://img1.daumcdn.net/thumb/R1280x0/?scode=mtistory2&amp;fname=https%3A%2F%2Fblog.kakaocdn.net%2Fdn%2FbASrXX%2FbtsHDbSvQbn%2F4fz6e8h9ShxZattsqT1pj0%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;800&quot; height=&quot;448&quot; data-origin-width=&quot;800&quot; data-origin-height=&quot;448&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;삼성 노트북의 경우 BIOS에 진입하면 위와 같은 화면을 확인할 수 있다. 여기서 초록색 배경에 Boot라고 쓰여있는 버튼을 클릭하면 아래와 같은 화면으로 이동하게 된다.&lt;/p&gt;
&lt;p&gt;&lt;figure class=&quot;imageblock alignCenter&quot; data-ke-mobileStyle=&quot;widthOrigin&quot; data-origin-width=&quot;600&quot; data-origin-height=&quot;327&quot;&gt;&lt;span data-url=&quot;https://blog.kakaocdn.net/dn/uopqJ/btsHDA5ChvN/xWlHhQKnJsLHyP2LO5R1SK/img.gif&quot; data-phocus=&quot;https://blog.kakaocdn.net/dn/uopqJ/btsHDA5ChvN/xWlHhQKnJsLHyP2LO5R1SK/img.gif&quot;&gt;&lt;img src=&quot;https://blog.kakaocdn.net/dn/uopqJ/btsHDA5ChvN/xWlHhQKnJsLHyP2LO5R1SK/img.gif&quot; srcset=&quot;https://blog.kakaocdn.net/dn/uopqJ/btsHDA5ChvN/xWlHhQKnJsLHyP2LO5R1SK/img.gif&quot; onerror=&quot;this.onerror=null; this.src='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png'; this.srcset='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png';&quot; loading=&quot;lazy&quot; width=&quot;732&quot; height=&quot;399&quot; data-origin-width=&quot;600&quot; data-origin-height=&quot;327&quot;/&gt;&lt;/span&gt;&lt;/figure&gt;
&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;해당 화면에서 Boot Device Priority 를 선택하고&lt;/p&gt;
&lt;p&gt;&lt;figure class=&quot;imageblock alignCenter&quot; data-ke-mobileStyle=&quot;widthOrigin&quot; data-origin-width=&quot;600&quot; data-origin-height=&quot;326&quot;&gt;&lt;span data-url=&quot;https://blog.kakaocdn.net/dn/D9Njk/btsHCW84I2y/kT0Okt0qZ9s0ruIJk8gZ50/img.gif&quot; data-phocus=&quot;https://blog.kakaocdn.net/dn/D9Njk/btsHCW84I2y/kT0Okt0qZ9s0ruIJk8gZ50/img.gif&quot;&gt;&lt;img src=&quot;https://blog.kakaocdn.net/dn/D9Njk/btsHCW84I2y/kT0Okt0qZ9s0ruIJk8gZ50/img.gif&quot; srcset=&quot;https://blog.kakaocdn.net/dn/D9Njk/btsHCW84I2y/kT0Okt0qZ9s0ruIJk8gZ50/img.gif&quot; onerror=&quot;this.onerror=null; this.src='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png'; this.srcset='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png';&quot; loading=&quot;lazy&quot; width=&quot;737&quot; height=&quot;400&quot; data-origin-width=&quot;600&quot; data-origin-height=&quot;326&quot;/&gt;&lt;/span&gt;&lt;/figure&gt;
&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;Boot Option에서 본인이 Rufus를 통해 부팅 가능한 USB로 만든 드라이브 장치가 Window보다 실행 우선순위가 높아지도록 순서를 변경해준 뒤, Save 버튼을 클릭하면 노트북이 재실행되고 Ubuntu 설치 화면으로 이동하게 된다.&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;추가적으로 구형 노트북을 사용할 경우 BIOS가 텍스트인 경우가 종종 존재한다고 한다. 본인의 BIOS가 텍스트 형태의 제품일 경우 아래의 링크를 참고하여 진행하길 바란다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;a href=&quot;https://www.samsungsvc.co.kr/solution/22116&quot; target=&quot;_blank&quot; rel=&quot;noopener&amp;nbsp;noreferrer&quot;&gt;https://www.samsungsvc.co.kr/solution/22116&lt;/a&gt;&lt;/p&gt;
&lt;figure id=&quot;og_1716713624304&quot; contenteditable=&quot;false&quot; data-ke-type=&quot;opengraph&quot; data-ke-align=&quot;alignCenter&quot; data-og-type=&quot;website&quot; data-og-title=&quot;[삼성 PC] 부팅 우선 순위 변경하고 싶어요.&quot; data-og-description=&quot; &quot; data-og-host=&quot;www.samsungsvc.co.kr&quot; data-og-source-url=&quot;https://www.samsungsvc.co.kr/solution/22116&quot; data-og-url=&quot;https://www.samsungsvc.co.kr/solution/22116&quot; data-og-image=&quot;https://scrap.kakaocdn.net/dn/c2Cc2b/hyV9UCTTAt/mJglIWGkXOEE17momgd6Lk/img.jpg?width=264&amp;amp;height=55&amp;amp;face=0_0_264_55,https://scrap.kakaocdn.net/dn/kS7Q8/hyV91aYBIY/6YAn9MLV0R3eEf5zMPVSnK/img.jpg?width=418&amp;amp;height=228&amp;amp;face=0_0_418_228,https://scrap.kakaocdn.net/dn/bJiCJO/hyV9OQePZI/MrK3xK1aS00hgqJzKpXVE1/img.jpg?width=384&amp;amp;height=224&amp;amp;face=0_0_384_224&quot;&gt;&lt;a href=&quot;https://www.samsungsvc.co.kr/solution/22116&quot; target=&quot;_blank&quot; rel=&quot;noopener&quot; data-source-url=&quot;https://www.samsungsvc.co.kr/solution/22116&quot;&gt;
&lt;div class=&quot;og-image&quot; style=&quot;background-image: url('https://scrap.kakaocdn.net/dn/c2Cc2b/hyV9UCTTAt/mJglIWGkXOEE17momgd6Lk/img.jpg?width=264&amp;amp;height=55&amp;amp;face=0_0_264_55,https://scrap.kakaocdn.net/dn/kS7Q8/hyV91aYBIY/6YAn9MLV0R3eEf5zMPVSnK/img.jpg?width=418&amp;amp;height=228&amp;amp;face=0_0_418_228,https://scrap.kakaocdn.net/dn/bJiCJO/hyV9OQePZI/MrK3xK1aS00hgqJzKpXVE1/img.jpg?width=384&amp;amp;height=224&amp;amp;face=0_0_384_224');&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;[삼성 PC] 부팅 우선 순위 변경하고 싶어요.&lt;/p&gt;
&lt;p class=&quot;og-desc&quot; data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p class=&quot;og-host&quot; data-ke-size=&quot;size16&quot;&gt;www.samsungsvc.co.kr&lt;/p&gt;
&lt;/div&gt;
&lt;/a&gt;&lt;/figure&gt;
&lt;h4 data-ke-size=&quot;size20&quot;&gt;Install Ubuntu 22.04&lt;/h4&gt;
&lt;p&gt;&lt;figure class=&quot;imageblock alignCenter&quot; data-ke-mobileStyle=&quot;widthOrigin&quot; data-origin-width=&quot;720&quot; data-origin-height=&quot;399&quot;&gt;&lt;span data-url=&quot;https://blog.kakaocdn.net/dn/Imbuy/btsHBOxFsSM/0lKcCqEsN3Y8S9hQVTdP2k/img.png&quot; data-phocus=&quot;https://blog.kakaocdn.net/dn/Imbuy/btsHBOxFsSM/0lKcCqEsN3Y8S9hQVTdP2k/img.png&quot;&gt;&lt;img src=&quot;https://blog.kakaocdn.net/dn/Imbuy/btsHBOxFsSM/0lKcCqEsN3Y8S9hQVTdP2k/img.png&quot; srcset=&quot;https://img1.daumcdn.net/thumb/R1280x0/?scode=mtistory2&amp;fname=https%3A%2F%2Fblog.kakaocdn.net%2Fdn%2FImbuy%2FbtsHBOxFsSM%2F0lKcCqEsN3Y8S9hQVTdP2k%2Fimg.png&quot; onerror=&quot;this.onerror=null; this.src='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png'; this.srcset='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png';&quot; loading=&quot;lazy&quot; width=&quot;720&quot; height=&quot;399&quot; data-origin-width=&quot;720&quot; data-origin-height=&quot;399&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;성공적으로 부트 순서가 변경됐다면 위처럼 Ubuntu 설치와 관련된 선택지를 확인할 수 있는 화면을 볼 수 있다. Try or Install Ubuntu 를 선택하여 설치를 진행하자.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;필자의 경우 Desktop 모드를 선택했기 때문에 Server 이미지를 선택했다면 나타나는 화면이 다를 것이다. 하지만 근본적으로 GUI냐 CLI냐의 차이를 제외하면 동일하기 때문에 잘 선택하여 진행하면 된다.&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;1024&quot; data-origin-height=&quot;640&quot;&gt;&lt;span data-url=&quot;https://blog.kakaocdn.net/dn/dw0cgd/btsHDbSv9AV/xPJmSqntIEeIbVaZEKtjMK/img.png&quot; data-phocus=&quot;https://blog.kakaocdn.net/dn/dw0cgd/btsHDbSv9AV/xPJmSqntIEeIbVaZEKtjMK/img.png&quot;&gt;&lt;img src=&quot;https://blog.kakaocdn.net/dn/dw0cgd/btsHDbSv9AV/xPJmSqntIEeIbVaZEKtjMK/img.png&quot; srcset=&quot;https://img1.daumcdn.net/thumb/R1280x0/?scode=mtistory2&amp;fname=https%3A%2F%2Fblog.kakaocdn.net%2Fdn%2Fdw0cgd%2FbtsHDbSv9AV%2FxPJmSqntIEeIbVaZEKtjMK%2Fimg.png&quot; onerror=&quot;this.onerror=null; this.src='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png'; this.srcset='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png';&quot; loading=&quot;lazy&quot; width=&quot;1024&quot; height=&quot;640&quot; data-origin-width=&quot;1024&quot; data-origin-height=&quot;640&quot;/&gt;&lt;/span&gt;&lt;/figure&gt;
&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;설치 언어를 선택하고 Install Ubuntu를 선택하자.&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;862&quot; data-origin-height=&quot;697&quot;&gt;&lt;span data-url=&quot;https://blog.kakaocdn.net/dn/djTncc/btsHB83rQYm/5iSjwhl73bPNArjNaSDCvK/img.png&quot; data-phocus=&quot;https://blog.kakaocdn.net/dn/djTncc/btsHB83rQYm/5iSjwhl73bPNArjNaSDCvK/img.png&quot;&gt;&lt;img src=&quot;https://blog.kakaocdn.net/dn/djTncc/btsHB83rQYm/5iSjwhl73bPNArjNaSDCvK/img.png&quot; srcset=&quot;https://img1.daumcdn.net/thumb/R1280x0/?scode=mtistory2&amp;fname=https%3A%2F%2Fblog.kakaocdn.net%2Fdn%2FdjTncc%2FbtsHB83rQYm%2F5iSjwhl73bPNArjNaSDCvK%2Fimg.png&quot; onerror=&quot;this.onerror=null; this.src='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png'; this.srcset='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png';&quot; loading=&quot;lazy&quot; width=&quot;862&quot; height=&quot;697&quot; data-origin-width=&quot;862&quot; data-origin-height=&quot;697&quot;/&gt;&lt;/span&gt;&lt;/figure&gt;
&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;본인의 설치 언어에 맞는 키보드 배열을 선택하면 된다. 보통은 English(US) 혹은 Korea 를 선택한다.&lt;/p&gt;
&lt;p&gt;&lt;figure class=&quot;imageblock alignCenter&quot; data-ke-mobileStyle=&quot;widthOrigin&quot; data-origin-width=&quot;862&quot; data-origin-height=&quot;695&quot;&gt;&lt;span data-url=&quot;https://blog.kakaocdn.net/dn/bz2Lqd/btsHDdiuM6B/n3f8HIebBup05osRwRd6HK/img.png&quot; data-phocus=&quot;https://blog.kakaocdn.net/dn/bz2Lqd/btsHDdiuM6B/n3f8HIebBup05osRwRd6HK/img.png&quot;&gt;&lt;img src=&quot;https://blog.kakaocdn.net/dn/bz2Lqd/btsHDdiuM6B/n3f8HIebBup05osRwRd6HK/img.png&quot; srcset=&quot;https://img1.daumcdn.net/thumb/R1280x0/?scode=mtistory2&amp;fname=https%3A%2F%2Fblog.kakaocdn.net%2Fdn%2Fbz2Lqd%2FbtsHDdiuM6B%2Fn3f8HIebBup05osRwRd6HK%2Fimg.png&quot; onerror=&quot;this.onerror=null; this.src='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png'; this.srcset='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png';&quot; loading=&quot;lazy&quot; width=&quot;862&quot; height=&quot;695&quot; data-origin-width=&quot;862&quot; data-origin-height=&quot;695&quot;/&gt;&lt;/span&gt;&lt;/figure&gt;
&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;이후 우분투 설치 시 함께 설치할 소프트웨어를 선택하면 된다. 필자는 Normal installation 으로 진행했다.&lt;/p&gt;
&lt;p&gt;&lt;figure class=&quot;imageblock alignCenter&quot; data-ke-mobileStyle=&quot;widthOrigin&quot; data-origin-width=&quot;820&quot; data-origin-height=&quot;697&quot;&gt;&lt;span data-url=&quot;https://blog.kakaocdn.net/dn/bleDIQ/btsHCz7livR/KE1P4XuF7vZkKGL68jAr5K/img.png&quot; data-phocus=&quot;https://blog.kakaocdn.net/dn/bleDIQ/btsHCz7livR/KE1P4XuF7vZkKGL68jAr5K/img.png&quot;&gt;&lt;img src=&quot;https://blog.kakaocdn.net/dn/bleDIQ/btsHCz7livR/KE1P4XuF7vZkKGL68jAr5K/img.png&quot; srcset=&quot;https://img1.daumcdn.net/thumb/R1280x0/?scode=mtistory2&amp;fname=https%3A%2F%2Fblog.kakaocdn.net%2Fdn%2FbleDIQ%2FbtsHCz7livR%2FKE1P4XuF7vZkKGL68jAr5K%2Fimg.png&quot; onerror=&quot;this.onerror=null; this.src='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png'; this.srcset='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png';&quot; loading=&quot;lazy&quot; width=&quot;820&quot; height=&quot;697&quot; data-origin-width=&quot;820&quot; data-origin-height=&quot;697&quot;/&gt;&lt;/span&gt;&lt;/figure&gt;
&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;Erase disk and install Ubuntu를 선택하고, Install Now 클릭하여 설치를 시작한다.&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;821&quot; data-origin-height=&quot;698&quot;&gt;&lt;span data-url=&quot;https://blog.kakaocdn.net/dn/Lx9KT/btsHDhSFFqf/H8j5xVUs5PlpoqPZdfVoB0/img.png&quot; data-phocus=&quot;https://blog.kakaocdn.net/dn/Lx9KT/btsHDhSFFqf/H8j5xVUs5PlpoqPZdfVoB0/img.png&quot;&gt;&lt;img src=&quot;https://blog.kakaocdn.net/dn/Lx9KT/btsHDhSFFqf/H8j5xVUs5PlpoqPZdfVoB0/img.png&quot; srcset=&quot;https://img1.daumcdn.net/thumb/R1280x0/?scode=mtistory2&amp;fname=https%3A%2F%2Fblog.kakaocdn.net%2Fdn%2FLx9KT%2FbtsHDhSFFqf%2FH8j5xVUs5PlpoqPZdfVoB0%2Fimg.png&quot; onerror=&quot;this.onerror=null; this.src='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png'; this.srcset='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png';&quot; loading=&quot;lazy&quot; width=&quot;821&quot; height=&quot;698&quot; data-origin-width=&quot;821&quot; data-origin-height=&quot;698&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;본인이 원하는 위치를 선택하고 Continue를 선택하면 사용자 정보를 입력하는 화면이 나타난다.&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;820&quot; data-origin-height=&quot;699&quot;&gt;&lt;span data-url=&quot;https://blog.kakaocdn.net/dn/cf2nfJ/btsHDMEKi81/nT8xgQ4RYNlLh9Revuf0A1/img.png&quot; data-phocus=&quot;https://blog.kakaocdn.net/dn/cf2nfJ/btsHDMEKi81/nT8xgQ4RYNlLh9Revuf0A1/img.png&quot;&gt;&lt;img src=&quot;https://blog.kakaocdn.net/dn/cf2nfJ/btsHDMEKi81/nT8xgQ4RYNlLh9Revuf0A1/img.png&quot; srcset=&quot;https://img1.daumcdn.net/thumb/R1280x0/?scode=mtistory2&amp;fname=https%3A%2F%2Fblog.kakaocdn.net%2Fdn%2Fcf2nfJ%2FbtsHDMEKi81%2FnT8xgQ4RYNlLh9Revuf0A1%2Fimg.png&quot; onerror=&quot;this.onerror=null; this.src='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png'; this.srcset='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png';&quot; loading=&quot;lazy&quot; width=&quot;820&quot; height=&quot;699&quot; data-origin-width=&quot;820&quot; data-origin-height=&quot;699&quot;/&gt;&lt;/span&gt;&lt;/figure&gt;
&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;사용자 정보를 입력하고 나면 설치가 본격적으로 시작된다.&lt;/p&gt;
&lt;p&gt;&lt;figure class=&quot;imageblock alignCenter&quot; data-ke-mobileStyle=&quot;widthOrigin&quot; data-origin-width=&quot;744&quot; data-origin-height=&quot;570&quot;&gt;&lt;span data-url=&quot;https://blog.kakaocdn.net/dn/bl8j3Y/btsHBVpLitB/fkfnmDTT3e16NP2Rt0aEJ0/img.png&quot; data-phocus=&quot;https://blog.kakaocdn.net/dn/bl8j3Y/btsHBVpLitB/fkfnmDTT3e16NP2Rt0aEJ0/img.png&quot;&gt;&lt;img src=&quot;https://blog.kakaocdn.net/dn/bl8j3Y/btsHBVpLitB/fkfnmDTT3e16NP2Rt0aEJ0/img.png&quot; srcset=&quot;https://img1.daumcdn.net/thumb/R1280x0/?scode=mtistory2&amp;fname=https%3A%2F%2Fblog.kakaocdn.net%2Fdn%2Fbl8j3Y%2FbtsHBVpLitB%2FfkfnmDTT3e16NP2Rt0aEJ0%2Fimg.png&quot; onerror=&quot;this.onerror=null; this.src='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png'; this.srcset='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png';&quot; loading=&quot;lazy&quot; width=&quot;744&quot; height=&quot;570&quot; data-origin-width=&quot;744&quot; data-origin-height=&quot;570&quot;/&gt;&lt;/span&gt;&lt;/figure&gt;
&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;설치가 완벽하게 끝나게 되면 아래와 같이 노트북을 재부팅하겠다는 알림창이 나타난다.&lt;/p&gt;
&lt;p&gt;&lt;figure class=&quot;imageblock alignCenter&quot; data-ke-mobileStyle=&quot;widthOrigin&quot; data-origin-width=&quot;489&quot; data-origin-height=&quot;158&quot;&gt;&lt;span data-url=&quot;https://blog.kakaocdn.net/dn/br4gAs/btsHCmAtzru/y1gmF3UTZgIzVhZeMYH7Ok/img.png&quot; data-phocus=&quot;https://blog.kakaocdn.net/dn/br4gAs/btsHCmAtzru/y1gmF3UTZgIzVhZeMYH7Ok/img.png&quot;&gt;&lt;img src=&quot;https://blog.kakaocdn.net/dn/br4gAs/btsHCmAtzru/y1gmF3UTZgIzVhZeMYH7Ok/img.png&quot; srcset=&quot;https://img1.daumcdn.net/thumb/R1280x0/?scode=mtistory2&amp;fname=https%3A%2F%2Fblog.kakaocdn.net%2Fdn%2Fbr4gAs%2FbtsHCmAtzru%2Fy1gmF3UTZgIzVhZeMYH7Ok%2Fimg.png&quot; onerror=&quot;this.onerror=null; this.src='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png'; this.srcset='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png';&quot; loading=&quot;lazy&quot; width=&quot;489&quot; height=&quot;158&quot; data-origin-width=&quot;489&quot; data-origin-height=&quot;158&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;노트북이 다시 부팅되고 나면 Ubuntu 22.04가 성공적으로 설치된 것을 확인할 수 있다.&lt;/p&gt;
&lt;p&gt;&lt;figure class=&quot;imageblock alignCenter&quot; data-ke-mobileStyle=&quot;widthOrigin&quot; data-origin-width=&quot;1911&quot; data-origin-height=&quot;1077&quot;&gt;&lt;span data-url=&quot;https://blog.kakaocdn.net/dn/wxNJh/btsHCR7NUga/xneB3Hnb6SvfyXKStgI6k0/img.png&quot; data-phocus=&quot;https://blog.kakaocdn.net/dn/wxNJh/btsHCR7NUga/xneB3Hnb6SvfyXKStgI6k0/img.png&quot;&gt;&lt;img src=&quot;https://blog.kakaocdn.net/dn/wxNJh/btsHCR7NUga/xneB3Hnb6SvfyXKStgI6k0/img.png&quot; srcset=&quot;https://img1.daumcdn.net/thumb/R1280x0/?scode=mtistory2&amp;fname=https%3A%2F%2Fblog.kakaocdn.net%2Fdn%2FwxNJh%2FbtsHCR7NUga%2FxneB3Hnb6SvfyXKStgI6k0%2Fimg.png&quot; onerror=&quot;this.onerror=null; this.src='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png'; this.srcset='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png';&quot; loading=&quot;lazy&quot; width=&quot;1911&quot; height=&quot;1077&quot; data-origin-width=&quot;1911&quot; data-origin-height=&quot;1077&quot;/&gt;&lt;/span&gt;&lt;/figure&gt;
&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;h4 data-ke-size=&quot;size20&quot;&gt;참고&lt;/h4&gt;
&lt;blockquote data-ke-style=&quot;style2&quot;&gt;https://rufus.ie/&lt;/blockquote&gt;
&lt;blockquote data-ke-style=&quot;style2&quot;&gt;https://ubuntu.com/download/desktop&lt;/blockquote&gt;</description>
      <category>HomeServer</category>
      <author>겨울바람_</author>
      <guid isPermaLink="true">https://breeze-winter.tistory.com/18</guid>
      <comments>https://breeze-winter.tistory.com/18#entry18comment</comments>
      <pubDate>Sun, 26 May 2024 18:08:58 +0900</pubDate>
    </item>
    <item>
      <title>CPU-Scheduling</title>
      <link>https://breeze-winter.tistory.com/17</link>
      <description>&lt;p&gt;&lt;figure class=&quot;imageblock alignCenter&quot; data-ke-mobileStyle=&quot;widthOrigin&quot; data-origin-width=&quot;740&quot; data-origin-height=&quot;507&quot;&gt;&lt;span data-url=&quot;https://blog.kakaocdn.net/dn/5CMgz/dJMcacJbV3H/VlAbV45vVRTxcp76U9t43k/img.jpg&quot; data-phocus=&quot;https://blog.kakaocdn.net/dn/5CMgz/dJMcacJbV3H/VlAbV45vVRTxcp76U9t43k/img.jpg&quot;&gt;&lt;img src=&quot;https://blog.kakaocdn.net/dn/5CMgz/dJMcacJbV3H/VlAbV45vVRTxcp76U9t43k/img.jpg&quot; srcset=&quot;https://img1.daumcdn.net/thumb/R1280x0/?scode=mtistory2&amp;fname=https%3A%2F%2Fblog.kakaocdn.net%2Fdn%2F5CMgz%2FdJMcacJbV3H%2FVlAbV45vVRTxcp76U9t43k%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;496&quot; height=&quot;340&quot; data-origin-width=&quot;740&quot; data-origin-height=&quot;507&quot;/&gt;&lt;/span&gt;&lt;/figure&gt;
&lt;/p&gt;
&lt;h3 data-ke-size=&quot;size23&quot;&gt;CPU-Scheduling 이란?&lt;/h3&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;모든 프로세스는 CPU를 필요로 하고 모든 프로세스는 먼저 CPU를 사용하고 싶어 하기에, 여러 프로세스들에게 합리적으로 CPU 자원을 할당하기 위해 운영체제는 어떤 프로세스에 어떤 CPU를 할당할지를 결정한다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;이렇게 운영체제가 프로세스들에게 합리적으로 CPU 자원을 배분하는 것을 CPU-Scheduling 이라고 한다. CPU-Scheduling 이 제대로 동작하지 않는다면, 반드시 실행되어야 할 프로세스들이 실행되지 못하거나, 중요하지 않은 프로세스들만 주로 실행되는 등 컴퓨터 성능에 직결되는 문제라고 볼 수 있다.&lt;/p&gt;
&lt;h3 data-ke-size=&quot;size23&quot;&gt;프로세스 우선순위&lt;/h3&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;각 프로세스마다 우선순위가 다른데, 우선순위가 높은 프로세스일수록 빨리 처리해야 하는 프로세스들을 의미한다. 우선순위가 높은 프로세스에는 대표적으로 입출력 작업이 많은 프로세스가 있다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;프로세스의 종류마다 입출력장치를 이용하는 시간과 CPU를 이용하는 시간의 양에는 차이가 있다. 입출력장치를 사용하는 경우가 많은 프로세스를 입출력 집중 프로세스, CPU를 사용하는 경우가 많은 프로세스를 CPU 집중 프로세스라고 하는데, 입출력 집중 프로세스는 실행 상태보다는 입출력을 위한 대기 상태에 더 많이 머무르게 된다. 반면에, CPU 집중 프로세스는 대기 상태보다는 실행 상태에 더 많이 머무르게 된다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;CPU 집중 프로세스와 입출력 집중 프로세스가 동시에 CPU 자원을 요구했을 때 입출력 집중 프로세스를 최대한 빨리 실행시켜 입출력장치를 꾸준히 작동시키고, 그다음 CPU 집중 프로세스에 CPU를 집중적으로 할당하는 것이 더 효율적이다. 입출력 집중 프로세스는 어차피 입출력장치가 입출력 작업을 완료하기 전까지는 대기 상태가 될 예정이기 때문에 입출력 집중 프로세스를 우선적으로 처리한다면 다른 프로세스가 CPU를 사용할 수 있기 때문이다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;이렇듯 운영체제는 프로세스의 중요도에 맞게 프로세스마다 우선순위를 부여하여 PCB에 명시한다.&lt;/p&gt;
&lt;h3 data-ke-size=&quot;size23&quot;&gt;Scheduling Queue&lt;/h3&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;PCB에 적혀있는 우선순위를 확인하기 위해 모든 프로세스의 PCB를 운영체제가 탐색하는 것은 비효율적이다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;이를 위해 운영체제는 프로세스들을 CPU 사용, 메모리 적재, 입출력장치 사용 등의 목적을 통해 구분하여 Scheduling Queue에 삽입한다. 대표적인 Scheduling Queue에는 CPU 사용을 위한 프로세스들을 적재하는 Ready Queue와 입출력장치 사용을 위한 프로세스들을 적재하는 Waiting Queue 가 존재한다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;준비 상태에 있는 프로세스들의 PCB는 Ready Queue의 마지막에 삽입되어 CPU를 사용할 차례를 기다린다. 운영체제는 PCB들이 큐에 삽입된 순서대로 프로세스를 하나씩 꺼내어 실행하되, 그중 우선순위가 높은 프로세스를 먼저 실행한다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;즉, 우선순위가 낮은 프로세스들이 먼저 큐에 삽입되었더라도 우선순위가 높은 프로스가 먼저 처리될 수 있다는 것이다.&lt;/p&gt;
&lt;h3 data-ke-size=&quot;size23&quot;&gt;Preemptive Scheduling (선점형 스케줄링)&lt;/h3&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;선점형 스케줄링이란, 프로세스가 CPU를 비롯한 자원을 사용하고 있더라고 운영체제가 프로세스로부터 자원을 강제로 빼앗아 다른 프로세스에게 할당할 수 있는 스케줄링 방식을 의미한다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;프로세스마다 정해진 시간만큼 CPU를 사용하고, 정해진 시간을 모두 소비하여 타이머 인터럽트가 발생하면 운영체제가 해당 프로세스로부터 CPU 자원을 빼앗아 다음 프로세스에게 할당하는 방식 또한 선점형 스케줄링의 일종으로 볼 수 있다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;선점형 스케줄링은 어느 한 프로세스의 자원 독점을 막고 프로세스들에게 골고루 자원을 배분할 수 있다는 장점이 있지만, 그만큼 문맥 교환 과정에서 오버헤드가 발생할 수 있다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;대부분의 운영체제에서는 선점형 스케줄링 방식을 차용하고 있다.&lt;/p&gt;
&lt;h3 data-ke-size=&quot;size23&quot;&gt;Non-Preemptive Scheduling (비선점형 스케줄링)&lt;/h3&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;비선점형 스케줄링이란, 하나의 프로세스가 자원을 사용하고 있다면 그 프로세스가 종료되거나 스스로 대기 상태에 접어들기 전까진 다른 프로세스가 끼어들 수 없는 스케줄링 방식을 의미한다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;이는 특정 프로세스가 자원을 독점할 수 있는 스케줄링 방식이며, 만약 비선점형 방식으로 자원을 이용하는 프로세스가 존재한다면 다른 프로세스들은 그 프로세스의 사용이 모두 끝날 때까지 기다려야 한다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;비선점형 스케줄링은 선점형 스케줄링에 비해 문맥 교환의 횟수가 적기 때문에 발생할 수 있는 오버헤드가 적지만, 하나의 프로세스가 자원을 사용 중이라면 당장 자원을 사용해야 하는 상황에서도 무작정 기다려야 하기 때문에 모든 프로세스가 골고루 자원을 사용할 수 없다는 단점이 있다.&lt;/p&gt;
&lt;h2 data-ke-size=&quot;size26&quot;&gt;Scheduling Algorithm&lt;/h2&gt;
&lt;h4 data-ke-size=&quot;size20&quot;&gt;FCFS Scheduling (선입 선처리 스케줄링)&lt;/h4&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;First Come First Served Scheduling 은 Scheduling Queue 에 삽입된 순서대로 프로세스들을 처리하는 비선점형 스케줄링 방식이다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;즉, CPU를 먼저 요청한 프로세스부터 CPU를 할당하는 방식인데, 이는 언뜻 가장 공정해 보이는 방식이지만, 프로세스들이 기다리는 시간이 매우 길어질 수 있다는 점에서 단점이 존재하는 방식이다.&lt;/p&gt;
&lt;h4 data-ke-size=&quot;size20&quot;&gt;SJF Scheduling (최단 작업 우선 스케줄링)&lt;/h4&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;FCFS 에서 발생할 수 있는 단점을 최소화하기 위해 CPU 사용 시간이 짧은 프로세스를 먼저 실행하는 스케줄링 방식을 Shortest Job First 스케줄링이라고 한다. 기본적으로 비선점형 스케줄링으로 분류되지만, 선점형으로도 구현될 수 있다.&lt;/p&gt;
&lt;h4 data-ke-size=&quot;size20&quot;&gt;Round Robin Scheduling&lt;/h4&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;Round Robin Scheduling 은 FCFS Scheduling 에 타임 슬라이스라는 개념이 더해진 스케줄링 방식이다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;타임 슬라이스란, 각 프로세스가 CPU를 사용할 수 있는 정해진 시간을 의미한다. 즉, Round Robin Scheduling 은 정해진 타임 슬라이스만큼의 시간 동안 돌아가며 CPU를 이용하는 선점형 스케줄링이다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;큐에 삽입된 프로세스들은 삽입된 순서대로 CPU를 이용하되 정해진 시간만큼만 CPU를 이용하고, 정해진 시간을 모두 사용하였음에도 아직 프로세스가 완료되지 않았다면, 다시 큐의 맨 뒤로 삽입된다. 이때 문맥 교환이 발생한다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;Round Robin Scheduling 에서는 타임 슬라이스의 크기가 매우 중요하다. 타임 슬라이스가 지나치게 크면 FCFS Scheduling 과 다를 바 없어지고, 타임 슬라이스가 지나치게 작으면 문맥 교환에 발생하는 비용이 커 CPU는 프로세스를 처리하는 일보다 프로세스를 전환하는데 더 많은 비용이 소모될 여지가 있기 때문이다.&lt;/p&gt;
&lt;h4 data-ke-size=&quot;size20&quot;&gt;SRT Scheduling (최소 잔여 시간 우선 스케줄링)&lt;/h4&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;Shortest Remaining Time Scheduling 은 SJF Scheduling과 Round Robin Scheduling 을 합친 스케줄링 방식이다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;프로세스들은 정해진 타임 슬라이스만큼 CPU를 사용하되, CPU를 사용할 다음 프로세스로는 남아있는 작업 시간이 가장 적은 프로세스가 선택된다.&lt;/p&gt;
&lt;h4 data-ke-size=&quot;size20&quot;&gt;Priority Scheduling (우선순위 스케줄링)&lt;/h4&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;Priority Scheduling 은 프로세스들에 우선순위를 부여하고, 가장 높은 우선순위를 가진 프로세스부터 실행하는 스케줄링 알고리즘이다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;SJF Scheduling, SRT Scheduling 이 넓은 의미에서 Priority Scheduling 의 일종으로 볼 수 있다. Priority Scheduling 은 우선순위가 높은 프로세스를 우선하여 처리하는 방식이기에 우선순위가 낮은 프로세스는 우선순위가 높은 프로세스들에 의해 실행이 계속해서 연기될 수 있다.&lt;/p&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;h4 data-ke-size=&quot;size20&quot;&gt;Multilevel Queue Scheduling (다단계 큐 스케줄링)&lt;/h4&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;Multilevel Queue Scheduling 은 Priority Scheduling의 발전된 형태로 우선순위별로 Ready Queue를 여러 개 사용하는 스케줄링 방식이다. 우선순위가 가장 높은 큐에 있는 프로세스들을 먼저 처리하고, 우선순위가 가장 높은 큐가 비어 있으면 그다음 우선순위 큐에 있는 프로세스들을 처리한다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;큐를 여러 개 두면 유형별로 우선순위를 구분하여 실행하는 것이 편리해진다. 또한 큐별로 타임 슬라이스를 여러 개 지정할 수도 있고, 큐마다 다른 스케줄링 알고리즘을 사용할 수 있다.&lt;/p&gt;
&lt;h4 data-ke-size=&quot;size20&quot;&gt;Multilevel Feedback Queue Scheduling (다단계 피드백 큐 스케줄링)&lt;/h4&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;다단계 피드백 큐 스케줄링은 다단계 큐 스케줄링의 발전된 형태로, 다단계 큐 스케줄링에서는 프로세스들이 큐 사이를 이동할 수 없다. 그렇기 때문에 우선순위가 낮은 프로세스들이 계속 연기되는 기아 현상이 발생할 수 있다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;다단계 피드백 큐 스케줄링은 다단계 큐 스케줄링과 비슷하게 작동하지만, 프로세스들이 큐 사이를 이동할 수 있다는 점에서 차이가 존재한다. 다단계 피드백 큐 스케줄링에서 새로 준비 상태가 된 프로세스가 있다면 우선 우선순위가 가장 높은 우선순위 큐에 삽입되고 타임 슬라이스 동안 실행된다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;만약 프로세스가 해당 큐에서 실행이 끝나지 않는다면 다음 우선순위 큐에 삽입되어 실행된다. 결국 CPU를 오래 사용해야 하는 프로세스는 점차 우선순위가 낮아진다. 이로 인해 CPU를 비교적 오래 사용해야 하는 CPU 집중 프로세스들은 자연스레 우선순위가 낮아지고, CPU를 비교적 적게 사용하는 입출력 집중 프로세스들은 자연스레 우선순위가 높은 큐에서 실행이 종료된다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;또한 낮은 우선순위 큐에서 너무 오래 기다리고 있는 프로세스가 존재한다면 점차 우선순위가 높은 큐로 이동시키는 에이징 기법을 적용하여 기아 현상을 예방할 수 있다. 다단계 피드백 큐 스케줄링은 어떤 프로세스의 CPU 이용 시간이 길면 낮은 우선순위 큐로 이동시키고, 어떤 프로세스가 낮은 우선순위 큐에서 너무 오래 기다린다면 높은 우선순위 큐로 이동시킬 수 있는 알고리즘이다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;다단계 피드백 큐 스케줄링은 구현이 복잡하지만, 가장 일반적인 CPU 스케줄링 알고리즘으로 알려져있다.&lt;/p&gt;</description>
      <category>Dev</category>
      <author>겨울바람_</author>
      <guid isPermaLink="true">https://breeze-winter.tistory.com/17</guid>
      <comments>https://breeze-winter.tistory.com/17#entry17comment</comments>
      <pubDate>Sun, 7 Apr 2024 11:28:28 +0900</pubDate>
    </item>
  </channel>
</rss>