<?xml version="1.0" encoding="UTF-8"?>
<rss version="2.0">
  <channel>
    <title>Chan != *</title>
    <link>https://blog.ggaman.com/</link>
    <description>속성을 알 수 없습니다.</description>
    <language>ko</language>
    <pubDate>Tue, 9 Jun 2026 14:22:01 +0900</pubDate>
    <generator>TISTORY</generator>
    <ttl>100</ttl>
    <managingEditor>찬</managingEditor>
    <image>
      <title>Chan != *</title>
      <url>https://t1.daumcdn.net/cfile/tistory/12039B0C4C320D9179</url>
      <link>https://blog.ggaman.com</link>
    </image>
    <item>
      <title>[책읽기] Tucker의 Go 언어 프로그래밍 - 공봉식, 골든래빗 ( 를 빙자한 Golang Cheat Sheet ㅋ )</title>
      <link>https://blog.ggaman.com/1033</link>
      <description>&lt;h2 style=&quot;text-align: center;&quot; data-ke-size=&quot;size26&quot;&gt;&lt;span style=&quot;background-color: #ffc1c8; color: #333333; text-align: center;&quot;&gt;책 읽은것 정리, 그리고 &lt;span style=&quot;background-color: #ffc1c8; color: #333333; text-align: center;&quot;&gt;Cheat Sheet 용으로&lt;span&gt;&amp;nbsp;&lt;/span&gt;&lt;/span&gt;ChatGPT랑 공부를 좀 했음&lt;/span&gt;&lt;/h2&gt;
&lt;h2 data-ke-size=&quot;size26&quot;&gt;제목 : Tucker의 Go 언어 프로그래밍 - 공봉식, 골든래빗&lt;/h2&gt;
&lt;p&gt;&lt;figure class=&quot;imageblock alignCenter&quot; data-ke-mobileStyle=&quot;widthOrigin&quot; data-origin-width=&quot;323&quot; data-origin-height=&quot;418&quot;&gt;&lt;span data-url=&quot;https://blog.kakaocdn.net/dn/bAVeQL/btsjWp8HgGS/AK5YlmjVZc9T1VOcK0b9t0/img.png&quot; data-phocus=&quot;https://blog.kakaocdn.net/dn/bAVeQL/btsjWp8HgGS/AK5YlmjVZc9T1VOcK0b9t0/img.png&quot;&gt;&lt;img src=&quot;https://blog.kakaocdn.net/dn/bAVeQL/btsjWp8HgGS/AK5YlmjVZc9T1VOcK0b9t0/img.png&quot; srcset=&quot;https://img1.daumcdn.net/thumb/R1280x0/?scode=mtistory2&amp;fname=https%3A%2F%2Fblog.kakaocdn.net%2Fdn%2FbAVeQL%2FbtsjWp8HgGS%2FAK5YlmjVZc9T1VOcK0b9t0%2Fimg.png&quot; onerror=&quot;this.onerror=null; this.src='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png'; this.srcset='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png';&quot; loading=&quot;lazy&quot; width=&quot;323&quot; height=&quot;418&quot; data-origin-width=&quot;323&quot; data-origin-height=&quot;418&quot;/&gt;&lt;/span&gt;&lt;/figure&gt;
&lt;/p&gt;
&lt;h2 data-ke-size=&quot;size26&quot;&gt;책을 읽기전&lt;/h2&gt;
&lt;ul style=&quot;list-style-type: disc;&quot; data-ke-list-type=&quot;disc&quot;&gt;
&lt;li&gt;Go라는 언어가 예전부터 나왔지만, 잠시 공부했다가 그냥 그런가 보다 했다.&lt;/li&gt;
&lt;li&gt;한창 Docker와 Kubernetes를 관련 업무를 진행할 때 이 쪽 관련 주 언어는 Go 인걸을 알게 되었다. 그래서 관심을 가지고 있다 읽게 된 책이다.&lt;/li&gt;
&lt;li&gt;나의 주 언어가 Java이긴 하지만, Spring이 좋고 말고를 떠나서 다른 언어들에 비해서 메모리도 많이 사용하고, 메모리 관리가 어렵다는게 큰 문제 라는것을 최근에 격고 있다.메모리 관리는 GC 가 알아서 해 주니 뭐가 문제냐고 생각할 수도 있지만, 내가 말하는 메모리 관리 문제는 Memory Leak 이나 혹은 Stack Overflow 따위의 문제를 말하는게 아니다.
&lt;ul style=&quot;list-style-type: disc;&quot; data-ke-list-type=&quot;disc&quot;&gt;
&lt;li&gt;자바는 기본적으로 VM에서 Heap 이라는곳에 이런 저런 정보들을 두기도 하는데, 문제는 단순히 stack이나 heap이라는 메모리만 사용하는게 아니라 &quot;native&quot; 메모리도 사용한다는거다.&lt;/li&gt;
&lt;li&gt;요즘에는 Container로 서비스를 올릴때, &quot;이 컨테이너는 요만큼의 메모리만 사용해라&quot;라고 지정해서 올리는데, Container OutOfMemory로 컨테이너가 죽는 경우가 있다. JVM이 Heap은 조금만 사용하는데 Native 메모리를 많이 사용하면서, 결과적으로 Container의 메모리가 부족해지는 경우가 있다.&lt;/li&gt;
&lt;li&gt;Heap 메모리가 부족해서 서비스가 죽으면, Heap dump라도 떠서 디버깅이라도 할 수 있는데, Native 메모리가 부족한 경우 추적하기가 너무 어렵다. Native단을 분석하는 시간보다 &quot;Container 메모리 늘리기&quot;라는 편한 방법을 취하게 된다는게 참 마음에 안 들었다.&lt;/li&gt;
&lt;/ul&gt;
&lt;/li&gt;
&lt;li&gt;이런 상황을 몇번 겪다보니 다른 언어에 눈이 가는것은 당연하긴 하다. 암튼 지금 내 생각이 그렇다고. ㅎ.&lt;/li&gt;
&lt;/ul&gt;
&lt;h2 data-ke-size=&quot;size26&quot;&gt;책 읽기&lt;/h2&gt;
&lt;ul style=&quot;list-style-type: disc;&quot; data-ke-list-type=&quot;disc&quot;&gt;
&lt;li&gt;이 책을 쓰신분이 Go 언어의 A-Z까지를 다루기 위해서 노력한 흔적이 보인다.&lt;/li&gt;
&lt;li&gt;이 책은 프로그래밍 자체를 모르는 초보자를 위한 책이라고 볼 수도 있다. 첫장의 제목이 &quot;컴퓨터의 원리&quot;라는것만 봐도 알 수 있다. 1장과 2장은 비트, 트랜지스터, 논리회로, op-code와 operand 에 대한 설명도 나온다. &quot;이라고 볼 수 있다&quot;라고 한 이유는, 1장과 2장이 &quot;너무 전공자스럽다&quot;는것이다. 4장도 다른 프로그래밍 언어 책에서는 그냥 넘어 갈만한 &quot;부호비트&quot; 따위에 대한 설명이 나온다. 제 글을 보고 이책을 보시는 분이 있다면, &quot;굳이 다 이해할 필요는 없다. 조금 더 똑똑해지고 난 이후에 이해해도 될 것이다&quot;&lt;/li&gt;
&lt;li&gt;5장과 6장은 간단한 화면, 키보드 입출력을 다루고 있으며, 6장은 연산자를 다루고 있다.&lt;/li&gt;
&lt;li&gt;7장부터 함수, 상수, if, switch, for, 배열, 구조체 포인터, 문자열, 패키지로 기본기를 끝내고&lt;/li&gt;
&lt;li&gt;18장부터 슬라이스, 메소드, 인터페이스 함수 고급, 자료구조 , 에러핸들링, 고루틴, 채널, 컨텍스트가 나온다.&lt;/li&gt;
&lt;li&gt;전공자급에서 배울법한 자료구조에 대한 설명도 나오고, 간단한 프로젝트들도 책에 포함되어 있으므로 책 한권을 모두 읽고 익힌다면 기본적인 프로그래밍은 할 수 있겠다 싶다.&lt;/li&gt;
&lt;/ul&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;책 정리는 이 정도 하고, 순전히 내가 나중에 참고하기 위해서 다른곳에서 구한 정보도 적절히 뒤죽박죽 정리한다. 위에도 적었다시피 Cheat Sheet 로 사용할 예정이니, 혹시 Go 언어에 대해서 이런 저런게 궁금하다면 이 문서에서 찾아 보면 되겠다.&lt;/p&gt;
&lt;h3 data-ke-size=&quot;size23&quot;&gt;기본정리&lt;/h3&gt;
&lt;h4 style=&quot;color: #000000;&quot; data-ke-size=&quot;size20&quot;&gt;변수, 상수, 타입 변환 및 타입 체킹( &lt;span style=&quot;background-color: #f7f7f8; color: #374151; text-align: start;&quot;&gt;&lt;/span&gt;type assertion )&lt;/h4&gt;
&lt;pre id=&quot;code_1686745754265&quot; class=&quot;go&quot; style=&quot;background-color: #f8f8f8; color: #383a42;&quot; data-ke-language=&quot;go&quot; data-ke-type=&quot;codeblock&quot;&gt;&lt;code&gt;////////////////////////////////////////////////////////////////////////
// 어떤 타입들이 있을까?
int, uint ( int와 uint는 32bit, 64bit 플랫폼에 따라 다른 정밀도를 가지게 되므로 되도록 절대 사용하지 말자 )
int8, int16, int32, int64
uint8, uint16, uint32, uint64
uintptr (포인터용)
float32, float64,
complex64, complex128 (복소수)
bool
string
rune ( Unicode CodePoint용, int32임 )
byte ( uint8 과 동일 )

////////////////////////////////////////////////////////////////////////
// 변수 선언
var b int64 = 10
// 타입을 적어도 되고, 안 적으면 알아서 추론 됨.
var e = 10
var f = 10.3

// := 를 사용하면 var이나 type을 적지 않아도 알아서 추론해서 값이 들어 간다.
// 근데 이때 걍 정수를 쓰면, int type으로 정의 된다. 그러므로 이렇게 쓰지 말자.
i := 10

// 물론 아래와 같이 쓰면 i는 int type, f는 float64로 들어감.
i, f := 10, 3.14  // 10, 3.14   -   int, float64

// 근데 := 를 사용할 때는 적어도 하나의 변수는 이전에 사용하지 않은거여야 한다.
i := 10            // i는 처음 사용하는 변수라 := 를 사용 가능
i, f := 20, 5.9    // i는 이전에 사용했지만, f는 처음 사용한거라 := 라 가능
i, f := 30, 7.3    // error. i와 f는 모두 이전에 사용된거라 컴파일 에러.

////////////////////////////////////////////////////////////////////////
// 상수 선언
// const 상수이름 타입 = 값
const ConstValue int = 10
// ConstValue가 상수로 정의 되었으므로, 당연히 값을 할당하면 에러가 발생함.

// 상수는 &quot;코드에 박히기 때문에&quot;, 메모리 주소를 출력할 수 없다.
// 그러므로 아래 코드는 에러가 발생함.
// fmt.Println(&amp;amp;C)

////////////////////////////////////////////////////////////////////////
// 타입변환, Go는 컴파일러가 암시적으로 타입변환 안해줌. 사용자가 명시적으로 해야함.
i := 42
f := float64(i)
u := uint(f)

// 호환되는 타입을 만든 경우, 상호 변환 가능.
type MyInt int 
var i int = 42
var m MyInt = MyInt(i) // int -&amp;gt; MyInt
var q int = int(m) // MyInt -&amp;gt; int

////////////////////////////////////////////////////////////////////////
// string과 다른 type 간의 변환
// Atoi는 return type이 int 라서 시스템마다 다르게 반환 될 수 있다. 그러니 쓰지 말자!!!
i, err := strconv.Atoi(&quot;123&quot;)  // 123 , int type이므로 system마다 다른 type이 될 수 있음!!
str, err := strconv.Itoa(123) // &quot;123&quot; 

// string &amp;lt;-&amp;gt; primitive 타입 변환은 아래꺼 쓰자. 단 return type을 잘 확인해야 한다.
b, err := strconv.ParseBool(s string) // return (boolean, error)

// s를 base진법, bit만큼의 정밀도로 변환하고, 변환 결과를 int64로
i, err := strconv.ParseInt(s string, base int, bit int) // return (int64, error)

// s를 base진법, bit만큼의 정밀도로 변환하고, 변환 결과를 uint64로
i, err := strconv.ParseUint(s string, base int, bit int) // return (uint64, error)

// string float로
f, err := strconv.ParseFloat(s, 64) // return (float64, error)

// strconv.ParseFloat이 float64로 반환되었을때, float32로 변경하고 싶다면?
f64, _ := strconv.ParseFloat(s, 32) // ParseFloat가 float64를 반환하지만, 32bit 정밀로도 변환
f32 := float32(f64) // f64는 float64지만, 32bit 정밀도로 되어 있고, 이걸 다시 float32로 변환


////////////////////////////////////////////////////////////////////////
// 타입체킹 및 타입 단언(type assertion).
// interface{} 는 any type과 동일, 어떤 타입이던 우선 변수에 할당 받을 수 있음.
// variable.(TYPE)를 이용해서 타입을 알 수 있음.
// 만약 type이 맞다면 ok에 true가 i에 값이 들어옴. 
// type이 맞지 않다면 ok에 false가 i는 초기값이 들어 있음.

var ivalue interface{} = 123
i, iok := ivalue.(int)
if iok {
    fmt.Println(&quot;value contains an int:&quot;, i)  // &quot;value contains an int: 123&quot;
} else {
    fmt.Println(&quot;value does not contain an int&quot;)
}

var svalue interface{} = &quot;hello&quot;
s, sok := svalue.(string)
if sok {
    fmt.Println(&quot;value contains a string:&quot;, s)  // &quot;value contains a string: hello&quot;
} else {
    fmt.Println(&quot;value does not contain a string&quot;)
}

////////////////////////////////////
// 인터페이스 타입이 아닌 경우 아래와 같이 타입 변환 불가
var f = 3.14
nf1 := f.(float64)  //  invalid operation: f (variable of type float64) is not an interface

// 아무타입이나 받을 수 있는 interface{} 를 이용해서, 받은뒤에 확인 가능.
var i interface{} = &quot;hello&quot;
f, ok := i.(float64)    // i가 float64 타입이면, f에 값을. float64가 아니면 ok에 false를.
fmt.Println(f, ok)

////////////////////////////////////
// 인터페이스 타입을 변환할때도, 사용하기 전에 잘 변환되었는지를 잘 확인해야 한다.
// 그렇지 않으면 painc 발생. golang이 실수를 줄이기 위해 꼼꼼하게 설계 되었다는것을 알 수 있다.
nf := i.(float64) // panic. nf 값을 사용하려고 할 때, ok 값을 확인하지 않았으면 문제 됨.
fmt.Println(nf)


// 위 문장을 아래와 같이 ok부분을 _로 받아 두면 문제가 발생하지 않음.
nf2, _ := i.(float64)    // 에러내용을 받았지만, &quot;난 일부러 무시할꺼야&quot; 라고 _ 로 받아 둔 것임.
fmt.Println(nf2)&lt;/code&gt;&lt;/pre&gt;
&lt;h4 data-ke-size=&quot;size20&quot;&gt;구조체, 타입 정의,&amp;nbsp; 함수, 포인터 관련&lt;/h4&gt;
&lt;pre id=&quot;code_1686743450309&quot; class=&quot;go&quot; style=&quot;background-color: #f8f8f8; color: #383a42; text-align: start;&quot; data-ke-language=&quot;go&quot; data-ke-type=&quot;codeblock&quot;&gt;&lt;code&gt;package main

import &quot;fmt&quot;

// 함수명이 대문자이면 외부에서 호출가능한 public method라고 생각하면 됨.

// 리턴값이 1개
func Add ( a int , b int ) int {
    return a + b
}

// 리턴값이 3개
func AddReturn3 ( a int , b int ) ( int, int, int ) {
    return a, b, a + b
}

// 리턴값이 3개인데, 어떤게 리턴 될지 이름도 미리 정해 놔서 걍 &quot;return&quot; 이라고만 쓸 수 있음
func AddReturn3WithName ( a int , b int ) ( a int, b int, sr int ) {
    sr := a + b
    return 
}

func main() {
    // 타입이 명확할 때는 타입을 정의하지 않은 변수를 := 를 이용해 바로 만들 수 있음 
    c := Add(3, 5)
    a1, b1, sr1 := AddReturn3WithName(3, 5)
}

////////////////////////////////////////////////////////////////
// 함수 가변 인자 및 ... 연산자
func Sum(nums ...int) int {     // nums는 슬라이스로 전달됨
	total := 0
	for _, num := range nums {
		total += num
	}
	return total
}

Sum(1, 2, 3)

nums := []int{1, 2, 3, 4, 5}
Sum(nums...)

////////////////////////////////////////////////////////////////
// 구조체
// 
// 구조체 정의
type Data sturct {
    value int
    data [200]int
}

// 중첩된 구조체
type User struct {
    Name string
}

type VIPUser struct {
    User               // 타입만 적고, 필드를 안 적었음. 그럼 필드가 풀려서 들어감.
    level int
}

vip := VIPUser { User{&quot;화랑&quot;}, 1 }
vip.Name // User에 정의한 Name를 VIPUser에서 바로 사용 가능.

// 만약 VIPUser에도 Name이 있으면, VIPUser.Name으로 접근. User.Name은 VIPUser.User.Name으로 접근
vip := VIPUser { &quot;VIP이름&quot;, User{&quot;User이름&quot;}, 1 }
vip.Name // VIP이름
vip.User.Name // User이름



////////////////////////////////////////////////////////////////
// 포인터
//
// 포인터는 이렇게 씀. 구체적인 설명은 생략.. 다들 대충 알잖아? ㅋ
var v int = 500
var p *int // 이 상태일때 p는 nil 상태(사실은 0임)
p = &amp;amp;v
*p = 100


////////////////////////////////
// 구조체의 포인터 사용법
// 
// 구조체 정의
type Data sturct {
    value int
    data [200]int
}

// 레퍼런스를 받는 함수
func dd ( arg *Data ) { // 포인터 주소로 구조체 객체 접근 가능.
    arg.value = 1
    arg.data[100] = 2
}


func main() {
    // 방법 1
    var data Data // 구조체 생성
    dd ( &amp;amp;data )  // 구조체 객체의 포인터 주소 전달

    // 방법 2
    var data Data
    var p *Data = &amp;amp;data
    dd ( p ) // 이미 p가 주소이기 때문에 굳이 다시 &amp;amp;를 붙이 필요가 없음.
    
    // 방법 3
    var p *Data = &amp;amp;Data{} // 구조체 생성한 주소를 바로 p 에 할당. 굳이 data 변수 안 만들어도 됨.
    var p *Data = &amp;amp;Data{3, 4} // 구조체 생성할때 필드 초기값을 주고 생성할 수도 있음.
    dd ( p )
    
    // 방법 4
    var p = new(Data) // new만 써도 알아서 타입에 맞는 포인트 변수 생성 가능. 단 필드 초기값은 못 줌.
    
}

////////////////////////////////
// 일반적으로 구조체를 생성할 때는 객체의 포인터를 반환하는 
// newXXX 따위의 생성자 같은 함수를 만들어 쓰더라.

type User struct {
    Name string,
    Age int
}

func NewUser( name string, age int ) *User {
    var u = User{name, age}
    return &amp;amp;u
}

userPointer := NewUser(&quot;AAA&quot;, 123)

////////////////////////////////////////////////////////////////
// 타입 정의. 함수의 타입도 정의 가능. 
// 함수가 1급 객체임. 즉, 함수를 변수 할당, 파라미터로 전달, 심지어 return도 가능.

func add(x int, y int) int {
    return x + y
}

type OperatorFunc func(int, int) int // 함수의 정의를 OperatorFunc 라는 타입으로 정의
func operate(x int, y int, op OperatorFunc) int {  // 파라미터로 OperatorFunc 라는 함수를 받을 수 있음.
    return op(x, y)
}

result = operate(3, 4, add) // add 라는 함수가 OperatorFunc 타입이니깐, 파라미터로 전달 가능.


////////////////////////////////////////////////////////////////
// 함수를 동적으로 생성할 수도 있음.
// 하지만 이 때 생성된 함수로 전달되는 변수는 라이프사이클이 완료될때 까지 참조로 유지됨
// 쉽게 말하면 클로저에 대한 설명임.

var funcs []func()

var i = 0

// 함수 리터럴을 생성하고, 변수 i의 참조를 함수로 전달합니다.
// i의 라이프사이클이 끝날때까지 함수 안에서 i는 참조로 활용됩니다.
for i = 0; i &amp;lt; 2; i++ {
    funcs = append(funcs, func() {
        fmt.Println(i)
    })
}

// i의 값을 10으로 바꿉니다.
i = 10

// 동적으로 생성한 함수에서 사용한 i 참조가 아직 죽지 않았으므로
// 함수가 호출 될 때 i의 &quot;최신 상태&quot;를 실시간으로 반영합니다.
// 그러므로 출력은 0 1이 아니라 10 10 이 출력됩니다.
for _, f := range funcs {
    f()
}

{
    // 동적으로 함수가 생성될 때 v가 사용되나, 이 블럭을 벗어나면 v는 더 이상 살아 있지 않습니다.
    // 하지만 함수 실행시 사용되어야 하므로, 이 함수 내에서만 v가 살아 있게 됩니다.
    // 이 함수를 여러번 호출하면 내부에 살아 있는 v가 1씩 더해 질 것입니다.
    // 이런걸 클로저리고 합니다.
    var v = 100
    funcs = append(funcs, func() {
        fmt.Println(v)
        v = v + 1
    })
}

// 생성한 함수를 호출합니다.
// 10 10 100 이 출력될 것입니다.
for _, f := range funcs {
    f()
}

// 내부에 살아 있는 v가 1씩 더해 질 것이므로
// 101 102 103 이 출력 될 것입니다.
funcs[2]()
funcs[2]()
funcs[2]()&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;Go가 함수를 호출하면 call by value 다. 그렇지만 &amp;amp;를 이용해서 주소 값을 얻어 낼 수 있으므로, reference를 전달 할 수 있기는하다.&lt;/li&gt;
&lt;li&gt;*(포인터)형 변수는 함수의 파라미터로 전달 될때, &amp;amp;를 붙이지 않아도 알아서 주소값이 전달된다. 대신 함수에서는 &amp;amp;로 받아야만 그 객체에 제대로 접근할 수 있다.&amp;nbsp;&lt;/li&gt;
&lt;/ul&gt;
&lt;h4 data-ke-size=&quot;size20&quot;&gt;문자열&lt;/h4&gt;
&lt;pre id=&quot;code_1686746809566&quot; class=&quot;go&quot; data-ke-language=&quot;go&quot; data-ke-type=&quot;codeblock&quot;&gt;&lt;code&gt;var c rune = '찬' // rune은 걍 int32임. 걍 c를 출력하면 52268 가 나옴. %c를 써야 '찬'이 나옴

///////////////////

str = &quot;찬찬찬&quot; // Go에서 string은 UTF-8을 기준으로 처리됨.
len(str) // 9. len()은 byte의 크기를 나타냄. 한글 1글자가 UTF-8에서 3byte니깐 9가 출력되는거임.

///////////////////

runes_str := []rune(str) // UTF-8 string을 rune n개로 즉, int32 3개로 변경함.
len(runes_str) // 3. int32 배열 3개니깐, 3이 나옴.

///////////////////
// 재미난건 string type이지만 range 를 사용하면, unicode 1개씩 루프를 돌 수 있다는거임.
// range라는 용어만보면 배열, 슬라이드, map의 loop만 돌것 같지만
// string이나 channel 에서도 &quot;특별한 방식&quot;으로 동작하니깐 알아 둬야함.
str = &quot;안녕함?&quot;
for _, v := range str {
    fmt.Printf(&quot;%c&quot;, v) // 안녕함? 이 출력됨
}

///////////////////
// 문자열 합치기
str1 := &quot;냐냐&quot;
str2 := &quot;냠냠&quot;
str3 := str1 + str2
str1 += &quot; &quot; + str2

///////////////////
// 문자열 비교, ==과 != 으로 비교 가능. &amp;gt; &amp;gt;= &amp;lt;= &amp;lt; 로 Unicode Index로 비교도 가능
str1 == str2
str1 &amp;gt; str2

///////////////////
// string은 사실 구조체임.
type StringHeader struct {
    Data uintptr // Data라는 놈은 사실 포인터임.
    Len int
}

str1 = &quot;뿅&quot;  // str1.Data(unintptr)값이 &quot;뿅&quot;이라는 글자의 주소를 가리킴.
str2 = str1  // str1에 있던 Data 값(주소)과 Len 값을 str2로 복사.
// 즉, str2에 할당될때, 사실은 포인터 값이 복사 되고 있는 거임.

///////////////////
// string의 타입이 변환 될 때는 &quot;복사&quot; 된 뒤 변환됨.
str := &quot;Hello&quot;
slice := []byte(str)
slice[1] = 'Q' // str은 여전히 Hello 이고, slice만 Qello 로 변함.&lt;/code&gt;&lt;/pre&gt;
&lt;h4 data-ke-size=&quot;size20&quot;&gt;fmt.Printf(...)&lt;/h4&gt;
&lt;pre id=&quot;code_1686752493140&quot; class=&quot;go&quot; data-ke-language=&quot;go&quot; data-ke-type=&quot;codeblock&quot;&gt;&lt;code&gt;fmt.Printf(&quot;%d&quot;, 12) // 이런식으로 출력할 수 있다.

// %v가 매우 특별한데, &quot;알아서 잘 출력하라&quot; 정도 된다.
fmt.Printf(&quot;%T %v&quot;, 12,  12)     // int 12  // 12라고 적으면 int type이 됨. 주의 필요.
fmt.Printf(&quot;%T %v&quot;, 19.6, 19.6)  // float64 19.6
fmt.Printf(&quot;%T %v&quot;, '찬', '찬')  // int32 52268  // rune은 int32, 즉 숫자니깐 숫자로 출력됨
fmt.Printf(&quot;%T %v&quot;, &quot;찬&quot;, &quot;찬&quot;)  // string 찬

// 출력가능한 형식 지정자(format specifier) ChatGPT가 알려 준 거 붙여둠. ㅎ.

////////////////////////////////////////////
// bool, int, float
// %t: 불리언 값을 출력합니다.
// %b: 정수를 2진수로 출력합니다.
// %d: 정수를 10진수로 출력합니다.
// %o: 정수를 8진수로 출력합니다.
// %x: 정수를 16진수로 출력합니다(소문자 a-f).
// %X: 정수를 16진수로 출력합니다(대문자 A-F).
// %f: 부동소수점 수를 소수점 아래 6자리까지 출력합니다.
// %F: 부동소수점 수를 소수점 아래 6자리까지 출력합니다(%f와 동일).


/////////////////////////////////////////////
// 문자, 문자열, 배열
// %c: 정수를 해당하는 유니코드 문자로 출력합니다.
// %q: 정수를 해당하는 유니코드 문자로 출력하되, 따옴표로 묶습니다.
// %s: 문자열이나 바이트 슬라이스를 직접 출력합니다.
// %q: 문자열이나 바이트 슬라이스를 따옴표로 묶어서 출력합니다.
// %U: 정수를 유니코드 형식으로 출력합니다.
// %x: 문자열이나 바이트 슬라이스의 각 바이트를 2자리 16진수로 출력합니다(소문자 a-f).
// %X: 문자열이나 바이트 슬라이스의 각 바이트를 2자리 16진수로 출력합니다(대문자 A-F).


/////////////////////////////////////////////
// 타입 별 특별한 것
// %T: 값의 타입을 출력합니다.
// %p: 포인터의 주소를 16진수로 출력합니다.
// 
// %v: 값을 &quot;기본 형식&quot;으로 출력합니다. 복합 타입의 경우, 요소들은 재귀적으로 출력됩니다.
// %+v: 구조체의 경우, 필드 이름을 함께 출력합니다.
// %-v: 구조체의 경우, 필드 이름을 함께 출력하되 구조체의 내용만 출력합니다.
// #%v: 값의 Go 구문 표현을 출력합니다. 예를 들어, 문자열은 따옴표로 묶이고, 배열과 슬라이스의 요소는 재귀적으로 출력됩니다.


/////////////////////////////////////////////
// 기타
// %%: 리터럴 퍼센트 문자를 출력합니다(%는 그 자체로 출력됩니다).


/////////////////////////////////////////////
// 잘 사용하지 않는 부동 소숫점
// %b: 부동소수점 수를 과학적 표기법으로 출력합니다(ex: -1234567890.123456789e+10).
// %e: 부동소수점 수를 소문자로 된 과학적 표기법으로 출력합니다(ex: -1.234567e+10).
// %E: 부동소수점 수를 대문자로 된 과학적 표기법으로 출력합니다(ex: -1.234567E+10).
// %g: 부동소수점 수를 더 짧은 Precision으로 출력합니다(%e 또는 %f 중 짧은 쪽).
// %G: 부동소수점 수를 더 짧은 Precision으로 출력합니다(%E 또는 %F 중 짧은 쪽).&lt;/code&gt;&lt;/pre&gt;
&lt;h3 data-ke-size=&quot;size23&quot;&gt;if, switch, for&lt;/h3&gt;
&lt;pre id=&quot;code_1686754464923&quot; class=&quot;go&quot; data-ke-language=&quot;go&quot; data-ke-type=&quot;codeblock&quot;&gt;&lt;code&gt;////////////////////////////////////////////////////////////////////////////////////
// if의 일반적인 문법
if conditon {
    //
} else if condition {
    //
} else {
    //
}

// if 예제
x := 10
if x &amp;gt; 10 {
    fmt.Println(&quot;x is greater than 10&quot;)
} else if x == 10 {
    fmt.Println(&quot;x is exactly 10&quot;)
} else {
    fmt.Println(&quot;x is less than 10&quot;)
}

//////////////////////////////////////////
// if 사용시 초기화 구문 포함 문법 ( for도 초기화 구문 문법이 있음 )
if statement; condition {
    //
}

// if 초기화 구문 예제
if x := 10; x &amp;gt; 5 {
    fmt.Println(&quot;x is greater than 5&quot;)
}

////////////////////////////////////////////////////////////////////////////////////
// switch 문법중 expression이 있는 문법. expression은 string도 가능하다.
switch expression {
    case value1:
        // expression이 value1이면 여기가 실행됨. 
        break; // break를 안 써도 되지만, 써도 됨.
    case value2, value3:
        // expression이 value2거나 value3면 여기가 실행됨.
        // !!!! 중요!!!! break가 없어도 이 부분만 실행됨.
    case value4:
        // expression이 value4이면 여기가 실행됨
        // 다만 아래와 같이 fallthrough 라고 적으면 다음 case로 진행됨. 사용하지 말것.
        fallthrough
    case value5:
        // expression이 value5면 여기가 실행됨.
        // !!!! 중요!!!! break가 없어도 이 부분만 실행됨.
    default:
        // 위의 모든 case가 아닐 때 실행됩니다.
}

// 예제 1
str := &quot;hello&quot;
switch str {
    case &quot;hello&quot;:
        fmt.Println(&quot;The string is hello&quot;)
    case &quot;world&quot;, &quot;help&quot;, &quot;me&quot; :
        fmt.Println(&quot;The string is world or help or me&quot;)
    default:
        fmt.Println(&quot;The string is not hello or world or help or me&quot;)
}

//////////////////////////////////////////
// switch 문법중 초기화 구문과 expression이 같이 있는 문법
// 이곳에서 지정된 statment(변수)는 switch 문 내에서만 사용할 수 있음.
switch statement; expression {
    case value1:
    ///
}

// 예
switch age := getAge(); age {
    case 10:
    ///
}

//////////////////////////////////////////
// switch 문법중 expression이 없는 문법
// 사실 expression 영역이 true 라고 박혀 있는것이랑 같다고 볼수 있음.
// case를 순차적으로 검사해 condition이 첫번째로 true인 곳을 실행

switch {
    case condition1:
        // condition1 이 true면 여기가 실행됨.
        // !!!! 중요!!!! break가 없어도 이 부분만 실행됨.
    case condition2:
        // condition2 이 true면 여기가 실행됨.
        // !!!! 중요!!!! 만약 condition1이 true였다면 condition1만 실행되고 여기는 실행 안됨.
        // !!!! 중요!!!! break가 없어도 이 부분만 실행됨.
    default:
        // 위의 모든 case가 아닐 때 실행됩니다.
}

// 예제
x := 3
switch {
    case x &amp;gt; 0:
        fmt.Println(&quot;x is positive&quot;)
        fallthrough
    case x &amp;gt; 2:
        fmt.Println(&quot;x is greater than 2&quot;)
    default:
        fmt.Println(&quot;x is zero or negative&quot;)
}

//////////////////////////////////////////
// switch 문법중 초기화 구문만 있는 문법.
// 사실 expression 영역이 true 라고 박혀 있는것이랑 같다고 볼수 있음.
switch statement; {
    case value1:
    ///
}

// 예
switch age := getAge(); {
    case age &amp;gt; 10:
    ///
}

////////////////////////////////////////////////////////////////////////////////////
// for 일반적인 문법
for initialization; condition; post {
    // loop body
}

//////////////////////////////////////////
// for 초기만문 없는 문법
i := 0
for ; i &amp;lt; 5 ; i++ {
    fmt.Println(i)
}

//////////////////////////////////////////
// for condition만 있는 문법
i := 0
for ; i &amp;lt; 5 ; {
    fmt.Println(i)
    i++
}

//////////////////////////////////////////
// for condition만 있는 경우 ; 제거 가능
i := 0
for i &amp;lt; 5 {
    fmt.Println(i)
    i++
}

//////////////////////////////////////////
// for 아무것도 없는 무한루프
for {
    // loop body
}

//////////////////////////////////////////
// for 에서 range 사용하는 방법
nums := []int{2, 3, 4}
for i, num := range nums {
    fmt.Printf(&quot;index %d: %d\n&quot;, i, num)
}&lt;/code&gt;&lt;/pre&gt;
&lt;h3 data-ke-size=&quot;size23&quot;&gt;배열, 슬라이스&lt;/h3&gt;
&lt;pre id=&quot;code_1686754857850&quot; class=&quot;go&quot; data-ke-language=&quot;go&quot; data-ke-type=&quot;codeblock&quot;&gt;&lt;code&gt;//////////////////////////////////////////////////////////////
// 배열
//
// 배열은 &quot;상수&quot; 크기로 만들 수 있음. const로 정의를 해도 됨. var 따위의 변수는 안됨.
// 동적으로 입력 받아서, 배열의 갯수를 만들 수 없음.

// 문법
var arrayName [arraySize]dataType

// 배열 크기 지정
var nums [5]int

// 아이템의 갯수에 맞춰 알아서 맞는 크기의 배열 생성, ...을 써야함.
nums := [...]int{2, 3, 4}
// !!!중요!!!  
// []int{2, 3, 4} 처럼 ... 없이 쓰는건 배열 생성이 아니라, 슬라이스 생성임 !!!!

// 배열의 크기를 지정하고, 아이템은 덜 넣을 수도 있음. 많이 넣으면 안됨.
nums := [5]int{1, 2, 3, }

// 배열은 value type임. 그러므로 배열에 배열을 할당하면 복사됨.
arr1 := [5]int{ 1, 2, 3 }
arr2 := arr1
arr2[0] = 9     // arr1 : 12300, arr2 : 92300

// 배열에 배열을 할당하면 복사 되어야 하므로, 배열의 크기가 서로 다르면 할당이 될 수 없음
arr1 := [5]int{1, 2, 3}
arr2 := [10]int{1, 2}
arr2 = arr1 // 배열 크기가 달라 컴파일 에러 발생. 배열의 크기가 작고, 크고의 문제가 아님.

//////////////////////////////////////////////////////////////
// 슬라이스
// 배열과 달리 고정이 아님. ArrayList 같은거임. 근데 이게 len이랑 cap 이라는게 있어 짜증남.
// 즉, 제대로 이해하지 못하면 무슨일이 생기는지 알 수 없으니 찬찬히 읽어 볼 것.
//
// 슬라이스 생성방법
s := make([]int, 3, 5)  // 길이가 3이고 용량이 5인 슬라이스 생성
s := []int{7,8,6}       // 길이가 3이고 용량이 3인 슬라이스 생성

///////////////////////////
s1 := make([]int, 3, 5)  // 길이가 3이고 용량이 5인 슬라이스 생성
s2 := append(s1, 10, 11) // s1에 아직 2개의 빈칸이 있고 거기에 10과 11을 넣고 s2가 그걸 가리킴
// s2는 길이가 5 용량도 5. 아직은 같은 데이터를 바라 보고 있음.
s1[0] = 99  // s1과 s2가 같은 데이터를 보고 있으므로, s1[0] 에 99를 넣으면, s2[0]도 99로 변함.
// 다만 s1은 len이 3이므로 99, 0, 0 만 봄
// 반면 s2의 len은 5이므로 99, 0, 0, 10, 11을 볼 수 있음

//// 여기서 중요!!!
s3 := append(s2, 12) // s2의 길이가 5이고 용량이 5이니, 12를 넣을 수 없는 상황임.
// 그러므로 s2의 데이터를 새 공간으로 복사하고 거기에 12를 추가. s3가 새 공간을 보게 됨.
// s3의 len은 6이 되고, cap은 10이 됨 ( 새 슬라이스가 생성될때 cap이 적절히 늘어날 꺼임 )
s3[1] = 55     // s3는 새공간이므로 s3[1]에 55를 넣는다고 해도, s1[1], s2[1]에는 영향 없음
// s1 = 99 0 0  , s2 = 99 0 0 10 11 , s3 = 99 55 0 0 10 11 12

///////////////////////////////////////
// 슬라이스의 일부를 새로 포인팅할 수 있는 슬라이스 만들기
s1 := []int{7, 8, 6, 5, 2}   // 길이가 5이고 용량이 5인 슬라이스 생성
ns = s1[2:4] //  s1의 index 2에서 4전까지(즉, index 2, 3)로 슬라이드(포인팅) 생성. ns는 5 6 임.
ns[1] = 99 // ns는 s1을 포인팅하고 있으므로, ns[1]도 99가 되고, s1[3]도 99가 된다.

///////////////////////////////////////
// 슬라이스의 상태가 관리가 어려우니깐 걍 복사해서 쓰면 편하겠지? 
// append와 ... 으로 풀어줘서 복사.
s2 := append([]int{}, s1...)

// copy로 복사
s_5_5 := []int{ 1, 2, 3, 4, 5 }
s_3_7 := make([]int, 3, 7)
s_7_7 := make([]int, 7, 7)
s3count := copy( s_3_7, s_5_5 ) // s_3_7이 1, 2, 3 으로 채워짐, return 복사된 갯수 3
s7count := copy( s_7_7, s_5_5 ) // s_7_7이 1, 2, 3, 4, 5, 0, 0 으로 채워짐. return 복사된 갯수 5&lt;/code&gt;&lt;/pre&gt;
&lt;h3 style=&quot;color: #000000;&quot; 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;/li&gt;
&lt;li&gt;Golang은 클래스의 상속관계가 없다. 하지만 interface Type으로 메소드를 호출할 수 있는 방법이 있다. 덕 타이핑이라는 방법이다. 덕 타이핑은 옛날 영국시인이 말했던 &quot;만약 어떤 새가 오리처럼 걷고, 오리처럼 꽥꽥거리면 그것은 오리다.&quot; 라는 개념이다.&lt;/li&gt;
&lt;li&gt;명시적인 상속관계로 부모 interface를 구현하는게 아니라, 그냥 부모 interface의 메소드를 구현하면, 부모 interface로 호출할 수 있다.&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;Closeable 라는 인터페이스에 Close() 라는 메소드가 정의되어 있다.&lt;/li&gt;
&lt;li&gt;Door라는 struct가(Door Type의 리시버) 호출 할 수 있도록 Close()&amp;nbsp; 메소드를 구현했다. - func ( d Door ) Close() { ... }&lt;/li&gt;
&lt;li&gt;Window라는 struct가(Window Type의 리시버) 호출 할 수 있도록 Close() 메소드를 구현했다. - func ( w Window ) Close() { ... }&lt;/li&gt;
&lt;li&gt;이제 Door struct나 Window struct를 Closeable 변수에 할당 할 수 있고, 부모 Type의 변수를 이용해 Close() 를 호출 할 수있다.&lt;/li&gt;
&lt;/ul&gt;
&lt;/li&gt;
&lt;li&gt;이렇게 되면 &quot;정보를 나타내는 구조체&quot;와 그에 어울리는 &quot;메소드&quot;를 나눠서 별개로 구현할 수 있다.&lt;/li&gt;
&lt;li&gt;Golang의 덕타이핑 방법과, Java의 상속 관계를 이용한 메소드 구현 방법의 차이점을 보자.
&lt;ul style=&quot;list-style-type: disc;&quot; data-ke-list-type=&quot;disc&quot;&gt;
&lt;li&gt;Java : Field만 있는 class를 만들때 final class로 만들어 버리면, 상속 받을 수 없기 때문에, 메소드 구현 불가.&lt;/li&gt;
&lt;li&gt;Golang
&lt;ul style=&quot;list-style-type: disc;&quot; data-ke-list-type=&quot;disc&quot;&gt;
&lt;li&gt;Field만 있는 struct는 그냥 두고, struct에 맞는 메소드를 &quot;별개&quot;로 구현 가능함.&lt;/li&gt;
&lt;li&gt;그렇기 때문에 Field만 있는 struct를 내가 수정할 수 없는 상황이 되어도, 메소드를 구현 가능함.&lt;/li&gt;
&lt;li&gt;심지어 내가 필요한 인터페이스와, 내가 필요한 메소드를 상속관계를 고민하지 않고 추가 가능.&lt;/li&gt;
&lt;/ul&gt;
&lt;/li&gt;
&lt;/ul&gt;
&lt;/li&gt;
&lt;li&gt;인터페이스, 메소드, 덕타이핑에 관련 예제는 아래를 참고하자. ( ChatGPT 에게 만들어 달라고 했음. ㅋ )&lt;/li&gt;
&lt;/ul&gt;
&lt;pre id=&quot;code_1686834941939&quot; class=&quot;go&quot; data-ke-language=&quot;go&quot; data-ke-type=&quot;codeblock&quot;&gt;&lt;code&gt;type Closeable interface {
    Close()
}

type Door struct {
    IsOpen bool
}
func (d *Door) Close() {
    d.IsOpen = false
}

type Window struct {
    IsOpen bool
}
func (w Window) Close() {
    w.IsOpen = false
}

func CloseAll(items []Closeable) {
    for _, item := range items {
        item.Close() // 포인터타입 &amp;amp;Door와 값타입 Window를 *나 &amp;amp;를 사용하지 않고 호출하네??
    }
}

func main() {
    door := &amp;amp;Door{IsOpen: true}
    window := Window{IsOpen: true}

    CloseAll([]Closeable{door, window})
}&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;위 예제에서 아주 중요한 것에 대해 설명하지 않았다. 코드의 마지막 main 함수를 보면 있는 door는 &amp;amp;, 즉 포인터형 변수이고, window는 값형 변수이다. 실제 메소드의 정의를 봐도 알겠지만 Door는 포인터형 리시버인 (d *Door) 를 사용하고 있고, Window는 값형 리시버인&amp;nbsp; (w Window) 를 사용하고 있다.&lt;/li&gt;
&lt;li&gt;하지만, 각 메소드의 실제 구현 코드를 확인해 보면, 값타입과 포인터 타입을 적절히 구분하기 위해서 * 나 &amp;amp; 를 사용해 코드를 짜지 않고, 걍 d 나 w를 바로 사용하고 있다.이건 Golang 이 알아서 적절히 *나 &amp;amp;가 붙은것 처럼 동작 시켜줘서 가능한거다. 그러니 걱정하지 말자.&lt;/li&gt;
&lt;/ul&gt;
&lt;h4 data-ke-size=&quot;size20&quot;&gt;인터페이스 좀 더 정리&lt;/h4&gt;
&lt;pre id=&quot;code_1686838226929&quot; class=&quot;go&quot; style=&quot;background-color: #f8f8f8; color: #383a42; text-align: start;&quot; data-ke-language=&quot;go&quot; data-ke-type=&quot;codeblock&quot;&gt;&lt;code&gt;///////////////////////////////////////////////////////////////////
// Reader, Writer, Closer 인터페이스를 만들고 이걸 모아둔 File 인터페이스 만들기
type Reader interface {
    Read(p []byte) (n int, err error)
}

type Writer interface {
    Write(p []byte) (n int, err error)
}

type Closer interface {
    Close() error
}


// 아래 코드는 마치 Java에서 interface File extends Reader, Writer, Closer 한것과 같이 동작한다.
type File interface {
    Reader
    Writer
    Closer
    Get() string // 이렇게 File만을 위한것도 따로 추가 가능.
    Close() error // Closer 인터페이스 메소드와 시그니처가 같으므로 합쳐짐. 시그니처가 다르면 에러남.
}

///////////////////////////////////////////////////////////////////
// 구조체 정의, 이 Type을 리시버로 하는 Get, Read, Write, Close 메소드를 구현할 꺼임
type MyFile struct {
    name string
}

///////////////////////////////////////////////////////////////////
// File 인터페이스를 구현하면 Reader, Writer, Closer 인터페이스를 모두 구현하게 될거다.
func (f *MyFile) Get() string {
    return f.name
}

func (f *MyFile) Read(p []byte) (n int, err error) {
    fmt.Printf(&quot;%s is reading\n&quot;, f.name)
    return 0, nil
}

func (f *MyFile) Write(p []byte) (n int, err error) {
    fmt.Printf(&quot;%s is writing\n&quot;, f.name)
    return 0, nil
}

func (f *MyFile) Close() error {
    fmt.Printf(&quot;%s is closed\n&quot;, f.name)
    return nil
}

func main() {
    f := &amp;amp;MyFile{&quot;MyFile&quot;}

    // MyFile 인스턴스는 File, Reader, Writer, Closer 인터페이스를 만족하므로
    // 이 인터페이스들의 메서드들을 직접 호출할 수 있습니다.
    var fi File = f
    var r Reader = f
    var w Writer = f
    var c Closer = f
    
    fmt.Printf(&quot;Processing %s...\n&quot;, fi.Get())
    r.Read(nil)
    w.Write(nil)
    c.Close()
}&lt;/code&gt;&lt;/pre&gt;
&lt;h4 data-ke-size=&quot;size20&quot;&gt;defer, error, panic, recover&lt;/h4&gt;
&lt;pre id=&quot;code_1686843247305&quot; class=&quot;go&quot; data-ke-language=&quot;go&quot; data-ke-type=&quot;codeblock&quot;&gt;&lt;code&gt;//////////////////////////////////////////////////////////////
// defer - 함수나 메소드를 벗어 나기 전에 실행됨. 예외나 에러가 발생해도 defer는 실행됨
// &quot;미리&quot; 적어둬도 알아서 실행되니 stream 따위를 open 한 다음줄에 적어 두면 좋음.
// 
// 아래에 예제는 1 3 2 순으로 출력된다. defer는 메소드를 벗어날때 실행되기 때문이다.
func main() {
    fmt.Println(&quot;1&quot;)
    defer fmt.Println(&quot;2&quot;)
    fmt.Println(&quot;3&quot;)
}

// 만약 함수나 메소드에 여러개의 defer가 있으면, 마지막에 있는 defer 부터 실행됨
// 아래 예제는 2가 출력되고, 1이 출력된다.
func main() {
	defer fmt.Println(&quot;1 defer&quot;)
	defer fmt.Println(&quot;2 defer&quot;)
}

// 주로 사용할 만한 예제는 파일을 열고 닫을때 사용하는 defer 일꺼다.
func main() {
	file, err := os.Open(&quot;test.txt&quot;)
	if err != nil {
		log.Fatal(err)
	}
	defer file.Close()  // 까 먹지 않게, 열자 마자 나중에 닫을꺼라고 적어 두자.

	bytes, err := ioutil.ReadAll(file)
	if err != nil {
		log.Fatal(err)
	}
	fmt.Println(string(bytes))
}



//////////////////////////////////////////////////////////////
// error - 함수 실행후 제대로 실행되지 않았음을 표시한다.
// errors.New 에러를 생성하거나 fmt.Errorf 를 이용해서 생성할 수 있음.
// 주로 리턴하는 할때 제일 마지막에 error 를 포함시킨다.

// MySqrt 함수는 제곱근을 계산합니다. 입력 값이 음수라면 에러를 반환합니다.
func MySqrt(x float64) (float64, error) {
	if x &amp;lt; 0 {
		return 0, errors.New(&quot;can't take square root of negative number&quot;)
        // fmt.Errorf(&quot;can't take square root of negative number&quot;) 로 생성해도 됨.
	}

	return x, nil
}

func main() {
	_, err := MySqrt(-1)
	if err != nil {
		fmt.Println(&quot;Error:&quot;, err)
	}
}


// error의 인터페이스인 Error() string만 구현하면 사용자 정의 에러도 만들 수 있다.
type MyError struct {
	Msg  string
	Code int
}

func (e MyError) Error() string {
	return fmt.Sprintf(&quot;MyError: %s, Code: %d&quot;, e.Msg, e.Code)
}

func doSomething() error {
	// 에러 발생 시 사용자 정의 에러를 반환
	return MyError{&quot;Something went wrong&quot;, 123}
}

err := doSomething()
if err != nil {
    switch e := err.(type) { // 에러의 타입을 확인해서, 내가 정의한 에러면 따로 처리 가능.
    case MyError:
        fmt.Println(&quot;Handling MyError:&quot;, e.Msg, e.Code)
    default:
        fmt.Println(&quot;Unknown error:&quot;, err)
    }
}


//////////////////////////////////////////////////////////////
// panic : 실행도중 문제가 생기면, interface{} 타입, 즉 모든 type의 정보를 throw 시킬 수 있다.
// recover : throw된 panic을 잡아서, panic이 보내준 정보를 받아 이런저런 처리를 할 수 있다.
// 
// panic이 발생하면 함수를 벗어날꺼고, 그럼 defer가 실행될거다.
// defer를 이용해 recover를 실행할 수 있도록 해 두면, panic이 던진 문제를 적절히 잡아서 해결 할 수 있다.

func main() {
    // defer로 함수를 하나 동적으로 생성해 지정해 주자.
    defer func() { 
        // 만약 painc이 발생했다면, recover()의 return 값이 있을꺼다. 그걸 보고 예외처리를 하자.
        if r := recover(); r != nil {
            switch t := r.(type) {
            case string:
                fmt.Println(&quot;String panic:&quot;, t)
            case func() int:  // 심지어 painc이 return type이 있는 function도 던질 수 있음.
                i := r.(func() int)() // 요런식으로, 타입 변환 후에 실행할 수도 있다.
                fmt.Println(i)
                fmt.Println(&quot;Function panic:&quot;, t)
            case PanicInfo:
                fmt.Println(&quot;PanicInfo panic:&quot;, t.Location, t.Problem)
            default:
                fmt.Println(&quot;Unknown panic:&quot;, t)
            }
        }
    }()

    // Panic with a string
    panic(&quot;A string panic&quot;)

    // Panic with a function
    panic(func() int { fmt.Println(&quot;An anonymous function&quot;); return 1 })

    // Panic with a struct
    panic(PanicInfo{&quot;main&quot;, &quot;A struct panic&quot;})
}&lt;/code&gt;&lt;/pre&gt;
&lt;p style=&quot;text-align: center;&quot; data-ke-size=&quot;size18&quot;&gt;&lt;span style=&quot;background-color: #ffc1c8;&quot;&gt;지금부터는 개념 설명이 많이 필요한 부분이라, 걍 대충 정리해야 겠다. 시간이 너무 오래 걸리네. ㅋ.&lt;/span&gt;&lt;/p&gt;
&lt;h4 data-ke-size=&quot;size20&quot;&gt;고루틴, 뮤텍스&lt;/h4&gt;
&lt;ul style=&quot;list-style-type: disc;&quot; data-ke-list-type=&quot;disc&quot;&gt;
&lt;li&gt;자바에서는 동시에 무언가를 실행 시키려고 하면 Thread라는것을 만들어야 한다. 이 때 생성되는 Thread는 무겁다.&amp;nbsp; 그냥 무겁다고 생각하자. 그런데 고루틴은 1개의 Thread에 n개의 작업이 병렬로 실행될 수 있다. 라고 썼는데.. 친절하지 않은것 같아 ChatGPT에게 물어 본거를 간단히 몇개만 써 둔다.
&lt;ul style=&quot;list-style-type: disc;&quot; data-ke-list-type=&quot;disc&quot;&gt;
&lt;li&gt;Java 쓰레드는 JVM에 의해서 관리. 하지만 OS 의존적이다. ( 그래서 Windows랑 Linux 랑 다를 수 있음 )&lt;/li&gt;
&lt;li&gt;Java는 한개의 Thead가 OS에서 관리하는 1개의 쓰레드다.&amp;nbsp; 하지만 고루틴은 직접 스케줄링해서 1개의 OS thread에 n개의 고루틴이 들어 갈 수 있다. 즉, CPU 단에서 발생하는 스레드 스위칭이 적을 수 밖에.&lt;/li&gt;
&lt;li&gt;Java는 한개의 Thread를 만들때 512KB ~ 1MB의 stack을 생성하나, 고루틴은 2~8KB 만 생성.&lt;/li&gt;
&lt;li&gt;그래서 고루틴은 하나의 프로세스에서 수천, 수백만개의 고루틴도 실행 될 수 있다.&lt;/li&gt;
&lt;/ul&gt;
&lt;/li&gt;
&lt;li&gt;참고로 Java도 19부터 Virtual Thread라는게 Preview로 들어 왔는데, 요 놈은 가볍다고 한다.&lt;/li&gt;
&lt;li&gt;각설하고 예제나 보자.&lt;/li&gt;
&lt;/ul&gt;
&lt;pre id=&quot;code_1686846207029&quot; class=&quot;go&quot; data-ke-language=&quot;go&quot; data-ke-type=&quot;codeblock&quot;&gt;&lt;code&gt;///////////////////////////////////////////////////////
// gorutine은 병렬로 실행될 함수명을 적으면 끝이다.
func printNumber() {
	for i := 1; i &amp;lt;= 5; i++ {
		time.Sleep(250 * time.Millisecond)
		fmt.Printf(&quot;Number: %d\n&quot;, i)
	}
}

func printLetters() {
	for i := 'a'; i &amp;lt;= 'e'; i++ {
		time.Sleep(400 * time.Millisecond)
		fmt.Printf(&quot;Letter: %c\n&quot;, i)
	}
}

func main() {
	go printNumber()   // 숫자 출력을 병렬로 실행
	go printLetters()  // 글자 출력을 병렬로 실행, 두 함수의 sleep시간 차이로 적절히 섞여서 출력된다.

    // 고루틴은 끝나지 않았지만, main 함수를 실행시키는 gorutine이 끝나면 프로세스가 종료된다.
    // 위 두개의 go rutine이 끝날때까지 main이 종료되지 않도록 하자.
    time.Sleep(3 * time.Second)
    
    // main 함수도 gorutine이라면, 왜 얘는 끝날때까지 기다리냐고? 그냥 그렇게 만들어 뒀기 때문이다. ㅎ.

}

///////////////////////////////////////////////////////
// gorutine이 끝날때까지 Sleep 하는건 말도 안되는 짓이니 sync.WaitGroup으로 좀 더 똑똑하게 해 보자.
// Java에 있는 CountDownLatch 같은거라고 생각하면 편하다.
// 
func worker(id int, wg *sync.WaitGroup) {
    // 함수를 벗어날때 waitGroup의 count에서 1개를 빼 준다.
	defer wg.Done()

    fmt.Printf(&quot;Worker %d starting\n&quot;, id)
	time.Sleep(time.Second)
	fmt.Printf(&quot;Worker %d done\n&quot;, id)
}

func main() {
    // waitGroup을 만들어 기다릴 수 있는 기능을 쓰자.
    // waitGroup에 카운트를 더했다(Add) 뺐다(Done) 하자.
    // wg.Wait()로 기다릴 수 있고, count가 0 이 되면 모든 작업이 끝났으니 다음으로 진행된다.
	var wg sync.WaitGroup

	for i := 1; i &amp;lt;= 5; i++ {
		wg.Add(1)          // waitGroup 에서 count 해야할 갯수를 추가한다.
		go worker(i, &amp;amp;wg) // wg를 포인터로 넘겨야 한다. 그래야 고루틴마다 &quot;상태 공유&quot;를 할 수 있다.
	}

    // waitGroup 만들어 둔게, Done 될 때까지 여기서 대기한다.
	wg.Wait()
}



///////////////////////////////////////////////////////
// Mutex 로 동시성 문제 해결하기. 동시성 문제에 대해서는 굳이 설명 안함.
// Mutex로 Lock을 잡고, 처리 한 뒤에 Unlock() 수행하면 됨. 끝.
// 
var x  = 0

func increment(wg *sync.WaitGroup, m *sync.Mutex) {
	m.Lock()
	defer m.Unlock()   // 세상에! 여기다 defer를 쓰면, Unlock을 하지 않는 실수를 줄일수 있다.
	x = x + 1
	wg.Done()   
}

func main() {
	var w sync.WaitGroup
	var m sync.Mutex

	for i := 0; i &amp;lt; 1000; i++ {
		w.Add(1)		
		go increment(&amp;amp;w, &amp;amp;m)
	}
	w.Wait()
	fmt.Println(&quot;final value of x&quot;, x)
}&lt;/code&gt;&lt;/pre&gt;
&lt;h4 data-ke-size=&quot;size20&quot;&gt;채널, 컨텍스트&lt;/h4&gt;
&lt;ul style=&quot;list-style-type: disc;&quot; data-ke-list-type=&quot;disc&quot;&gt;
&lt;li&gt;고 언어에서는 채널이 매우 중요한 개념이다. 하지만 설명하기 귀찮으므로, 예제만 고고싱.&lt;/li&gt;
&lt;/ul&gt;
&lt;pre id=&quot;code_1686848493329&quot; class=&quot;go&quot; data-ke-language=&quot;go&quot; data-ke-type=&quot;codeblock&quot;&gt;&lt;code&gt;////////////////////////////////////////////////////////////////////
// 채널은 특정 Type이 들어 갈 수 있는 BlockingQueue라고 생각하면 편하다.

// 채널은 아래와 같은 형식으로 생성한다.
ichannel := make(chan int)

// 채널이 몇개의 buffer를 가질지도 정할 수 있다.
// buffer만큼까지는 바로 집어 넣을 수 있고, 
// buffer가 가득차면 넣는애도 buffer가 빌때까지 &quot;대기&quot;해야 한다.
ch := make(chan int, 2) // 두개의 buffer를 가진다.
ch &amp;lt;- 1  // 첫번째꺼 넣고
ch &amp;lt;- 2  // 두번째꺼 넣어 buffer가 가득 찼음.
ch &amp;lt;- 3  // 원래라면 이미 buffer가 가득 찼으므로 대기해야함.
// 근데 go는 똑똑하게 buffer를 비워줄 애가 없다는것을 알고 있어서, deadlock 이 생겼다면서 죽음.

// 채널을 닫으려면 아래와 같이 close를 사용하면 된다.
close(ichannel)

////////////////////////////////////////////////////////////////////
// 채널과 go루틴을 활용하는 간단한 예제
// string들이 보관될 수 있는 채널(Queue) 생성
messageChan := make(chan string)

// 함수를 하나 go루틴으로 실행한다.
// string이 보관될 채널에 &quot;Hello, Channel!&quot; 을 넣는다.
go func() {
    messageChan &amp;lt;- &quot;Hello, Channel!&quot;
}()

// string이 보관된 채널에서 값을 읽는다.
// 위 함수가 실행되지 않았다면 messageChan 에 아무런 값이 없을 것이므로, &quot;대기&quot; 한다.
// 위 함수가 실행되면 messageChan에 &quot;Hello, Channel!&quot; 가 있을 것이므로, 꺼내온다.
message := &amp;lt;-messageChan
fmt.Println(message) // &quot;Hello, Channel!&quot; 출력

////////////////////////////////////////////////////////////////////
// 채널에서 데이터가 올때마다 처리하기.. ragne 사용

func produce(c chan&amp;lt;- int) {
	for i := 0; i &amp;lt; 10; i++ {
		time.Sleep(100 * time.Millisecond)  // sleep for 100 milliseconds
		c &amp;lt;- i
	}
	close(c)
}

func main() {
	c := make(chan int)
    
	// c 채널에 일정시간마다 값을 추가하는 채널 고루틴 실행.
	go produce(c)

	// 아래와 같이 하면 채널에 값이 채워 질 때마다 v 에 값을 할당하고 실행된다.
    // c 채널이 close 될때까지 실행된다.
	for v := range c {
		fmt.Println(v)
	}
}

////////////////////////////////////////////////////////////////////
// 채널이 2개 이상이라면, 하나의 채널에서 데이터가 올때까지 기다리고 있으면
// 나머지 채널에서 데이터가 도착했더라도, 받을 수 없는 문제가 있다.
// 이 때는 select를 사용하면, 각 채널 별로 오는 데이터를 동시에 기다릴 수 있다
// 동시에 기다릴 수 있는거지, 동시에 처리할 수 있는것은 아니다.
// 만약 동시에 처리하고 싶다면, select case에서 또 go 루틴을 만들면 될 거다.
func sender(ch chan&amp;lt;- string, msg string, waitTime time.Duration) {
	time.Sleep(waitTime)
	ch &amp;lt;- msg
}

func main() {
	ch1 := make(chan string)
	ch2 := make(chan string)

	go sender(ch1, &quot;Message from channel 1&quot;, 2*time.Second)
	go sender(ch2, &quot;Message from channel 2&quot;, 1*time.Second)

	for i := 0; i &amp;lt; 2; i++ {
		select {
		case msg1 := &amp;lt;-ch1:
			fmt.Println(msg1)
		case msg2 := &amp;lt;-ch2:
			fmt.Println(msg2)
		}
	}
}

////////////////////////////////////////////////////////////////////
// 채널과 select, tick을 사용해서 일정 시간마다 뭔가 실행하게 하기
// time.Tick이나 time.After는 정해진 시간에 채널을 생성해 줌.
// 단, time.Tick는 종료 방법이 없으므로 주의해서 사용해야 함.
// Ticker는 조정이 가능하므로, 상황에 맞춰서 쓰자.

func main() {
	ticker := time.NewTicker(500 * time.Millisecond)
	defer ticker.Stop()

	done := time.After(5 * time.Second) // 5초후에 이벤트를 발생시킬 채널 생성.

	for {
		select {
        // done이라는 채널에서 값이 오면 처리 됨. 
        // 아래 코드에서는 현재 시간을 안 받았는데 case t := &amp;lt;-done 을 쓰면 현재 시간 알 수 있음.
		case &amp;lt;-done:  
			fmt.Println(&quot;Done!&quot;)
			return
		case t := &amp;lt;-ticker.C: // ticker.C 에 현재 시간이 들어 있음.
			fmt.Println(&quot;Current time: &quot;, t)
		}
	}
}


////////////////////////////////////////////////////////////////////
// 컨텍스트 : 동시성을 제어해 줌. 
// 쉽게 말하면 고루틴에 정보를 전달하거나, 작업을 취소하거나, Timeout 등이 가능함.

func main() {
	// 1초후에 ctx.Done 채널에 값을 넣어 주는 컨텍스트 생성
    // 파리미터의 context.Background() 는 부모 ctx 다. 
    // 지금 생성한 ctx를 다음 context 생성할 때 넣어 주면, 추가적으로 적용된다. 아래에 예제 있음.
	ctx, cancel := context.WithTimeout(context.Background(), 1*time.Second)
	defer cancel()

	ch := make(chan int)

	// 2초 후에 ch 채널에 숫자 1 추가하는 고 루틴 실행.
	go func() {
		time.Sleep(2 * time.Second)
		ch &amp;lt;- 1
	}()

    // ctx.Done() 이 반환하는 채널에는 1초후에 데이터가 채워질거고,
    // ch 채널에는 2초후에 숫자 1 데이터가 채워질 것이므로, 
    // 결과저으로는 ctx.Done() 부분이 실행되면서 Timeout 이 출력됨.
	select {
	case &amp;lt;-ctx.Done():
		fmt.Println(&quot;Timeout&quot;)
	case &amp;lt;-ch:
		fmt.Println(&quot;Success&quot;)
	}
}


// 이런 컨텍스트는 아래와 같은게 있음
// context.WithDeadline : 언제가 되면 Done() 채널에 값 추가. 
// context.WithTimeout : 얼마가 지나면 Done() 채널에 값 추가. WithDeadline(parent, time.Now().Add(timeout)) 과 동일함.
// context.WithCancel : cancel 함수를 호출하면 Done() 채널에 값 추가.
// context.WithValue : context에 map 따위로 데이터 추가. 걍 고루틴이 실행되는 함수의 파라미터로 해도 될것 같지만, 이게 표준이래...


///////////////////////////////////////////////////////////
// context.WithCancel 예제
func operation(ctx context.Context) {
	for {
		select {
		case &amp;lt;-ctx.Done():
			fmt.Println(&quot;Operation stopped.&quot;)
			return
		default:
			fmt.Println(&quot;Operation in progress...&quot;)
			time.Sleep(1 * time.Second)
		}
	}
}

func main() {
	ctx, cancel := context.WithCancel(context.Background())

	go operation(ctx)

	time.Sleep(5 * time.Second)
	fmt.Println(&quot;About to cancel operation...&quot;)
	cancel()
	time.Sleep(1 * time.Second)
}


///////////////////////////////////////////////////////////
// context.WithValue 예제
func doWork(ctx context.Context) {
	// Check if userID exists in context
	if v := ctx.Value(&quot;userID&quot;); v != nil {
		fmt.Printf(&quot;Doing work for user %s\n&quot;, v.(string))

		// Simulate work
		time.Sleep(time.Second)
	} else {
		fmt.Println(&quot;userID not found in context&quot;)
	}
}

func main() {
	// 아래와 같이 하면 n개의 key vlaue를 추가 가능함.
	ctx := context.WithValue(context.Background(), &quot;userID&quot;, &quot;user123&quot;)
	ctx = context.WithValue(ctx, &quot;userName&quot;, &quot;Chan&quot;)

	// Do work for specific user
	go doWork(ctx)

	// Wait for work to finish
	time.Sleep(2 * time.Second)
}

///////////////////////////////////////////////////////////
// 여러개의 context를 하나로 만들어서 전달하고 싶은 경우 아래와 같이 할 수 있음
ctx := context.Background()
ctx = context.WithValue(ctx, &quot;key1&quot;, &quot;value1&quot;)
ctx = context.WithValue(ctx, &quot;key2&quot;, &quot;value2&quot;)

// Set a timeout for the context
ctx, cancel := context.WithTimeout(ctx, time.Second*10)
defer cancel()

// The context will now have both the key-value pairs and the timeout&lt;/code&gt;&lt;/pre&gt;
&lt;h4 style=&quot;color: #000000;&quot; data-ke-size=&quot;size20&quot;&gt;모듈, 패키지&lt;/h4&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;모듈, 패키지 부분은 당연히 책에서는 설명 되어 있으나, 나는 여러번 읽었으나 잘 이해하기가 어려웠다. 특히 이전에 사용하던 GOPATH 와 모듈의 도입, 그리고 github에 있는 모듈을 로컬로 연결해 주기 위해서 이런저런것을 한다는것 자체가 잘 이해가 안 됐다. 머리가 말랑말랑하지 못한가 보다. ㅎ.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;ChatGPT에게 모듈과 패키지에 대해서 물어 봤고, 그 중에서 정리할 만한것만 대충 적어 둔다. 그런데 ChatGPT 가 예전 정보만 알고 있어서, 지금 이렇게 정리 되는글이 맞는지 모르겠다. 따로 확인하지 않았으니 그렇게 알자. 그리고 이 글만 3일째 적고 있어서 ;; 이제 그만 적을려고...&amp;nbsp;&lt;/p&gt;
&lt;h4 data-ke-size=&quot;size20&quot;&gt;Go 모듈&lt;/h4&gt;
&lt;ul style=&quot;list-style-type: disc;&quot; data-ke-list-type=&quot;disc&quot;&gt;
&lt;li&gt;Go 1.11 부터 적용됨, 그 전에는 GOPATH 따위를 사용했음.&lt;/li&gt;
&lt;li&gt;Go 코드들의 묶음. 각 모듈은 하나 이상의 패키지로 구성. 특정 디렉토리에 위치함. 경로가 모듈이름 결정.&lt;/li&gt;
&lt;li&gt;go mod init MODULE_NAME 을 통해서 모듈 생성 가능.일반적으로 github 경로를 사용&lt;/li&gt;
&lt;li&gt;go&amp;nbsp;mod&amp;nbsp;init&amp;nbsp;github.com/yourusername/yourproject&lt;/li&gt;
&lt;li&gt;go get github.com/yourusername/yourproject@v1.2, @latest 따위로 모듈을 다운로드 받음.&lt;/li&gt;
&lt;li&gt;다만, github.com/yourusername/yourproject 에 소스코드가 반드시 있어야 하는것은 아님&lt;/li&gt;
&lt;li&gt;해당 경로의 HTML에 &amp;lt;meta name=&quot;go-import&quot; content=&quot;import-prefix vcs repo-root&quot;&amp;gt; 확인하기도 함.&lt;/li&gt;
&lt;li&gt;go mod tidy : 로컬에 다운로드 받았지만 코드에서 사용하지 않는 모듈들을 제거할 수 있음.&lt;/li&gt;
&lt;li&gt;go mod vendor : 온라인에 있는 모듈은 전부 local /vender 디렉토리에 다운로드 받음. 빌드 할 때도 go build -mod=vendor 를 사용해 로컬에 다운로드 받은 모듈로 빌드 할 수 있음.&lt;/li&gt;
&lt;/ul&gt;
&lt;h4 data-ke-size=&quot;size20&quot;&gt;패키지&lt;/h4&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;이건 코드와 같이 보는게 좋으니, ChatGPT 내용을 걍 이미지로 복붙한다. ㅎ.&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;757&quot;&gt;&lt;span data-url=&quot;https://blog.kakaocdn.net/dn/FnIdE/btsj3npFRMc/WYETXHg885KyOCAQVticA0/img.png&quot; data-phocus=&quot;https://blog.kakaocdn.net/dn/FnIdE/btsj3npFRMc/WYETXHg885KyOCAQVticA0/img.png&quot;&gt;&lt;img src=&quot;https://blog.kakaocdn.net/dn/FnIdE/btsj3npFRMc/WYETXHg885KyOCAQVticA0/img.png&quot; srcset=&quot;https://img1.daumcdn.net/thumb/R1280x0/?scode=mtistory2&amp;fname=https%3A%2F%2Fblog.kakaocdn.net%2Fdn%2FFnIdE%2Fbtsj3npFRMc%2FWYETXHg885KyOCAQVticA0%2Fimg.png&quot; onerror=&quot;this.onerror=null; this.src='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png'; this.srcset='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png';&quot; loading=&quot;lazy&quot; width=&quot;669&quot; height=&quot;757&quot; data-origin-width=&quot;669&quot; data-origin-height=&quot;757&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;691&quot; data-origin-height=&quot;537&quot;&gt;&lt;span data-url=&quot;https://blog.kakaocdn.net/dn/bS3Q5q/btskaEpgnhz/aeHNTvyvXLs2vk8znV1fb1/img.png&quot; data-phocus=&quot;https://blog.kakaocdn.net/dn/bS3Q5q/btskaEpgnhz/aeHNTvyvXLs2vk8znV1fb1/img.png&quot;&gt;&lt;img src=&quot;https://blog.kakaocdn.net/dn/bS3Q5q/btskaEpgnhz/aeHNTvyvXLs2vk8znV1fb1/img.png&quot; srcset=&quot;https://img1.daumcdn.net/thumb/R1280x0/?scode=mtistory2&amp;fname=https%3A%2F%2Fblog.kakaocdn.net%2Fdn%2FbS3Q5q%2FbtskaEpgnhz%2FaeHNTvyvXLs2vk8znV1fb1%2Fimg.png&quot; onerror=&quot;this.onerror=null; this.src='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png'; this.srcset='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png';&quot; loading=&quot;lazy&quot; width=&quot;691&quot; height=&quot;537&quot; data-origin-width=&quot;691&quot; data-origin-height=&quot;537&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;670&quot; data-origin-height=&quot;617&quot;&gt;&lt;span data-url=&quot;https://blog.kakaocdn.net/dn/YvRec/btsj9C6aFmw/7ciWI857tq6BRZ4SSbnG1K/img.png&quot; data-phocus=&quot;https://blog.kakaocdn.net/dn/YvRec/btsj9C6aFmw/7ciWI857tq6BRZ4SSbnG1K/img.png&quot;&gt;&lt;img src=&quot;https://blog.kakaocdn.net/dn/YvRec/btsj9C6aFmw/7ciWI857tq6BRZ4SSbnG1K/img.png&quot; srcset=&quot;https://img1.daumcdn.net/thumb/R1280x0/?scode=mtistory2&amp;fname=https%3A%2F%2Fblog.kakaocdn.net%2Fdn%2FYvRec%2Fbtsj9C6aFmw%2F7ciWI857tq6BRZ4SSbnG1K%2Fimg.png&quot; onerror=&quot;this.onerror=null; this.src='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png'; this.srcset='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png';&quot; loading=&quot;lazy&quot; width=&quot;670&quot; height=&quot;617&quot; data-origin-width=&quot;670&quot; data-origin-height=&quot;617&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;673&quot; data-origin-height=&quot;470&quot;&gt;&lt;span data-url=&quot;https://blog.kakaocdn.net/dn/SDAd3/btskaBe0bfR/yaqjeskPAjAvpBrKdoCGtk/img.png&quot; data-phocus=&quot;https://blog.kakaocdn.net/dn/SDAd3/btskaBe0bfR/yaqjeskPAjAvpBrKdoCGtk/img.png&quot;&gt;&lt;img src=&quot;https://blog.kakaocdn.net/dn/SDAd3/btskaBe0bfR/yaqjeskPAjAvpBrKdoCGtk/img.png&quot; srcset=&quot;https://img1.daumcdn.net/thumb/R1280x0/?scode=mtistory2&amp;fname=https%3A%2F%2Fblog.kakaocdn.net%2Fdn%2FSDAd3%2FbtskaBe0bfR%2FyaqjeskPAjAvpBrKdoCGtk%2Fimg.png&quot; onerror=&quot;this.onerror=null; this.src='//t1.daumcdn.net/tistory_admin/static/images/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;470&quot; data-origin-width=&quot;673&quot; data-origin-height=&quot;470&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;672&quot; data-origin-height=&quot;526&quot;&gt;&lt;span data-url=&quot;https://blog.kakaocdn.net/dn/OUFHF/btsj54QnWpt/vAu2ndxI7IIS4UI1wHHFn1/img.png&quot; data-phocus=&quot;https://blog.kakaocdn.net/dn/OUFHF/btsj54QnWpt/vAu2ndxI7IIS4UI1wHHFn1/img.png&quot;&gt;&lt;img src=&quot;https://blog.kakaocdn.net/dn/OUFHF/btsj54QnWpt/vAu2ndxI7IIS4UI1wHHFn1/img.png&quot; srcset=&quot;https://img1.daumcdn.net/thumb/R1280x0/?scode=mtistory2&amp;fname=https%3A%2F%2Fblog.kakaocdn.net%2Fdn%2FOUFHF%2Fbtsj54QnWpt%2FvAu2ndxI7IIS4UI1wHHFn1%2Fimg.png&quot; onerror=&quot;this.onerror=null; this.src='//t1.daumcdn.net/tistory_admin/static/images/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;526&quot; data-origin-width=&quot;672&quot; data-origin-height=&quot;526&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;669&quot; data-origin-height=&quot;438&quot;&gt;&lt;span data-url=&quot;https://blog.kakaocdn.net/dn/bNKDLS/btsj7buIeAs/tjppiZJNkyl8ZdyoSZHddK/img.png&quot; data-phocus=&quot;https://blog.kakaocdn.net/dn/bNKDLS/btsj7buIeAs/tjppiZJNkyl8ZdyoSZHddK/img.png&quot;&gt;&lt;img src=&quot;https://blog.kakaocdn.net/dn/bNKDLS/btsj7buIeAs/tjppiZJNkyl8ZdyoSZHddK/img.png&quot; srcset=&quot;https://img1.daumcdn.net/thumb/R1280x0/?scode=mtistory2&amp;fname=https%3A%2F%2Fblog.kakaocdn.net%2Fdn%2FbNKDLS%2Fbtsj7buIeAs%2FtjppiZJNkyl8ZdyoSZHddK%2Fimg.png&quot; onerror=&quot;this.onerror=null; this.src='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png'; this.srcset='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png';&quot; loading=&quot;lazy&quot; width=&quot;669&quot; height=&quot;438&quot; data-origin-width=&quot;669&quot; data-origin-height=&quot;438&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>공부/컴퓨터</category>
      <category>channel</category>
      <category>Duck Typing</category>
      <category>Golang</category>
      <category>gorutine</category>
      <category>mutex</category>
      <category>Tucker의 Go 언어 프로그래밍</category>
      <category>고루틴</category>
      <category>덕타이핑</category>
      <category>뮤텍스</category>
      <category>채널</category>
      <author>찬</author>
      <guid isPermaLink="true">https://blog.ggaman.com/1033</guid>
      <comments>https://blog.ggaman.com/1033#entry1033comment</comments>
      <pubDate>Fri, 16 Jun 2023 03:40:19 +0900</pubDate>
    </item>
    <item>
      <title>[책읽기] Kubernetes Best Practices 쿠버테니스 모범 사례 - 오라일리, 한빛미디어</title>
      <link>https://blog.ggaman.com/1032</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;2년전쯤부터 한컴싸인(&lt;a href=&quot;https://www.hancomsign.com/&quot; target=&quot;_blank&quot; rel=&quot;noopener&quot;&gt;https://www.hancomsign.com&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;그전 AI를 하던때부터 지금까지 많은 책들을 읽었는데, 정리할 시간이 없어 오랜만에 일찍 퇴근한 김에 읽었던 책들중 몇권을 순차적으로 정리해 본다. 오래전에 읽었던 책들도 있어서, 지금 이 글을 읽는 사람에게는 이미 오래된 책일 수 있이라 도움이 될런지 모르겠지만, 그래도 읽은 티라도 내 보려고 대충 정리해 본다. 그러니 걍 그런가 보다 하자.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;h2 data-ke-size=&quot;size26&quot;&gt;제목 : Kubernetes&amp;nbsp;Best&amp;nbsp;Practices&amp;nbsp;쿠버테니스&amp;nbsp;모범&amp;nbsp;사례&amp;nbsp;-&amp;nbsp;오라일리,&amp;nbsp;한빛미디어&lt;/h2&gt;
&lt;p&gt;&lt;figure class=&quot;imageblock alignCenter&quot; data-ke-mobileStyle=&quot;widthOrigin&quot; data-filename=&quot;kubernetes best practices-min.png&quot; data-origin-width=&quot;505&quot; data-origin-height=&quot;555&quot;&gt;&lt;span data-url=&quot;https://blog.kakaocdn.net/dn/uDvNM/btsjWsj1UVk/sQ8Ex7cZMX8imNYxCOWKPk/img.png&quot; data-phocus=&quot;https://blog.kakaocdn.net/dn/uDvNM/btsjWsj1UVk/sQ8Ex7cZMX8imNYxCOWKPk/img.png&quot;&gt;&lt;img src=&quot;https://blog.kakaocdn.net/dn/uDvNM/btsjWsj1UVk/sQ8Ex7cZMX8imNYxCOWKPk/img.png&quot; srcset=&quot;https://img1.daumcdn.net/thumb/R1280x0/?scode=mtistory2&amp;fname=https%3A%2F%2Fblog.kakaocdn.net%2Fdn%2FuDvNM%2FbtsjWsj1UVk%2FsQ8Ex7cZMX8imNYxCOWKPk%2Fimg.png&quot; onerror=&quot;this.onerror=null; this.src='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png'; this.srcset='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png';&quot; loading=&quot;lazy&quot; width=&quot;505&quot; height=&quot;555&quot; data-filename=&quot;kubernetes best practices-min.png&quot; data-origin-width=&quot;505&quot; data-origin-height=&quot;555&quot;/&gt;&lt;/span&gt;&lt;/figure&gt;
&lt;/p&gt;
&lt;h2 data-ke-size=&quot;size26&quot;&gt;책 읽기&lt;/h2&gt;
&lt;ul style=&quot;list-style-type: disc;&quot; data-ke-list-type=&quot;disc&quot;&gt;
&lt;li&gt;이 책은 초보자용 책이 아니다. 이미 쿠버네티스의 개념과 동작에 대해서 잘 파악하고 있는 사람이 실제 문제들을 어떤식으로 해결할 수 있을지에 대한 정보를 주는 책이다.&amp;nbsp;물론 그렇다고 정답을 주는것은 아니지만, 어떤 점들을 고려해야 하고 어떤식으로 해결 할 수 있을지 정도를 알려 준다.&lt;/li&gt;
&lt;li&gt;Best Practices-모범사례를 살펴보는것의 좋은점은, &quot;이런걸 하고 싶을때는 어떻게 하지?&quot; 라던지, &quot;이런 경우에는 어떻게 문제를 해결하지?&quot; 라던지 혹은 생각하지도 못한 상황에 대해서 미리 생각해 보고, 대비할 수 있게 해 준다는 점이다. 막상 닥치면 또 적당히 알아서 잘 하겠지만, 그래도 &quot;이렇게 해결해도 되는거야?&quot;에 대한 스스로에 대한 의심을 조금 걷어내 줄 수 있다는 점이 좋다.&lt;/li&gt;
&lt;li&gt;책은 설치부터 어드미션 컨트롤러까지 거의 모든 부분을 커버하고 있다. 책 한권에 이걸 다 커버할 수 있나? 라는 생각이 든다면 정확하다. 그렇기 때문에 이 책은 초보자용 책은 아니다.&lt;/li&gt;
&lt;/ul&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;책의 모든 내용을 옮기는것은 문제가 되기도 하고, 나도 그럴 시간도 없으니 이 책에 나오는 주요 내용 몇개만 정리해 보겠다. 그리고 내 경험과 생각도 살짝 추가해 둔다. 만약 아래 내용을 보고 &quot;오~ 그럴듯하게 정리되어 있네?&quot; 싶다면, 책을 구매해서 차근히 읽어 보도록 하자.&lt;/p&gt;
&lt;h2 data-ke-size=&quot;size26&quot;&gt;모범사례 (중 일부...)&lt;/h2&gt;
&lt;ul style=&quot;list-style-type: disc;&quot; data-ke-list-type=&quot;disc&quot;&gt;
&lt;li&gt;개발하는 도중에는 latest tag를 사용해도 되나, 실제로 배포하는 운영환경에서는 명확한 tag를 사용하도록 하자.
&lt;ul style=&quot;list-style-type: disc;&quot; data-ke-list-type=&quot;disc&quot;&gt;
&lt;li&gt;찬 : 이건 사실 CI 쪽에서 잘 해 줘야 한다. 나 같은 경우에는 빌드가 나올때도 빌드번호, 커밋ID, 브랜치이름, 배포환경의 값을 모두 조합해서 tag 이름을 매겼었다. 같은 branch에 같은 commit 이라고 해도, 배포환경이 다른 경우 다른 빌드 결과물이 나올 수 있기 때문이다. 물론 develop branch의 경우에는 tag를 latest로 매겨 로컬 개발환경에서는 누구든 항상 최신의 이미지를 사용할 수 있도록 했다.&lt;/li&gt;
&lt;/ul&gt;
&lt;/li&gt;
&lt;li&gt;사용자 혹은 목적에 따라 namespace를 분리해라. 그래야 관리가 편하다.&lt;/li&gt;
&lt;li&gt;매트릭, 로깅을 잘 관리하자. 쿠버네티스 시스템의 &quot;문제 신호&quot;를 감지 할 수 있고, 상황을 잘 알려(슬랙 따위로 알림) 줄 수 있어야&amp;nbsp; 안정적으로 클러스터를 운영할 수도 있고 웹서비스의 문제점도 잘 파악할 수도 있다. 이런 정보를 수집하고 관리하는 애들은 웹서비스들이 모여 있는 클러스터에 같이 두지 말고, &quot;모니터링 클러스터&quot;를 따로 만들자.
&lt;ul style=&quot;list-style-type: disc;&quot; data-ke-list-type=&quot;disc&quot;&gt;
&lt;li&gt;찬 : 여기서는 쿠버네티스단에서 관리 혹은 추출할 수 있는 정보들만 이야기 하고 있다. 예를들면 Istio 따위를 통해서 트래픽이 전달된다면, 그곳의 출입구만 확인하면 트래픽이 들어 왔다가 나가는데 얼마의 시간이 걸렸는지 혹은 Http라면 어떤 응답을 했는지 따위를 알 수 있을거다. 하지만 웹서비스 레벨의 메트릭은 자동으로 수집되지 않으므로, 필요하다면 웹서비스에서 적절하게 처리해 줘야 한다. 예를 들면, Spring의 경우 actuator를 추가하여 &quot;모니터링 클러스터&quot;에 있는 Prometheus 에게 전달 할 수 있도록 수동으로 설정해 줘야 한다.&lt;/li&gt;
&lt;/ul&gt;
&lt;/li&gt;
&lt;li&gt;configmap 따위를 변경했다고 해서 바로 Pod 에 적용되는게 아니므로, 반드시 Pod를 내렸다 올리는 수준의 작업을 해 줘야 한다.
&lt;ul style=&quot;list-style-type: disc;&quot; data-ke-list-type=&quot;disc&quot;&gt;
&lt;li&gt;찬 : 이건 어찌 생각해 보면 당연한거다. Pod가 뜰때 ENV 값들이 셋팅되고 그 이후에 Pod에 있는 command 가 실행된다. 보통 프로그램을 작성할때 딱 한번만 ENV 값을 읽어오고, 이후에는 그냥 사용만 하도록 만든다. 그러므로 ENV 값을 동적으로 바꾼다고 해도 적용되지 않는 경우가 대부분이다. 만약 이 값이 동적으로 바뀐다면 프로그램이 오동작하는 경우도 매우 만을 것이다.&lt;/li&gt;
&lt;/ul&gt;
&lt;/li&gt;
&lt;li&gt;클러스터에 있는 많은 Node(컴퓨터)들은 각자 상황이 다를 수도 있다. 그러므로 Pod들을 적절한 Node에 배치 될 필요가 있다. 이런걸 강제하고 싶은 경우는 Node에 Label을 주고, nodeSelector 를 이용하거나, 혹은 tolerations 를 사용해 Pod가 제대로 된 Node에 배치 될 수 있도록 조정해 줘야 한다.
&lt;ul style=&quot;list-style-type: disc;&quot; data-ke-list-type=&quot;disc&quot;&gt;
&lt;li&gt;찬 : 예를들어 어떤 컴퓨터는 GPU가 달려 있을 수도 있고, 어떤 컴퓨터는 낮은 버전의 CPU가 달려 있어서, CPU 명령어중 avx2가 지원되지 않을 수도 있다. ( 관련글 : &lt;a href=&quot;http://https://blog.ggaman.com/1028&quot; target=&quot;_blank&quot; rel=&quot;noopener&quot;&gt;[PyTorch] x86 CPU에서 양자화(Quantization) 관련 실행시 에러가 나는 경우 - Didn't find engine for operation quantized::conv_prepack NoQEngine&lt;/a&gt; ) 그러므로 Pod들은 &quot;적절한 node&quot;에 배치될 필요가 있으므로, 그런 경우에는 이런걸 활용하면 된다.&lt;/li&gt;
&lt;/ul&gt;
&lt;/li&gt;
&lt;li&gt;애플리케이션이 확장될 필요가 있을 때는 HPA-수평확장-쉽게 말하면 걍 Pod 갯수 늘리기를 하는게 좋다. VPA-수직확장-쉽게말하면 CPU랑 RAM늘리기도 가능하겠지만 추천되지 않고 있으니 아예 할 생각을 말자.&lt;/li&gt;
&lt;li&gt;웹서비스의 로그를 출력하는 곳이 로컬 디스크인지 확인하고, 그렇다면 stdout이나 stderr로 빼도록 하자.
&lt;ul style=&quot;list-style-type: disc;&quot; data-ke-list-type=&quot;disc&quot;&gt;
&lt;li&gt;찬 : 로컬디스크에 로그가 출력되면, Pod가 내려가면 로컬 디스크도 같이 없어지므로 로그를 구할 수 없다. 만약 로컬이 아니라 네트워크 디렉토리에 출력한다고 해도, &quot;그 파일을 다시 S3 따위의 장기간 저장소&quot;로 옮겨줘야 하는것을 웹서시브측에서 구현해야 하는 문제가 있다. 웹서비스를 개발한다면 로그의 출력을 stdout과 stderr로 보내도록 하고, 쿠버테이스(혹은 클러스터) 관리자에게, &quot;로그 좀 알아서 잘 모아 주세요.&quot; 라고 요청하자. 그러면 그 관리자가 여유가 된다면, &quot;검색&quot;도 할 수 있도록 ElasticSearch(혹은 OpenSearch)따위에 알아서 올려 줄 거다. 그러니 웹서비스 개발자라면 &quot;&lt;b&gt;제발!!!!&lt;/b&gt;&quot; 로그를 걍 stdout과 stderr에 출력하도록 하자.&lt;/li&gt;
&lt;/ul&gt;
&lt;/li&gt;
&lt;li&gt;찬 : 요건 책에 나와 있는지 모르겠지만, 생각났으니 써 본다. &lt;b&gt;제발!!!!!&lt;/b&gt; 웹서비스를 만들때, &lt;span style=&quot;background-color: #ffc1c8;&quot;&gt;배포 환경에 따라 달라 질 수 있는 값들은 &lt;b&gt;모두!!!!&lt;/b&gt; 환경변수로 빼 두자.&lt;/span&gt; 이걸 한번 해 두면, 배포하는 사람이 알아서 내가 만든 웹서비스를 이리저리 설치해서 배포할 수 있다. 만약 이런걸 코드에 박아둔다면, 배포하는 환경마다 매번 코드를 수정해야 하고, 매번 빌드를 새롭게 만들어서 전달해야 할 것이다. 그러니 코드를 짤 때 부터, 각종 URL, KEY, ID, Password, Email 주소등 변경 될 법한 것들에 대해서 모두 고려하는것이 좋다.&lt;/li&gt;
&lt;/ul&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;오늘 시간 난김에 읽은 책을 몇개 더 정리하려고하니.. 요 정도로 끝.&lt;/p&gt;</description>
      <category>공부/컴퓨터</category>
      <category>k8s</category>
      <category>kubernetes</category>
      <category>node</category>
      <category>NodeSelector</category>
      <category>POD</category>
      <category>tolerations</category>
      <category>쿠버네티스</category>
      <author>찬</author>
      <guid isPermaLink="true">https://blog.ggaman.com/1032</guid>
      <comments>https://blog.ggaman.com/1032#entry1032comment</comments>
      <pubDate>Wed, 14 Jun 2023 20:18:16 +0900</pubDate>
    </item>
    <item>
      <title>WSL2에서 docker-compose 사용시 FileNotFoundError: [Errno 2] No such file or directory 에러 발생시 문제 해결</title>
      <link>https://blog.ggaman.com/1031</link>
      <description>&lt;p data-ke-size=&quot;size16&quot;&gt;백만년만에 블로그 글을 쓴다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;제작년 중순까지 AI쪽을 하다가, 이제는 웹서비스를 개발(이라기 보다는 관리??) 하고 있다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;우선 이 문제가 발생되는 상황은 아래와 같다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;1. Windows 10에서 IntelliJ를 이용해서 &lt;span style=&quot;background-color: #dddddd;&quot;&gt;c:\works&lt;/span&gt; 디렉토리에 있는 코드를 건들고 있음.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;2. &lt;span style=&quot;letter-spacing: 0px;&quot;&gt;&lt;span style=&quot;background-color: #dddddd;&quot;&gt;docker-compos.yml&lt;/span&gt; 파일은 &lt;span style=&quot;background-color: #dddddd;&quot;&gt;c:\works&lt;/span&gt; 에 있음&lt;/span&gt;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;3. WSL2에서 &lt;span style=&quot;background-color: #dddddd;&quot;&gt;/mnt/c/works&lt;/span&gt; 하위 디렉토리로 이동 후 &lt;span style=&quot;background-color: #dddddd;&quot;&gt;docker-compose up&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;background-color: #dddddd;&quot;&gt;docker-compose&lt;/span&gt; 가 실행되고 있는 상태에서 Ctrl + C 등을 눌러 강제로 죽이뒤에 다시 &lt;span style=&quot;background-color: #dddddd;&quot;&gt;docker-compose&lt;/span&gt; 를 실행시키면 아래처럼 에러가 나는경우가 있다.&lt;/p&gt;
&lt;p&gt;&lt;figure class=&quot;imageblock alignCenter&quot; data-ke-mobileStyle=&quot;widthOrigin&quot; data-origin-width=&quot;991&quot; data-origin-height=&quot;303&quot;&gt;&lt;span data-url=&quot;https://blog.kakaocdn.net/dn/bzO2Qs/btr0ilQibVd/THfP0lndftRbA1NOrc6Pa1/img.png&quot; data-phocus=&quot;https://blog.kakaocdn.net/dn/bzO2Qs/btr0ilQibVd/THfP0lndftRbA1NOrc6Pa1/img.png&quot;&gt;&lt;img src=&quot;https://blog.kakaocdn.net/dn/bzO2Qs/btr0ilQibVd/THfP0lndftRbA1NOrc6Pa1/img.png&quot; srcset=&quot;https://img1.daumcdn.net/thumb/R1280x0/?scode=mtistory2&amp;fname=https%3A%2F%2Fblog.kakaocdn.net%2Fdn%2FbzO2Qs%2Fbtr0ilQibVd%2FTHfP0lndftRbA1NOrc6Pa1%2Fimg.png&quot; onerror=&quot;this.onerror=null; this.src='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png'; this.srcset='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png';&quot; loading=&quot;lazy&quot; width=&quot;991&quot; height=&quot;303&quot; data-origin-width=&quot;991&quot; data-origin-height=&quot;303&quot;/&gt;&lt;/span&gt;&lt;/figure&gt;
&lt;/p&gt;
&lt;pre id=&quot;code_1676991680610&quot; class=&quot;bash&quot; data-ke-language=&quot;bash&quot; data-ke-type=&quot;codeblock&quot;&gt;&lt;code&gt;chan@DESKTOP-OQG0APE:~/xxxxxxxxxxxxxxxxxxxxxxxxxx$ docker-compose down
Traceback (most recent call last):
  File &quot;docker-compose&quot;, line 3, in &amp;lt;module&amp;gt;
  File &quot;compose/cli/main.py&quot;, line 81, in main
  File &quot;compose/cli/main.py&quot;, line 200, in perform_command
  File &quot;compose/cli/command.py&quot;, line 70, in project_from_options
  File &quot;compose/cli/command.py&quot;, line 146, in get_project
  File &quot;compose/cli/command.py&quot;, line 206, in get_project_name
  File &quot;posixpath.py&quot;, line 383, in abspath
FileNotFoundError: [Errno 2] No such file or directory
[11284] Failed to execute script docker-compose&lt;/code&gt;&lt;/pre&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;여기저기 살펴보면 이건 docker-compose 와 WSL 간에 문제가 발생하는듯 하다. 이 이슈를 계속 트래킹 되다가... docker-compose v1은 더이상 트래킹 하지 않으니, docker-compose v2에서 확인하라고 한다.&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;a href=&quot;https://github.com/docker/compose/issues/7899&quot; target=&quot;_blank&quot; rel=&quot;noopener&quot;&gt;https://github.com/docker/compose/issues/7899&lt;/a&gt;&lt;/p&gt;
&lt;figure id=&quot;og_1676991895817&quot; contenteditable=&quot;false&quot; data-ke-type=&quot;opengraph&quot; data-ke-align=&quot;alignCenter&quot; data-og-type=&quot;object&quot; data-og-title=&quot;docker-compose is unstable using Docker Desktop and WSL2 &amp;middot; Issue #7899 &amp;middot; docker/compose&quot; data-og-description=&quot;Description of the issue In some conditions, some command like docker-compose up or docker-compose ps leads to the following error : Traceback (most recent call last): File &amp;quot;bin/docker-compose...&quot; data-og-host=&quot;github.com&quot; data-og-source-url=&quot;https://github.com/docker/compose/issues/7899&quot; data-og-url=&quot;https://github.com/docker/compose/issues/7899&quot; data-og-image=&quot;https://scrap.kakaocdn.net/dn/PfL5I/hyRHua1J7Y/aMBCmG8R3kA8pnC1MUgylk/img.png?width=1200&amp;amp;height=600&amp;amp;face=0_0_1200_600&quot;&gt;&lt;a href=&quot;https://github.com/docker/compose/issues/7899&quot; target=&quot;_blank&quot; rel=&quot;noopener&quot; data-source-url=&quot;https://github.com/docker/compose/issues/7899&quot;&gt;
&lt;div class=&quot;og-image&quot; style=&quot;background-image: url('https://scrap.kakaocdn.net/dn/PfL5I/hyRHua1J7Y/aMBCmG8R3kA8pnC1MUgylk/img.png?width=1200&amp;amp;height=600&amp;amp;face=0_0_1200_600');&quot;&gt;&amp;nbsp;&lt;/div&gt;
&lt;div class=&quot;og-text&quot;&gt;
&lt;p class=&quot;og-title&quot; data-ke-size=&quot;size16&quot;&gt;docker-compose is unstable using Docker Desktop and WSL2 &amp;middot; Issue #7899 &amp;middot; docker/compose&lt;/p&gt;
&lt;p class=&quot;og-desc&quot; data-ke-size=&quot;size16&quot;&gt;Description of the issue In some conditions, some command like docker-compose up or docker-compose ps leads to the following error : Traceback (most recent call last): File &quot;bin/docker-compose...&lt;/p&gt;
&lt;p class=&quot;og-host&quot; data-ke-size=&quot;size16&quot;&gt;github.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;하지만 난 귀찮으니, 걍 workaround 를 사용하는 것으로...&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;위 문제가 발생하면 &lt;span style=&quot;background-color: #dddddd;&quot;&gt;cd .&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;background-color: #dddddd;&quot;&gt;cd; cd -&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;</description>
      <category>공부/컴퓨터</category>
      <category>docker</category>
      <category>docker-compose</category>
      <category>failed to execute script docker-compose</category>
      <category>no such file or directory</category>
      <category>WSL2</category>
      <author>찬</author>
      <guid isPermaLink="true">https://blog.ggaman.com/1031</guid>
      <comments>https://blog.ggaman.com/1031#entry1031comment</comments>
      <pubDate>Wed, 22 Feb 2023 00:07:32 +0900</pubDate>
    </item>
    <item>
      <title>docker로 Mattermost 설치시 let's encrypt 이용해  tls 적용하는 방법</title>
      <link>https://blog.ggaman.com/1030</link>
      <description>&lt;h1&gt;slack을 쓰다가...&lt;/h1&gt;
&lt;ul&gt;
&lt;li&gt;팀에서 Slack을 잘 사용하고 있었다.&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;Rocket.chat/Mattermost 를 대충 써 보니..&lt;/h1&gt;
&lt;ul&gt;
&lt;li&gt;Rocket.chat 과 Mattermost가 설치형으로 사용할 수 있다.&lt;/li&gt;
&lt;li&gt;둘 다 설치해 봤는데, Rocket.chat이 사용이 좀 더 불편해서 Mattermost 를 설치하는것으로 결정.&lt;/li&gt;
&lt;/ul&gt;
&lt;h1&gt;Mattermost 설치하기&lt;/h1&gt;
&lt;ul&gt;
&lt;li&gt;역시나 docker로 설치하면 편하다.&lt;/li&gt;
&lt;li&gt;멀티노드로 실행하려면 다음의 링크를 타고 가자 : &lt;a href=&quot;https://docs.mattermost.com/install/prod-docker.html&quot;&gt;https://docs.mattermost.com/install/prod-docker.html&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;나는 싱글노드(컴퓨터 1대)에 설치할거라 걍 쉬운 방법으로 : &lt;a href=&quot;https://docs.mattermost.com/install/docker-local-machine.html&quot;&gt;https://docs.mattermost.com/install/docker-local-machine.html&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;여기서 알려주는 명령어는 아래와 같다. ( 이거 쓰면 안되니깐 우선 먼저 내용을 읽자. )&lt;ul&gt;
&lt;li&gt;&lt;code&gt;docker run --name mattermost-preview -d --publish 8065:8065 --add-host dockerhost:127.0.0.1 mattermost/mattermost-preview&lt;/code&gt;&lt;/li&gt;
&lt;/ul&gt;
&lt;/li&gt;
&lt;li&gt;하지만, Let&amp;#39;s Encrypt를 이용한 TLS 설정도 할 거면 좀 바꿔야 한다.&lt;/li&gt;
&lt;li&gt;우선 설명을 먼저하고 제대로 된 명령어는 저 아래에 적어 두겠다.&lt;/li&gt;
&lt;/ul&gt;
&lt;h2&gt;DNS 설정부터&lt;/h2&gt;
&lt;ul&gt;
&lt;li&gt;나는 Let&amp;#39;s Encrypt 를 사용해서 무료로 제공하는 TLS를 사용할 거다.&lt;/li&gt;
&lt;li&gt;Let&amp;#39;s Encrypt를 사용하려면 먼저 IP와 FQDN(도메인)을 맵핑해야 한다. 즉, DNS 서버에 A Record를 먼저 설정해야 한다.&lt;/li&gt;
&lt;li&gt;이 설명에서는 서버의 IP를 &lt;code&gt;101.101.111.222&lt;/code&gt;라고 가정하고, FQDN은 &lt;code&gt;mattermost.somedomain.com&lt;/code&gt;라고 가정하겠다.&lt;/li&gt;
&lt;li&gt;DNS에 A Record를 설정한 후에는 업데이트 되기까지 짧으면 2~3분, 길면 24시간이 걸리는 경우도 있다.&lt;/li&gt;
&lt;li&gt;이 작업을 완료한 뒤, Mattermost가 설치될 서버에서 &lt;code&gt;ping mattermost.somedomain.com&lt;/code&gt; 명령어로 IP가 잘 연결 되었는지 확인 후 아래를 진행한다.&lt;/li&gt;
&lt;/ul&gt;
&lt;h1&gt;Mattermost 에서 TLS 설정&lt;/h1&gt;
&lt;ul&gt;
&lt;li&gt;위에 알려준 &lt;code&gt;docker&lt;/code&gt; 명령을 실행하면, Mattermost가 8065번 port로 뜬다. ( 아래 제대로 된 명령어가 나오니, 우선 읽자. )&lt;/li&gt;
&lt;li&gt;그럼 &lt;a href=&quot;http://101.101.111.222:8065/&quot;&gt;http://101.101.111.222:8065/&lt;/a&gt; 로 접근하면, admin 계정을 만들게 되고, 이후 &lt;code&gt;System Console&lt;/code&gt; 혹은 &lt;code&gt;Admin console&lt;/code&gt;에서 각종 설정을 할 수 있다.&lt;/li&gt;
&lt;li&gt;접속 주소는 &lt;a href=&quot;http://101.101.111.222:8065/admin_console&quot;&gt;http://101.101.111.222:8065/admin_console&lt;/a&gt; 이다.&lt;/li&gt;
&lt;li&gt;TLS를 설정하려면 &lt;code&gt;System Console&lt;/code&gt;에서 &lt;code&gt;ENVIRONMENT&lt;/code&gt; 부분에 &lt;code&gt;Web Server&lt;/code&gt; 부분을 수정해야 한다.&lt;/li&gt;
&lt;/ul&gt;
&lt;h2&gt;System Console에서 Web Server부분을 다시 설정하자.&lt;/h2&gt;
&lt;ul&gt;
&lt;li&gt;아래의 설정을 입력해 보자.&lt;ul&gt;
&lt;li&gt;&lt;code&gt;Site URL&lt;/code&gt;은 아직 TLS 설정이 되지 않았지만, 그냥 &lt;code&gt;https://mattermost.somedomain.com&lt;/code&gt; 으로 적어 줘야 한다. ( 중간에 있는 &lt;code&gt;Test Live URL&lt;/code&gt;은 눌러봤자 동작안한다. 아직 https 서버가 안 떴기 때문이다. )&lt;/li&gt;
&lt;li&gt;&lt;code&gt;Listen Address&lt;/code&gt;는 &lt;code&gt;:443&lt;/code&gt;으로 적어 준다.&lt;/li&gt;
&lt;li&gt;&lt;code&gt;Forward port 80 to 443&lt;/code&gt;은 &lt;code&gt;true&lt;/code&gt;를 선택해 준다.&lt;/li&gt;
&lt;li&gt;&lt;code&gt;Connection Security&lt;/code&gt;는 &lt;code&gt;TLS&lt;/code&gt;를 골라 준다.&lt;/li&gt;
&lt;li&gt;&lt;code&gt;Use Let&amp;#39;s Encrypt&lt;/code&gt;는 &lt;code&gt;true&lt;/code&gt;를 선택해 준다.&lt;/li&gt;
&lt;li&gt;화면 아래에 있는 &lt;code&gt;Save&lt;/code&gt; 버튼을 누른다.&lt;/li&gt;
&lt;/ul&gt;
&lt;/li&gt;
&lt;li&gt;그림으로 보면 아래와 같다.&lt;/li&gt;
&lt;/ul&gt;
&lt;p&gt;&lt;figure class=&quot;imageblock alignCenter&quot; data-origin-width=&quot;848&quot; data-origin-height=&quot;700&quot; data-ke-mobilestyle=&quot;widthOrigin&quot;&gt;&lt;span data-url=&quot;https://blog.kakaocdn.net/dn/bqACv1/btq9AIbs6St/LN7KXTEm0kBpT1FOvxL9q1/img.png&quot; data-phocus=&quot;https://blog.kakaocdn.net/dn/bqACv1/btq9AIbs6St/LN7KXTEm0kBpT1FOvxL9q1/img.png&quot;&gt;&lt;img src=&quot;https://blog.kakaocdn.net/dn/bqACv1/btq9AIbs6St/LN7KXTEm0kBpT1FOvxL9q1/img.png&quot; srcset=&quot;https://img1.daumcdn.net/thumb/R1280x0/?scode=mtistory2&amp;fname=https%3A%2F%2Fblog.kakaocdn.net%2Fdn%2FbqACv1%2Fbtq9AIbs6St%2FLN7KXTEm0kBpT1FOvxL9q1%2Fimg.png&quot; data-origin-width=&quot;848&quot; data-origin-height=&quot;700&quot; data-ke-mobilestyle=&quot;widthOrigin&quot; onerror=&quot;this.onerror=null; this.src='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png'; this.srcset='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png';&quot;/&gt;&lt;/span&gt;&lt;/figure&gt;
&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;위와 같이 설정했다는것은, 80번 http로 접근하면 443으로 포워딩하겠다는거다. 그리고 Let&amp;#39;s Encrypt로 443에 대한 TLS 설정을 하겠다는것이다.&lt;/li&gt;
&lt;li&gt;즉, 우리가 만들어야 하는 docker container는 총 3개의 port를 오픈해야 한다.&lt;ul&gt;
&lt;li&gt;8065 : 최초 Mattermost를 띄우고 접근해서 각종 설정을 하는 port.&lt;/li&gt;
&lt;li&gt;80 : 일반 http 접속을 받아줄 port. 이곳으로 접근하면 443 port로 보내 https로 동작하도록 한다.&lt;/li&gt;
&lt;li&gt;443 : Let&amp;#39;s Encrypt 에서 TLS 설정까지 해 준 https port.&lt;/li&gt;
&lt;/ul&gt;
&lt;/li&gt;
&lt;li&gt;위 작업이 완료되면 docker container를 restart 시켜야 한다. 위에서 이야기한 docker 명령어를 먼저 쳤으면 당연히 제대로 동작하지 않을것이다. 그 명령은 8065만 연결해 뒀기 때문이다.&lt;/li&gt;
&lt;/ul&gt;
&lt;h1&gt;다시 Mattermost 설치하기, TLS 설정하기, 재시작하기, https로 접속하기&lt;/h1&gt;
&lt;ul&gt;
&lt;li&gt;아래의 명령어를 이용해 docker로 Mattermost를 설치하자. ( 중간에 있는 &lt;code&gt;-e MATTERMOST_ENABLE_SSL=true&lt;/code&gt;는 제거해도 된다.)&lt;/li&gt;
&lt;li&gt;&lt;code&gt;docker run --name mattermost-preview --restart=always -d -p 80:80 --publish 8065:8065 -p 443:443 -e MATTERMOST_ENABLE_SSL=true --add-host dockerhost:127.0.0.1 mattermost/mattermost-preview&lt;/code&gt;&lt;/li&gt;
&lt;li&gt;이제 다시 위로 올라가서, TLS 설정을 진행하자.&lt;/li&gt;
&lt;li&gt;TLS 설정을 완료하고 &lt;code&gt;SAVE&lt;/code&gt; 버튼을 눌러 저장 한 뒤에 docker container를 재시작하자.&lt;/li&gt;
&lt;li&gt;&lt;code&gt;docker restart mattermost-preview&lt;/code&gt;&lt;/li&gt;
&lt;li&gt;Mattermost가 제대로 실행될 수 있도록 30초~60초정도 기다 린뒤에 웹브라우져로 &lt;code&gt;https://mattermost.somedomain.com&lt;/code&gt;으로 접근해 보면 TLS가 잘 동작하는것을 볼 수 있다.&lt;/li&gt;
&lt;/ul&gt;</description>
      <category>공부/컴퓨터</category>
      <category>docker</category>
      <category>let's encrypt</category>
      <category>mattermost</category>
      <category>Slack</category>
      <category>TLS</category>
      <author>찬</author>
      <guid isPermaLink="true">https://blog.ggaman.com/1030</guid>
      <comments>https://blog.ggaman.com/1030#entry1030comment</comments>
      <pubDate>Wed, 14 Jul 2021 00:33:14 +0900</pubDate>
    </item>
    <item>
      <title>NVIDIA GPU에서 nvidia-smi 명령시 NVML: Driver/library version mismatch 발생 원인은 아마도?</title>
      <link>https://blog.ggaman.com/1029</link>
      <description>&lt;h1&gt;Kubernetes의 Node에 GPU Pod가 뜰 때, 문제가 계속 발생하네?&lt;/h1&gt;
&lt;ul style=&quot;list-style-type: disc;&quot; data-ke-list-type=&quot;disc&quot;&gt;
&lt;li&gt;GPU를 사용하는 Pod가 떠 있을때는 문제가 안 됐는데, 가끔씩 새로 띄울때 Pod가 안 뜨는 문제가 있었다.&lt;/li&gt;
&lt;li&gt;주로 이런 경우 &lt;code&gt;nvidia-smi&lt;/code&gt; 명령을 치면 아래와 같은 에러 메세지가 떴었다.&lt;/li&gt;
&lt;/ul&gt;
&lt;p&gt;&lt;figure class=&quot;imageblock alignCenter&quot; data-origin-width=&quot;493&quot; data-origin-height=&quot;39&quot; data-ke-mobilestyle=&quot;widthContent&quot;&gt;&lt;span data-url=&quot;https://blog.kakaocdn.net/dn/bQjx7Z/btq3stGwVll/wrIK8Rx7dC5uv0uTwoZ7K1/img.png&quot; data-phocus=&quot;https://blog.kakaocdn.net/dn/bQjx7Z/btq3stGwVll/wrIK8Rx7dC5uv0uTwoZ7K1/img.png&quot;&gt;&lt;img src=&quot;https://blog.kakaocdn.net/dn/bQjx7Z/btq3stGwVll/wrIK8Rx7dC5uv0uTwoZ7K1/img.png&quot; srcset=&quot;https://img1.daumcdn.net/thumb/R1280x0/?scode=mtistory2&amp;fname=https%3A%2F%2Fblog.kakaocdn.net%2Fdn%2FbQjx7Z%2Fbtq3stGwVll%2FwrIK8Rx7dC5uv0uTwoZ7K1%2Fimg.png&quot; data-origin-width=&quot;493&quot; data-origin-height=&quot;39&quot; data-ke-mobilestyle=&quot;widthContent&quot; onerror=&quot;this.onerror=null; this.src='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png'; this.srcset='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png';&quot;/&gt;&lt;/span&gt;&lt;/figure&gt;
&lt;/p&gt;
&lt;pre class=&quot;applescript&quot;&gt;&lt;code&gt;Failed to initialize NVML: Driver/library version mismatch&lt;/code&gt;&lt;/pre&gt;
&lt;ul style=&quot;list-style-type: disc;&quot; data-ke-list-type=&quot;disc&quot;&gt;
&lt;li&gt;단순히 에러 메세지로만 보면, 드라이버와 라이브러리가 매치 되지 않는다는건데, 난 서버에 아무런 짓도 안 해 줬는데도 에러가 생기는 것이다.&lt;/li&gt;
&lt;li&gt;약 1년전부터 가끔 발생한 문제였는데, 이것저것 찾아 보았지만, 다들 재부팅하면 해결 될 거라는 이야기만...&lt;/li&gt;
&lt;li&gt;&lt;a href=&quot;https://stackoverflow.com/questions/43022843/nvidia-nvml-driver-library-version-mismatch&quot;&gt;stackoverflow.com/questions/43022843/nvidia-nvml-driver-library-version-mismatch&lt;/a&gt;&lt;/li&gt;
&lt;/ul&gt;
&lt;p&gt;&lt;figure class=&quot;imageblock alignCenter&quot; data-origin-width=&quot;740&quot; data-origin-height=&quot;205&quot; data-ke-mobilestyle=&quot;widthContent&quot;&gt;&lt;span data-url=&quot;https://blog.kakaocdn.net/dn/lLFJq/btq3q6rlDBY/8lx0wDPC47cSR9yzc90Uj0/img.png&quot; data-phocus=&quot;https://blog.kakaocdn.net/dn/lLFJq/btq3q6rlDBY/8lx0wDPC47cSR9yzc90Uj0/img.png&quot;&gt;&lt;img src=&quot;https://blog.kakaocdn.net/dn/lLFJq/btq3q6rlDBY/8lx0wDPC47cSR9yzc90Uj0/img.png&quot; srcset=&quot;https://img1.daumcdn.net/thumb/R1280x0/?scode=mtistory2&amp;fname=https%3A%2F%2Fblog.kakaocdn.net%2Fdn%2FlLFJq%2Fbtq3q6rlDBY%2F8lx0wDPC47cSR9yzc90Uj0%2Fimg.png&quot; data-origin-width=&quot;740&quot; data-origin-height=&quot;205&quot; data-ke-mobilestyle=&quot;widthContent&quot; onerror=&quot;this.onerror=null; this.src='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png'; this.srcset='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png';&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;아니면 apt update를 통해서 nvidia-driver를 업데이트 해 보라는 둥...&lt;/li&gt;
&lt;li&gt;&lt;a href=&quot;https://stackoverflow.com/questions/62250491/nvml-driver-library-mismatch-after-libnvidia-compute-update&quot;&gt;stackoverflow.com/questions/62250491/nvml-driver-library-mismatch-after-libnvidia-compute-update&lt;/a&gt;&lt;/li&gt;
&lt;/ul&gt;
&lt;p&gt;&lt;figure class=&quot;imageblock alignCenter&quot; data-origin-width=&quot;725&quot; data-origin-height=&quot;312&quot; data-ke-mobilestyle=&quot;widthContent&quot;&gt;&lt;span data-url=&quot;https://blog.kakaocdn.net/dn/cyelaI/btq3qN6ceHQ/7cjShqjTb283NBuidyc4X0/img.png&quot; data-phocus=&quot;https://blog.kakaocdn.net/dn/cyelaI/btq3qN6ceHQ/7cjShqjTb283NBuidyc4X0/img.png&quot;&gt;&lt;img src=&quot;https://blog.kakaocdn.net/dn/cyelaI/btq3qN6ceHQ/7cjShqjTb283NBuidyc4X0/img.png&quot; srcset=&quot;https://img1.daumcdn.net/thumb/R1280x0/?scode=mtistory2&amp;fname=https%3A%2F%2Fblog.kakaocdn.net%2Fdn%2FcyelaI%2Fbtq3qN6ceHQ%2F7cjShqjTb283NBuidyc4X0%2Fimg.png&quot; data-origin-width=&quot;725&quot; data-origin-height=&quot;312&quot; data-ke-mobilestyle=&quot;widthContent&quot; onerror=&quot;this.onerror=null; this.src='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png'; this.srcset='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png';&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;NVIDIA driver module을 내렸다가 올리는라는 둥...&lt;/li&gt;
&lt;li&gt;&lt;a href=&quot;https://m.blog.naver.com/jaeyoon_95/221773869080&quot;&gt;m.blog.naver.com/jaeyoon_95/221773869080&lt;/a&gt;&lt;/li&gt;
&lt;/ul&gt;
&lt;p&gt;&lt;figure class=&quot;imageblock alignCenter&quot; data-origin-width=&quot;760&quot; data-origin-height=&quot;188&quot; data-ke-mobilestyle=&quot;widthContent&quot;&gt;&lt;span data-url=&quot;https://blog.kakaocdn.net/dn/bLpfm3/btq3xOJLIoX/KjGV1GYV2P6oKBp1ZAQd90/img.png&quot; data-phocus=&quot;https://blog.kakaocdn.net/dn/bLpfm3/btq3xOJLIoX/KjGV1GYV2P6oKBp1ZAQd90/img.png&quot;&gt;&lt;img src=&quot;https://blog.kakaocdn.net/dn/bLpfm3/btq3xOJLIoX/KjGV1GYV2P6oKBp1ZAQd90/img.png&quot; srcset=&quot;https://img1.daumcdn.net/thumb/R1280x0/?scode=mtistory2&amp;fname=https%3A%2F%2Fblog.kakaocdn.net%2Fdn%2FbLpfm3%2Fbtq3xOJLIoX%2FKjGV1GYV2P6oKBp1ZAQd90%2Fimg.png&quot; data-origin-width=&quot;760&quot; data-origin-height=&quot;188&quot; data-ke-mobilestyle=&quot;widthContent&quot; onerror=&quot;this.onerror=null; this.src='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png'; this.srcset='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png';&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;서비스를 운영하는데 노드들을 아무렇지도 않게 재부팅하는것도 문제가 있고, 여러 노드가 한꺼번에 저 문제가 생기면, 다른 노드로 가서 붙어야할 GPU Pod들이 같은 문제로 인해서 안 뜨는 문제가 생길 것이다.&lt;/li&gt;
&lt;li&gt;그런데 다들 왜 해결책만 있고, 그 원인을 이야기 하지 않을까?&lt;/li&gt;
&lt;/ul&gt;
&lt;h1&gt;mismatch 가 나는 이유는? nvidia-smi 를 살펴 보자.&lt;/h1&gt;
&lt;ul style=&quot;list-style-type: disc;&quot; data-ke-list-type=&quot;disc&quot;&gt;
&lt;li&gt;기존에는 잘 동작하던 것들이 mismatch가 나는 이유는, 말 그대로 mismatch 상태가 되었기 때문일 거다.&lt;/li&gt;
&lt;li&gt;분명 누군가가 무언가를 바꾸었기 때문에 mismatch 상태가 되었을 것이다라고 예측했다.&lt;/li&gt;
&lt;li&gt;누가 nvidia-smi 파일을 바꿔치기 했나? 싶어 &lt;code&gt;stat /usr/bin/nvidia-smi&lt;/code&gt; 명령을 통해 nvidia-smi 명령의 파일 상태를 확인해 보았다.&lt;/li&gt;
&lt;/ul&gt;
&lt;p&gt;&lt;figure class=&quot;imageblock alignCenter&quot; data-origin-width=&quot;589&quot; data-origin-height=&quot;138&quot; data-ke-mobilestyle=&quot;widthContent&quot;&gt;&lt;span data-url=&quot;https://blog.kakaocdn.net/dn/R5Ua9/btq3vViclM6/LkkSb1NqQ1SkxD7f4adddK/img.png&quot; data-phocus=&quot;https://blog.kakaocdn.net/dn/R5Ua9/btq3vViclM6/LkkSb1NqQ1SkxD7f4adddK/img.png&quot;&gt;&lt;img src=&quot;https://blog.kakaocdn.net/dn/R5Ua9/btq3vViclM6/LkkSb1NqQ1SkxD7f4adddK/img.png&quot; srcset=&quot;https://img1.daumcdn.net/thumb/R1280x0/?scode=mtistory2&amp;fname=https%3A%2F%2Fblog.kakaocdn.net%2Fdn%2FR5Ua9%2Fbtq3vViclM6%2FLkkSb1NqQ1SkxD7f4adddK%2Fimg.png&quot; data-origin-width=&quot;589&quot; data-origin-height=&quot;138&quot; data-ke-mobilestyle=&quot;widthContent&quot; onerror=&quot;this.onerror=null; this.src='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png'; this.srcset='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png';&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;으잉?? 왜 Modify 시간이... 3월 30일이지? 난 이 서버를 껐다 킨적도 없는데? &lt;code&gt;history&lt;/code&gt; 명령을 이용해서 그간의 실행 기록을 살펴 봤지만, 아무도 그래픽 관련 무언가를 손 댄 적이 없다.&lt;/li&gt;
&lt;li&gt;도대체 누구의 소행인가?&lt;/li&gt;
&lt;/ul&gt;
&lt;h1&gt;재부팅을 하지 않아도 업데이트를 하네? unattended-upgrade&lt;/h1&gt;
&lt;ul style=&quot;list-style-type: disc;&quot; data-ke-list-type=&quot;disc&quot;&gt;
&lt;li&gt;내가 저 파일을 손대지 않았는데, Modify 시간이 최근으로 달라졌다는것은.. 누군가가 업데이트 했다는것이다.&lt;/li&gt;
&lt;li&gt;그래서 업데이트 관련해서 찾아 봤더니&amp;nbsp;&lt;code&gt;unattended-upgade&lt;/code&gt;라는게 있네?&lt;/li&gt;
&lt;li&gt;이 놈이 자동 업데이트를 해 주고, 시큐리티 패치들을 알아서 적용해 준다고 한다. 그리고 적용된 내용들을 &lt;code&gt;/var/log/unattended-upgrades.log&lt;/code&gt;에 남겨 둔다고 한다.&lt;/li&gt;
&lt;li&gt;그 내용중 일부는 아래와 같다.&lt;/li&gt;
&lt;/ul&gt;
&lt;p&gt;&lt;figure class=&quot;imageblock alignCenter&quot; data-origin-width=&quot;827&quot; data-origin-height=&quot;436&quot; data-ke-mobilestyle=&quot;widthContent&quot;&gt;&lt;span data-url=&quot;https://blog.kakaocdn.net/dn/bvPtQu/btq3AqOFuFg/6iBjKV8ZVDtN3c4cAyw2KK/img.png&quot; data-phocus=&quot;https://blog.kakaocdn.net/dn/bvPtQu/btq3AqOFuFg/6iBjKV8ZVDtN3c4cAyw2KK/img.png&quot;&gt;&lt;img src=&quot;https://blog.kakaocdn.net/dn/bvPtQu/btq3AqOFuFg/6iBjKV8ZVDtN3c4cAyw2KK/img.png&quot; srcset=&quot;https://img1.daumcdn.net/thumb/R1280x0/?scode=mtistory2&amp;fname=https%3A%2F%2Fblog.kakaocdn.net%2Fdn%2FbvPtQu%2Fbtq3AqOFuFg%2F6iBjKV8ZVDtN3c4cAyw2KK%2Fimg.png&quot; data-origin-width=&quot;827&quot; data-origin-height=&quot;436&quot; data-ke-mobilestyle=&quot;widthContent&quot; onerror=&quot;this.onerror=null; this.src='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png'; this.srcset='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png';&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;/ul&gt;
&lt;h1&gt;해결책은?&lt;/h1&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;unattended-upgade&lt;/code&gt; 를 끄면 될 것으로 예상된다. 그럼 자동 업데이트도 없을테니깐?&lt;/li&gt;
&lt;li&gt;여기에 대한 글은 아래 링크를 확인해 보자.&lt;/li&gt;
&lt;li&gt;&lt;a href=&quot;https://chrisalbon.com/deep_learning/setup/prevent_nvidia_drivers_from_upgrading/&quot;&gt;https://chrisalbon.com/deep_learning/setup/prevent_nvidia_drivers_from_upgrading/&lt;/a&gt;&lt;/li&gt;
&lt;/ul&gt;
&lt;p&gt;&lt;figure class=&quot;imageblock alignCenter&quot; data-origin-width=&quot;818&quot; data-origin-height=&quot;153&quot; data-ke-mobilestyle=&quot;widthContent&quot;&gt;&lt;span data-url=&quot;https://blog.kakaocdn.net/dn/b1ykdB/btq3zndKsAb/BWuswoyXC2dOb8gIRnK641/img.png&quot; data-phocus=&quot;https://blog.kakaocdn.net/dn/b1ykdB/btq3zndKsAb/BWuswoyXC2dOb8gIRnK641/img.png&quot;&gt;&lt;img src=&quot;https://blog.kakaocdn.net/dn/b1ykdB/btq3zndKsAb/BWuswoyXC2dOb8gIRnK641/img.png&quot; srcset=&quot;https://img1.daumcdn.net/thumb/R1280x0/?scode=mtistory2&amp;fname=https%3A%2F%2Fblog.kakaocdn.net%2Fdn%2Fb1ykdB%2Fbtq3zndKsAb%2FBWuswoyXC2dOb8gIRnK641%2Fimg.png&quot; data-origin-width=&quot;818&quot; data-origin-height=&quot;153&quot; data-ke-mobilestyle=&quot;widthContent&quot; onerror=&quot;this.onerror=null; this.src='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png'; this.srcset='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png';&quot;/&gt;&lt;/span&gt;&lt;/figure&gt;
&lt;figure class=&quot;imageblock alignCenter&quot; data-origin-width=&quot;559&quot; data-origin-height=&quot;349&quot; data-ke-mobilestyle=&quot;widthContent&quot;&gt;&lt;span data-url=&quot;https://blog.kakaocdn.net/dn/dAEaj2/btq3udcoCUK/aTr6bUWulemHkELVDWa6zk/img.png&quot; data-phocus=&quot;https://blog.kakaocdn.net/dn/dAEaj2/btq3udcoCUK/aTr6bUWulemHkELVDWa6zk/img.png&quot;&gt;&lt;img src=&quot;https://blog.kakaocdn.net/dn/dAEaj2/btq3udcoCUK/aTr6bUWulemHkELVDWa6zk/img.png&quot; srcset=&quot;https://img1.daumcdn.net/thumb/R1280x0/?scode=mtistory2&amp;fname=https%3A%2F%2Fblog.kakaocdn.net%2Fdn%2FdAEaj2%2Fbtq3udcoCUK%2FaTr6bUWulemHkELVDWa6zk%2Fimg.png&quot; data-origin-width=&quot;559&quot; data-origin-height=&quot;349&quot; data-ke-mobilestyle=&quot;widthContent&quot; onerror=&quot;this.onerror=null; this.src='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png'; this.srcset='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png';&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;오래된 글이지만 아래 링크에 가 보면, &quot;환경이 깨지는것을 방지할 수 있도록&quot; Blacklist를 올려둔 사람이 있으니 그것을 참고하면 더 좋겠다.&lt;/li&gt;
&lt;li&gt;&lt;a style=&quot;letter-spacing: 0px;&quot; href=&quot;https://forums.linuxmint.com/viewtopic.php?p=1098633#p1098633&quot;&gt;forums.linuxmint.com/viewtopic.php?p=1098633#p1098633&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;그 사람의 예에 따르면 아래와 같이 작성하면 될 듯하다. 아님 말고. ㅎ.&lt;/li&gt;
&lt;/ul&gt;
&lt;p&gt;&lt;figure class=&quot;imageblock alignCenter&quot; data-origin-width=&quot;460&quot; data-origin-height=&quot;86&quot; data-ke-mobilestyle=&quot;widthContent&quot;&gt;&lt;span data-url=&quot;https://blog.kakaocdn.net/dn/ch7aVg/btq3ss1X2Hg/Eb1tALt8s0Wa91KCfPF6L1/img.png&quot; data-phocus=&quot;https://blog.kakaocdn.net/dn/ch7aVg/btq3ss1X2Hg/Eb1tALt8s0Wa91KCfPF6L1/img.png&quot;&gt;&lt;img src=&quot;https://blog.kakaocdn.net/dn/ch7aVg/btq3ss1X2Hg/Eb1tALt8s0Wa91KCfPF6L1/img.png&quot; srcset=&quot;https://img1.daumcdn.net/thumb/R1280x0/?scode=mtistory2&amp;fname=https%3A%2F%2Fblog.kakaocdn.net%2Fdn%2Fch7aVg%2Fbtq3ss1X2Hg%2FEb1tALt8s0Wa91KCfPF6L1%2Fimg.png&quot; data-origin-width=&quot;460&quot; data-origin-height=&quot;86&quot; data-ke-mobilestyle=&quot;widthContent&quot; onerror=&quot;this.onerror=null; this.src='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png'; this.srcset='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png';&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>공부/컴퓨터</category>
      <category>GPU</category>
      <category>kubernetes</category>
      <category>nvidia-smi</category>
      <category>NVML</category>
      <author>찬</author>
      <guid isPermaLink="true">https://blog.ggaman.com/1029</guid>
      <comments>https://blog.ggaman.com/1029#entry1029comment</comments>
      <pubDate>Tue, 27 Apr 2021 01:45:59 +0900</pubDate>
    </item>
    <item>
      <title>[PyTorch] x86 CPU에서 양자화(Quantization) 관련 실행시 에러가 나는 경우 - Didn't find engine for operation quantized::conv_prepack NoQEngine</title>
      <link>https://blog.ggaman.com/1028</link>
      <description>&lt;h1&gt;문제 상황 발생&lt;/h1&gt;
&lt;ul&gt;
&lt;li&gt;Kubernetes에서 AI 엔진을 돌리는데, GPU로 사용할때는 문제가 없었는데...&lt;/li&gt;
&lt;li&gt;CPU를 사용하도록 해서 동작시키니 동작하지 않는 문제가 발생&lt;/li&gt;
&lt;/ul&gt;
&lt;h2&gt;에러 메세지&lt;/h2&gt;
&lt;ul&gt;
&lt;li&gt;아래와 비슷한 문제가 발생하면서 동작하지 않는 문제가 있었다.&lt;/li&gt;
&lt;li&gt;&lt;code&gt;Didn't find engine for operation quantized::conv_prepack NoQEngine&lt;/code&gt;&lt;/li&gt;
&lt;li&gt;&lt;code&gt;conv_prepack&lt;/code&gt;뿐만 아니라, &lt;code&gt;linear_prepack&lt;/code&gt; 라는 에러가 발생하기도 한다.&lt;/li&gt;
&lt;li&gt;알고 봤더니, CPU를 사용할 때는 Quantization 과정에서 문제가 발생한것이었다.&lt;/li&gt;
&lt;/ul&gt;
&lt;h1&gt;분석하기&lt;/h1&gt;
&lt;h2&gt;소스에서 에러 메세지 찾기&lt;/h2&gt;
&lt;ul&gt;
&lt;li&gt;해당 에러를 출력하는 코드를 찾아보면 아래와 같다.&lt;/li&gt;
&lt;li&gt;관련링크 : &lt;a href=&quot;https://github.com/pytorch/pytorch/blob/f9185973d19d30e8522d29bead9b0789897f8652/aten/src/ATen/native/quantized/cpu/qconv_prepack.cpp#L374&quot;&gt;qconv_prepack.cpp&lt;/a&gt;&lt;/li&gt;
&lt;/ul&gt;
&lt;p&gt;&lt;figure class=&quot;imageblock alignCenter&quot; data-origin-width=&quot;0&quot; data-origin-height=&quot;0&quot; data-ke-mobilestyle=&quot;widthContent&quot;&gt;&lt;span data-url=&quot;https://blog.kakaocdn.net/dn/bOduJ9/btq0fkKWT3Q/VIW1m1o7b4XsSWA0pNKFv0/img.png&quot; data-phocus=&quot;https://blog.kakaocdn.net/dn/bOduJ9/btq0fkKWT3Q/VIW1m1o7b4XsSWA0pNKFv0/img.png&quot;&gt;&lt;img src=&quot;https://blog.kakaocdn.net/dn/bOduJ9/btq0fkKWT3Q/VIW1m1o7b4XsSWA0pNKFv0/img.png&quot; srcset=&quot;https://img1.daumcdn.net/thumb/R1280x0/?scode=mtistory2&amp;fname=https%3A%2F%2Fblog.kakaocdn.net%2Fdn%2FbOduJ9%2Fbtq0fkKWT3Q%2FVIW1m1o7b4XsSWA0pNKFv0%2Fimg.png&quot; data-origin-width=&quot;0&quot; data-origin-height=&quot;0&quot; data-ke-mobilestyle=&quot;widthContent&quot; onerror=&quot;this.onerror=null; this.src='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png'; this.srcset='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png';&quot;/&gt;&lt;/span&gt;&lt;/figure&gt;
&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;위 그림에서 코드의 제일 윗부분부터 확인해 보면
&lt;ul&gt;
&lt;li&gt;&lt;code&gt;_run(...)&lt;/code&gt; 함수를 실행 시킬때,&lt;/li&gt;
&lt;li&gt;&lt;code&gt;USE_FBGEMM&lt;/code&gt; 값이 정의되어 있으면, &lt;code&gt;FBGEMM&lt;/code&gt; 엔진을 사용해서 처리하고,&lt;/li&gt;
&lt;li&gt;&lt;code&gt;USE_PYTORCH_QNNPACK&lt;/code&gt; 값이 정의되어 있으면, &lt;code&gt;QNNPACK&lt;/code&gt; 엔진을 이용해서 처리 한다.&lt;/li&gt;
&lt;li&gt;둘다 값이 없다면 &lt;code&gt;TORCH_CHECK&lt;/code&gt;에 의해서 &lt;code&gt;conv2d_prepack를 실행시킬 엔진을 찾을 수 없다&lt;/code&gt;면서 에러를 낸다.&lt;/li&gt;
&lt;/ul&gt;
&lt;/li&gt;
&lt;li&gt;즉, 두 값 중에 하나라도 누가 정의를 했으면 잘 동작할텐데, 그 어느것도 설정이 되지 않아서 문제가 된것이다.&lt;/li&gt;
&lt;/ul&gt;
&lt;h2&gt;FBGEMM 혹은 QNNPACK 은 무엇인가?&lt;/h2&gt;
&lt;ul&gt;
&lt;li&gt;그렇다면 FBGEMM 혹은 QNNPACK은 무엇인가?&lt;/li&gt;
&lt;li&gt;관련링크 : &lt;a href=&quot;https://pytorch.org/docs/stable/quantization.html&quot;&gt;https://pytorch.org/docs/stable/quantization.html&lt;/a&gt;&lt;/li&gt;
&lt;/ul&gt;
&lt;p&gt;&lt;figure class=&quot;imageblock alignCenter&quot; data-origin-width=&quot;0&quot; data-origin-height=&quot;0&quot; data-ke-mobilestyle=&quot;widthContent&quot;&gt;&lt;span data-url=&quot;https://blog.kakaocdn.net/dn/ols5o/btqZ7LDhkKW/DY3uEMzQqXWnKvG4i1c9qk/img.png&quot; data-phocus=&quot;https://blog.kakaocdn.net/dn/ols5o/btqZ7LDhkKW/DY3uEMzQqXWnKvG4i1c9qk/img.png&quot;&gt;&lt;img src=&quot;https://blog.kakaocdn.net/dn/ols5o/btqZ7LDhkKW/DY3uEMzQqXWnKvG4i1c9qk/img.png&quot; srcset=&quot;https://img1.daumcdn.net/thumb/R1280x0/?scode=mtistory2&amp;fname=https%3A%2F%2Fblog.kakaocdn.net%2Fdn%2Fols5o%2FbtqZ7LDhkKW%2FDY3uEMzQqXWnKvG4i1c9qk%2Fimg.png&quot; data-origin-width=&quot;0&quot; data-origin-height=&quot;0&quot; data-ke-mobilestyle=&quot;widthContent&quot; onerror=&quot;this.onerror=null; this.src='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png'; this.srcset='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png';&quot;/&gt;&lt;/span&gt;&lt;/figure&gt;
&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;설명에 나와 있다시피, Quantization을 지원하는 두개의 backend가 있는데, x86용으로는 FBGEMM, arm용으로는 QNPACK이 있다고 한다.&lt;/li&gt;
&lt;li&gt;나는 x86용에서 테스트를 했으므로 backend 로는 자동으로 FBGEMM이 사용되었어야 하는데 &lt;code&gt;#ifdef USE_FBGEMM&lt;/code&gt; 부분이 제대로 동작하지 않아, &lt;code&gt;TORCH_CHECK&lt;/code&gt; 부분에서 에러가 발생한 것이었다.&lt;/li&gt;
&lt;/ul&gt;
&lt;h2&gt;그렇다면 내 환경에서는 왜 FBGEMM이 사용되지 않았을까?&lt;/h2&gt;
&lt;ul&gt;
&lt;li&gt;&lt;code&gt;FBGEMM&lt;/code&gt; 의 홈페이지에 찾아가 보자.&lt;/li&gt;
&lt;li&gt;관련링크 : &lt;a href=&quot;https://github.com/pytorch/FBGEMM&quot;&gt;https://github.com/pytorch/FBGEMM&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;&lt;code&gt;FBGEMM&lt;/code&gt;은 페이스북에서 만든 행렬처리용 라이브러리다.그리고 Caffe2 와 PyTorch의 양자화 연산에 사용된다고 적혀 있다.&lt;/li&gt;
&lt;/ul&gt;
&lt;p&gt;&lt;figure class=&quot;imageblock alignCenter&quot; data-origin-width=&quot;0&quot; data-origin-height=&quot;0&quot; data-ke-mobilestyle=&quot;widthContent&quot;&gt;&lt;span data-url=&quot;https://blog.kakaocdn.net/dn/brm7ZO/btqZ4AoJ52K/O72es0O2iSZbUlgRrQT2b1/img.png&quot; data-phocus=&quot;https://blog.kakaocdn.net/dn/brm7ZO/btqZ4AoJ52K/O72es0O2iSZbUlgRrQT2b1/img.png&quot;&gt;&lt;img src=&quot;https://blog.kakaocdn.net/dn/brm7ZO/btqZ4AoJ52K/O72es0O2iSZbUlgRrQT2b1/img.png&quot; srcset=&quot;https://img1.daumcdn.net/thumb/R1280x0/?scode=mtistory2&amp;fname=https%3A%2F%2Fblog.kakaocdn.net%2Fdn%2Fbrm7ZO%2FbtqZ4AoJ52K%2FO72es0O2iSZbUlgRrQT2b1%2Fimg.png&quot; data-origin-width=&quot;0&quot; data-origin-height=&quot;0&quot; data-ke-mobilestyle=&quot;widthContent&quot; onerror=&quot;this.onerror=null; this.src='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png'; this.srcset='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png';&quot;/&gt;&lt;/span&gt;&lt;/figure&gt;
&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;그 아래의 내용을 보면 &lt;code&gt;FBGEMM&lt;/code&gt;의 디펜던시에 대해서 설명하고 있는데, &lt;code&gt;gcc5+&lt;/code&gt;와 &lt;code&gt;avx2&lt;/code&gt;를 instruction set을 지원해야 한다고 한다. 또한 CPU instruction set을 확인할때는 &lt;code&gt;cpuinfo&lt;/code&gt; 정보를 활용하여 &lt;code&gt;runtime&lt;/code&gt;에 확인한다고 한다.&lt;/li&gt;
&lt;/ul&gt;
&lt;p&gt;&lt;figure class=&quot;imageblock alignCenter&quot; data-origin-width=&quot;0&quot; data-origin-height=&quot;0&quot; data-ke-mobilestyle=&quot;widthContent&quot;&gt;&lt;span data-url=&quot;https://blog.kakaocdn.net/dn/qR9vS/btqZ6jz3sAu/UvyjOLgM9GQmHI2ckVpDKk/img.png&quot; data-phocus=&quot;https://blog.kakaocdn.net/dn/qR9vS/btqZ6jz3sAu/UvyjOLgM9GQmHI2ckVpDKk/img.png&quot;&gt;&lt;img src=&quot;https://blog.kakaocdn.net/dn/qR9vS/btqZ6jz3sAu/UvyjOLgM9GQmHI2ckVpDKk/img.png&quot; srcset=&quot;https://img1.daumcdn.net/thumb/R1280x0/?scode=mtistory2&amp;fname=https%3A%2F%2Fblog.kakaocdn.net%2Fdn%2FqR9vS%2FbtqZ6jz3sAu%2FUvyjOLgM9GQmHI2ckVpDKk%2Fimg.png&quot; data-origin-width=&quot;0&quot; data-origin-height=&quot;0&quot; data-ke-mobilestyle=&quot;widthContent&quot; onerror=&quot;this.onerror=null; this.src='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png'; this.srcset='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png';&quot;/&gt;&lt;/span&gt;&lt;/figure&gt;
&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;즉, PyTorch에서 양자화 관련 기능을 사용할때 x86에서는 &lt;code&gt;FBGEMM&lt;/code&gt; 라이브러리를 사용하지만, 그 라이브러리도 CPU 상태 정보를 볼 수 있는 &lt;code&gt;cpuinfo&lt;/code&gt; 를 활용해서 &lt;code&gt;avx2&lt;/code&gt;가 지원하는 경우에만 동작하도록 되어 있다는 뜻이다.&lt;/li&gt;
&lt;li&gt;사실 해당 내용은 PyTorch 홈페이지의 &lt;code&gt;QUANTIZATION&lt;/code&gt; 관련 설명에도 &lt;code&gt;avx2&lt;/code&gt; 관련 내용이 이미 나와 있는데, 빙빙 둘러 온 것이다.&lt;/li&gt;
&lt;li&gt;관련링크 : &lt;a href=&quot;https://pytorch.org/docs/stable/quantization.html&quot;&gt;https://pytorch.org/docs/stable/quantization.html&lt;/a&gt;&lt;/li&gt;
&lt;/ul&gt;
&lt;p&gt;&lt;figure class=&quot;imageblock alignCenter&quot; data-origin-width=&quot;0&quot; data-origin-height=&quot;0&quot; data-ke-mobilestyle=&quot;widthContent&quot;&gt;&lt;span data-url=&quot;https://blog.kakaocdn.net/dn/bECCBO/btq0dnOVEoo/jjBruVjSHqa5Fb5fQgrNjk/img.png&quot; data-phocus=&quot;https://blog.kakaocdn.net/dn/bECCBO/btq0dnOVEoo/jjBruVjSHqa5Fb5fQgrNjk/img.png&quot;&gt;&lt;img src=&quot;https://blog.kakaocdn.net/dn/bECCBO/btq0dnOVEoo/jjBruVjSHqa5Fb5fQgrNjk/img.png&quot; srcset=&quot;https://img1.daumcdn.net/thumb/R1280x0/?scode=mtistory2&amp;fname=https%3A%2F%2Fblog.kakaocdn.net%2Fdn%2FbECCBO%2Fbtq0dnOVEoo%2FjjBruVjSHqa5Fb5fQgrNjk%2Fimg.png&quot; data-origin-width=&quot;0&quot; data-origin-height=&quot;0&quot; data-ke-mobilestyle=&quot;widthContent&quot; onerror=&quot;this.onerror=null; this.src='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png'; this.srcset='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png';&quot;/&gt;&lt;/span&gt;&lt;/figure&gt;
&lt;/p&gt;
&lt;h2&gt;그렇다면 avx2가 지원되는 CPU는 어떻게 확인하면 될까?&lt;/h2&gt;
&lt;ul&gt;
&lt;li&gt;사실 요즘 Intel CPU는 모두 지원한다고 보면 된다.&lt;/li&gt;
&lt;li&gt;AVX를 지원하는 Intel CPU들은, 샌디브릿지, 아이비브릿지이며&lt;/li&gt;
&lt;li&gt;AVX2를 지원하는 Intel CPU들은, 하스웰, 브로드웰, 스카이레이크, 카비레이크 다.&lt;/li&gt;
&lt;li&gt;하지만 위의 패밀리 이름으로는 꼭 믿을게 못되는게, 같은 패밀리라도 CPU에 따라서 지원되고 안되고 하더라.&lt;/li&gt;
&lt;li&gt;리눅스에서는 &lt;code&gt;cat /proc/cpuinfo&lt;/code&gt;를 쳐 보면 CPU 관련 정보가 주루룩 나오는곳에 avx 관련이 있는지 볼 수 있다.&lt;/li&gt;
&lt;li&gt;옛날글 &lt;a href=&quot;https://blog.ggaman.com/1026&quot;&gt;[Vultr] VPS Instance Type 별 CPU 속도 확인&lt;/a&gt; 의 사진을 아래에 붙여 둠.&lt;/li&gt;
&lt;/ul&gt;
&lt;p&gt;&lt;figure class=&quot;imageblock alignCenter&quot; data-origin-width=&quot;0&quot; data-origin-height=&quot;0&quot; data-ke-mobilestyle=&quot;widthContent&quot;&gt;&lt;span data-url=&quot;https://blog.kakaocdn.net/dn/bNRCn8/btq0efQFoxV/812PdmKvhZcFVSYhGSv830/img.png&quot; data-phocus=&quot;https://blog.kakaocdn.net/dn/bNRCn8/btq0efQFoxV/812PdmKvhZcFVSYhGSv830/img.png&quot;&gt;&lt;img src=&quot;https://blog.kakaocdn.net/dn/bNRCn8/btq0efQFoxV/812PdmKvhZcFVSYhGSv830/img.png&quot; srcset=&quot;https://img1.daumcdn.net/thumb/R1280x0/?scode=mtistory2&amp;fname=https%3A%2F%2Fblog.kakaocdn.net%2Fdn%2FbNRCn8%2Fbtq0efQFoxV%2F812PdmKvhZcFVSYhGSv830%2Fimg.png&quot; data-origin-width=&quot;0&quot; data-origin-height=&quot;0&quot; data-ke-mobilestyle=&quot;widthContent&quot; onerror=&quot;this.onerror=null; this.src='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png'; this.srcset='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png';&quot;/&gt;&lt;/span&gt;&lt;/figure&gt;
&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;인텔 홈페이지에 가서 제품명을 쳐서 검색해도 알 수 있다.&lt;/li&gt;
&lt;li&gt;관련링크 : &lt;a href=&quot;https://ark.intel.com/content/www/us/en/ark.html&quot;&gt;https://ark.intel.com/content/www/us/en/ark.html&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;아래 그림은 i7-7500U 를 검색했을때 나오는 일부이다.&lt;/li&gt;
&lt;/ul&gt;
&lt;p&gt;&lt;figure class=&quot;imageblock alignCenter&quot; data-origin-width=&quot;686&quot; data-origin-height=&quot;419&quot; data-ke-mobilestyle=&quot;widthContent&quot;&gt;&lt;span data-url=&quot;https://blog.kakaocdn.net/dn/cHfPcy/btq0fkqEwoC/pG6lQ7pBCs3pZk1Wz4Mmw0/img.png&quot; data-phocus=&quot;https://blog.kakaocdn.net/dn/cHfPcy/btq0fkqEwoC/pG6lQ7pBCs3pZk1Wz4Mmw0/img.png&quot;&gt;&lt;img src=&quot;https://blog.kakaocdn.net/dn/cHfPcy/btq0fkqEwoC/pG6lQ7pBCs3pZk1Wz4Mmw0/img.png&quot; srcset=&quot;https://img1.daumcdn.net/thumb/R1280x0/?scode=mtistory2&amp;fname=https%3A%2F%2Fblog.kakaocdn.net%2Fdn%2FcHfPcy%2Fbtq0fkqEwoC%2FpG6lQ7pBCs3pZk1Wz4Mmw0%2Fimg.png&quot; data-origin-width=&quot;686&quot; data-origin-height=&quot;419&quot; data-ke-mobilestyle=&quot;widthContent&quot; onerror=&quot;this.onerror=null; this.src='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png'; this.srcset='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png';&quot;/&gt;&lt;/span&gt;&lt;/figure&gt;
&lt;/p&gt;
&lt;h1&gt;그래서 어떻게 해결했냐고?&lt;/h1&gt;
&lt;ul&gt;
&lt;li&gt;좀 더 좋은 CPU가 달린 Node에 Pod를 배치했다.&lt;/li&gt;
&lt;li&gt;오래된 CPU가 있는 장비에는 다른 Pod들을 배치하면 되니깐. ㅎㅎ&lt;/li&gt;
&lt;/ul&gt;</description>
      <category>공부/컴퓨터</category>
      <category>AVX2</category>
      <category>conv_prepack</category>
      <category>FBGEMM</category>
      <category>kubernetes</category>
      <category>NoQEngine</category>
      <category>pytorch</category>
      <category>Quantization</category>
      <category>양자화</category>
      <category>쿠버네티스</category>
      <category>파이토치</category>
      <author>찬</author>
      <guid isPermaLink="true">https://blog.ggaman.com/1028</guid>
      <comments>https://blog.ggaman.com/1028#entry1028comment</comments>
      <pubDate>Tue, 16 Mar 2021 01:04:25 +0900</pubDate>
    </item>
    <item>
      <title>[PyTorch] 1.8 release와 함께 GPU memory fraction 이 지원됩니다. - torch.cuda.set_per_process_memory_fraction</title>
      <link>https://blog.ggaman.com/1027</link>
      <description>&lt;h1&gt;인공지능할때 GPU 메모리를 나눠쓰고 싶어요.&lt;/h1&gt;
&lt;ul&gt;
&lt;li&gt;AI 서비스를 운영하려고 하면, GPU 메모리를 나눠써야 하는 경우가 있다.&lt;/li&gt;
&lt;li&gt;관련링크 : &lt;a href=&quot;https://ggaman.tistory.com/1025&quot;&gt;[kubernetes] Extended Resource로 나만의 리소스 제약 (request, limit) 만들어서 사용하기 - GPU RAM 나눠쓰기&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;예전글에 적었다시피, 운영하는 장비의 GPU memory이 너무 큰데 한놈이 다 쓴다거나,&lt;/li&gt;
&lt;li&gt;혹은 한 놈이 비정상적 동작으로 인해서 GPU memory를 너무 많이 쓴다면 다른 애들에 문제가 생길거다.&lt;/li&gt;
&lt;/ul&gt;
&lt;h1&gt;Tensorflow 에서는 나눠쓰는것을 옛날부터 지원했다.&lt;/h1&gt;
&lt;ul&gt;
&lt;li&gt;tensorflow에서는 1.x 대 부터 &lt;code&gt;per_process_gpu_memory_fraction&lt;/code&gt; 를 사용해서 process당 사용할 수 있는 GPU 메모리를 지정할 수 있었다.&lt;/li&gt;
&lt;li&gt;tensorflow 2.x 대 부터는 &lt;code&gt;tf.config.LogicalDeviceConfiguration&lt;/code&gt;의 설정으로 MB 단위로 설정가능 하다. (현재는 GPU 일때만 지원)&lt;/li&gt;
&lt;li&gt;관련링크 : &lt;a href=&quot;https://www.tensorflow.org/api_docs/python/tf/config/LogicalDeviceConfiguration&quot;&gt;https://www.tensorflow.org/api_docs/python/tf/config/LogicalDeviceConfiguration&lt;/a&gt;&lt;/li&gt;
&lt;/ul&gt;
&lt;p&gt;&lt;figure class=&quot;imageblock alignCenter&quot; data-origin-width=&quot;0&quot; data-origin-height=&quot;0&quot; data-ke-mobilestyle=&quot;widthContent&quot;&gt;&lt;span data-url=&quot;https://blog.kakaocdn.net/dn/bqMcho/btqZZTazcCH/j7kHK07u8k8T5c9j6ugss1/img.png&quot; data-phocus=&quot;https://blog.kakaocdn.net/dn/bqMcho/btqZZTazcCH/j7kHK07u8k8T5c9j6ugss1/img.png&quot;&gt;&lt;img src=&quot;https://blog.kakaocdn.net/dn/bqMcho/btqZZTazcCH/j7kHK07u8k8T5c9j6ugss1/img.png&quot; srcset=&quot;https://img1.daumcdn.net/thumb/R1280x0/?scode=mtistory2&amp;fname=https%3A%2F%2Fblog.kakaocdn.net%2Fdn%2FbqMcho%2FbtqZZTazcCH%2Fj7kHK07u8k8T5c9j6ugss1%2Fimg.png&quot; data-origin-width=&quot;0&quot; data-origin-height=&quot;0&quot; data-ke-mobilestyle=&quot;widthContent&quot; onerror=&quot;this.onerror=null; this.src='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png'; this.srcset='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png';&quot;/&gt;&lt;/span&gt;&lt;/figure&gt;
&lt;/p&gt;
&lt;h1&gt;PyTorch는 없었으나, 1.8이 릴리즈되면서 가능하도록 추가 되었다.&lt;/h1&gt;
&lt;ul&gt;
&lt;li&gt;파이토치에서는 몇년전부터 텐서플로우는 있는데 왜 PyTorch에는 지원해 주지 않느냐~ 라면서 꾸준히 많은 사람들이 이슈를 제기했다.&lt;/li&gt;
&lt;li&gt;그리고 2020년 11월 18일, Pull Request를 통해서 코드가 제출 되었고, 2021년 3월 4일 PyTorch 1.8 이 릴리즈되면서 해당 기능이 추가 되었다. ( torch.cuda.set_per_process_memory_fraction )&lt;/li&gt;
&lt;li&gt;관련링크 : &lt;a href=&quot;https://github.com/pytorch/pytorch/pull/48172/commits/ada19609a4f61cac69bd772cafd6c89471ff5253&quot;&gt;https://github.com/pytorch/pytorch/pull/48172/commits/ada19609a4f61cac69bd772cafd6c89471ff5253&lt;/a&gt;&lt;/li&gt;
&lt;/ul&gt;
&lt;p&gt;&lt;figure class=&quot;imageblock alignCenter&quot; data-origin-width=&quot;0&quot; data-origin-height=&quot;0&quot; data-ke-mobilestyle=&quot;widthContent&quot;&gt;&lt;span data-url=&quot;https://blog.kakaocdn.net/dn/qKk3Q/btqZ6j6pBsm/DgVTdgQMQQwaXkvPTeut4K/img.png&quot; data-phocus=&quot;https://blog.kakaocdn.net/dn/qKk3Q/btqZ6j6pBsm/DgVTdgQMQQwaXkvPTeut4K/img.png&quot;&gt;&lt;img src=&quot;https://blog.kakaocdn.net/dn/qKk3Q/btqZ6j6pBsm/DgVTdgQMQQwaXkvPTeut4K/img.png&quot; srcset=&quot;https://img1.daumcdn.net/thumb/R1280x0/?scode=mtistory2&amp;fname=https%3A%2F%2Fblog.kakaocdn.net%2Fdn%2FqKk3Q%2FbtqZ6j6pBsm%2FDgVTdgQMQQwaXkvPTeut4K%2Fimg.png&quot; data-origin-width=&quot;0&quot; data-origin-height=&quot;0&quot; data-ke-mobilestyle=&quot;widthContent&quot; onerror=&quot;this.onerror=null; this.src='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png'; this.srcset='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png';&quot;/&gt;&lt;/span&gt;&lt;/figure&gt;
&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;위 그림에 이미 사용법이 나와 있지만, 그래도 해당 기능의 함수형태와 사용법은 아래와 같다.&lt;/li&gt;
&lt;li&gt;관련링크 : &lt;a href=&quot;https://pytorch.org/docs/stable/cuda.html&quot;&gt;https://pytorch.org/docs/stable/cuda.html&lt;/a&gt;&lt;/li&gt;
&lt;/ul&gt;
&lt;p&gt;&lt;figure class=&quot;imageblock alignCenter&quot; data-origin-width=&quot;0&quot; data-origin-height=&quot;0&quot; data-ke-mobilestyle=&quot;widthContent&quot;&gt;&lt;span data-url=&quot;https://blog.kakaocdn.net/dn/wUWIS/btqZZTBATdB/CAKbkw5R0Ixa2nMgzoyDxk/img.png&quot; data-phocus=&quot;https://blog.kakaocdn.net/dn/wUWIS/btqZZTBATdB/CAKbkw5R0Ixa2nMgzoyDxk/img.png&quot;&gt;&lt;img src=&quot;https://blog.kakaocdn.net/dn/wUWIS/btqZZTBATdB/CAKbkw5R0Ixa2nMgzoyDxk/img.png&quot; srcset=&quot;https://img1.daumcdn.net/thumb/R1280x0/?scode=mtistory2&amp;fname=https%3A%2F%2Fblog.kakaocdn.net%2Fdn%2FwUWIS%2FbtqZZTBATdB%2FCAKbkw5R0Ixa2nMgzoyDxk%2Fimg.png&quot; data-origin-width=&quot;0&quot; data-origin-height=&quot;0&quot; data-ke-mobilestyle=&quot;widthContent&quot; onerror=&quot;this.onerror=null; this.src='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png'; this.srcset='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png';&quot;/&gt;&lt;/span&gt;&lt;/figure&gt;
&lt;figure class=&quot;imageblock alignCenter&quot; data-origin-width=&quot;0&quot; data-origin-height=&quot;0&quot; data-ke-mobilestyle=&quot;widthContent&quot;&gt;&lt;span data-url=&quot;https://blog.kakaocdn.net/dn/ehk34x/btqZ3eLjCTJ/oaceekUlyjymmWOWqM8kpK/img.png&quot; data-phocus=&quot;https://blog.kakaocdn.net/dn/ehk34x/btqZ3eLjCTJ/oaceekUlyjymmWOWqM8kpK/img.png&quot;&gt;&lt;img src=&quot;https://blog.kakaocdn.net/dn/ehk34x/btqZ3eLjCTJ/oaceekUlyjymmWOWqM8kpK/img.png&quot; srcset=&quot;https://img1.daumcdn.net/thumb/R1280x0/?scode=mtistory2&amp;fname=https%3A%2F%2Fblog.kakaocdn.net%2Fdn%2Fehk34x%2FbtqZ3eLjCTJ%2FoaceekUlyjymmWOWqM8kpK%2Fimg.png&quot; data-origin-width=&quot;0&quot; data-origin-height=&quot;0&quot; data-ke-mobilestyle=&quot;widthContent&quot; onerror=&quot;this.onerror=null; this.src='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png'; this.srcset='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png';&quot;/&gt;&lt;/span&gt;&lt;/figure&gt;
&lt;/p&gt;
&lt;h1&gt;그래서?&lt;/h1&gt;
&lt;ul&gt;
&lt;li&gt;서비스를 운영할때 좀 더 유연하게 운영할 수 있게 되었다. 비정상적인 애들은 걍 걔 혼자만 문제가 될 거고 다른애들에게는 영향을 안 줄 수 있기 때문에 ㅎㅎ.&lt;/li&gt;
&lt;li&gt;문제가 있다면, 지금까지 동작중인 애들이 PyTorch 1.8 에서 별 이상 없이 잘 도는지 테스트 해야 하는데... 그건... 음... 그래.. 우선 넘어가자. ㅎ.&lt;/li&gt;
&lt;/ul&gt;</description>
      <category>공부/컴퓨터</category>
      <category>Fraction</category>
      <category>GPU</category>
      <category>Memory</category>
      <category>pytorch</category>
      <category>tensroflow</category>
      <category>torch.cuda.set_per_process_memory_fraction</category>
      <category>vRAM</category>
      <author>찬</author>
      <guid isPermaLink="true">https://blog.ggaman.com/1027</guid>
      <comments>https://blog.ggaman.com/1027#entry1027comment</comments>
      <pubDate>Sun, 14 Mar 2021 00:10:36 +0900</pubDate>
    </item>
    <item>
      <title>[Vultr] VPS Instance Type 별 CPU 속도 확인</title>
      <link>https://blog.ggaman.com/1026</link>
      <description>&lt;h1&gt;Vultr&lt;/h1&gt;
&lt;ul&gt;
&lt;li&gt;전에도 이야기 했지만, 난 Vultr을 사용하고 있다.&lt;/li&gt;
&lt;li&gt;관련링크 : &lt;a href=&quot;https://blog.ggaman.com/1019&quot;&gt;https://blog.ggaman.com/1019&lt;/a&gt;&lt;/li&gt;
&lt;/ul&gt;
&lt;h1&gt;VPC Instance에서 CPU는 어떤것을 사용할까?&lt;/h1&gt;
&lt;ul&gt;
&lt;li&gt;Vultr는 4가지 Type의 Instance를 지원해 준다. ( &lt;code&gt;Cloud Compute&lt;/code&gt;, &lt;code&gt;High Frequency&lt;/code&gt;, &lt;code&gt;Bare Metal&lt;/code&gt;, &lt;code&gt;Dedicated Cloud&lt;/code&gt; )&lt;/li&gt;
&lt;/ul&gt;
&lt;p&gt;&lt;figure class=&quot;imageblock alignCenter&quot; data-origin-width=&quot;0&quot; data-origin-height=&quot;0&quot; data-ke-mobilestyle=&quot;widthContent&quot;&gt;&lt;span data-url=&quot;https://blog.kakaocdn.net/dn/nVgU1/btqYJ87MvOx/2RK0ooYRYhra0d0oMib7M0/img.png&quot; data-phocus=&quot;https://blog.kakaocdn.net/dn/nVgU1/btqYJ87MvOx/2RK0ooYRYhra0d0oMib7M0/img.png&quot;&gt;&lt;img src=&quot;https://blog.kakaocdn.net/dn/nVgU1/btqYJ87MvOx/2RK0ooYRYhra0d0oMib7M0/img.png&quot; srcset=&quot;https://img1.daumcdn.net/thumb/R1280x0/?scode=mtistory2&amp;fname=https%3A%2F%2Fblog.kakaocdn.net%2Fdn%2FnVgU1%2FbtqYJ87MvOx%2F2RK0ooYRYhra0d0oMib7M0%2Fimg.png&quot; data-origin-width=&quot;0&quot; data-origin-height=&quot;0&quot; data-ke-mobilestyle=&quot;widthContent&quot; onerror=&quot;this.onerror=null; this.src='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png'; this.srcset='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png';&quot;/&gt;&lt;/span&gt;&lt;/figure&gt;
&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;당연히 &lt;code&gt;High Frequency&lt;/code&gt;는 &lt;code&gt;Cloud Compute&lt;/code&gt;보다는 빠르겠지... 하지만 얼마나 빠른지, 혹은 &lt;code&gt;Cloud Compute&lt;/code&gt;에서 충분한 속도가 난다면 굳이 더 비싼 제품을 고를 필요가 있을까?&lt;/li&gt;
&lt;li&gt;그래서 각 제품의 &lt;code&gt;/proc/cpuinfo&lt;/code&gt; 정보를 확인해 보았다.&lt;/li&gt;
&lt;/ul&gt;
&lt;h2&gt;Cloud Compute&lt;/h2&gt;
&lt;ul&gt;
&lt;li&gt;한국 리전에서 만들수 있어서, &lt;code&gt;1vCPU&lt;/code&gt;, &lt;code&gt;1GByte RAM&lt;/code&gt;으로 만들어서 확인했다.&lt;/li&gt;
&lt;li&gt;CPU는 &lt;code&gt;Cascadelake&lt;/code&gt;를 사용하고 있으며, 속도는 &lt;code&gt;3GHz&lt;/code&gt;, 캐시는 &lt;code&gt;16MByte&lt;/code&gt; 이다.&lt;/li&gt;
&lt;li&gt;flags 쪽에 보면, &lt;code&gt;avx512&lt;/code&gt;를 지원하는것을 알 수 있다.&lt;/li&gt;
&lt;/ul&gt;
&lt;p&gt;&lt;figure class=&quot;imageblock alignCenter&quot; data-origin-width=&quot;0&quot; data-origin-height=&quot;0&quot; data-ke-mobilestyle=&quot;widthContent&quot;&gt;&lt;span data-url=&quot;https://blog.kakaocdn.net/dn/bLR8Sz/btqYE6P83TJ/4QSpuw91AQ5VNWTDdpJg01/img.png&quot; data-phocus=&quot;https://blog.kakaocdn.net/dn/bLR8Sz/btqYE6P83TJ/4QSpuw91AQ5VNWTDdpJg01/img.png&quot;&gt;&lt;img src=&quot;https://blog.kakaocdn.net/dn/bLR8Sz/btqYE6P83TJ/4QSpuw91AQ5VNWTDdpJg01/img.png&quot; srcset=&quot;https://img1.daumcdn.net/thumb/R1280x0/?scode=mtistory2&amp;fname=https%3A%2F%2Fblog.kakaocdn.net%2Fdn%2FbLR8Sz%2FbtqYE6P83TJ%2F4QSpuw91AQ5VNWTDdpJg01%2Fimg.png&quot; data-origin-width=&quot;0&quot; data-origin-height=&quot;0&quot; data-ke-mobilestyle=&quot;widthContent&quot; onerror=&quot;this.onerror=null; this.src='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png'; this.srcset='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png';&quot;/&gt;&lt;/span&gt;&lt;/figure&gt;
&lt;/p&gt;
&lt;h2&gt;High Frequency&lt;/h2&gt;
&lt;ul&gt;
&lt;li&gt;한국 리전에서 만들수 있어서, &lt;code&gt;1vCPU&lt;/code&gt;, &lt;code&gt;1GByte RAM&lt;/code&gt;으로 만들어서 확인했다.&lt;/li&gt;
&lt;li&gt;CPU는 &lt;code&gt;Skylake, IBRS&lt;/code&gt;를 사용하고 있으며, 속도는 &lt;code&gt;3.7Ghz&lt;/code&gt;, 캐시는 &lt;code&gt;16Mbyte&lt;/code&gt; 이다.&lt;/li&gt;
&lt;li&gt;flag 쪽을 봐도 &lt;code&gt;avx2&lt;/code&gt;까지만 지원하지, &lt;code&gt;avx512&lt;/code&gt;를 지원하지 않는다.&lt;/li&gt;
&lt;/ul&gt;
&lt;p&gt;&lt;figure class=&quot;imageblock alignCenter&quot; data-origin-width=&quot;0&quot; data-origin-height=&quot;0&quot; data-ke-mobilestyle=&quot;widthContent&quot;&gt;&lt;span data-url=&quot;https://blog.kakaocdn.net/dn/d1GkXB/btqYW2SGicA/UlXsrARkYFPoMYOQhio6n0/img.png&quot; data-phocus=&quot;https://blog.kakaocdn.net/dn/d1GkXB/btqYW2SGicA/UlXsrARkYFPoMYOQhio6n0/img.png&quot;&gt;&lt;img src=&quot;https://blog.kakaocdn.net/dn/d1GkXB/btqYW2SGicA/UlXsrARkYFPoMYOQhio6n0/img.png&quot; srcset=&quot;https://img1.daumcdn.net/thumb/R1280x0/?scode=mtistory2&amp;fname=https%3A%2F%2Fblog.kakaocdn.net%2Fdn%2Fd1GkXB%2FbtqYW2SGicA%2FUlXsrARkYFPoMYOQhio6n0%2Fimg.png&quot; data-origin-width=&quot;0&quot; data-origin-height=&quot;0&quot; data-ke-mobilestyle=&quot;widthContent&quot; onerror=&quot;this.onerror=null; this.src='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png'; this.srcset='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png';&quot;/&gt;&lt;/span&gt;&lt;/figure&gt;
&lt;/p&gt;
&lt;h2&gt;Bare Metal&lt;/h2&gt;
&lt;ul&gt;
&lt;li&gt;&lt;code&gt;Bare Metal&lt;/code&gt;은 현재 몇군데의 리전에서만 지원하는데, 그것도 다 &lt;code&gt;Sold out&lt;/code&gt; 상태라서 체크하지 못함&lt;/li&gt;
&lt;/ul&gt;
&lt;h2&gt;Dedicated Cloud&lt;/h2&gt;
&lt;ul&gt;
&lt;li&gt;&lt;code&gt;Dedicated Cloud&lt;/code&gt;는 아시아에 일본 밖에 없어서 일본에 있는 것으로 확인했다. &lt;code&gt;2vCPU&lt;/code&gt;, &lt;code&gt;8GByte RAM&lt;/code&gt;으로 만들어서 확인했다.&lt;/li&gt;
&lt;li&gt;CPU는 &lt;code&gt;Haswell, no TSX, IBRS&lt;/code&gt;를 사용하고 있으며, 속도는 &lt;code&gt;3.6Ghz&lt;/code&gt;, 캐시는 &lt;code&gt;16MByte&lt;/code&gt;이다.&lt;/li&gt;
&lt;li&gt;flag 쪽을 봐도 &lt;code&gt;avx2&lt;/code&gt;까지만 지원하지, &lt;code&gt;avx512&lt;/code&gt;를 지원하지 않는다.&lt;/li&gt;
&lt;/ul&gt;
&lt;p&gt;&lt;figure class=&quot;imageblock alignCenter&quot; data-origin-width=&quot;0&quot; data-origin-height=&quot;0&quot; data-ke-mobilestyle=&quot;widthContent&quot;&gt;&lt;span data-url=&quot;https://blog.kakaocdn.net/dn/DVoIA/btqY2sXA9lk/3q1y65meta8VyP1rR3wMp1/img.png&quot; data-phocus=&quot;https://blog.kakaocdn.net/dn/DVoIA/btqY2sXA9lk/3q1y65meta8VyP1rR3wMp1/img.png&quot;&gt;&lt;img src=&quot;https://blog.kakaocdn.net/dn/DVoIA/btqY2sXA9lk/3q1y65meta8VyP1rR3wMp1/img.png&quot; srcset=&quot;https://img1.daumcdn.net/thumb/R1280x0/?scode=mtistory2&amp;fname=https%3A%2F%2Fblog.kakaocdn.net%2Fdn%2FDVoIA%2FbtqY2sXA9lk%2F3q1y65meta8VyP1rR3wMp1%2Fimg.png&quot; data-origin-width=&quot;0&quot; data-origin-height=&quot;0&quot; data-ke-mobilestyle=&quot;widthContent&quot; onerror=&quot;this.onerror=null; this.src='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png'; this.srcset='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png';&quot;/&gt;&lt;/span&gt;&lt;/figure&gt;
&lt;/p&gt;
&lt;h1&gt;정리&lt;/h1&gt;
&lt;ul&gt;
&lt;li&gt;일반적인 상황이라면 &lt;code&gt;Cloud Compute&lt;/code&gt; 를 사용하면 될 듯 하다. 우선 &lt;code&gt;Cloud Compute&lt;/code&gt;를 사용해 봐라.&lt;/li&gt;
&lt;li&gt;더 빠른 속도가 필요하다면 &lt;code&gt;High Frequncey&lt;/code&gt;를 사용하면 된다.&lt;/li&gt;
&lt;li&gt;&lt;code&gt;Dedicated Cloud&lt;/code&gt; 는 걍 쓰지 않아도 될 듯 하다.&lt;/li&gt;
&lt;/ul&gt;
&lt;h1&gt;참고 - AVX가 뭔데?&lt;/h1&gt;
&lt;ul&gt;
&lt;li&gt;CPU 비교를 할 때, 속도나 캐시 같은것만 보면 되지 왜 자꾸 AVX 관련 이야기를 할까?&lt;/li&gt;
&lt;li&gt;&lt;code&gt;AVX(Advanced Vector Extensions)&lt;/code&gt;는 Intel에서 계산을 빨리 하도록 만들어둔, x86 확장 명령어 집합이다.&lt;/li&gt;
&lt;li&gt;&lt;code&gt;Intel&lt;/code&gt;에서 만든 &lt;code&gt;확장 명령어 집합&lt;/code&gt;이니깐, &lt;code&gt;AMD&lt;/code&gt;에는 지원하지 않는 명령어다.&lt;/li&gt;
&lt;li&gt;&lt;code&gt;AVX&lt;/code&gt;가 있으면 뭐가 좋냐면, 이름 그대로 Vector 연산을 빠르게 할 수 있도록 도움을 준다.&lt;/li&gt;
&lt;li&gt;&lt;code&gt;AVX2&lt;/code&gt;는256bit 단위로 빠르게 연산할 수 있고, &lt;code&gt;AVX512&lt;/code&gt;는 512bit 단위로 빠르게 연산할 수 있다.&lt;/li&gt;
&lt;li&gt;아래 그림을 보면 이해가 빠를 것이다. 사진 아래에 있는 링크를 클릭해서 동영상을 보는것을 추천한다.&lt;/li&gt;
&lt;/ul&gt;
&lt;p&gt;&lt;figure class=&quot;imageblock alignCenter&quot; data-origin-width=&quot;0&quot; data-origin-height=&quot;0&quot; data-ke-mobilestyle=&quot;widthContent&quot;&gt;&lt;span data-url=&quot;https://blog.kakaocdn.net/dn/dXji63/btqYE7hhTCW/9y9HLkhOvUubMXo2nXtQ11/img.png&quot; data-phocus=&quot;https://blog.kakaocdn.net/dn/dXji63/btqYE7hhTCW/9y9HLkhOvUubMXo2nXtQ11/img.png&quot;&gt;&lt;img src=&quot;https://blog.kakaocdn.net/dn/dXji63/btqYE7hhTCW/9y9HLkhOvUubMXo2nXtQ11/img.png&quot; srcset=&quot;https://img1.daumcdn.net/thumb/R1280x0/?scode=mtistory2&amp;fname=https%3A%2F%2Fblog.kakaocdn.net%2Fdn%2FdXji63%2FbtqYE7hhTCW%2F9y9HLkhOvUubMXo2nXtQ11%2Fimg.png&quot; data-origin-width=&quot;0&quot; data-origin-height=&quot;0&quot; data-ke-mobilestyle=&quot;widthContent&quot; onerror=&quot;this.onerror=null; this.src='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png'; this.srcset='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png';&quot;/&gt;&lt;/span&gt;&lt;/figure&gt;
&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;관련링크 1 Youtube : &lt;a href=&quot;http://youtu.be/PcIFbx5bqYc?t=29&quot;&gt;http://youtu.be/PcIFbx5bqYc?t=29&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;관련링크 2 Intel : &lt;a href=&quot;https://www.intel.co.kr/content/www/kr/ko/architecture-and-technology/avx-512-animation.html#t=30s&quot;&gt;https://www.intel.co.kr/content/www/kr/ko/architecture-and-technology/avx-512-animation.html#t=30s&lt;/a&gt;&lt;/li&gt;
&lt;/ul&gt;
&lt;h2&gt;그럼 인공지능에는 어떤게 좋을까?&lt;/h2&gt;
&lt;ul&gt;
&lt;li&gt;&lt;code&gt;AVX&lt;/code&gt;는 Vector 연산에 효율적이라고 하니, 무언가 계산이 많은 인공지능에서도 도움이 될 듯 하다.&lt;/li&gt;
&lt;li&gt;그럼 인공지능 엔진을 돌릴때 &lt;code&gt;AVX&lt;/code&gt;를 사용하면 좀 더 빠르지 않을까?&lt;/li&gt;
&lt;/ul&gt;
&lt;h2&gt;Tensorflow에서 Intel AVX 사용하기&lt;/h2&gt;
&lt;ul&gt;
&lt;li&gt;몇년전 Anaconda에서 Intel의 &lt;code&gt;MKL(Math Kernel Library)&lt;/code&gt;를 지원하겠다면서, 직접 이런저런 라이브러리를 설치해서 쓰지 말고, Anaconda를 이용하라고 광고? 를 했던 적이 있다.&lt;/li&gt;
&lt;li&gt;관련링크 1 : &lt;a href=&quot;https://www.anaconda.com/blog/anaconda-distribution-5-3-0-released&quot;&gt;https://www.anaconda.com/blog/anaconda-distribution-5-3-0-released&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;관련링크 2 : &lt;a href=&quot;https://docs.anaconda.com/mkl-optimizations/index.html&quot;&gt;https://docs.anaconda.com/mkl-optimizations/index.html&lt;/a&gt;&lt;/li&gt;
&lt;/ul&gt;
&lt;p&gt;&lt;figure class=&quot;imageblock alignCenter&quot; data-origin-width=&quot;0&quot; data-origin-height=&quot;0&quot; data-ke-mobilestyle=&quot;widthContent&quot;&gt;&lt;span data-url=&quot;https://blog.kakaocdn.net/dn/LpKDG/btqYW2Fbkl0/4ND3NopcEKodBYf5F3weKk/img.png&quot; data-phocus=&quot;https://blog.kakaocdn.net/dn/LpKDG/btqYW2Fbkl0/4ND3NopcEKodBYf5F3weKk/img.png&quot;&gt;&lt;img src=&quot;https://blog.kakaocdn.net/dn/LpKDG/btqYW2Fbkl0/4ND3NopcEKodBYf5F3weKk/img.png&quot; srcset=&quot;https://img1.daumcdn.net/thumb/R1280x0/?scode=mtistory2&amp;fname=https%3A%2F%2Fblog.kakaocdn.net%2Fdn%2FLpKDG%2FbtqYW2Fbkl0%2F4ND3NopcEKodBYf5F3weKk%2Fimg.png&quot; data-origin-width=&quot;0&quot; data-origin-height=&quot;0&quot; data-ke-mobilestyle=&quot;widthContent&quot; onerror=&quot;this.onerror=null; this.src='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png'; this.srcset='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png';&quot;/&gt;&lt;/span&gt;&lt;/figure&gt;
&lt;figure class=&quot;imageblock alignCenter&quot; data-origin-width=&quot;0&quot; data-origin-height=&quot;0&quot; data-ke-mobilestyle=&quot;widthContent&quot;&gt;&lt;span data-url=&quot;https://blog.kakaocdn.net/dn/oPdHj/btqYJ8UfkwM/yUpcg1nAxupL2otNahqm6k/img.png&quot; data-phocus=&quot;https://blog.kakaocdn.net/dn/oPdHj/btqYJ8UfkwM/yUpcg1nAxupL2otNahqm6k/img.png&quot;&gt;&lt;img src=&quot;https://blog.kakaocdn.net/dn/oPdHj/btqYJ8UfkwM/yUpcg1nAxupL2otNahqm6k/img.png&quot; srcset=&quot;https://img1.daumcdn.net/thumb/R1280x0/?scode=mtistory2&amp;fname=https%3A%2F%2Fblog.kakaocdn.net%2Fdn%2FoPdHj%2FbtqYJ8UfkwM%2FyUpcg1nAxupL2otNahqm6k%2Fimg.png&quot; data-origin-width=&quot;0&quot; data-origin-height=&quot;0&quot; data-ke-mobilestyle=&quot;widthContent&quot; onerror=&quot;this.onerror=null; this.src='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png'; this.srcset='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png';&quot;/&gt;&lt;/span&gt;&lt;/figure&gt;
&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;Intel에서 Docker Image는 제공해 주기도 한다.&lt;/li&gt;
&lt;li&gt;관련링크 : &lt;a href=&quot;https://software.intel.com/content/www/us/en/develop/articles/intel-optimization-for-tensorflow-installation-guide.html&quot;&gt;https://software.intel.com/content/www/us/en/develop/articles/intel-optimization-for-tensorflow-installation-guide.html&lt;/a&gt;&lt;/li&gt;
&lt;/ul&gt;
&lt;p&gt;&lt;figure class=&quot;imageblock alignCenter&quot; data-origin-width=&quot;0&quot; data-origin-height=&quot;0&quot; data-ke-mobilestyle=&quot;widthContent&quot;&gt;&lt;span data-url=&quot;https://blog.kakaocdn.net/dn/bj0Fkv/btqYQv8Z9ma/M7b173ZXxrtQba0EinaEFk/img.png&quot; data-phocus=&quot;https://blog.kakaocdn.net/dn/bj0Fkv/btqYQv8Z9ma/M7b173ZXxrtQba0EinaEFk/img.png&quot;&gt;&lt;img src=&quot;https://blog.kakaocdn.net/dn/bj0Fkv/btqYQv8Z9ma/M7b173ZXxrtQba0EinaEFk/img.png&quot; srcset=&quot;https://img1.daumcdn.net/thumb/R1280x0/?scode=mtistory2&amp;fname=https%3A%2F%2Fblog.kakaocdn.net%2Fdn%2Fbj0Fkv%2FbtqYQv8Z9ma%2FM7b173ZXxrtQba0EinaEFk%2Fimg.png&quot; data-origin-width=&quot;0&quot; data-origin-height=&quot;0&quot; data-ke-mobilestyle=&quot;widthContent&quot; onerror=&quot;this.onerror=null; this.src='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png'; this.srcset='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png';&quot;/&gt;&lt;/span&gt;&lt;/figure&gt;
&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;더 이상 알아 보는건 Vultr 속도 측정이랑 큰 상관이 없으므로, 여기서 멈춤. ㅎ.&lt;/li&gt;
&lt;/ul&gt;
&lt;h2&gt;또 참고&lt;/h2&gt;
&lt;ul&gt;
&lt;li&gt;네이버클라우드에서도 AVX512를 지원하는 CPU를 사용한다고 광고 ( 물론, Intel이랑 협약을 맺었으니 광고하는거겠지? ㅎ )&lt;/li&gt;
&lt;li&gt;관련링크 : &lt;a href=&quot;https://www.ncloud.com/intel&quot;&gt;https://www.ncloud.com/intel&lt;/a&gt;&lt;/li&gt;
&lt;/ul&gt;
&lt;p&gt;&lt;figure class=&quot;imageblock alignCenter&quot; data-origin-width=&quot;0&quot; data-origin-height=&quot;0&quot; data-ke-mobilestyle=&quot;widthContent&quot;&gt;&lt;span data-url=&quot;https://blog.kakaocdn.net/dn/brVDo7/btqYZ7zria1/GdUrQ6lG0BaxkGOgT4WTVk/img.png&quot; data-phocus=&quot;https://blog.kakaocdn.net/dn/brVDo7/btqYZ7zria1/GdUrQ6lG0BaxkGOgT4WTVk/img.png&quot;&gt;&lt;img src=&quot;https://blog.kakaocdn.net/dn/brVDo7/btqYZ7zria1/GdUrQ6lG0BaxkGOgT4WTVk/img.png&quot; srcset=&quot;https://img1.daumcdn.net/thumb/R1280x0/?scode=mtistory2&amp;fname=https%3A%2F%2Fblog.kakaocdn.net%2Fdn%2FbrVDo7%2FbtqYZ7zria1%2FGdUrQ6lG0BaxkGOgT4WTVk%2Fimg.png&quot; data-origin-width=&quot;0&quot; data-origin-height=&quot;0&quot; data-ke-mobilestyle=&quot;widthContent&quot; onerror=&quot;this.onerror=null; this.src='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png'; this.srcset='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png';&quot;/&gt;&lt;/span&gt;&lt;/figure&gt;
&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;AVX 속도 관련해서 테스트하신 분에 따르면 Tensorflow Serving시 AVX512가 지원되도록 Custom Build 한 후 동작시켜 보았더니, 10~20% 정도의 성능 향상이 있었다고 한다.&lt;/li&gt;
&lt;li&gt;관련링크 : &lt;a href=&quot;https://jeongukjae.github.io/posts/tensorflow-serving-custom-build/&quot;&gt;https://jeongukjae.github.io/posts/tensorflow-serving-custom-build/&lt;/a&gt;&lt;/li&gt;
&lt;/ul&gt;</description>
      <category>공부/컴퓨터</category>
      <category>AVX</category>
      <category>AVX2</category>
      <category>AVX512</category>
      <category>Cloud Compute</category>
      <category>High Frequncey</category>
      <category>VULTR</category>
      <author>찬</author>
      <guid isPermaLink="true">https://blog.ggaman.com/1026</guid>
      <comments>https://blog.ggaman.com/1026#entry1026comment</comments>
      <pubDate>Mon, 1 Mar 2021 22:46:03 +0900</pubDate>
    </item>
    <item>
      <title>[kubernetes] Extended Resource로 나만의 리소스 제약 (request, limit) 만들어서 사용하기 - GPU RAM 나눠쓰기</title>
      <link>https://blog.ggaman.com/1025</link>
      <description>&lt;h1&gt;여기서 말하는 Resource는 &quot;CRD(Custom Resource Definition)&quot;의 Resource가 아니라, 정말 &quot;자원&quot;개념의 &quot;Resource&quot;다.&lt;/h1&gt;
&lt;h1&gt;GPU는 왜 나눠쓸 수 없을까?&lt;/h1&gt;
&lt;ul&gt;
&lt;li&gt;나는 NVIDIA RTX 8000 GPU를 Node에 1개 달아둔 환경에서 작업중이다.&lt;/li&gt;
&lt;li&gt;NVIDIA RTX 8000은 Datacenter에서 사용할 수 있도록 허용된 NVIDIA 드라이버 라이선스가 있으며, 무려 VRAM이 48GB 나 된다!!!&lt;/li&gt;
&lt;/ul&gt;
&lt;p&gt;&lt;figure class=&quot;imageblock alignCenter&quot; data-origin-width=&quot;0&quot; data-origin-height=&quot;0&quot; data-ke-mobilestyle=&quot;widthContent&quot;&gt;&lt;span data-url=&quot;https://blog.kakaocdn.net/dn/cwFIQx/btqYB5KICF1/Ffr4XGveNHYsIa0b5B4cR1/img.png&quot; data-phocus=&quot;https://blog.kakaocdn.net/dn/cwFIQx/btqYB5KICF1/Ffr4XGveNHYsIa0b5B4cR1/img.png&quot;&gt;&lt;img src=&quot;https://blog.kakaocdn.net/dn/cwFIQx/btqYB5KICF1/Ffr4XGveNHYsIa0b5B4cR1/img.png&quot; srcset=&quot;https://img1.daumcdn.net/thumb/R1280x0/?scode=mtistory2&amp;fname=https%3A%2F%2Fblog.kakaocdn.net%2Fdn%2FcwFIQx%2FbtqYB5KICF1%2FFfr4XGveNHYsIa0b5B4cR1%2Fimg.png&quot; data-origin-width=&quot;0&quot; data-origin-height=&quot;0&quot; data-ke-mobilestyle=&quot;widthContent&quot; onerror=&quot;this.onerror=null; this.src='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png'; this.srcset='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png';&quot;/&gt;&lt;/span&gt;&lt;/figure&gt;
&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;Kubernetes에는 리소스 쿼터라는 개념이 있어서, CPU, RAM을 나눠서 사용할 수 있도록 기능을 제공해 준다.&lt;/li&gt;
&lt;li&gt;관련링크 : kubernetes.io/docs/concepts/configuration/manage-resources-containers/&lt;/li&gt;
&lt;/ul&gt;
&lt;p&gt;&lt;figure class=&quot;imageblock alignCenter&quot; data-origin-width=&quot;0&quot; data-origin-height=&quot;0&quot; data-ke-mobilestyle=&quot;widthContent&quot;&gt;&lt;span data-url=&quot;https://blog.kakaocdn.net/dn/bgxcnb/btqYJ9efpLK/mwPWpwGW8iuApuEpDRCBW0/img.png&quot; data-phocus=&quot;https://blog.kakaocdn.net/dn/bgxcnb/btqYJ9efpLK/mwPWpwGW8iuApuEpDRCBW0/img.png&quot;&gt;&lt;img src=&quot;https://blog.kakaocdn.net/dn/bgxcnb/btqYJ9efpLK/mwPWpwGW8iuApuEpDRCBW0/img.png&quot; srcset=&quot;https://img1.daumcdn.net/thumb/R1280x0/?scode=mtistory2&amp;fname=https%3A%2F%2Fblog.kakaocdn.net%2Fdn%2Fbgxcnb%2FbtqYJ9efpLK%2FmwPWpwGW8iuApuEpDRCBW0%2Fimg.png&quot; data-origin-width=&quot;0&quot; data-origin-height=&quot;0&quot; data-ke-mobilestyle=&quot;widthContent&quot; onerror=&quot;this.onerror=null; this.src='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png'; this.srcset='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png';&quot;/&gt;&lt;/span&gt;&lt;/figure&gt;
&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;예를들어, Pod를 띄울때 아래와 같은 설정을 추가하면
&lt;ul&gt;
&lt;li&gt;Pod가 최소한 0.25vCPU 와 64MByte 메모리를 사용할 수 있도록 확보-선점(requests)한 상태에서 뜨게 되고,&lt;/li&gt;
&lt;li&gt;Pod가 최대한 0.5vCPU와 128MByte 메모리까지(limit)만 사용할 수 있게 된다.&lt;/li&gt;
&lt;li&gt;관련링크 : &lt;a href=&quot;https://kubernetes.io/docs/concepts/configuration/manage-resources-containers/&quot;&gt;kubernetes.io/docs/concepts/configuration/manage-resources-containers/&lt;/a&gt;&lt;/li&gt;
&lt;/ul&gt;
&lt;/li&gt;
&lt;/ul&gt;
&lt;p&gt;&lt;figure class=&quot;imageblock alignCenter&quot; data-origin-width=&quot;0&quot; data-origin-height=&quot;0&quot; data-ke-mobilestyle=&quot;widthContent&quot;&gt;&lt;span data-url=&quot;https://blog.kakaocdn.net/dn/GMZkp/btqYTN8Z6kt/8O5a9jwmxjKjxu8IavXpV0/img.png&quot; data-phocus=&quot;https://blog.kakaocdn.net/dn/GMZkp/btqYTN8Z6kt/8O5a9jwmxjKjxu8IavXpV0/img.png&quot;&gt;&lt;img src=&quot;https://blog.kakaocdn.net/dn/GMZkp/btqYTN8Z6kt/8O5a9jwmxjKjxu8IavXpV0/img.png&quot; srcset=&quot;https://img1.daumcdn.net/thumb/R1280x0/?scode=mtistory2&amp;fname=https%3A%2F%2Fblog.kakaocdn.net%2Fdn%2FGMZkp%2FbtqYTN8Z6kt%2F8O5a9jwmxjKjxu8IavXpV0%2Fimg.png&quot; data-origin-width=&quot;0&quot; data-origin-height=&quot;0&quot; data-ke-mobilestyle=&quot;widthContent&quot; onerror=&quot;this.onerror=null; this.src='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png'; this.srcset='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png';&quot;/&gt;&lt;/span&gt;&lt;/figure&gt;
&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;Pod에서 GPU를 사용하려고 할 때도, 위와 비슷한 방법을 제공한다.
&lt;ul&gt;
&lt;li&gt;NVIDIA GPU Device Plugin을 설치하면, 아래의 방법으로 GPU를 할당 받아서 사용할 수 있다.&lt;/li&gt;
&lt;li&gt;관련링크 1 : &lt;a href=&quot;https://kubernetes.io/docs/tasks/manage-gpus/scheduling-gpus/&quot;&gt;https://kubernetes.io/docs/tasks/manage-gpus/scheduling-gpus/&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;관련링크 2 : &lt;a href=&quot;https://github.com/NVIDIA/k8s-device-plugin&quot;&gt;https://github.com/NVIDIA/k8s-device-plugin&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;아래와 같이 &lt;code&gt;spec/containers/resources/limits/nvidia.com/gpu : 1&lt;/code&gt;와 같이 값을 주면, 해당 컨테이너에서 GPU를 1개 사용하겠다는 뜻이다.&lt;/li&gt;
&lt;/ul&gt;
&lt;/li&gt;
&lt;/ul&gt;
&lt;p&gt;&lt;figure class=&quot;imageblock alignCenter&quot; data-origin-width=&quot;0&quot; data-origin-height=&quot;0&quot; data-ke-mobilestyle=&quot;widthContent&quot;&gt;&lt;span data-url=&quot;https://blog.kakaocdn.net/dn/cWwfbr/btqYDj3aHI7/oNBsKsOKanWtCFS0DJAR3K/img.png&quot; data-phocus=&quot;https://blog.kakaocdn.net/dn/cWwfbr/btqYDj3aHI7/oNBsKsOKanWtCFS0DJAR3K/img.png&quot;&gt;&lt;img src=&quot;https://blog.kakaocdn.net/dn/cWwfbr/btqYDj3aHI7/oNBsKsOKanWtCFS0DJAR3K/img.png&quot; srcset=&quot;https://img1.daumcdn.net/thumb/R1280x0/?scode=mtistory2&amp;fname=https%3A%2F%2Fblog.kakaocdn.net%2Fdn%2FcWwfbr%2FbtqYDj3aHI7%2FoNBsKsOKanWtCFS0DJAR3K%2Fimg.png&quot; data-origin-width=&quot;0&quot; data-origin-height=&quot;0&quot; data-ke-mobilestyle=&quot;widthContent&quot; onerror=&quot;this.onerror=null; this.src='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png'; this.srcset='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png';&quot;/&gt;&lt;/span&gt;&lt;/figure&gt;
&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;하지만, 이 방법은 나에게는 치명적인 문제가 있다. 위와 같이 사용하면 해당 컨테이너가 1개의 GPU를 &quot;온전히 선점해서&quot; 사용하도록 설정되므로, 다른 컨테이너 설정에 resource limits를 또 적어 준다면, 뒤에 띄울 컨테이너를 GPU를 할당 받지 못해 컨테이너가 뜨지 않는 문제가 있다.&lt;/li&gt;
&lt;li&gt;아, CPU와 RAM 처럼 Request와 Limit 같은걸로 최소는 0개, 최대는 1개로 설정하면 안되겠냐고? 안된다. 왜나면 안된다고 적혀 있기 때문에...&lt;/li&gt;
&lt;li&gt;또한, GPU 1개를 여러개로 쪼개서 쓸 수 없냐고? 없다. 왜냐면 안된다고 적혀 있기 때문에...&lt;/li&gt;
&lt;/ul&gt;
&lt;p&gt;&lt;figure class=&quot;imageblock alignCenter&quot; data-origin-width=&quot;0&quot; data-origin-height=&quot;0&quot; data-ke-mobilestyle=&quot;widthContent&quot;&gt;&lt;span data-url=&quot;https://blog.kakaocdn.net/dn/dwqZNj/btqYE7Vz7iR/ZUsA0Y0GWvW2LjsFuSFFgk/img.png&quot; data-phocus=&quot;https://blog.kakaocdn.net/dn/dwqZNj/btqYE7Vz7iR/ZUsA0Y0GWvW2LjsFuSFFgk/img.png&quot;&gt;&lt;img src=&quot;https://blog.kakaocdn.net/dn/dwqZNj/btqYE7Vz7iR/ZUsA0Y0GWvW2LjsFuSFFgk/img.png&quot; srcset=&quot;https://img1.daumcdn.net/thumb/R1280x0/?scode=mtistory2&amp;fname=https%3A%2F%2Fblog.kakaocdn.net%2Fdn%2FdwqZNj%2FbtqYE7Vz7iR%2FZUsA0Y0GWvW2LjsFuSFFgk%2Fimg.png&quot; data-origin-width=&quot;0&quot; data-origin-height=&quot;0&quot; data-ke-mobilestyle=&quot;widthContent&quot; onerror=&quot;this.onerror=null; this.src='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png'; this.srcset='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png';&quot;/&gt;&lt;/span&gt;&lt;/figure&gt;
&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;즉, Kubernetes에서 현재 지원하는 GPU 할당 개념은, GPU 개수 단위로만 할당이 가능하다.&lt;/li&gt;
&lt;li&gt;만약 내가 띄워야 할 Container에서 GPU를 겨우 VRAM 4GB를 사용하는데, 48GB 가 달린 GPU 1개를 전체로 할당해 버리면, 나머지 44GB는 어떻게 활용하지?&lt;/li&gt;
&lt;li&gt;결론적으로 이야기하면 내 환경에서는 &lt;code&gt;nvidia.com/gpu&lt;/code&gt; resource 제약을 사용하면 바보다. ㅎ.&lt;/li&gt;
&lt;/ul&gt;
&lt;h1&gt;그럼 nvidia.com/gpu 를 옵션을 아예 빼고 사용하면 안될까?&lt;/h1&gt;
&lt;ul&gt;
&lt;li&gt;당연히 안된다.&lt;/li&gt;
&lt;li&gt;예를들어 내가 운영중인 Node의 하드웨어 스펙이 아래와 같다고 하자.
&lt;ul&gt;
&lt;li&gt;vCPU : 10 core&lt;/li&gt;
&lt;li&gt;RAM : 32 GByte&lt;/li&gt;
&lt;li&gt;VRAM : 48 GByte&lt;/li&gt;
&lt;/ul&gt;
&lt;/li&gt;
&lt;li&gt;그리고 내가 띄우려고 하는 Container의 스펙은 아래와 같다고 하자. ( request 만 계산 )
&lt;ul&gt;
&lt;li&gt;vCPU : 1 core&lt;/li&gt;
&lt;li&gt;RAM : 2 GByte&lt;/li&gt;
&lt;li&gt;VRAM : 12GB&lt;/li&gt;
&lt;/ul&gt;
&lt;/li&gt;
&lt;li&gt;이 상황에서 Container를 5개 띄운다고 계산해 보자.
&lt;ul&gt;
&lt;li&gt;컨테이너 1개당 vCPU는 1 core를 사용하니까, 5개를 띄워도 5core를 사용하니 cpu는 문제 없음&lt;/li&gt;
&lt;li&gt;컨테이너 1개당 RAM은 2GB를 사용하니까, 5개를 띄워도 10GB 를 사용하니 RAM은 문제 없음.&lt;/li&gt;
&lt;li&gt;컨테이너 1개당 VRAM은 12GB를 사용하니까, 5개를 띄우면 60GB를 사용해 실제 VRAM 사용 가능량 보다 초과 됨.&lt;/li&gt;
&lt;/ul&gt;
&lt;/li&gt;
&lt;li&gt;Resource에 대한 request 제약은 cpu와 RAM만 줄 수 있는 상황이다.&lt;/li&gt;
&lt;li&gt;VRAM의 제약에 대해서 kubernetes는 알지 못하므로, 5번째 container가 뜰때, process에서 VRAM을 사용할 것이다. 그런데 실제 하드웨어에서는 VRAM이 부족해서 process가 뜨다가 죽을 것이다.&lt;/li&gt;
&lt;li&gt;Kubernetes는 &quot;상태유지&quot;를 위해서 죽은 애를 살릴 것이다. 그러므로 kubernetes는 계속 5번째 container를 띄우려고 하고, 계속 죽을 것이다.&lt;/li&gt;
&lt;li&gt;Resource가 부족해서 Pending 되면 문제 발견이라도 쉬울텐데, 이건 걍 살리려다 죽고, 다시 살리고, 다시 죽고...&lt;/li&gt;
&lt;/ul&gt;
&lt;h1&gt;Extended Resource 를 활용해, 편법으로 회피하자&lt;/h1&gt;
&lt;ul&gt;
&lt;li&gt;Kubernetes에서는 &quot;나만의 Resource&quot;를 정의해서 사용할 수 있게 만들어 두었다.&lt;/li&gt;
&lt;li&gt;여기서 말하는 Resource는 &lt;code&gt;CRD(Custom Resource Definition)&lt;/code&gt;이 아니라, 정말 &lt;code&gt;자원&lt;/code&gt;개념의 &lt;code&gt;Resource&lt;/code&gt;다.&lt;/li&gt;
&lt;li&gt;관련링크 : &lt;a href=&quot;https://kubernetes.io/docs/concepts/configuration/manage-resources-containers/#extended-resources&quot;&gt;https://kubernetes.io/docs/concepts/configuration/manage-resources-containers/#extended-resources&lt;/a&gt;&lt;/li&gt;
&lt;/ul&gt;
&lt;p&gt;&lt;figure class=&quot;imageblock alignCenter&quot; data-origin-width=&quot;0&quot; data-origin-height=&quot;0&quot; data-ke-mobilestyle=&quot;widthContent&quot;&gt;&lt;span data-url=&quot;https://blog.kakaocdn.net/dn/biwSNc/btqYW2Sl3y8/ThIUa5LHitbmN92pVWwnhK/img.png&quot; data-phocus=&quot;https://blog.kakaocdn.net/dn/biwSNc/btqYW2Sl3y8/ThIUa5LHitbmN92pVWwnhK/img.png&quot;&gt;&lt;img src=&quot;https://blog.kakaocdn.net/dn/biwSNc/btqYW2Sl3y8/ThIUa5LHitbmN92pVWwnhK/img.png&quot; srcset=&quot;https://img1.daumcdn.net/thumb/R1280x0/?scode=mtistory2&amp;fname=https%3A%2F%2Fblog.kakaocdn.net%2Fdn%2FbiwSNc%2FbtqYW2Sl3y8%2FThIUa5LHitbmN92pVWwnhK%2Fimg.png&quot; data-origin-width=&quot;0&quot; data-origin-height=&quot;0&quot; data-ke-mobilestyle=&quot;widthContent&quot; onerror=&quot;this.onerror=null; this.src='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png'; this.srcset='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png';&quot;/&gt;&lt;/span&gt;&lt;/figure&gt;
&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;쉽게 말하면, &lt;code&gt;node&lt;/code&gt; 별로 값(&lt;code&gt;capacity&lt;/code&gt;)을 정해놓고, 이 값을 Cotainer에 할당할 수 있다. 만약 Container에서 필요로 한 양보다 남아 있는 값이 작으면, Deploy 상태가 Pending 되어, 문제 상황을 확인할 수 있다.&lt;/li&gt;
&lt;li&gt;Container에서 사용하는 VRAM의 값을 잘 알고 있다면, VRAM에 대한 Extended Resource를 등록하고, Container에서 VRAM 값을 깍아 가면서 사용하면 될 것이다.&lt;/li&gt;
&lt;li&gt;참고로, 위에서 NVIDIA GPU에 대한 제약을 정할때 &lt;code&gt;Device Plugin&lt;/code&gt;이라는것을 설치했는데, 거기서도 &lt;code&gt;Extended Resource&lt;/code&gt;가 정의되어 사용된 것이다.&lt;/li&gt;
&lt;/ul&gt;
&lt;h2&gt;테스트 환경&lt;/h2&gt;
&lt;ul&gt;
&lt;li&gt;테스트하는 환경은 아래와 같다.
&lt;ul&gt;
&lt;li&gt;k3s-m1 : k3s로 master(cluster-init) 설치, 1vCPU, RAM 2GB&lt;/li&gt;
&lt;li&gt;k3s-w1 : k3s로 worker(agent) 설치, 1vCPU, RAM 2GB, VRAM은 4GB 있다는 가정.&lt;/li&gt;
&lt;li&gt;k3s-w2 : k3s로 worker(agent) 설치, 1vCPU, RAM 2GB, VRAM은 8GB 있다는 가정.&lt;/li&gt;
&lt;/ul&gt;
&lt;/li&gt;
&lt;/ul&gt;
&lt;h2&gt;현재 Resource 확인하기&lt;/h2&gt;
&lt;ul&gt;
&lt;li&gt;kubernetes API Server에 http &lt;code&gt;PATCH&lt;/code&gt;를 보내서 값을 업데이트 해 주면 된다.&lt;/li&gt;
&lt;li&gt;우선, 지금 node에 설정되어 있는 Resource 목록들을 먼저 살펴 보자. &lt;code&gt;kubectl describe node k3s-w2&lt;/code&gt; 명령을 사용하면 &lt;code&gt;k3s-w2&lt;/code&gt; node에 설정된 Resource 목록을 볼 수 있다. ( 출력의 위 아래는 생략했다. )&lt;/li&gt;
&lt;/ul&gt;
&lt;p&gt;&lt;figure class=&quot;imageblock alignCenter&quot; data-origin-width=&quot;0&quot; data-origin-height=&quot;0&quot; data-ke-mobilestyle=&quot;widthContent&quot;&gt;&lt;span data-url=&quot;https://blog.kakaocdn.net/dn/kFbDQ/btqYLy5TOZN/4hjY0KAnrTkIolKC30Dy51/img.png&quot; data-phocus=&quot;https://blog.kakaocdn.net/dn/kFbDQ/btqYLy5TOZN/4hjY0KAnrTkIolKC30Dy51/img.png&quot;&gt;&lt;img src=&quot;https://blog.kakaocdn.net/dn/kFbDQ/btqYLy5TOZN/4hjY0KAnrTkIolKC30Dy51/img.png&quot; srcset=&quot;https://img1.daumcdn.net/thumb/R1280x0/?scode=mtistory2&amp;fname=https%3A%2F%2Fblog.kakaocdn.net%2Fdn%2FkFbDQ%2FbtqYLy5TOZN%2F4hjY0KAnrTkIolKC30Dy51%2Fimg.png&quot; data-origin-width=&quot;0&quot; data-origin-height=&quot;0&quot; data-ke-mobilestyle=&quot;widthContent&quot; onerror=&quot;this.onerror=null; this.src='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png'; this.srcset='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png';&quot;/&gt;&lt;/span&gt;&lt;/figure&gt;
&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;&lt;code&gt;k3s-w2&lt;/code&gt; node에는&lt;code&gt;cpu&lt;/code&gt;,&lt;code&gt;memory&lt;/code&gt;,&lt;code&gt;pods&lt;/code&gt; 등의 값을 Resource로 가지고 있다.&lt;/li&gt;
&lt;/ul&gt;
&lt;h2&gt;Extended Resource를 node에 추가 하기&lt;/h2&gt;
&lt;ul&gt;
&lt;li&gt;우선 API Server에 HTTP call을 편리하게 하기 위해서 proxy를 열어 주자. master 서버에서 하면 편할것이다. 명령어는 &lt;code&gt;kubectl proxy&lt;/code&gt; 이다.&lt;/li&gt;
&lt;/ul&gt;
&lt;p&gt;&lt;figure class=&quot;imageblock alignCenter&quot; data-origin-width=&quot;0&quot; data-origin-height=&quot;0&quot; data-ke-mobilestyle=&quot;widthContent&quot;&gt;&lt;span data-url=&quot;https://blog.kakaocdn.net/dn/mNEts/btqYB455sD2/HbjLYYdm5M4DfQ8sTSzyg1/img.png&quot; data-phocus=&quot;https://blog.kakaocdn.net/dn/mNEts/btqYB455sD2/HbjLYYdm5M4DfQ8sTSzyg1/img.png&quot;&gt;&lt;img src=&quot;https://blog.kakaocdn.net/dn/mNEts/btqYB455sD2/HbjLYYdm5M4DfQ8sTSzyg1/img.png&quot; srcset=&quot;https://img1.daumcdn.net/thumb/R1280x0/?scode=mtistory2&amp;fname=https%3A%2F%2Fblog.kakaocdn.net%2Fdn%2FmNEts%2FbtqYB455sD2%2FHbjLYYdm5M4DfQ8sTSzyg1%2Fimg.png&quot; data-origin-width=&quot;0&quot; data-origin-height=&quot;0&quot; data-ke-mobilestyle=&quot;widthContent&quot; onerror=&quot;this.onerror=null; this.src='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png'; this.srcset='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png';&quot;/&gt;&lt;/span&gt;&lt;/figure&gt;
&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;&lt;code&gt;kubectl proxy&lt;/code&gt;명령을 치고 나면, &lt;code&gt;127.0.0.1:8001&lt;/code&gt; 로 port가 열리게 된다.&lt;/li&gt;
&lt;li&gt;이제, 나만의 &lt;code&gt;vram Resource&lt;/code&gt;를 등록하자. proxy를 위해서 위 명령창은 떠 있는 상태로 둬야 하므로 새로운 터미널 창을 열어 아래의 명령을 입력하자.&lt;/li&gt;
&lt;/ul&gt;
&lt;pre class=&quot;jboss-cli&quot;&gt;&lt;code&gt;curl --header &quot;Content-Type: application/json-patch+json&quot; --request PATCH --data '[{&quot;op&quot;: &quot;add&quot;, &quot;path&quot;: &quot;/status/capacity/ggaman.com~1vram&quot;, &quot;value&quot;: &quot;8&quot;}]' http://localhost:8001/api/v1/nodes/k3s-w2/status&lt;/code&gt;&lt;/pre&gt;
&lt;ul&gt;
&lt;li&gt;위 명령은 &lt;code&gt;k3s-w2&lt;/code&gt; node에 &lt;code&gt;ggaman.com/vram&lt;/code&gt;이라는 Resource를 &lt;code&gt;8&lt;/code&gt;로 등록한 것이다. 이 명령을 치고 나면 결과가 주루룩 출력된다. 그 결과를 봐도 좋겠지만, 우리는 node의 정보를 다시 확인하면서 vram 관련 항목이 추가 되었는지 확인해 보자. &lt;code&gt;kubectl describe node k3s-w2&lt;/code&gt; 명령을 치면 아래과 같은 내용을 확인해 볼 수 있다.&lt;/li&gt;
&lt;/ul&gt;
&lt;p&gt;&lt;figure class=&quot;imageblock alignCenter&quot; data-origin-width=&quot;0&quot; data-origin-height=&quot;0&quot; data-ke-mobilestyle=&quot;widthContent&quot;&gt;&lt;span data-url=&quot;https://blog.kakaocdn.net/dn/caX55t/btqYN016X9L/3nBZuV5HnaQoyi4sKEk5Kk/img.png&quot; data-phocus=&quot;https://blog.kakaocdn.net/dn/caX55t/btqYN016X9L/3nBZuV5HnaQoyi4sKEk5Kk/img.png&quot;&gt;&lt;img src=&quot;https://blog.kakaocdn.net/dn/caX55t/btqYN016X9L/3nBZuV5HnaQoyi4sKEk5Kk/img.png&quot; srcset=&quot;https://img1.daumcdn.net/thumb/R1280x0/?scode=mtistory2&amp;fname=https%3A%2F%2Fblog.kakaocdn.net%2Fdn%2FcaX55t%2FbtqYN016X9L%2F3nBZuV5HnaQoyi4sKEk5Kk%2Fimg.png&quot; data-origin-width=&quot;0&quot; data-origin-height=&quot;0&quot; data-ke-mobilestyle=&quot;widthContent&quot; onerror=&quot;this.onerror=null; this.src='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png'; this.srcset='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png';&quot;/&gt;&lt;/span&gt;&lt;/figure&gt;
&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;아, 왜 이름을 &lt;code&gt;ggaman.com/vram&lt;/code&gt;으로 했냐면, 그렇게 하라고 하니깐 그런거다.&lt;/li&gt;
&lt;/ul&gt;
&lt;p&gt;&lt;figure class=&quot;imageblock alignCenter&quot; data-origin-width=&quot;0&quot; data-origin-height=&quot;0&quot; data-ke-mobilestyle=&quot;widthContent&quot;&gt;&lt;span data-url=&quot;https://blog.kakaocdn.net/dn/NwTy4/btqYTNA9TOI/5tlHBZdvCZAc2Rha94kZz1/img.png&quot; data-phocus=&quot;https://blog.kakaocdn.net/dn/NwTy4/btqYTNA9TOI/5tlHBZdvCZAc2Rha94kZz1/img.png&quot;&gt;&lt;img src=&quot;https://blog.kakaocdn.net/dn/NwTy4/btqYTNA9TOI/5tlHBZdvCZAc2Rha94kZz1/img.png&quot; srcset=&quot;https://img1.daumcdn.net/thumb/R1280x0/?scode=mtistory2&amp;fname=https%3A%2F%2Fblog.kakaocdn.net%2Fdn%2FNwTy4%2FbtqYTNA9TOI%2F5tlHBZdvCZAc2Rha94kZz1%2Fimg.png&quot; data-origin-width=&quot;0&quot; data-origin-height=&quot;0&quot; data-ke-mobilestyle=&quot;widthContent&quot; onerror=&quot;this.onerror=null; this.src='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png'; this.srcset='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png';&quot;/&gt;&lt;/span&gt;&lt;/figure&gt;
&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;좀 더 정확히 &lt;code&gt;curl&lt;/code&gt; 명령을 정리하면 아래와 같다.&lt;/li&gt;
&lt;/ul&gt;
&lt;pre class=&quot;jboss-cli&quot;&gt;&lt;code&gt;curl --header &quot;Content-Type: application/json-patch+json&quot; --request PATCH --data '[{&quot;op&quot;: &quot;add&quot;, &quot;path&quot;: &quot;/status/capacity/[내가정의하고자하는ExtendedResource이름]&quot;, &quot;value&quot;: &quot;[리소스개수]&quot;}]' http://localhost:8001/api/v1/nodes/[새Resource를등록할node이름]/status&lt;/code&gt;&lt;/pre&gt;
&lt;ul&gt;
&lt;li&gt;만약 Extended Resource를 지우고 싶다면, 위 json에서 &lt;code&gt;add&lt;/code&gt;를 &lt;code&gt;remove&lt;/code&gt;로 변경해 주면 된다.&lt;/li&gt;
&lt;/ul&gt;
&lt;h2&gt;Extended Resource 사용하기&lt;/h2&gt;
&lt;ul&gt;
&lt;li&gt;사용하는건 간단하다. 기존 Pod 생성할 때 Resource 설정하는것 처럼 하면 된다. vram을 3개씩 사용하는 Pod를 여러개 띄워 보자.&lt;/li&gt;
&lt;li&gt;관련링크 : &lt;a href=&quot;https://kubernetes.io/docs/tasks/configure-pod-container/extended-resource/&quot;&gt;https://kubernetes.io/docs/tasks/configure-pod-container/extended-resource/&lt;/a&gt;&lt;/li&gt;
&lt;/ul&gt;
&lt;pre class=&quot;yaml&quot;&gt;&lt;code&gt;apiVersion: v1
kind: Pod
metadata:
name: extended-resource-demo-1
spec:
containers:
- name: extended-resource-demo-ctr-1
image: nginx
resources:
  requests:
    ggaman.com/vram: 3
  limits:
    ggaman.com/vram: 3&lt;/code&gt;&lt;/pre&gt;
&lt;ul&gt;
&lt;li&gt;위 Pod의 이름을 1~4까지 값을 주면서 띄워 보자.&lt;/li&gt;
&lt;li&gt;Extened Resource는 아래와 같이 등록해 두었다. 위에 알려준 명령으로 등록하면 된다.
&lt;ul&gt;
&lt;li&gt;k3s-w1 - ggaman.com/vram capacity : 4&lt;/li&gt;
&lt;li&gt;k3s-w2 - ggaman.com/vram capacity : 8&lt;/li&gt;
&lt;/ul&gt;
&lt;/li&gt;
&lt;li&gt;이 상황이면, &lt;code&gt;k3s-w1&lt;/code&gt; 에 1개의 Pod가, &lt;code&gt;k3s-w2&lt;/code&gt; 에 2개의 Pod가 배치되며, 4번째 Pod는 Pending 되어야 할 것이다.&lt;/li&gt;
&lt;li&gt;순차적으로 수행해 보자.&lt;/li&gt;
&lt;/ul&gt;
&lt;p&gt;&lt;figure class=&quot;imageblock alignCenter&quot; data-origin-width=&quot;0&quot; data-origin-height=&quot;0&quot; data-ke-mobilestyle=&quot;widthContent&quot;&gt;&lt;span data-url=&quot;https://blog.kakaocdn.net/dn/cgb9gj/btqYW3qbvqL/OqWFmkdarbi0VcbpvSQGqK/img.png&quot; data-phocus=&quot;https://blog.kakaocdn.net/dn/cgb9gj/btqYW3qbvqL/OqWFmkdarbi0VcbpvSQGqK/img.png&quot;&gt;&lt;img src=&quot;https://blog.kakaocdn.net/dn/cgb9gj/btqYW3qbvqL/OqWFmkdarbi0VcbpvSQGqK/img.png&quot; srcset=&quot;https://img1.daumcdn.net/thumb/R1280x0/?scode=mtistory2&amp;fname=https%3A%2F%2Fblog.kakaocdn.net%2Fdn%2Fcgb9gj%2FbtqYW3qbvqL%2FOqWFmkdarbi0VcbpvSQGqK%2Fimg.png&quot; data-origin-width=&quot;0&quot; data-origin-height=&quot;0&quot; data-ke-mobilestyle=&quot;widthContent&quot; onerror=&quot;this.onerror=null; this.src='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png'; this.srcset='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png';&quot;/&gt;&lt;/span&gt;&lt;/figure&gt;
&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;처음으로 배포된 Pod는 &lt;code&gt;k3s-w1&lt;/code&gt; 배포 되었다는것을 알 수 있다. 이제 &lt;code&gt;k3s-w1&lt;/code&gt; node 정보를 확인해 보자. &lt;code&gt;kubectl describe node k3s-w1&lt;/code&gt; 명령을 통해 확인 할 수 있다.&lt;/li&gt;
&lt;/ul&gt;
&lt;p&gt;&lt;figure class=&quot;imageblock alignCenter&quot; data-origin-width=&quot;0&quot; data-origin-height=&quot;0&quot; data-ke-mobilestyle=&quot;widthContent&quot;&gt;&lt;span data-url=&quot;https://blog.kakaocdn.net/dn/bbd3FH/btqYE784GVX/dFv3OJAU4ZmSsIbAOB2vrk/img.png&quot; data-phocus=&quot;https://blog.kakaocdn.net/dn/bbd3FH/btqYE784GVX/dFv3OJAU4ZmSsIbAOB2vrk/img.png&quot;&gt;&lt;img src=&quot;https://blog.kakaocdn.net/dn/bbd3FH/btqYE784GVX/dFv3OJAU4ZmSsIbAOB2vrk/img.png&quot; srcset=&quot;https://img1.daumcdn.net/thumb/R1280x0/?scode=mtistory2&amp;fname=https%3A%2F%2Fblog.kakaocdn.net%2Fdn%2Fbbd3FH%2FbtqYE784GVX%2FdFv3OJAU4ZmSsIbAOB2vrk%2Fimg.png&quot; data-origin-width=&quot;0&quot; data-origin-height=&quot;0&quot; data-ke-mobilestyle=&quot;widthContent&quot; onerror=&quot;this.onerror=null; this.src='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png'; this.srcset='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png';&quot;/&gt;&lt;/span&gt;&lt;/figure&gt;
&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;보다시피 &lt;code&gt;k3s-w1&lt;/code&gt;에 &lt;code&gt;Allocated resources&lt;/code&gt;에 &lt;code&gt;ggaman.com/vram&lt;/code&gt;이 할당 된 것을 볼 수 있다.&lt;/li&gt;
&lt;li&gt;이제 두번째 Pod를 배포해 보자.&lt;/li&gt;
&lt;/ul&gt;
&lt;p&gt;&lt;figure class=&quot;imageblock alignCenter&quot; data-origin-width=&quot;0&quot; data-origin-height=&quot;0&quot; data-ke-mobilestyle=&quot;widthContent&quot;&gt;&lt;span data-url=&quot;https://blog.kakaocdn.net/dn/24ObK/btqYB6bIEg2/eokGMgSjZwvUIePvKqsS6k/img.png&quot; data-phocus=&quot;https://blog.kakaocdn.net/dn/24ObK/btqYB6bIEg2/eokGMgSjZwvUIePvKqsS6k/img.png&quot;&gt;&lt;img src=&quot;https://blog.kakaocdn.net/dn/24ObK/btqYB6bIEg2/eokGMgSjZwvUIePvKqsS6k/img.png&quot; srcset=&quot;https://img1.daumcdn.net/thumb/R1280x0/?scode=mtistory2&amp;fname=https%3A%2F%2Fblog.kakaocdn.net%2Fdn%2F24ObK%2FbtqYB6bIEg2%2FeokGMgSjZwvUIePvKqsS6k%2Fimg.png&quot; data-origin-width=&quot;0&quot; data-origin-height=&quot;0&quot; data-ke-mobilestyle=&quot;widthContent&quot; onerror=&quot;this.onerror=null; this.src='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png'; this.srcset='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png';&quot;/&gt;&lt;/span&gt;&lt;/figure&gt;
&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;보다시피 두번째 Pod는 &lt;code&gt;k3s-w2&lt;/code&gt;에 배포 되었다. 우리가 설정한 &lt;code&gt;ggaman.com/vram&lt;/code&gt; Resource를 잘 사용했는지 확인해 보자.&lt;/li&gt;
&lt;/ul&gt;
&lt;p&gt;&lt;figure class=&quot;imageblock alignCenter&quot; data-origin-width=&quot;0&quot; data-origin-height=&quot;0&quot; data-ke-mobilestyle=&quot;widthContent&quot;&gt;&lt;span data-url=&quot;https://blog.kakaocdn.net/dn/utP86/btqYIjg6wWs/MfBo1BzmgmM5GTvJpMThIK/img.png&quot; data-phocus=&quot;https://blog.kakaocdn.net/dn/utP86/btqYIjg6wWs/MfBo1BzmgmM5GTvJpMThIK/img.png&quot;&gt;&lt;img src=&quot;https://blog.kakaocdn.net/dn/utP86/btqYIjg6wWs/MfBo1BzmgmM5GTvJpMThIK/img.png&quot; srcset=&quot;https://img1.daumcdn.net/thumb/R1280x0/?scode=mtistory2&amp;fname=https%3A%2F%2Fblog.kakaocdn.net%2Fdn%2FutP86%2FbtqYIjg6wWs%2FMfBo1BzmgmM5GTvJpMThIK%2Fimg.png&quot; data-origin-width=&quot;0&quot; data-origin-height=&quot;0&quot; data-ke-mobilestyle=&quot;widthContent&quot; onerror=&quot;this.onerror=null; this.src='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png'; this.srcset='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png';&quot;/&gt;&lt;/span&gt;&lt;/figure&gt;
&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;뭐 볼거 없이 잘 사용 되었다. 이제 세번째 Pod를 배포해 보자. 지금 예상대로라면, &lt;code&gt;k3s-w1&lt;/code&gt;에는 &lt;code&gt;ggaman.com/vram&lt;/code&gt; 이 &lt;code&gt;1&lt;/code&gt;이 남아 있고, &lt;code&gt;k3s-w2&lt;/code&gt;에는 &lt;code&gt;5&lt;/code&gt;가 남았을 것이므로, &lt;code&gt;k3s-w2&lt;/code&gt;에 배포되어야 한다. 자, 이제 세번째 Pod를 배포하고 결과를 보자.&lt;/li&gt;
&lt;/ul&gt;
&lt;p&gt;&lt;figure class=&quot;imageblock alignCenter&quot; data-origin-width=&quot;0&quot; data-origin-height=&quot;0&quot; data-ke-mobilestyle=&quot;widthContent&quot;&gt;&lt;span data-url=&quot;https://blog.kakaocdn.net/dn/bhhCCK/btqYTNnFbUm/HT0fjdmDe8CNXtKMeuIY11/img.png&quot; data-phocus=&quot;https://blog.kakaocdn.net/dn/bhhCCK/btqYTNnFbUm/HT0fjdmDe8CNXtKMeuIY11/img.png&quot;&gt;&lt;img src=&quot;https://blog.kakaocdn.net/dn/bhhCCK/btqYTNnFbUm/HT0fjdmDe8CNXtKMeuIY11/img.png&quot; srcset=&quot;https://img1.daumcdn.net/thumb/R1280x0/?scode=mtistory2&amp;fname=https%3A%2F%2Fblog.kakaocdn.net%2Fdn%2FbhhCCK%2FbtqYTNnFbUm%2FHT0fjdmDe8CNXtKMeuIY11%2Fimg.png&quot; data-origin-width=&quot;0&quot; data-origin-height=&quot;0&quot; data-ke-mobilestyle=&quot;widthContent&quot; onerror=&quot;this.onerror=null; this.src='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png'; this.srcset='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png';&quot;/&gt;&lt;/span&gt;&lt;/figure&gt;
&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;예상한대로 동작했다. 세번째 Pod는 &lt;code&gt;k3s-w2&lt;/code&gt;에 배포 되었고, 우리가 정의한 &lt;code&gt;ggaman.com/vram&lt;/code&gt; Resource 도 3개가 더 사용되어 현재 사용중인 Resource는 총 6개가 되었다.&lt;/li&gt;
&lt;li&gt;이제 &lt;code&gt;k3s-w1&lt;/code&gt;에 남아 있는 &lt;code&gt;ggaman.com/vram&lt;/code&gt;의 Resource는 1개, &lt;code&gt;k3s-w2&lt;/code&gt;에 남아 있는 &lt;code&gt;ggaman.com/vram&lt;/code&gt; Resource는 2개이므로, 네번째 Pod를 배포하면, &lt;code&gt;ggaman.com/vram&lt;/code&gt;의 Resource가 충분한 node가 없으므로, 어디에도 배포되지 못하고 &lt;code&gt;Pending&lt;/code&gt; 되어야 할 것이다. 자, 시도해 보자.&lt;/li&gt;
&lt;/ul&gt;
&lt;p&gt;&lt;figure class=&quot;imageblock alignCenter&quot; data-origin-width=&quot;0&quot; data-origin-height=&quot;0&quot; data-ke-mobilestyle=&quot;widthContent&quot;&gt;&lt;span data-url=&quot;https://blog.kakaocdn.net/dn/K5cIu/btqYW3wWMa0/mpZSqfSAOKAKubVXBeLRMK/img.png&quot; data-phocus=&quot;https://blog.kakaocdn.net/dn/K5cIu/btqYW3wWMa0/mpZSqfSAOKAKubVXBeLRMK/img.png&quot;&gt;&lt;img src=&quot;https://blog.kakaocdn.net/dn/K5cIu/btqYW3wWMa0/mpZSqfSAOKAKubVXBeLRMK/img.png&quot; srcset=&quot;https://img1.daumcdn.net/thumb/R1280x0/?scode=mtistory2&amp;fname=https%3A%2F%2Fblog.kakaocdn.net%2Fdn%2FK5cIu%2FbtqYW3wWMa0%2FmpZSqfSAOKAKubVXBeLRMK%2Fimg.png&quot; data-origin-width=&quot;0&quot; data-origin-height=&quot;0&quot; data-ke-mobilestyle=&quot;widthContent&quot; onerror=&quot;this.onerror=null; this.src='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png'; this.srcset='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png';&quot;/&gt;&lt;/span&gt;&lt;/figure&gt;
&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;예상한대로 네번째 Pod는 적절한 node를 찾지 못하고 &lt;code&gt;Pending&lt;/code&gt; 된 상태이다. &lt;code&gt;Pending&lt;/code&gt;된 이유도 살펴 보자. 예상대로라면 &quot;&lt;code&gt;ggaman.com/vram&lt;/code&gt;에 대한 Resource가 부족해서 배포되지 못하고 있다.&quot; 정도의 메세지가 보이면 될 것이다.&lt;/li&gt;
&lt;/ul&gt;
&lt;p&gt;&lt;figure class=&quot;imageblock alignCenter&quot; data-origin-width=&quot;0&quot; data-origin-height=&quot;0&quot; data-ke-mobilestyle=&quot;widthContent&quot;&gt;&lt;span data-url=&quot;https://blog.kakaocdn.net/dn/dFu5Df/btqYW2x10V2/XkwLAbqq7BrdBtyZCQm490/img.png&quot; data-phocus=&quot;https://blog.kakaocdn.net/dn/dFu5Df/btqYW2x10V2/XkwLAbqq7BrdBtyZCQm490/img.png&quot;&gt;&lt;img src=&quot;https://blog.kakaocdn.net/dn/dFu5Df/btqYW2x10V2/XkwLAbqq7BrdBtyZCQm490/img.png&quot; srcset=&quot;https://img1.daumcdn.net/thumb/R1280x0/?scode=mtistory2&amp;fname=https%3A%2F%2Fblog.kakaocdn.net%2Fdn%2FdFu5Df%2FbtqYW2x10V2%2FXkwLAbqq7BrdBtyZCQm490%2Fimg.png&quot; data-origin-width=&quot;0&quot; data-origin-height=&quot;0&quot; data-ke-mobilestyle=&quot;widthContent&quot; onerror=&quot;this.onerror=null; this.src='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png'; this.srcset='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png';&quot;/&gt;&lt;/span&gt;&lt;/figure&gt;
&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;모든것이 예상대로 동작했다.&lt;/li&gt;
&lt;/ul&gt;
&lt;h1&gt;정리&lt;/h1&gt;
&lt;ul&gt;
&lt;li&gt;Kuberntes에서는 아직 1개의 GPU를 여러개로 쪼개서 사용하는게 불가능하다.&lt;/li&gt;
&lt;li&gt;그렇기 때문에, GPU의 VRAM을 나눠서 사용하고 싶다면 다른 방법을 찾아야 한다.&lt;/li&gt;
&lt;li&gt;만약, Container에서 사용하는 GPU의 사용량을 정확하게 안다면, &lt;code&gt;Extended Resource&lt;/code&gt;를 사용하면 편법으로 나눠서 사용할 수 있다.&lt;/li&gt;
&lt;li&gt;&lt;code&gt;Extended Resource&lt;/code&gt;를 설정하는것은 Kubernetes API Server로 PATCH request를 던지를 방법으로 추가 할 수 있다.&lt;/li&gt;
&lt;li&gt;Pod를 생성할때 우리가 지정한 자원을 &lt;code&gt;resources&lt;/code&gt;의 &lt;code&gt;requests&lt;/code&gt;, &lt;code&gt;limits&lt;/code&gt;에 적절히 설정하면 배포가 가능한 node에 가서 알아서 배포가 된다.&lt;/li&gt;
&lt;li&gt;만약 우리가 지정한 자원의 양이 부족해서, 배포가 될 node가 없다면 &lt;code&gt;Pending&lt;/code&gt; 걸리게 된다.&lt;/li&gt;
&lt;/ul&gt;
&lt;h1&gt;참고&lt;/h1&gt;
&lt;ul&gt;
&lt;li&gt;만약 Container에서 동작중인 엔진이 우리가 &lt;code&gt;ggaman.com/vram&lt;/code&gt;에 정의한 양보다 더 많은 VRAM을 사용한다고 해도 막을 수 있는 방법은 없다.&lt;/li&gt;
&lt;li&gt;그러므로 &lt;code&gt;ggaman.com/vram&lt;/code&gt;의 값을 줄 때는 해당 엔진이 최대로 사용할 수 있는 VRAM의 양을 측정한 뒤에 사용해야 한다.&lt;/li&gt;
&lt;li&gt;Sidecar container에서 main container에서 돌아가는 엔진의 VRAM 사용량을 주기적으로 체크해, 과도하게 커지면 죽이는 방법도 있을듯 하다. ( &lt;code&gt;shareProcessNamespace: true&lt;/code&gt; 사용 ) 하지만, 그런 트릭은 쓰지 말고 처음부터 잘 측정하자.(고 말하기에는 이것도 트릭인데.. ㅎㅎ )&lt;/li&gt;
&lt;li&gt;Kubernetes에서 1개의 GPU를 여러개로 쪼개쓰는것은 안되지만, GPU자체를 Virtualize 해서 여러 VM에 할당하고, 각 VM을 Kubernetes node로 연결하는 방법(vComputeServer)은 있는듯 하다. 하지만, VMWare 같은 프로그램이 필요하고, DataCenter에서 사용가능한 비싼 GPU 모델에서만 동작한다. 심지어 유료다. T_T 다음에 기회가 되면 관련 글을 써 보도록. ㅎ.&lt;/li&gt;
&lt;/ul&gt;</description>
      <category>공부/컴퓨터</category>
      <category>Extended Resource</category>
      <category>GPU</category>
      <category>k3s</category>
      <category>kubernetes</category>
      <category>memory fraction</category>
      <category>nVidia</category>
      <category>RTX 8000</category>
      <category>status/capacity</category>
      <category>vComputeServer</category>
      <category>vRAM</category>
      <author>찬</author>
      <guid isPermaLink="true">https://blog.ggaman.com/1025</guid>
      <comments>https://blog.ggaman.com/1025#entry1025comment</comments>
      <pubDate>Mon, 1 Mar 2021 05:39:26 +0900</pubDate>
    </item>
    <item>
      <title>[Kubernetes] k3s 1.20이하에서 Traefik 1.81 제거하고  Traefik 2.x 설치하기</title>
      <link>https://blog.ggaman.com/1024</link>
      <description>&lt;h1&gt;2021년 5월 업데이트...&lt;/h1&gt;
&lt;ul style=&quot;list-style-type: disc;&quot; data-ke-list-type=&quot;disc&quot;&gt;
&lt;li&gt;k3s가 1.21부터는 Traefik v1이 설치되어 있지 않다면, Traefik v2를 기본으로 설치 한다고 합니다. 그러니 최신 버젼을 사용하는 사람은 아래 내용은 그냥 참고삼아 읽으시면 됩니다.&amp;nbsp;&lt;/li&gt;
&lt;/ul&gt;
&lt;p&gt;&lt;figure class=&quot;imageblock alignCenter&quot; data-origin-width=&quot;1485&quot; data-origin-height=&quot;933&quot; data-ke-mobilestyle=&quot;widthOrigin&quot;&gt;&lt;span data-url=&quot;https://blog.kakaocdn.net/dn/OXBLS/btq5RvmVkY6/C5i2uhbUSkxr1DTRCu86kk/img.png&quot; data-phocus=&quot;https://blog.kakaocdn.net/dn/OXBLS/btq5RvmVkY6/C5i2uhbUSkxr1DTRCu86kk/img.png&quot;&gt;&lt;img src=&quot;https://blog.kakaocdn.net/dn/OXBLS/btq5RvmVkY6/C5i2uhbUSkxr1DTRCu86kk/img.png&quot; srcset=&quot;https://img1.daumcdn.net/thumb/R1280x0/?scode=mtistory2&amp;fname=https%3A%2F%2Fblog.kakaocdn.net%2Fdn%2FOXBLS%2Fbtq5RvmVkY6%2FC5i2uhbUSkxr1DTRCu86kk%2Fimg.png&quot; data-origin-width=&quot;1485&quot; data-origin-height=&quot;933&quot; data-ke-mobilestyle=&quot;widthOrigin&quot; onerror=&quot;this.onerror=null; this.src='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png'; this.srcset='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png';&quot;/&gt;&lt;/span&gt;&lt;/figure&gt;
&lt;/p&gt;
&lt;h1&gt;k3s가 좋긴한데... Traefik 이 문제라...&lt;/h1&gt;
&lt;ul style=&quot;list-style-type: disc;&quot; data-ke-list-type=&quot;disc&quot;&gt;
&lt;li&gt;k3s를 이용하면 단한줄의 명령어만으로 single node kubernetes를 구성할 수 있다.&lt;/li&gt;
&lt;/ul&gt;
&lt;p&gt;&lt;figure class=&quot;imageblock alignCenter&quot; data-origin-width=&quot;914&quot; data-origin-height=&quot;251&quot; data-ke-mobilestyle=&quot;widthContent&quot;&gt;&lt;span data-url=&quot;https://blog.kakaocdn.net/dn/b4RRmd/btqYveAcHzj/SCG5rBivhwI8lRKFhGWpcK/img.png&quot; data-phocus=&quot;https://blog.kakaocdn.net/dn/b4RRmd/btqYveAcHzj/SCG5rBivhwI8lRKFhGWpcK/img.png&quot;&gt;&lt;img src=&quot;https://blog.kakaocdn.net/dn/b4RRmd/btqYveAcHzj/SCG5rBivhwI8lRKFhGWpcK/img.png&quot; srcset=&quot;https://img1.daumcdn.net/thumb/R1280x0/?scode=mtistory2&amp;fname=https%3A%2F%2Fblog.kakaocdn.net%2Fdn%2Fb4RRmd%2FbtqYveAcHzj%2FSCG5rBivhwI8lRKFhGWpcK%2Fimg.png&quot; data-origin-width=&quot;914&quot; data-origin-height=&quot;251&quot; data-ke-mobilestyle=&quot;widthContent&quot; onerror=&quot;this.onerror=null; this.src='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png'; this.srcset='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png';&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;관련 링크 1 : &lt;a href=&quot;https://blog.ggaman.com/1018?category=332239&quot;&gt;https://blog.ggaman.com/1018?category=332239&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;관련 링크 2 : &lt;a href=&quot;https://rancher.com/docs/k3s/latest/en/installation/install-options/&quot;&gt;https://rancher.com/docs/k3s/latest/en/installation/install-options/&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;&lt;code&gt;curl -sfL https://get.k3s.io | sh -&lt;/code&gt;&lt;/li&gt;
&lt;li&gt;단, 한줄로 무언가를 할 수 있다는 말은, 기본적인 것은 알아서 다 설치해 줄께~ 정도의 말이 될 것이다.&lt;/li&gt;
&lt;li&gt;그래서 문제다.&lt;/li&gt;
&lt;/ul&gt;
&lt;h1&gt;k3s 설치되는 IngressController&lt;/h1&gt;
&lt;ul style=&quot;list-style-type: disc;&quot; data-ke-list-type=&quot;disc&quot;&gt;
&lt;li&gt;k3s는 별 설정 없어도 Kubernetes 에 필요한 이것저것을 자동으로 설치해 쉽게 환경을 만들어 준다고 이야기 했다.&lt;/li&gt;
&lt;li&gt;그 중에서 IngressController 기능을 할 것으로 Traefik 을 자동으로 설치해 사용하도록 되어 있다.&lt;/li&gt;
&lt;/ul&gt;
&lt;p&gt;&lt;figure class=&quot;imageblock alignCenter&quot; data-origin-width=&quot;899&quot; data-origin-height=&quot;220&quot; data-ke-mobilestyle=&quot;widthContent&quot;&gt;&lt;span data-url=&quot;https://blog.kakaocdn.net/dn/A9LSy/btqYE6HnCcK/K7XPr7mJAG9y55LkGHn4wk/img.png&quot; data-phocus=&quot;https://blog.kakaocdn.net/dn/A9LSy/btqYE6HnCcK/K7XPr7mJAG9y55LkGHn4wk/img.png&quot;&gt;&lt;img src=&quot;https://blog.kakaocdn.net/dn/A9LSy/btqYE6HnCcK/K7XPr7mJAG9y55LkGHn4wk/img.png&quot; srcset=&quot;https://img1.daumcdn.net/thumb/R1280x0/?scode=mtistory2&amp;fname=https%3A%2F%2Fblog.kakaocdn.net%2Fdn%2FA9LSy%2FbtqYE6HnCcK%2FK7XPr7mJAG9y55LkGHn4wk%2Fimg.png&quot; data-origin-width=&quot;899&quot; data-origin-height=&quot;220&quot; data-ke-mobilestyle=&quot;widthContent&quot; onerror=&quot;this.onerror=null; this.src='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png'; this.srcset='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png';&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;문제는 k3s에서 자동으로 설치해 주는 IngressController가 Traefik 1.81 버젼이라는것이다.&lt;/li&gt;
&lt;li&gt;관련 링크 :&lt;a href=&quot;https://github.com/k3s-io/k3s/blob/master/manifests/traefik.yaml&quot;&gt;https://github.com/k3s-io/k3s/blob/master/manifests/traefik.yaml&lt;/a&gt;&lt;/li&gt;
&lt;/ul&gt;
&lt;p&gt;&lt;figure class=&quot;imageblock alignCenter&quot; data-origin-width=&quot;573&quot; data-origin-height=&quot;400&quot; data-ke-mobilestyle=&quot;widthContent&quot;&gt;&lt;span data-url=&quot;https://blog.kakaocdn.net/dn/VlexU/btqYvduxtOn/28RVGUBBBRuiY0NNGMkg41/img.png&quot; data-phocus=&quot;https://blog.kakaocdn.net/dn/VlexU/btqYvduxtOn/28RVGUBBBRuiY0NNGMkg41/img.png&quot;&gt;&lt;img src=&quot;https://blog.kakaocdn.net/dn/VlexU/btqYvduxtOn/28RVGUBBBRuiY0NNGMkg41/img.png&quot; srcset=&quot;https://img1.daumcdn.net/thumb/R1280x0/?scode=mtistory2&amp;fname=https%3A%2F%2Fblog.kakaocdn.net%2Fdn%2FVlexU%2FbtqYvduxtOn%2F28RVGUBBBRuiY0NNGMkg41%2Fimg.png&quot; data-origin-width=&quot;573&quot; data-origin-height=&quot;400&quot; data-ke-mobilestyle=&quot;widthContent&quot; onerror=&quot;this.onerror=null; this.src='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png'; this.srcset='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png';&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;Traefik 2.x 대부터는 Ingress를 쉽게 쓰기 위해서 IngressRoute라는 CRD(Custom Resource Definition)을 지원한다.&lt;/li&gt;
&lt;li&gt;그러니, 난 Traefik2.x 를 사용하고 싶었고, 그러니.. Traefik2.x 를 설치해야 했다.&lt;/li&gt;
&lt;/ul&gt;
&lt;h1&gt;k3s 설치시 traefik 2.x 설치하기&lt;/h1&gt;
&lt;ul style=&quot;list-style-type: disc;&quot; data-ke-list-type=&quot;disc&quot;&gt;
&lt;li&gt;우선 k3s를 설치할때 traefik 1.x 를 설치않도록 하고, 이후 traefik 2.x를 설치하는 방식으로 진행.&lt;/li&gt;
&lt;li&gt;&lt;code&gt;curl -sfL https://get.k3s.io | sh -s - --disable-traefik&lt;/code&gt;&lt;/li&gt;
&lt;/ul&gt;
&lt;h2 data-ke-size=&quot;size26&quot;&gt;k3s 설치시 traefik1.x 설치하지 않기&lt;/h2&gt;
&lt;ul style=&quot;list-style-type: disc;&quot; data-ke-list-type=&quot;disc&quot;&gt;
&lt;li&gt;k3s는 설치가 쉽기도 하지만, 특정 기능을 빼거나 변경하면서 설치하기도 쉽게 되어 있다.&lt;/li&gt;
&lt;li&gt;&lt;code&gt;curl -sfL https://get.k3s.io | sh -s - --disable traefik&lt;/code&gt;&lt;/li&gt;
&lt;li&gt;위와 같이 설치하면, traefik 이 빠져서 설치된다.&lt;/li&gt;
&lt;/ul&gt;
&lt;h2 data-ke-size=&quot;size26&quot;&gt;k3s에 traefik 2.x 설치하기&lt;/h2&gt;
&lt;ul style=&quot;list-style-type: disc;&quot; data-ke-list-type=&quot;disc&quot;&gt;
&lt;li&gt;최초 설치시에는 traefik 을 빼고 설치 했으나, 이제는 k3s가 traefik 2.x을 사용할 수 있도록 해야 한다.&lt;/li&gt;
&lt;li&gt;물론 수동으로 traefik을 설치해도 된다.&lt;/li&gt;
&lt;li&gt;관련 링크 : &lt;a href=&quot;https://community.traefik.io/t/traefik-v2-helm-a-tour-of-the-traefik-2-helm-chart/6126&quot;&gt;https://community.traefik.io/t/traefik-v2-helm-a-tour-of-the-traefik-2-helm-chart/6126&lt;/a&gt;
&lt;ul style=&quot;list-style-type: disc;&quot; data-ke-list-type=&quot;disc&quot;&gt;
&lt;li&gt;&lt;code&gt;helm repo add traefik https://containous.github.io/traefik-helm-chart&lt;/code&gt;&lt;/li&gt;
&lt;li&gt;&lt;code&gt;helm repo update&lt;/code&gt;&lt;/li&gt;
&lt;li&gt;&lt;code&gt;helm install traefik traefik/traefik&lt;/code&gt;&lt;/li&gt;
&lt;li&gt;참고로 &lt;code&gt;helm install traefik stable/traefik&lt;/code&gt; 으로 설치하면 1.x가 설치 된다.&lt;/li&gt;
&lt;/ul&gt;
&lt;/li&gt;
&lt;li&gt;traefik을 따로 설치하면, kubernetes 자체에 관한 설정이 k3s 쪽과 분리되어 있게 되므로, 관리의 편의를 위해서 될 수 있으면, k3s 설정 관련되는 디렉토리에 정보를 넣어 주는 방법이 좋을 것이다.&lt;/li&gt;
&lt;/ul&gt;
&lt;h3 data-ke-size=&quot;size23&quot;&gt;근데 k3s가 이것저것 알아서 설치해 준다고 했는데 어떻게?&lt;/h3&gt;
&lt;ul style=&quot;list-style-type: disc;&quot; data-ke-list-type=&quot;disc&quot;&gt;
&lt;li&gt;k3s는 kubernetes에서 이것저것 설치해야 할 것들을 자동으로 설치해 준다고 했다. 이건 어떻게 하는것일까?&lt;/li&gt;
&lt;li&gt;단순하다, k3s를 설치할때, 특정 디렉토리에 yaml 파일들을 두게 되어 있는데, 거기 있는것들이 자동으로 설치 되는 것이다.&lt;/li&gt;
&lt;/ul&gt;
&lt;p&gt;&lt;figure class=&quot;imageblock alignCenter&quot; data-origin-width=&quot;931&quot; data-origin-height=&quot;215&quot; data-ke-mobilestyle=&quot;widthContent&quot;&gt;&lt;span data-url=&quot;https://blog.kakaocdn.net/dn/bAgTPi/btqYB5CevHU/VK7A4AKOH1qmEjQXEgKQ90/img.png&quot; data-phocus=&quot;https://blog.kakaocdn.net/dn/bAgTPi/btqYB5CevHU/VK7A4AKOH1qmEjQXEgKQ90/img.png&quot;&gt;&lt;img src=&quot;https://blog.kakaocdn.net/dn/bAgTPi/btqYB5CevHU/VK7A4AKOH1qmEjQXEgKQ90/img.png&quot; srcset=&quot;https://img1.daumcdn.net/thumb/R1280x0/?scode=mtistory2&amp;fname=https%3A%2F%2Fblog.kakaocdn.net%2Fdn%2FbAgTPi%2FbtqYB5CevHU%2FVK7A4AKOH1qmEjQXEgKQ90%2Fimg.png&quot; data-origin-width=&quot;931&quot; data-origin-height=&quot;215&quot; data-ke-mobilestyle=&quot;widthContent&quot; onerror=&quot;this.onerror=null; this.src='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png'; this.srcset='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png';&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;/ul&gt;
&lt;p&gt;&lt;figure class=&quot;imageblock alignCenter&quot; data-origin-width=&quot;569&quot; data-origin-height=&quot;168&quot; data-ke-mobilestyle=&quot;widthContent&quot;&gt;&lt;span data-url=&quot;https://blog.kakaocdn.net/dn/cMv7aC/btqYDjmICDd/cynuJ6bXOCeqvvAAGGjQ81/img.png&quot; data-phocus=&quot;https://blog.kakaocdn.net/dn/cMv7aC/btqYDjmICDd/cynuJ6bXOCeqvvAAGGjQ81/img.png&quot;&gt;&lt;img src=&quot;https://blog.kakaocdn.net/dn/cMv7aC/btqYDjmICDd/cynuJ6bXOCeqvvAAGGjQ81/img.png&quot; srcset=&quot;https://img1.daumcdn.net/thumb/R1280x0/?scode=mtistory2&amp;fname=https%3A%2F%2Fblog.kakaocdn.net%2Fdn%2FcMv7aC%2FbtqYDjmICDd%2FcynuJ6bXOCeqvvAAGGjQ81%2Fimg.png&quot; data-origin-width=&quot;569&quot; data-origin-height=&quot;168&quot; data-ke-mobilestyle=&quot;widthContent&quot; onerror=&quot;this.onerror=null; this.src='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png'; this.srcset='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png';&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;여기에 Traefik2.x 관련 yaml 파일들을 두면, k3s가 디렉토리를 감시하다가 알아서 Traefik 2.x 를 설치해 줄 것이다.&lt;/li&gt;
&lt;/ul&gt;
&lt;h3 data-ke-size=&quot;size23&quot;&gt;k3s에 traefik 2.x를 설치하자.&lt;/h3&gt;
&lt;ul style=&quot;list-style-type: disc;&quot; data-ke-list-type=&quot;disc&quot;&gt;
&lt;li&gt;인터넷을 찾아보면 간단하게 Traefik 2.x를 설치 할 수 있는 정보를 찾을 수 있다.&lt;/li&gt;
&lt;li&gt;관련 링크 : &lt;a href=&quot;https://github.com/k3s-io/k3s/issues/1141#issuecomment-612823047&quot;&gt;https://github.com/k3s-io/k3s/issues/1141#issuecomment-612823047&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;아래와 같은 내용으로 yaml 파일을 작성하면 된다. &lt;b&gt;주의 : 해당 내용을 작성하는 파일명이 traefik.yaml 이면 안된다.!!!&lt;/b&gt;&lt;/li&gt;
&lt;/ul&gt;
&lt;pre class=&quot;dts&quot;&gt;&lt;code&gt;apiVersion: helm.cattle.io/v1
kind: HelmChart
metadata:
  name: traefik
  namespace: kube-system
spec:
  chart: traefik
  repo: https://containous.github.io/traefik-helm-chart
  set:
    image.tag: &quot;2.2&quot;
  valuesContent: |-
    additionalArguments:
    - --providers.file.filename=/data/traefik-config.yaml
    - ....어쩌고 저쩌고...
    - ....이러쿵 저러쿵...
&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;traefik.yaml&lt;/code&gt; 이라는 이름으로 파일을 만드니깐, 파일이 계속 사라지면저 적용이되지 않는 문제가 있있다.&lt;/li&gt;
&lt;li&gt;아마도 k3s 를 설치할때 traefik 을 disable 시켰으니깐, 그런게 아닌가 싶긴 한데.. 정확한건 모르겠다. ㅎ.&lt;/li&gt;
&lt;li&gt;암튼 나는 &lt;b&gt;&lt;code&gt;traefik2.yaml&lt;/code&gt; 파일로 위 내용을 적용해서 traefik 2.x&lt;/b&gt; 를 설치 했다.&lt;/li&gt;
&lt;/ul&gt;</description>
      <category>공부/컴퓨터</category>
      <category>disable-traefik</category>
      <category>helm</category>
      <category>HelmChart</category>
      <category>IngressController</category>
      <category>k3s</category>
      <category>k8s</category>
      <category>kubernetes</category>
      <category>traefik</category>
      <category>쿠버네티스</category>
      <author>찬</author>
      <guid isPermaLink="true">https://blog.ggaman.com/1024</guid>
      <comments>https://blog.ggaman.com/1024#entry1024comment</comments>
      <pubDate>Fri, 26 Feb 2021 00:26:13 +0900</pubDate>
    </item>
    <item>
      <title>[python] Flask로 app.run() 실행시 두개의 Process가 뜨는 문제</title>
      <link>https://blog.ggaman.com/1023</link>
      <description>&lt;h1&gt;오늘도 트러블슈팅&lt;/h1&gt;
&lt;h1&gt;문제 상황&lt;/h1&gt;
&lt;ul&gt;
&lt;li&gt;AI 엔진을 띄우면 GPU를 사용하는 Process가 2개가 뜨면서 GPU RAM을 쓸데 없이 두번 먹는 현상.&lt;/li&gt;
&lt;/ul&gt;
&lt;h1&gt;문제 분석&lt;/h1&gt;
&lt;ul&gt;
&lt;li&gt;웹 서버를 띄울때 Flask를 활용하고 있음&lt;/li&gt;
&lt;li&gt;&amp;quot;&lt;strong&gt;main&lt;/strong&gt;&amp;quot; 에서 model을 로딩하면 최초 실행된 python process가 GPU 메모리를 1.5GB 정도 사용&lt;/li&gt;
&lt;li&gt;이후 Flask의 app.run 을 실행.&lt;/li&gt;
&lt;li&gt;Flask의 app.run을 실행하면 python process가 1개 더 뜨면서 GPU 메모리를 1.5GB 정도 사용&lt;/li&gt;
&lt;li&gt;아마도 app.run 실행시 python process 가 fork() 되면서 GPU메모리까지 clone 되는것으로 예측??&lt;/li&gt;
&lt;/ul&gt;
&lt;h1&gt;문제 확인&lt;/h1&gt;
&lt;ul&gt;
&lt;li&gt;&lt;code&gt;main&lt;/code&gt;부분에서 바로 model을 로딩하지 않고, 바로 Flask의 app.run을 실행&lt;/li&gt;
&lt;li&gt;최초 predict request시 model을 한번만 로딩하도록 코드 수정&lt;/li&gt;
&lt;li&gt;nvidia-smi 확인시 /usr/bin/python3.7 단 1개만 GPU를 사용하는것을 확인&lt;/li&gt;
&lt;/ul&gt;
&lt;p&gt;&lt;figure class=&quot;imageblock alignCenter&quot; data-origin-width=&quot;0&quot; data-origin-height=&quot;0&quot; data-ke-mobilestyle=&quot;widthContent&quot;&gt;&lt;span data-url=&quot;https://blog.kakaocdn.net/dn/b2dvN9/btqYwiansiU/aP1WOM2EhMJiwX6O6hSan1/img.png&quot; data-phocus=&quot;https://blog.kakaocdn.net/dn/b2dvN9/btqYwiansiU/aP1WOM2EhMJiwX6O6hSan1/img.png&quot;&gt;&lt;img src=&quot;https://blog.kakaocdn.net/dn/b2dvN9/btqYwiansiU/aP1WOM2EhMJiwX6O6hSan1/img.png&quot; srcset=&quot;https://img1.daumcdn.net/thumb/R1280x0/?scode=mtistory2&amp;fname=https%3A%2F%2Fblog.kakaocdn.net%2Fdn%2Fb2dvN9%2FbtqYwiansiU%2FaP1WOM2EhMJiwX6O6hSan1%2Fimg.png&quot; data-origin-width=&quot;0&quot; data-origin-height=&quot;0&quot; data-ke-mobilestyle=&quot;widthContent&quot; onerror=&quot;this.onerror=null; this.src='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png'; this.srcset='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png';&quot;/&gt;&lt;/span&gt;&lt;/figure&gt;
&lt;/p&gt;
&lt;h1&gt;해결법 제안 ( 1번 방식 )&lt;/h1&gt;
&lt;ul&gt;
&lt;li&gt;Flask 사용시 app.run 이전에 최대한 메모리 사용을 하지 않도록 코드 작성 필요&lt;/li&gt;
&lt;li&gt;Flask 의 Context가 초기화 된 직후, Flask process에서 모델을 로딩하도록 수정.  &lt;ul&gt;
&lt;li&gt;예1&amp;gt; 코드에 model이 null 인 경우에만, 초기화 ( 아래 그림 )&lt;/li&gt;
&lt;li&gt;예2&amp;gt; Flask의 annotaion 중 app.before_first_request 를 사용하여, 최초 requset 직전에 GPU 사용하는 모델을 로딩하도록 수정 필요&lt;/li&gt;
&lt;/ul&gt;
&lt;/li&gt;
&lt;/ul&gt;
&lt;p&gt;&lt;figure class=&quot;imageblock alignCenter&quot; data-origin-width=&quot;0&quot; data-origin-height=&quot;0&quot; data-ke-mobilestyle=&quot;widthContent&quot;&gt;&lt;span data-url=&quot;https://blog.kakaocdn.net/dn/cOFE84/btqYxo2uHAU/3mZXdXBHbFvu7D4QPa2Ma1/img.png&quot; data-phocus=&quot;https://blog.kakaocdn.net/dn/cOFE84/btqYxo2uHAU/3mZXdXBHbFvu7D4QPa2Ma1/img.png&quot;&gt;&lt;img src=&quot;https://blog.kakaocdn.net/dn/cOFE84/btqYxo2uHAU/3mZXdXBHbFvu7D4QPa2Ma1/img.png&quot; srcset=&quot;https://img1.daumcdn.net/thumb/R1280x0/?scode=mtistory2&amp;fname=https%3A%2F%2Fblog.kakaocdn.net%2Fdn%2FcOFE84%2FbtqYxo2uHAU%2F3mZXdXBHbFvu7D4QPa2Ma1%2Fimg.png&quot; data-origin-width=&quot;0&quot; data-origin-height=&quot;0&quot; data-ke-mobilestyle=&quot;widthContent&quot; onerror=&quot;this.onerror=null; this.src='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png'; this.srcset='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png';&quot;/&gt;&lt;/span&gt;&lt;/figure&gt;
&lt;/p&gt;
&lt;h1&gt;진짜 문제 상황&lt;/h1&gt;
&lt;ul&gt;
&lt;li&gt;해당 문제는 Flask에서 사용되는 WSGI(Web Service Gateway Interface)와 관련이 있는 듯 하다.&lt;/li&gt;
&lt;li&gt;Werkzeug 라는 놈은 개발 편의를 위해 dev mode인 경우, code의 변경을 감시해 새롭게 서버를 띄우는 기능이 있는듯 하다. ( 즉, 이전에 process가 fork 된다고 이야기 했던것은 틀렸고, child process로 같은 프로세를 하나 더 실행 시키는 형태 였음 )&lt;/li&gt;
&lt;li&gt;관련 링크 1 : &lt;a href=&quot;https://stackoverflow.com/questions/25504149/why-does-running-the-flask-dev-server-run-itself-twice&quot;&gt;https://stackoverflow.com/questions/25504149/why-does-running-the-flask-dev-server-run-itself-twice&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;관련 링크 2 : &lt;a href=&quot;https://github.com/pallets/werkzeug/blob/54acdd16b247f7037482737e72ec52fc6d50a78d/src/werkzeug/_reloader.py#L160-L185&quot;&gt;https://github.com/pallets/werkzeug/blob/54acdd16b247f7037482737e72ec52fc6d50a78d/src/werkzeug/_reloader.py#L160-L185&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;즉, Flask를 사용하여 서버를 띄우면, 두개의 Process가 뜨게 된다.&lt;ul&gt;
&lt;li&gt;첫번째 프로세스 : Flask 역할 ( Http Request, Response를 처리 )&lt;/li&gt;
&lt;li&gt;두번째 프로세스 : Flask 관련 코드가 변경되면 자동으로 dev서버를 reload 해 주는 역할&lt;/li&gt;
&lt;/ul&gt;
&lt;/li&gt;
&lt;/ul&gt;
&lt;h1&gt;진짜 문제 해결 ( 2번 방식 )&lt;/h1&gt;
&lt;ul&gt;
&lt;li&gt;그래서 어쩌면 되냐고? &lt;strong&gt;auto reload 모드를 끄면 된다&lt;/strong&gt;고 한다.&lt;/li&gt;
&lt;li&gt;python 코드 안에서 설정하기 : &lt;code&gt;app.run(use_reloader=False)&lt;/code&gt;&lt;/li&gt;
&lt;li&gt;하지만, &lt;strong&gt;&lt;code&gt;pyhon web.py&lt;/code&gt;와 같은 방식으로 실행시키는 경우 코드가 두번 실행되는 이슈&lt;/strong&gt;가 있으니, &lt;code&gt;flask run --no-reload&lt;/code&gt; 방식으로 실행시키라고 이야기 하고 있다.&lt;/li&gt;
&lt;li&gt;관련 링크 3 : &lt;a href=&quot;https://flask.palletsprojects.com/en/1.1.x/server/&quot;&gt;https://flask.palletsprojects.com/en/1.1.x/server/&lt;/a&gt;&lt;/li&gt;
&lt;/ul&gt;
&lt;p&gt;&lt;figure class=&quot;imageblock alignCenter&quot; data-origin-width=&quot;0&quot; data-origin-height=&quot;0&quot; data-ke-mobilestyle=&quot;widthContent&quot;&gt;&lt;span data-url=&quot;https://blog.kakaocdn.net/dn/RdnzE/btqYnop9mCq/IFzyAnCPVOdMFW9CB97gv0/img.png&quot; data-phocus=&quot;https://blog.kakaocdn.net/dn/RdnzE/btqYnop9mCq/IFzyAnCPVOdMFW9CB97gv0/img.png&quot;&gt;&lt;img src=&quot;https://blog.kakaocdn.net/dn/RdnzE/btqYnop9mCq/IFzyAnCPVOdMFW9CB97gv0/img.png&quot; srcset=&quot;https://img1.daumcdn.net/thumb/R1280x0/?scode=mtistory2&amp;fname=https%3A%2F%2Fblog.kakaocdn.net%2Fdn%2FRdnzE%2FbtqYnop9mCq%2FIFzyAnCPVOdMFW9CB97gv0%2Fimg.png&quot; data-origin-width=&quot;0&quot; data-origin-height=&quot;0&quot; data-ke-mobilestyle=&quot;widthContent&quot; onerror=&quot;this.onerror=null; this.src='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png'; this.srcset='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png';&quot;/&gt;&lt;/span&gt;&lt;/figure&gt;
&lt;/p&gt;
&lt;h1&gt;그럼 뭘 써야 할까?&lt;/h1&gt;
&lt;ul&gt;
&lt;li&gt;개발을 할 때는 코드가 변경되었을때 바로바로 업데이트되면 편하니깐, Debug를 켜두고 1번 방식을 적용하면 좋을것이다.&lt;/li&gt;
&lt;li&gt;실제로 production에서 사용할꺼면, debug 모드도 끄고, reload도 끄는게 좋을 테니깐 2번 방식을 적용하면 더 좋을것이다. 물론 2번 방식을 적용할때 1번 방식을 같이 적용해도 괜찮을것이다.&lt;/li&gt;
&lt;/ul&gt;</description>
      <category>공부/컴퓨터</category>
      <category>Flask</category>
      <category>no-reload</category>
      <category>Python</category>
      <category>use_reloader</category>
      <category>Werkzeug</category>
      <category>wsgi</category>
      <category>두번실행</category>
      <category>파이썬</category>
      <category>플라스크</category>
      <author>찬</author>
      <guid isPermaLink="true">https://blog.ggaman.com/1023</guid>
      <comments>https://blog.ggaman.com/1023#entry1023comment</comments>
      <pubDate>Thu, 25 Feb 2021 00:18:49 +0900</pubDate>
    </item>
    <item>
      <title>[Kubernetes] k3s에서 컨테이너를 띄웠는데 왜 GPU를 못 쓰지?</title>
      <link>https://blog.ggaman.com/1022</link>
      <description>&lt;h1&gt;k3s에서 GPU를 왜 못쓰지?&lt;/h1&gt;
&lt;ul&gt;
&lt;li&gt;docker를 설치하고, nvidiai-docker를 설치하고, k3s를 설치 했다.&lt;/li&gt;
&lt;li&gt;docker를 이용해서 GPU를 사용하는 컨테이너를 띄웠을때는 GPU를 잘 사용하는데...&lt;/li&gt;
&lt;li&gt;kubernetes에서 Pod를 띄우니 GPU를 못 사용하네? 왜 그럴까?&lt;/li&gt;
&lt;/ul&gt;
&lt;h1&gt;미리 정리하면...&lt;/h1&gt;
&lt;ul&gt;
&lt;li&gt;조건 : docker 설치. nvidia-docker 설치. k3s 설치&lt;/li&gt;
&lt;li&gt;문제 : Kubernetes에서 띄운 Pod에서 GPU를 사용하지 못함.&lt;/li&gt;
&lt;li&gt;해결 : k3s를 설치하면 기본적으로 containerd 를 사용하게 되어 있음. nvidia-docker를 써야만 GPU를 활용할 수 있음. 즉, k3s의 container runtime을 docker로 변경해야 함.
&lt;ul&gt;
&lt;li&gt;k3s 설치시 &lt;code&gt;--docker&lt;/code&gt; 옵션 추가 필요&lt;/li&gt;
&lt;li&gt;&lt;code&gt;curl -sfL https://get.k3s.io | sh -s - --docker&lt;/code&gt;&lt;/li&gt;
&lt;/ul&gt;
&lt;/li&gt;
&lt;/ul&gt;
&lt;h1&gt;좀 더 길게 적어 보면&lt;/h1&gt;
&lt;h2&gt;왜 AI 엔진이 뜨지 않을까?&lt;/h2&gt;
&lt;ul&gt;
&lt;li&gt;오늘 k3s에서 AI 엔진을 띄우려고 했는데, 아무리해도 프로세스가 제대로 안 뜨는것이다.&lt;/li&gt;
&lt;li&gt;&lt;code&gt;nvidia-smi&lt;/code&gt; 명령을 이용해서 확인해도 python process가 안 뜨고 있었다.&lt;/li&gt;
&lt;li&gt;&lt;code&gt;docker ps -a&lt;/code&gt; 명령어를 쳐 보니.. 엇? container가 하나도 안 떠 있네?&lt;/li&gt;
&lt;/ul&gt;
&lt;h2&gt;왜 docker 컨테이너가 하나도 없지?&lt;/h2&gt;
&lt;ul&gt;
&lt;li&gt;확인 결과 k3s를 설치할때 아무런 옵션을 주지 않으면 container runtime을 containerd 를 사용하도록 된다.&lt;/li&gt;
&lt;/ul&gt;
&lt;p&gt;&lt;figure class=&quot;imageblock alignCenter&quot; data-origin-width=&quot;0&quot; data-origin-height=&quot;0&quot; data-ke-mobilestyle=&quot;widthContent&quot;&gt;&lt;span data-url=&quot;https://blog.kakaocdn.net/dn/bbYBkY/btqYkboGxjS/D9bUyhBFPP5ndWBjRoQkpk/img.png&quot; data-phocus=&quot;https://blog.kakaocdn.net/dn/bbYBkY/btqYkboGxjS/D9bUyhBFPP5ndWBjRoQkpk/img.png&quot; data-alt=&quot;k3s에서는 containerd를 default container runtime으로 사용한다.&quot;&gt;&lt;img src=&quot;https://blog.kakaocdn.net/dn/bbYBkY/btqYkboGxjS/D9bUyhBFPP5ndWBjRoQkpk/img.png&quot; srcset=&quot;https://img1.daumcdn.net/thumb/R1280x0/?scode=mtistory2&amp;fname=https%3A%2F%2Fblog.kakaocdn.net%2Fdn%2FbbYBkY%2FbtqYkboGxjS%2FD9bUyhBFPP5ndWBjRoQkpk%2Fimg.png&quot; data-origin-width=&quot;0&quot; data-origin-height=&quot;0&quot; data-ke-mobilestyle=&quot;widthContent&quot; onerror=&quot;this.onerror=null; this.src='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png'; this.srcset='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png';&quot;/&gt;&lt;/span&gt;&lt;figcaption&gt;k3s에서는 containerd를 default container runtime으로 사용한다.&lt;/figcaption&gt;
&lt;/figure&gt;
&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;그러므로 &lt;code&gt;docker ps -a&lt;/code&gt; 따위의 명령을 써도, 아무것도 안 떠 있는것처럼 보인 것이다.&lt;/li&gt;
&lt;/ul&gt;
&lt;h2&gt;k3s의 container runtime을 docker로 변경&lt;/h2&gt;
&lt;ul&gt;
&lt;li&gt;관련 링크 : &lt;a href=&quot;https://rancher.com/docs/k3s/latest/en/advanced/&quot;&gt;https://rancher.com/docs/k3s/latest/en/advanced/&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;containerd 에서는 GPU를 사용하도록 설정하지 않았기 때문에, 당근 GPU를 사용할 수 없다.&lt;/li&gt;
&lt;li&gt;그러므로 GPU를 사용할 수 있도록 설정된 docker 를 k3s의 container runtime으로 사용하도록 설정해야 한다.&lt;/li&gt;
&lt;li&gt;k3s 를 설치할때 container runtime을 docker로 지정해서 설치하면 된다.&lt;/li&gt;
&lt;/ul&gt;
&lt;p&gt;&lt;figure class=&quot;imageblock alignCenter&quot; data-origin-width=&quot;0&quot; data-origin-height=&quot;0&quot; data-ke-mobilestyle=&quot;widthContent&quot;&gt;&lt;span data-url=&quot;https://blog.kakaocdn.net/dn/Me2IH/btqYhyq9qr0/72kSxdp1pdPChwjpPG7ohK/img.png&quot; data-phocus=&quot;https://blog.kakaocdn.net/dn/Me2IH/btqYhyq9qr0/72kSxdp1pdPChwjpPG7ohK/img.png&quot; data-alt=&quot;--docker 옵션을 이용해서 default container runtime을 docker로 변경하자.&quot;&gt;&lt;img src=&quot;https://blog.kakaocdn.net/dn/Me2IH/btqYhyq9qr0/72kSxdp1pdPChwjpPG7ohK/img.png&quot; srcset=&quot;https://img1.daumcdn.net/thumb/R1280x0/?scode=mtistory2&amp;fname=https%3A%2F%2Fblog.kakaocdn.net%2Fdn%2FMe2IH%2FbtqYhyq9qr0%2F72kSxdp1pdPChwjpPG7ohK%2Fimg.png&quot; data-origin-width=&quot;0&quot; data-origin-height=&quot;0&quot; data-ke-mobilestyle=&quot;widthContent&quot; onerror=&quot;this.onerror=null; this.src='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png'; this.srcset='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png';&quot;/&gt;&lt;/span&gt;&lt;figcaption&gt;--docker 옵션을 이용해서 default container runtime을 docker로 변경하자.&lt;/figcaption&gt;
&lt;/figure&gt;
&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;이 작업 이후 &lt;code&gt;docker ps -a&lt;/code&gt; 명령을 쓰면, kubernetes 관련된 많은 container들이 떠 있는것을 확인할 수 있다.&lt;/li&gt;
&lt;/ul&gt;
&lt;p&gt;&lt;figure class=&quot;imageblock alignCenter&quot; data-origin-width=&quot;0&quot; data-origin-height=&quot;0&quot; data-ke-mobilestyle=&quot;widthContent&quot;&gt;&lt;span data-url=&quot;https://blog.kakaocdn.net/dn/k0nst/btqYiCNCoKv/WRcuUqxrfW4QPuZtlXJZe1/img.png&quot; data-phocus=&quot;https://blog.kakaocdn.net/dn/k0nst/btqYiCNCoKv/WRcuUqxrfW4QPuZtlXJZe1/img.png&quot; data-alt=&quot;이제 docker ps 명령을 통해서 kubernetes에 떠 있는 container들을 볼 수 있다.&quot;&gt;&lt;img src=&quot;https://blog.kakaocdn.net/dn/k0nst/btqYiCNCoKv/WRcuUqxrfW4QPuZtlXJZe1/img.png&quot; srcset=&quot;https://img1.daumcdn.net/thumb/R1280x0/?scode=mtistory2&amp;fname=https%3A%2F%2Fblog.kakaocdn.net%2Fdn%2Fk0nst%2FbtqYiCNCoKv%2FWRcuUqxrfW4QPuZtlXJZe1%2Fimg.png&quot; data-origin-width=&quot;0&quot; data-origin-height=&quot;0&quot; data-ke-mobilestyle=&quot;widthContent&quot; onerror=&quot;this.onerror=null; this.src='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png'; this.srcset='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png';&quot;/&gt;&lt;/span&gt;&lt;figcaption&gt;이제 docker ps 명령을 통해서 kubernetes에 떠 있는 container들을 볼 수 있다.&lt;/figcaption&gt;
&lt;/figure&gt;
&lt;/p&gt;
&lt;p&gt;&amp;nbsp;&lt;/p&gt;</description>
      <category>공부/컴퓨터</category>
      <category>containerd</category>
      <category>docker</category>
      <category>k3s</category>
      <category>kubernetes</category>
      <category>도커</category>
      <category>쿠버네티스</category>
      <author>찬</author>
      <guid isPermaLink="true">https://blog.ggaman.com/1022</guid>
      <comments>https://blog.ggaman.com/1022#entry1022comment</comments>
      <pubDate>Mon, 22 Feb 2021 23:22:58 +0900</pubDate>
    </item>
    <item>
      <title>[kubernetes] node에 달린 NVIDIA GPU를 Pod가 사용하지 않도록 하기</title>
      <link>https://blog.ggaman.com/1021</link>
      <description>&lt;h1&gt;오랜만?&lt;/h1&gt;
&lt;p&gt;오래만에 글이다. ( 라고 맨날쓴다. ㅎ ) kubernetes에서 GPU를 사용하다 보니 문제가 몇가지 생겨서 이를 해결하는 방법을 찾아, 기록으로 남겨두고자 한다.&lt;/p&gt;
&lt;h1&gt;너무 기니깐.. 정리하면&lt;/h1&gt;
&lt;ul&gt;
&lt;li&gt;조건 : Kubernetes에서 Container Runtime을 Docker로 사용하고, NVIDIA GPU 사용을 위해 docker의 default-runtime을 nvidia-docker로 설정한 경우.&lt;/li&gt;
&lt;li&gt;문제 : Kubernetes에서 뜨는 Pod에서 GPU 자원을 못쓰게 하고 싶은데, docker nvidia runtime으로 인해 Container가 무조건 GPU를 보게 되는 상황.&lt;/li&gt;
&lt;li&gt;해결 : 환경 변수로 &lt;code&gt;CUDA_VISIBLE_DEVICES=&lt;/code&gt; 값을 줘, CUDA Library 단에서 GPU 자원을 사용하지 않게 막기.&lt;/li&gt;
&lt;/ul&gt;
&lt;h1&gt;현재 상황&lt;/h1&gt;
&lt;p&gt;현재 운영 환경은 k8s master 3대, worker 8대로 총 11대가 동작중이다. 이 중 worker의 서버 상황은 아래와 같다.&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;O/S : Ubuntu 18.04&lt;/li&gt;
&lt;li&gt;GPU : NVIDIA GPU&lt;/li&gt;
&lt;li&gt;Container runtime : docker&lt;/li&gt;
&lt;li&gt;Docker default runtime : nvidia&lt;/li&gt;
&lt;/ul&gt;
&lt;p&gt;Container Runtime을 Docker 로 설치를 했고, nvidia-docker2도 설치 했음. &lt;span style=&quot;color: #333333;&quot;&gt;&lt;span&gt;&amp;nbsp;&lt;/span&gt;nvidia-docker를 쉽게 사용하기 위해서&lt;/span&gt;&amp;nbsp;/etc/docker/daemon.json 파일에 default-runtime을 nvidia로 변경해 두었다.&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;Nvidia docker2 설치 방법 : &lt;a href=&quot;https://docs.nvidia.com/datacenter/cloud-native/container-toolkit/install-guide.html#docker&quot;&gt;https://docs.nvidia.com/datacenter/cloud-native/container-toolkit/install-guide.html#docker&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;docker 실행시 nvidia-docker가 실행되도록 하는 방법 : &lt;a href=&quot;http://haanjack.github.io/docker/2018/02/21/nvidia-docker2-runtime.html&quot;&gt;http://haanjack.github.io/docker/2018/02/21/nvidia-docker2-runtime.html&lt;/a&gt;&lt;/li&gt;
&lt;/ul&gt;
&lt;h1&gt;문제 상황&lt;/h1&gt;
&lt;p&gt;서비스를 운영하는 입장에서는 동작시킬 서비스가, 외부의 설정만으로 기능이나 동작이 쉽게 변경 될 수 있도록 만들어 주는것이 좋을 것이다. 하지만, 실제 만들때에는 &quot;내 환경&quot;에서 개발하므로 그런 생각들을 별로 하지 않고 개발되기가 쉽다. 지금 설명하려고 하는것도 그렇다.&lt;br /&gt;AI 서비스를 개발할 때도 무조건 GPU를 사용한다는 가정하게 동작되는 경우들이 많이 있다. 학습하고 개발하는 환경에서는 GPU가 있을 것이기 때문에, 신경을 쓰지 않기 때문이다. 그러므로 GPU 사용 여부를 &quot;외부에서 설정&quot;하도록 개발하지 않은 경우가 훨씬 많을 것이다.&lt;br /&gt;자, 그렇다면 Container에서는 GPU를 사용할 수 있지만, 그 GPU를 사용하지 않도록 하고 싶은 경우라면 어떻게 해야 할까? 지금 이야기 하고자 하는 이야기가 바로 그것이다.&lt;/p&gt;
&lt;h1&gt;문제 해결 - Docker에서는...&lt;/h1&gt;
&lt;p&gt;예에에에에전 Docker말고, 요즘 docker는 runtime을 골라서 실행 할 수 있다. nvdidia GPU를 사용하지 않은 컨테이너를 사용하고 싶다면 아래와 같이 &lt;code&gt;runtime&lt;/code&gt;에 &lt;code&gt;runc&lt;/code&gt;를 사용하면 된다.&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;&lt;code&gt;docker run -it --runtime=runc ubuntu:18.04 /bin/bash&lt;/code&gt;&lt;/li&gt;
&lt;/ul&gt;
&lt;p&gt;NVIDIA GPU를 사용할 Container를 만들고 싶다면 다음과 같은 형태의 명령어를 사용하면 된다.&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;&lt;code&gt;docker run -it --runntime=nvidia ubuntu:18.04 /bin/bash&lt;/code&gt;&lt;/li&gt;
&lt;/ul&gt;
&lt;p&gt;아무것도 적지 않으면 /etc/docker/daemon.json 에 &lt;code&gt;default-runtime&lt;/code&gt;으로 설정된 값이 사용되므로, 지금 우리의 상태로라면 아무것도 적지 않으면 &lt;code&gt;nvidia&lt;/code&gt;로 사용된다. 즉, 생각 없이 그냥 &lt;code&gt;docker&lt;/code&gt; 명령을 쓰면 &lt;code&gt;nvidia-docker2&lt;/code&gt; 가 실행되며, &lt;span style=&quot;color: #333333;&quot;&gt;NVIDIA&lt;span&gt;&amp;nbsp;&lt;/span&gt;&lt;/span&gt;GPU를 사용하도록 되어 있다.&lt;/p&gt;
&lt;p&gt;즉, 필요한 경우 &lt;code&gt;--runtime&lt;/code&gt; 옵션을 변경하면 GPU 사용을 on/off 시킬 수 있다.&lt;/p&gt;
&lt;h1&gt;문제 해결 - Kubernetes에서는...&lt;/h1&gt;
&lt;p&gt;지금 우리의 문제가 까다롭다. Kubernetes에 설정된 container runtime은 &lt;code&gt;docker&lt;/code&gt;로 설정되어 있으며, docker의 &lt;code&gt;default-runtime&lt;/code&gt;이 &lt;code&gt;nvidia&lt;/code&gt;로 설정되어 있으므로, kubernetes에서 생각없이 컨테이너를 띄우게 되면 &lt;code&gt;nvidia runtime&lt;/code&gt;이 실행되며, 그로 인해서 무조건 GPU를 사용하는 환경으로 셋팅되게 된다.&lt;br /&gt;Kubernetes에서도 &lt;code&gt;docker&lt;/code&gt;의 &lt;code&gt;--runtime&lt;/code&gt; 옵션처럼, 필요한 runtime을 골라서 실행 시킬수는 없을까?&lt;/p&gt;
&lt;h2&gt;Kubernetes GPU 스케쥴링 ( 지금의 문제와 상관없는 하등 쓸데 없는 이야기지만 걍 정리하는 겸? 무시하고 넘기세요. ㅎ.)&lt;/h2&gt;
&lt;p&gt;그 전에 먼저 Kubernetes Node에서 GPU를 &quot;제대로&quot; 사용하는 방법에 대한 설명이 필요할 수 있는데, 이건 좀 더 설명이 필요하니, 여기서는 대충 정리하고, 시간이 되면 이 내용만 빼서 글을 하나 따로 정리할 예정.&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;Kubernetes에서는 Pod에 GPU를 할당할 수 있는 기능을 제공한다.&lt;/li&gt;
&lt;li&gt;AMD와 NVIDIA GPU를 Pod에 할당 할 수 있다.&lt;/li&gt;
&lt;li&gt;NVIDIA GPU는 &lt;code&gt;nvidia-docker2&lt;/code&gt;를 이용해서 제공되며, docker의 default-runtime을 &lt;code&gt;nvidia&lt;/code&gt;로 설정해야 한다.&lt;/li&gt;
&lt;li&gt;GPU를 할당하고 싶은 Pod의 yaml에 &lt;code&gt;spec.containers.resources.xxxxx.nvidia.com/gpu:1&lt;/code&gt; 등으로 GPU 갯수를 할당 가능하다&lt;/li&gt;
&lt;li&gt;다만 GPU를 쪼개서(fraction)쓰는 기능은 지원하지 않는다.&lt;/li&gt;
&lt;li&gt;그래서 대용량 메모리의 GPU를 사용한다면, resource limit 등을 설정하지 않고 쓰는게 좋다. ( 관리는 알아서... )&lt;/li&gt;
&lt;/ul&gt;
&lt;p&gt;참고로, 나는 위 상황에서 Pod1개에 GPU 1개를 할당해야 하는 문제 때문에, Resource Limit를 걸어서 사용하고 있지 않고 있다.&lt;br /&gt;관련 링크 : &lt;a href=&quot;https://kubernetes.io/docs/tasks/manage-gpus/scheduling-gpus/&quot;&gt;https://kubernetes.io/docs/tasks/manage-gpus/scheduling-gpus/&lt;/a&gt;&lt;/p&gt;
&lt;p&gt;&lt;figure class=&quot;imageblock alignCenter&quot; data-origin-width=&quot;0&quot; data-origin-height=&quot;0&quot; data-ke-mobilestyle=&quot;widthContent&quot;&gt;&lt;span data-url=&quot;https://blog.kakaocdn.net/dn/m5wlZ/btqYbIAn19p/eL2TlYp9k9MzLDjl8eHgn0/img.png&quot; data-phocus=&quot;https://blog.kakaocdn.net/dn/m5wlZ/btqYbIAn19p/eL2TlYp9k9MzLDjl8eHgn0/img.png&quot;&gt;&lt;img src=&quot;https://blog.kakaocdn.net/dn/m5wlZ/btqYbIAn19p/eL2TlYp9k9MzLDjl8eHgn0/img.png&quot; srcset=&quot;https://img1.daumcdn.net/thumb/R1280x0/?scode=mtistory2&amp;fname=https%3A%2F%2Fblog.kakaocdn.net%2Fdn%2Fm5wlZ%2FbtqYbIAn19p%2FeL2TlYp9k9MzLDjl8eHgn0%2Fimg.png&quot; data-origin-width=&quot;0&quot; data-origin-height=&quot;0&quot; data-ke-mobilestyle=&quot;widthContent&quot; onerror=&quot;this.onerror=null; this.src='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png'; this.srcset='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png';&quot;/&gt;&lt;/span&gt;&lt;/figure&gt;
&lt;/p&gt;
&lt;h2&gt;Kubernetes의 RuntimeClass 리소스&lt;/h2&gt;
&lt;p&gt;다시 원래대로 돌아와서 이야기 하자.&amp;nbsp; docker는 실행시킬때마다 runtime을 고를 수 있었는데, kubernetes에서 runtime을 골라서 실행 시킬 수는 없을까? Kubernetes에서도 Pod의 spec을 적을때, runtime을 고를 수 있도록 해 두었다. 해당 기능은 kubernetes v1.20에 stable로 넘어온 기능이다. &lt;span style=&quot;color: #333333;&quot;&gt;( 물론 우리가 runtime Resource를 만들 수도 있겠지만, 그 어려운걸 누가 만들겠나? 만들어진것을 잘 써야지. ㅎㅎ )&lt;/span&gt;&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;Kubernetes Resource 중에는 &lt;code&gt;RuntimeClass&lt;/code&gt; 라는 리소스가 있다.&lt;/li&gt;
&lt;li&gt;이 값을 변경하면, Pod를 실행할때 어떤 runtime 을 골라서 실행할지 정할 수 있다.&lt;/li&gt;
&lt;li&gt;Runtime을 지정해서 실행시키고 싶음 Pod의 yaml에 &lt;code&gt;spec.runtimeClassName: xxxxxx&lt;/code&gt;를 주면 된다.&lt;/li&gt;
&lt;li&gt;Kubernetes 홈페이지에는 CRI(Container Runtime Interface)를 구현한 몇가지 Runtime에 대해 설정해 주고 있다. ( &lt;a href=&quot;https://kubernetes.io/docs/setup/production-environment/container-runtimes/&quot;&gt;https://kubernetes.io/docs/setup/production-environment/container-runtimes/&lt;/a&gt; )&lt;/li&gt;
&lt;li&gt;홈페이지에는 &lt;code&gt;containerd&lt;/code&gt;, &lt;code&gt;CRI-O&lt;/code&gt;, &lt;code&gt;Docker&lt;/code&gt; 에 대해서 설명하고 있다.&lt;/li&gt;
&lt;/ul&gt;
&lt;p&gt;자, 그렇다면 우리가 Pod를 만들때 &lt;code&gt;spec.runtimeClassName: nvidia&lt;/code&gt;를 사용하면 GPU를 사용할 수 있게 되고, &lt;code&gt;runc&lt;/code&gt;를 사용하면 GPU를 사용하지 못하도록 할 수 있을까? 아쉽지만 정답은 아니다.&lt;br /&gt;우리는 현재 k8s의 Container runtime으로 Docker를 사용하고 있고, Docker는 &lt;code&gt;runtimeClassName&lt;/code&gt; 값을 존중해주지 않는다.&lt;/p&gt;
&lt;p&gt;&lt;figure class=&quot;imageblock alignCenter&quot; data-origin-width=&quot;0&quot; data-origin-height=&quot;0&quot; data-ke-mobilestyle=&quot;widthContent&quot;&gt;&lt;span data-url=&quot;https://blog.kakaocdn.net/dn/bFGryk/btqX32sSBCY/vVznwOYz7kTwIvZDbuVIk0/img.png&quot; data-phocus=&quot;https://blog.kakaocdn.net/dn/bFGryk/btqX32sSBCY/vVznwOYz7kTwIvZDbuVIk0/img.png&quot;&gt;&lt;img src=&quot;https://blog.kakaocdn.net/dn/bFGryk/btqX32sSBCY/vVznwOYz7kTwIvZDbuVIk0/img.png&quot; srcset=&quot;https://img1.daumcdn.net/thumb/R1280x0/?scode=mtistory2&amp;fname=https%3A%2F%2Fblog.kakaocdn.net%2Fdn%2FbFGryk%2FbtqX32sSBCY%2FvVznwOYz7kTwIvZDbuVIk0%2Fimg.png&quot; data-origin-width=&quot;0&quot; data-origin-height=&quot;0&quot; data-ke-mobilestyle=&quot;widthContent&quot; onerror=&quot;this.onerror=null; this.src='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png'; this.srcset='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png';&quot;/&gt;&lt;/span&gt;&lt;/figure&gt;
&lt;/p&gt;
&lt;p&gt;&lt;code&gt;Dockershim&lt;/code&gt; 은 kubernetes와 docker를 연결해 주는 놈이다. 즉, 우리가 k8s container runtime으로 &lt;code&gt;docker&lt;/code&gt;를 사용한다면 아무리 지..라.ㄹ..ㅂ....ㄱ...을 해 봐도 안되는거다. ( &lt;code&gt;containerd&lt;/code&gt; 를 사용하면 되겠지만, 바꾸는게 무섭고 싫다.. 엉엉 )&lt;/p&gt;
&lt;p&gt;참고로, 작년 말에 kubernetes가 &quot;더 이상 docker는 지원하지 않을꺼예요&quot; 라는 결정을 내렸다. kubernetes 1.22까지는 dockershim을 지원하며, 1.23부터는 지원하지 않을 것이라고 했다. 아... 망...&lt;br /&gt;관련 링크 : &lt;a href=&quot;https://kubernetes.io/blog/2020/12/02/dockershim-faq/&quot;&gt;https://kubernetes.io/blog/2020/12/02/dockershim-faq/&lt;/a&gt;&lt;/p&gt;
&lt;p&gt;위 뉴스가 났을때 &quot;그럼 나는 Docker를 유지한채로, 1.22까지 업그레이드해서 사용하면되지&quot; 싶었는데, rumtime handler를 제대로 사용하려면, container runtime의 상태를 바꿔야하는 배보다.배꼽..상황이 되어 버렸다. ( docker도 내부적으로는 containerd 를 사용한다고 알고 있지만, k8s의 설정을 띡 하고 바꾸었을때 잘 된다는 보장이 없으니.. )&lt;br /&gt;docker를 계속 사용해야 하는 상황이 하나 더 있는데, Kubernetes 에서 GPU를 사용하는것에 대한 가이드도 &quot;nvidia-docker2 를 설치하고 써라~&quot; 따위의 안내만 하고 있으니... 이건 &lt;b&gt;문제 해결&lt;/b&gt; 상황이 아니라, 다시 문제가 생겼다.&lt;/p&gt;
&lt;h1&gt;또 다시 Kubernetes에서 GPU 사용하지 못하게 문제 해결&lt;/h1&gt;
&lt;p&gt;내 입장에서 docker를 계속 써야 하는 이유는 아래와 같다.&lt;/p&gt;
&lt;ol&gt;
&lt;li&gt;docker로 다 구축해 놓은거, container runtime을 containerd로 변경한다는게 두려워...&lt;/li&gt;
&lt;li&gt;아직도 여전히 docker 명령을 사용해서 이것저것 많이 해 보니깐... ( volume이라던지.. )&lt;/li&gt;
&lt;li&gt;Kubernetes에서 GPU 관련된 가이드 자체가 docker 기반으로 되어 있음...&lt;/li&gt;
&lt;/ol&gt;
&lt;p&gt;다행히 3번은 어느정도 해결 가능할지도 모르겠다. Kubernetes 홈페이지에는 GPU를 스케쥴링에 관한 내용이 docker에 대해서만 설명되고 있지만, 2021년 2월 1일 NVIDIA에서 &lt;code&gt;containerd&lt;/code&gt; 에서 &lt;code&gt;NVIDIA GPU Opertator&lt;/code&gt;을 지원한다고 공개 했다.&lt;br /&gt;관련 링크 : &lt;a href=&quot;https://developer.nvidia.com/blog/announcing-containerd-support-for-the-nvidia-gpu-operator/&quot;&gt;https://developer.nvidia.com/blog/announcing-containerd-support-for-the-nvidia-gpu-operator/&lt;/a&gt;&lt;/p&gt;
&lt;p&gt;&lt;figure class=&quot;imageblock alignCenter&quot; data-origin-width=&quot;0&quot; data-origin-height=&quot;0&quot; data-ke-mobilestyle=&quot;widthContent&quot;&gt;&lt;span data-url=&quot;https://blog.kakaocdn.net/dn/6oIgc/btqX0phStXk/oK1WeXvT2K7koI1FDKOER1/img.png&quot; data-phocus=&quot;https://blog.kakaocdn.net/dn/6oIgc/btqX0phStXk/oK1WeXvT2K7koI1FDKOER1/img.png&quot;&gt;&lt;img src=&quot;https://blog.kakaocdn.net/dn/6oIgc/btqX0phStXk/oK1WeXvT2K7koI1FDKOER1/img.png&quot; srcset=&quot;https://img1.daumcdn.net/thumb/R1280x0/?scode=mtistory2&amp;fname=https%3A%2F%2Fblog.kakaocdn.net%2Fdn%2F6oIgc%2FbtqX0phStXk%2FoK1WeXvT2K7koI1FDKOER1%2Fimg.png&quot; data-origin-width=&quot;0&quot; data-origin-height=&quot;0&quot; data-ke-mobilestyle=&quot;widthContent&quot; onerror=&quot;this.onerror=null; this.src='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png'; this.srcset='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png';&quot;/&gt;&lt;/span&gt;&lt;/figure&gt;
&lt;/p&gt;
&lt;p&gt;Helm으로 설치할 수 있도록 되어 있고, 설치를 하면, nvidia 라는 이름으로 runtime을 설정해서 사용할 수 있을듯 하다.&lt;/p&gt;
&lt;p&gt;&lt;figure class=&quot;imageblock alignCenter&quot; data-origin-width=&quot;0&quot; data-origin-height=&quot;0&quot; data-ke-mobilestyle=&quot;widthContent&quot;&gt;&lt;span data-url=&quot;https://blog.kakaocdn.net/dn/bbgOUQ/btqX0qHOLCs/kfsf9YZ7OB2OKpb4C22FN0/img.png&quot; data-phocus=&quot;https://blog.kakaocdn.net/dn/bbgOUQ/btqX0qHOLCs/kfsf9YZ7OB2OKpb4C22FN0/img.png&quot;&gt;&lt;img src=&quot;https://blog.kakaocdn.net/dn/bbgOUQ/btqX0qHOLCs/kfsf9YZ7OB2OKpb4C22FN0/img.png&quot; srcset=&quot;https://img1.daumcdn.net/thumb/R1280x0/?scode=mtistory2&amp;fname=https%3A%2F%2Fblog.kakaocdn.net%2Fdn%2FbbgOUQ%2FbtqX0qHOLCs%2Fkfsf9YZ7OB2OKpb4C22FN0%2Fimg.png&quot; data-origin-width=&quot;0&quot; data-origin-height=&quot;0&quot; data-ke-mobilestyle=&quot;widthContent&quot; onerror=&quot;this.onerror=null; this.src='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png'; this.srcset='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png';&quot;/&gt;&lt;/span&gt;&lt;/figure&gt;
&lt;/p&gt;
&lt;p&gt;하지만, 내가 알기론 NVIDIA GPU Operator는 기존에 GPU를 사용하기 위해서 설정된 형태인 OS 위에, NVIDIA Driver 위에, docker 위에, nvidia-docker를 이용해서 kubernetes를 운영하는것이 아니라, OS 위에 Kubernetes위에 바로 NVIDIA Driver 가 동작하는 방식이라, 이것 역시 기존의 환경을 완전히 엎어야 하는것이므로, 할일이 태산일듯....&lt;/p&gt;
&lt;p&gt;관련 링크 : &lt;a href=&quot;https://developer.nvidia.com/blog/nvidia-gpu-operator-simplifying-gpu-management-in-kubernetes/&quot; target=&quot;_blank&quot; rel=&quot;noopener&quot;&gt;developer.nvidia.com/blog/nvidia-gpu-operator-simplifying-gpu-management-in-kubernetes/&lt;/a&gt;&lt;/p&gt;
&lt;p&gt;&lt;figure class=&quot;imageblock alignCenter&quot; data-origin-width=&quot;0&quot; data-origin-height=&quot;0&quot; data-ke-mobilestyle=&quot;widthContent&quot;&gt;&lt;span data-url=&quot;https://blog.kakaocdn.net/dn/buefgv/btqX0pa74C5/zkJhMK6HwRN5PevOlRrV4k/img.png&quot; data-phocus=&quot;https://blog.kakaocdn.net/dn/buefgv/btqX0pa74C5/zkJhMK6HwRN5PevOlRrV4k/img.png&quot;&gt;&lt;img src=&quot;https://blog.kakaocdn.net/dn/buefgv/btqX0pa74C5/zkJhMK6HwRN5PevOlRrV4k/img.png&quot; srcset=&quot;https://img1.daumcdn.net/thumb/R1280x0/?scode=mtistory2&amp;fname=https%3A%2F%2Fblog.kakaocdn.net%2Fdn%2Fbuefgv%2FbtqX0pa74C5%2FzkJhMK6HwRN5PevOlRrV4k%2Fimg.png&quot; data-origin-width=&quot;0&quot; data-origin-height=&quot;0&quot; data-ke-mobilestyle=&quot;widthContent&quot; onerror=&quot;this.onerror=null; this.src='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png'; this.srcset='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png';&quot;/&gt;&lt;/span&gt;&lt;/figure&gt;
&lt;/p&gt;
&lt;p&gt;암튼, 나는 뒤엎는게 두려우니, 아무것도 바꾸지 않고 GPU를 사용하지 못하게 하고 싶다.&lt;/p&gt;
&lt;h2&gt;답은 CUDA 환경 변수에....&lt;/h2&gt;
&lt;p&gt;답은 간단한데 있었다.&lt;br /&gt;예전에, Docker에서 GPU 사용에 대한 제약을 걸고 싶을때 사용했던 옵션을 그대로 활용하면 된다.&lt;br /&gt;( 관련 링크 : &lt;a href=&quot;https://developer.nvidia.com/blog/cuda-pro-tip-control-gpu-visibility-cuda_visible_devices/&quot;&gt;https://developer.nvidia.com/blog/cuda-pro-tip-control-gpu-visibility-cuda_visible_devices/&lt;/a&gt; )&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;Container를 띄울때, GPU index를 같이 주면, 해당 index의 GPU만을 사용하도록 할 수 있었다.&lt;/li&gt;
&lt;li&gt;&lt;code&gt;CUDA_VISIBLE_DEVICES=0&lt;/code&gt; 으로 하면 0번 index의 GPU만 해당 컨테이너에서 사용가능&lt;/li&gt;
&lt;li&gt;&lt;code&gt;CUDA_VISIBLE_DEVICES=2,3&lt;/code&gt; 으로 하면 2번 index, 3번 index의 GPU(총 2개)를 해당 컨테이너에서 사용가능&lt;/li&gt;
&lt;li&gt;&lt;code&gt;CUDA_VISIBLE_DEVICES=&lt;/code&gt; 으로 하면, 해당 컨테이너에는 아무런 GPU가 할당되지 않음 ( CUDA api를 사용할 경우 &lt;code&gt;no CUDA-capable device is detected&lt;/code&gt; 출력됨 )&lt;/li&gt;
&lt;/ul&gt;
&lt;p&gt;&lt;figure class=&quot;imageblock alignCenter&quot; data-origin-width=&quot;0&quot; data-origin-height=&quot;0&quot; data-ke-mobilestyle=&quot;widthContent&quot;&gt;&lt;span data-url=&quot;https://blog.kakaocdn.net/dn/vPKxE/btqX6hpLvyY/WUAa8SuAZ6k2xdkCFeKw4k/img.png&quot; data-phocus=&quot;https://blog.kakaocdn.net/dn/vPKxE/btqX6hpLvyY/WUAa8SuAZ6k2xdkCFeKw4k/img.png&quot;&gt;&lt;img src=&quot;https://blog.kakaocdn.net/dn/vPKxE/btqX6hpLvyY/WUAa8SuAZ6k2xdkCFeKw4k/img.png&quot; srcset=&quot;https://img1.daumcdn.net/thumb/R1280x0/?scode=mtistory2&amp;fname=https%3A%2F%2Fblog.kakaocdn.net%2Fdn%2FvPKxE%2FbtqX6hpLvyY%2FWUAa8SuAZ6k2xdkCFeKw4k%2Fimg.png&quot; data-origin-width=&quot;0&quot; data-origin-height=&quot;0&quot; data-ke-mobilestyle=&quot;widthContent&quot; onerror=&quot;this.onerror=null; this.src='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png'; this.srcset='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png';&quot;/&gt;&lt;/span&gt;&lt;/figure&gt;
&lt;/p&gt;
&lt;p&gt;위 환경 변수를 주고, Container를 실행하면 된다. Container로 띄울 원래 이미지가 nvidia-smi등의 명령이 있으면 GPU 목록이 등장하기는 한다. 하지만, 실제로 CUDA를 사용하려고 할 때, cuda 라이브러리에서 에러가 발생해서 못 쓰게 된다. 즉, 실제 GPU가 잡히긴 하지만 CUDA 설정에 따라서 CUDA Library 단에서 GPU를 사용하지 못하게 막는것이다.&lt;/p&gt;
&lt;p&gt;&lt;figure class=&quot;imageblock alignCenter&quot; data-origin-width=&quot;0&quot; data-origin-height=&quot;0&quot; data-ke-mobilestyle=&quot;widthContent&quot;&gt;&lt;span data-url=&quot;https://blog.kakaocdn.net/dn/y4MeR/btqX318z9Pr/FVlhsIyeLKbS1cI6EsIYx0/img.png&quot; data-phocus=&quot;https://blog.kakaocdn.net/dn/y4MeR/btqX318z9Pr/FVlhsIyeLKbS1cI6EsIYx0/img.png&quot;&gt;&lt;img src=&quot;https://blog.kakaocdn.net/dn/y4MeR/btqX318z9Pr/FVlhsIyeLKbS1cI6EsIYx0/img.png&quot; srcset=&quot;https://img1.daumcdn.net/thumb/R1280x0/?scode=mtistory2&amp;fname=https%3A%2F%2Fblog.kakaocdn.net%2Fdn%2Fy4MeR%2FbtqX318z9Pr%2FFVlhsIyeLKbS1cI6EsIYx0%2Fimg.png&quot; data-origin-width=&quot;0&quot; data-origin-height=&quot;0&quot; data-ke-mobilestyle=&quot;widthContent&quot; onerror=&quot;this.onerror=null; this.src='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png'; this.srcset='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png';&quot;/&gt;&lt;/span&gt;&lt;/figure&gt;
&lt;/p&gt;
&lt;p&gt;GPU 디바이스까지 안 잡히게 하는건 아니라, 편법으로 CUDA Library 사용시 GPU를 사용하지 못하게 하는것이다. 어차피 &quot;GPU 자원을 사용하지 못하게 하기&quot;가 목적이니 엎(어)치나 메치나 GPU를 못사용하게 되었으니...&lt;br /&gt;다만, AI 엔진들 중, 시스템상에서 GPU Device 갯수만 보고 무조건 GPU가 있다고 가정하에 동작하게 했다면, 그 문제는 이 방법으로 해결할 수 없을 것이다.&lt;/p&gt;
&lt;p&gt;결론은, &lt;code&gt;처음부터 상상을 잘 해서 코드를 잘 만들자.&lt;/code&gt; 이다.&lt;/p&gt;</description>
      <category>공부/컴퓨터</category>
      <category>Container</category>
      <category>CUDA</category>
      <category>CUDA_VISIBLE_DEVICES</category>
      <category>docker</category>
      <category>k8s</category>
      <category>kubernetes</category>
      <category>nVidia</category>
      <category>Runtime</category>
      <category>RuntimeClass</category>
      <category>쿠버네티스</category>
      <author>찬</author>
      <guid isPermaLink="true">https://blog.ggaman.com/1021</guid>
      <comments>https://blog.ggaman.com/1021#entry1021comment</comments>
      <pubDate>Mon, 22 Feb 2021 02:33:37 +0900</pubDate>
    </item>
    <item>
      <title>[Kubernetes] k3s를 이용해 multi node 쿠버네티스 클러스터 구축하기</title>
      <link>https://blog.ggaman.com/1020</link>
      <description>&lt;p&gt;이전 글에서는 &lt;code&gt;k3s&lt;/code&gt;를 이용하여 쉽게 single node kubernetes cluster를 구축하는 법을 알아 보았다. ( &lt;a href=&quot;https://blog.ggaman.com/1018&quot;&gt;https://blog.ggaman.com/1018&lt;/a&gt; ) 이번 글에서는 k3s를 이용해서 multi node kubernetes cluster를 구축하는 법을 알아 보겠다. 즉, 컴퓨터 여러대를 묶어서 사용하겠다는것이다. 이전의 글을 보고 왔으면 크게 할 일이 없지만 몇가지 사소하게 설정이 필요한 부분이 있어서 이 글도 따로 작성하게 되었다.&lt;/p&gt;
&lt;p&gt;이전글에도 적어 두었지만, 쿠버네티스는 &lt;code&gt;master node&lt;/code&gt;와 &lt;code&gt;worker node&lt;/code&gt;로 구분되고, master node가 worker node를 조작한다고 설명했다. 그렇기 때문에 실제 서비스가 돌아가는곳은 worker node이므로, master node의 성능이 매우 좋을 필요는 없다. 이전 글에서는 single node kubernetes cluster를 구축했기 때문에 master node와 worker node가 같은 장비였으므로, 좀 좋은 스펙을 가진 VM Instance를 생성하여 구성해 보았다.&lt;/p&gt;
&lt;p&gt;이 글에서는 multi node cluster 를 구축하는것을 보여주려고 한다.&lt;/p&gt;
&lt;p&gt;우선 vultr에서 master용 VM Instance를 몇개 만들어 보자. &lt;a href=&quot;https://my.vultr.com/deploy/&quot;&gt;https://my.vultr.com/deploy/&lt;/a&gt; 에 접근한 뒤에, &lt;code&gt;Products -&amp;gt; Cloud Compute -&amp;gt; Asia -&amp;gt; Seoul -&amp;gt; Ubuntu 18.04&lt;/code&gt; 를 선택하자. $20 짜리 Instance 를 고른다. ( 2vCPU, 4GB, 3TB Traffic, 80GB SSD ) 시간당 가격은 50원 정도이다. vultr에 대한 사용법을 알고 싶다면 &lt;a href=&quot;https://blog.ggaman.com/1019&quot;&gt;https://blog.ggaman.com/1019&lt;/a&gt; 링크의 내용을 참고하면 된다.&lt;/p&gt;
&lt;p&gt;왜 $20짜리를 고르라고 하냐면, k3s 홈페이지에서 그렇게 안내하고 있기 때문이다. ^_^&lt;/p&gt;
&lt;p&gt;&lt;figure class=&quot;imageblock alignCenter&quot; data-origin-width=&quot;0&quot; data-origin-height=&quot;0&quot; data-ke-mobilestyle=&quot;widthContent&quot;&gt;&lt;span data-url=&quot;https://blog.kakaocdn.net/dn/bzEqDC/btqFT7GkQHQ/QTRghc7xHjQaXkbtdyWwT1/img.png&quot; data-phocus=&quot;https://blog.kakaocdn.net/dn/bzEqDC/btqFT7GkQHQ/QTRghc7xHjQaXkbtdyWwT1/img.png&quot; data-alt=&quot;https://rancher.com/docs/k3s/latest/en/installation/installation-requirements/&quot;&gt;&lt;img src=&quot;https://blog.kakaocdn.net/dn/bzEqDC/btqFT7GkQHQ/QTRghc7xHjQaXkbtdyWwT1/img.png&quot; srcset=&quot;https://img1.daumcdn.net/thumb/R1280x0/?scode=mtistory2&amp;fname=https%3A%2F%2Fblog.kakaocdn.net%2Fdn%2FbzEqDC%2FbtqFT7GkQHQ%2FQTRghc7xHjQaXkbtdyWwT1%2Fimg.png&quot; data-origin-width=&quot;0&quot; data-origin-height=&quot;0&quot; data-ke-mobilestyle=&quot;widthContent&quot; onerror=&quot;this.onerror=null; this.src='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png'; this.srcset='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png';&quot;/&gt;&lt;/span&gt;&lt;figcaption&gt;https://rancher.com/docs/k3s/latest/en/installation/installation-requirements/&lt;/figcaption&gt;
&lt;/figure&gt;
&lt;/p&gt;
&lt;p&gt;&lt;s&gt;multi node cluster는 &quot;안정성&quot;과 &quot;확장성&quot;을 위해서 사용하는데, &lt;code&gt;DB(etc, PostgreSQL, MySQL)&lt;/code&gt;를 따로 분리해서 설치하는것을 추천하고 있다. 하지만 우리는 &quot;간단하게&quot; 사용하기 위해서 설치하는것이므로, 여기에서 굳이 확인하지 않았다. 참고로, 아직 실험적인 기능을 사용하면 &lt;code&gt;DQLite&lt;/code&gt;를 사용해서 DB에 대한 고민을 덜 해도 될 듯 하다. 관심이 있다면 다음 링크를 이용해서 확인해 보도록 하자.&lt;/s&gt;&lt;/p&gt;
&lt;p&gt;&lt;figure class=&quot;imageblock alignCenter&quot; data-origin-width=&quot;0&quot; data-origin-height=&quot;0&quot; width=&quot;550&quot; data-ke-mobilestyle=&quot;widthContent&quot;&gt;&lt;span data-url=&quot;https://blog.kakaocdn.net/dn/cr57Er/btqFUSoqqCz/TKKX8T7KcECCoKp9FrfsM0/img.png&quot; data-phocus=&quot;https://blog.kakaocdn.net/dn/cr57Er/btqFUSoqqCz/TKKX8T7KcECCoKp9FrfsM0/img.png&quot; data-alt=&quot;Embedded DB 를 사용하면 굳이 DB를 따로 설치할 필요가 없다. 다만, 실험적인 기능인것을 알고 써야겠지?&quot;&gt;&lt;img src=&quot;https://blog.kakaocdn.net/dn/cr57Er/btqFUSoqqCz/TKKX8T7KcECCoKp9FrfsM0/img.png&quot; srcset=&quot;https://img1.daumcdn.net/thumb/R1280x0/?scode=mtistory2&amp;fname=https%3A%2F%2Fblog.kakaocdn.net%2Fdn%2Fcr57Er%2FbtqFUSoqqCz%2FTKKX8T7KcECCoKp9FrfsM0%2Fimg.png&quot; data-origin-width=&quot;0&quot; data-origin-height=&quot;0&quot; width=&quot;550&quot; data-ke-mobilestyle=&quot;widthContent&quot; onerror=&quot;this.onerror=null; this.src='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png'; this.srcset='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png';&quot;/&gt;&lt;/span&gt;&lt;figcaption&gt;Embedded DB 를 사용하면 굳이 DB를 따로 설치할 필요가 없다. 다만, 실험적인 기능인것을 알고 써야겠지?&lt;/figcaption&gt;
&lt;/figure&gt;
&lt;/p&gt;
&lt;p&gt;(2021년 2월 21일 추가 ) k3s&lt;span&gt;&amp;nbsp;&lt;/span&gt;&lt;span&gt;v1.19.5+k3s1 버젼부터는 embedded etcd를 정식 지원하도록 변경되었다. 그러므로 Dqlite를 사용하는 경우 업그레이드에 문제가 있을 수 있다.&lt;/span&gt;&lt;/p&gt;
&lt;p&gt;&lt;figure class=&quot;imageblock alignCenter&quot; data-origin-width=&quot;0&quot; data-origin-height=&quot;0&quot; data-ke-mobilestyle=&quot;widthContent&quot;&gt;&lt;span data-url=&quot;https://blog.kakaocdn.net/dn/bG3VRF/btqX82eLvZb/DRr7bH1kei2oJVQVEwxHwk/img.png&quot; data-phocus=&quot;https://blog.kakaocdn.net/dn/bG3VRF/btqX82eLvZb/DRr7bH1kei2oJVQVEwxHwk/img.png&quot;&gt;&lt;img src=&quot;https://blog.kakaocdn.net/dn/bG3VRF/btqX82eLvZb/DRr7bH1kei2oJVQVEwxHwk/img.png&quot; srcset=&quot;https://img1.daumcdn.net/thumb/R1280x0/?scode=mtistory2&amp;fname=https%3A%2F%2Fblog.kakaocdn.net%2Fdn%2FbG3VRF%2FbtqX82eLvZb%2FDRr7bH1kei2oJVQVEwxHwk%2Fimg.png&quot; data-origin-width=&quot;0&quot; data-origin-height=&quot;0&quot; data-ke-mobilestyle=&quot;widthContent&quot; onerror=&quot;this.onerror=null; this.src='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png'; this.srcset='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png';&quot;/&gt;&lt;/span&gt;&lt;/figure&gt;
&lt;/p&gt;
&lt;p&gt;(&lt;span&gt;&amp;nbsp;&lt;/span&gt;&lt;a href=&quot;https://rancher.com/docs/k3s/latest/en/installation/ha-embedded/&quot;&gt;https://rancher.com/docs/k3s/latest/en/installation/ha-embedded/&lt;/a&gt;&lt;span&gt;&amp;nbsp;&lt;/span&gt;)&lt;/p&gt;
&lt;p&gt;k3s에 대한 설명이 좀 길었으니, 이제 다시 vultr 화면으로 돌아 가자. master는 총 3대를 만들어야 한다. 중요한 것은 server의 hostname을 모두 다르게 설정해야 한다는것이다. 서버의 이름이 같으면 클러스터 구축이 제대로 되지 않는다. 반드시 서로 다른 이름을 주고 VM Instance 를 생성하도록 하자. 이렇게 3개의 인스턴스를 생성하면 1시간에 약 100원이 소모된다.&lt;/p&gt;
&lt;p&gt;&lt;figure class=&quot;imageblock alignCenter&quot; data-origin-width=&quot;0&quot; data-origin-height=&quot;0&quot; width=&quot;650&quot; data-ke-mobilestyle=&quot;widthContent&quot;&gt;&lt;span data-url=&quot;https://blog.kakaocdn.net/dn/PyZrr/btqFWYVs9B3/rFVL8rGkAM9TAy6bUcf1PK/img.png&quot; data-phocus=&quot;https://blog.kakaocdn.net/dn/PyZrr/btqFWYVs9B3/rFVL8rGkAM9TAy6bUcf1PK/img.png&quot; data-alt=&quot;hostname을 반드시 각자 다른 이름으로 지정해서 띄우도록 하자&quot;&gt;&lt;img src=&quot;https://blog.kakaocdn.net/dn/PyZrr/btqFWYVs9B3/rFVL8rGkAM9TAy6bUcf1PK/img.png&quot; srcset=&quot;https://img1.daumcdn.net/thumb/R1280x0/?scode=mtistory2&amp;fname=https%3A%2F%2Fblog.kakaocdn.net%2Fdn%2FPyZrr%2FbtqFWYVs9B3%2FrFVL8rGkAM9TAy6bUcf1PK%2Fimg.png&quot; data-origin-width=&quot;0&quot; data-origin-height=&quot;0&quot; width=&quot;650&quot; data-ke-mobilestyle=&quot;widthContent&quot; onerror=&quot;this.onerror=null; this.src='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png'; this.srcset='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png';&quot;/&gt;&lt;/span&gt;&lt;figcaption&gt;hostname을 반드시 각자 다른 이름으로 지정해서 띄우도록 하자&lt;/figcaption&gt;
&lt;/figure&gt;
&lt;/p&gt;
&lt;p&gt;이전 게시물에서 설명한 방법으로, IP, username, password 를 이용하여 ssh에 접속하도록 하자. ( &lt;a href=&quot;https://blog.ggaman.com/1018&quot;&gt;https://blog.ggaman.com/1018&lt;/a&gt; )&lt;/p&gt;
&lt;p&gt;k3s를 이용하여 쿠버네티스 클러스터를 설치하는것은 이전에 이야기 했다시피 하나의 명령어만 실행하면 끝난다. 우선 master-1 서버에 아래의 명령을 이용해서 k3s를 설치하도록 하자. 여기서는 실험적(experimental) 기능이긴 하지만 DQLite를 이용하는 방법을 써 보도록 하자.&lt;/p&gt;
&lt;pre class=&quot;vim&quot;&gt;&lt;code&gt;curl -sfL https://get.k3s.io | sh -s - --write-kubeconfig-mode 644 --cluster-init&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;당연히 별 문제 없이 실행되었을 것이다. 이제 kubectl 명령을 통해서 몇개의 node가 kubernetes cluster로 묶여 있는지 확인해 보자. 당연히 1대의 컴퓨터에서만 k3s를 설치 했으므로, 1대만 출력 될 것이다.&lt;/p&gt;
&lt;p&gt;&lt;figure class=&quot;imageblock alignCenter&quot; data-origin-width=&quot;0&quot; data-origin-height=&quot;0&quot; data-ke-mobilestyle=&quot;widthContent&quot;&gt;&lt;span data-url=&quot;https://blog.kakaocdn.net/dn/pym2d/btqFWcNsJk3/b0ukBpdQMqjqwenJd4kFMk/img.png&quot; data-phocus=&quot;https://blog.kakaocdn.net/dn/pym2d/btqFWcNsJk3/b0ukBpdQMqjqwenJd4kFMk/img.png&quot; data-alt=&quot;아직 1대의 node만 연결된 cluster&quot;&gt;&lt;img src=&quot;https://blog.kakaocdn.net/dn/pym2d/btqFWcNsJk3/b0ukBpdQMqjqwenJd4kFMk/img.png&quot; srcset=&quot;https://img1.daumcdn.net/thumb/R1280x0/?scode=mtistory2&amp;fname=https%3A%2F%2Fblog.kakaocdn.net%2Fdn%2Fpym2d%2FbtqFWcNsJk3%2Fb0ukBpdQMqjqwenJd4kFMk%2Fimg.png&quot; data-origin-width=&quot;0&quot; data-origin-height=&quot;0&quot; data-ke-mobilestyle=&quot;widthContent&quot; onerror=&quot;this.onerror=null; this.src='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png'; this.srcset='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png';&quot;/&gt;&lt;/span&gt;&lt;figcaption&gt;아직 1대의 node만 연결된 cluster&lt;/figcaption&gt;
&lt;/figure&gt;
&lt;/p&gt;
&lt;p&gt;우리의 목적은 총 3대의 master node를 만드는것이므로, 나머지 2대를 master node로 만들어 보도록 하자. k3s를 설치하게 되면 기본적으로 master node로 설치된다. 그리고 나머지 2대를 처음 설치만 master node에 연결하는 형태로 master node를 늘릴 수 있게 된다. &lt;code&gt;master-1&lt;/code&gt; 서버에서 아래의 명령을 이용해서 TOKEN 값을 얻자. TOKEN은 &lt;code&gt;/var/lib/rancher/k3s/server/node-token&lt;/code&gt; 위치에 있다.&lt;/p&gt;
&lt;pre class=&quot;crystal&quot;&gt;&lt;code&gt;cat /var/lib/rancher/k3s/server/node-token&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;이제 &lt;code&gt;master-2&lt;/code&gt;와 &lt;code&gt;master-3&lt;/code&gt;의 서버에 ssh를 접속하여 아래의 명령을 실행하면 master node들이 늘어나게 된다. MASTER-1-IP 에 &lt;code&gt;master-1&lt;/code&gt;의 ip를, NODE_TOKEN은 위 명령을 이용해서 얻은 값을 적어주면 된다. ( &lt;code&gt;master-1&lt;/code&gt; 에서 설치할 때는 &lt;code&gt;--cluster-init&lt;/code&gt; 을 넣어 줬지만, 나머지 2대의 서버에 실행시킬 아래 명령에는 그 값이 없다는것에 주의해야 한다. )&lt;/p&gt;
&lt;pre class=&quot;vim&quot;&gt;&lt;code&gt;curl -sfL https://get.k3s.io | sh -s - --write-kubeconfig-mode 644 --server https://MASTER-1-IP:6443 --token NODE_TOKEN&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;내 환경에서는 아래와 같이 입력하였다.&lt;/p&gt;
&lt;pre class=&quot;llvm&quot;&gt;&lt;code&gt;curl -sfL https://get.k3s.io | sh -s - --write-kubeconfig-mode 644 --server https://158.247.192.125:6443 --token K1081d886e341e3172b0c9f14e920652df428c0c0c5ba00fa040661c3a2018443a2::server:8883b89d6fdd7d48f7ab0b29c6479177&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;&lt;code&gt;master-2&lt;/code&gt;와 &lt;code&gt;master-3&lt;/code&gt;에서 위 명령을 설치한 이후에 다시 &lt;code&gt;master-1&lt;/code&gt;에 와서 &lt;code&gt;kubecl get nodes&lt;/code&gt; 명령을 입력해 master node가 몇개로 변했는지 확인해 보자.&lt;/p&gt;
&lt;p&gt;&lt;figure class=&quot;imageblock alignCenter&quot; data-origin-width=&quot;0&quot; data-origin-height=&quot;0&quot; width=&quot;360&quot; data-ke-mobilestyle=&quot;widthContent&quot;&gt;&lt;span data-url=&quot;https://blog.kakaocdn.net/dn/HRs5U/btqFTTheN4y/m34EAoxsXAiTK8Va8kSvCK/img.png&quot; data-phocus=&quot;https://blog.kakaocdn.net/dn/HRs5U/btqFTTheN4y/m34EAoxsXAiTK8Va8kSvCK/img.png&quot; data-alt=&quot;master가 3개로 변했다!!!!&quot;&gt;&lt;img src=&quot;https://blog.kakaocdn.net/dn/HRs5U/btqFTTheN4y/m34EAoxsXAiTK8Va8kSvCK/img.png&quot; srcset=&quot;https://img1.daumcdn.net/thumb/R1280x0/?scode=mtistory2&amp;fname=https%3A%2F%2Fblog.kakaocdn.net%2Fdn%2FHRs5U%2FbtqFTTheN4y%2Fm34EAoxsXAiTK8Va8kSvCK%2Fimg.png&quot; data-origin-width=&quot;0&quot; data-origin-height=&quot;0&quot; width=&quot;360&quot; data-ke-mobilestyle=&quot;widthContent&quot; onerror=&quot;this.onerror=null; this.src='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png'; this.srcset='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png';&quot;/&gt;&lt;/span&gt;&lt;figcaption&gt;master가 3개로 변했다!!!!&lt;/figcaption&gt;
&lt;/figure&gt;
&lt;/p&gt;
&lt;p&gt;위 과정을 거쳐 master가 총 3개가 생성되었다. master를 3개나 만들어 두었으니, 한대쯤은 죽어도 kubernetes 가 잘 운영 될 것이다.&lt;/p&gt;
&lt;p&gt;&lt;code&gt;master&lt;/code&gt;를 여러개 만들었으니, 이제 &lt;code&gt;worker&lt;/code&gt; 들도 여러개를 등록해 보자. &lt;code&gt;k3s&lt;/code&gt;에서는 &lt;code&gt;worker&lt;/code&gt;를 &lt;code&gt;agent&lt;/code&gt; 라는 이름으로 부른다. &lt;code&gt;agent&lt;/code&gt;를 등록하는 방법도 매우 간단하다. agent를 위한 VM Instance를 더 만들도록 하자. 이 때도 각 VM의 이름을 다르게 줘야 한다는것을 잊지 말자. 이번에는 VM Instance를 생성할때, $40 ( 4 vCPU, 8GB ) 4개를 생성하겠다. 1시간에 약 300원이 소모된다. worker에 더 많은 서비스들이 동작할 것이기 때문에 높게 잡아서 진행하는것이다. 만약 테스트를 위해서라면 $20 짜리를 4개를 생성해도 된다. 약 150원 정도 소모 된다. 그것도 부담되면 2개만 생성해도 된다. ^^ 중요한것은 이름을 다르게 줘야 한다는것이다.&lt;/p&gt;
&lt;p&gt;&lt;figure class=&quot;imageblock alignCenter&quot; data-origin-width=&quot;0&quot; data-origin-height=&quot;0&quot; width=&quot;650&quot; data-ke-mobilestyle=&quot;widthContent&quot;&gt;&lt;span data-url=&quot;https://blog.kakaocdn.net/dn/etEgJo/btqFXISb10g/MNGvNGso39lCnYWVqnNznK/img.png&quot; data-phocus=&quot;https://blog.kakaocdn.net/dn/etEgJo/btqFXISb10g/MNGvNGso39lCnYWVqnNznK/img.png&quot; data-alt=&quot;worker도 이름을 다르게 주자&quot;&gt;&lt;img src=&quot;https://blog.kakaocdn.net/dn/etEgJo/btqFXISb10g/MNGvNGso39lCnYWVqnNznK/img.png&quot; srcset=&quot;https://img1.daumcdn.net/thumb/R1280x0/?scode=mtistory2&amp;fname=https%3A%2F%2Fblog.kakaocdn.net%2Fdn%2FetEgJo%2FbtqFXISb10g%2FMNGvNGso39lCnYWVqnNznK%2Fimg.png&quot; data-origin-width=&quot;0&quot; data-origin-height=&quot;0&quot; width=&quot;650&quot; data-ke-mobilestyle=&quot;widthContent&quot; onerror=&quot;this.onerror=null; this.src='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png'; this.srcset='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png';&quot;/&gt;&lt;/span&gt;&lt;figcaption&gt;worker도 이름을 다르게 주자&lt;/figcaption&gt;
&lt;/figure&gt;
&lt;/p&gt;
&lt;p&gt;worker를 모두 생성하고 나면, &lt;code&gt;worker-1&lt;/code&gt;에서 &lt;code&gt;worker-4&lt;/code&gt;까지 모든 worker에 ssh로 접속하여 아래의 명령을 날려 주도록 하자. 아까 &lt;code&gt;master-2&lt;/code&gt;, &lt;code&gt;master-3&lt;/code&gt; 에 날려준 형태와 비슷하긴 한데, 옵션이 아니라 환경변수로 &lt;code&gt;master-1&lt;/code&gt;의 주소와, &lt;code&gt;master-1&lt;/code&gt;의 TOKEN 정보를 전달하는 형식이다.&lt;/p&gt;
&lt;pre class=&quot;llvm&quot;&gt;&lt;code&gt;curl -sfL https://get.k3s.io | K3S_URL=https://158.247.192.125:6443 K3S_TOKEN=K1081d886e341e3172b0c9f14e920652df428c0c0c5ba00fa040661c3a2018443a2::server:8883b89d6fdd7d48f7ab0b29c6479177 sh -&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;&lt;figure class=&quot;imageblock alignCenter&quot; data-origin-width=&quot;0&quot; data-origin-height=&quot;0&quot; data-ke-mobilestyle=&quot;widthContent&quot;&gt;&lt;span data-url=&quot;https://blog.kakaocdn.net/dn/SeZ93/btqFXJDzlQ6/KHlnjMCl3xUtPk6cPaOrWK/img.png&quot; data-phocus=&quot;https://blog.kakaocdn.net/dn/SeZ93/btqFXJDzlQ6/KHlnjMCl3xUtPk6cPaOrWK/img.png&quot; data-alt=&quot;agent를 설치하고 나면 제일 마지막에 k3s-agent 가 잘 실행되었다고 출력 된다.&quot;&gt;&lt;img src=&quot;https://blog.kakaocdn.net/dn/SeZ93/btqFXJDzlQ6/KHlnjMCl3xUtPk6cPaOrWK/img.png&quot; srcset=&quot;https://img1.daumcdn.net/thumb/R1280x0/?scode=mtistory2&amp;fname=https%3A%2F%2Fblog.kakaocdn.net%2Fdn%2FSeZ93%2FbtqFXJDzlQ6%2FKHlnjMCl3xUtPk6cPaOrWK%2Fimg.png&quot; data-origin-width=&quot;0&quot; data-origin-height=&quot;0&quot; data-ke-mobilestyle=&quot;widthContent&quot; onerror=&quot;this.onerror=null; this.src='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png'; this.srcset='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png';&quot;/&gt;&lt;/span&gt;&lt;figcaption&gt;agent를 설치하고 나면 제일 마지막에 k3s-agent 가 잘 실행되었다고 출력 된다.&lt;/figcaption&gt;
&lt;/figure&gt;
&lt;/p&gt;
&lt;p&gt;모든 worker에서 위 명령을 실행 뒤, master-1의 &lt;code&gt;kubectl get nodes&lt;/code&gt; 명령을 실행해 몇개의 node가 cluster로 묶였는지 확인해 보자. ( 반드시 master-1에서 실행할 필요는 없다. master 중 아무데서나 실행해도 된다. )&lt;/p&gt;
&lt;p&gt;&lt;figure class=&quot;imageblock alignCenter&quot; data-origin-width=&quot;0&quot; data-origin-height=&quot;0&quot; data-ke-mobilestyle=&quot;widthContent&quot;&gt;&lt;span data-url=&quot;https://blog.kakaocdn.net/dn/P6Ug1/btqFTT9qzqv/rAgWRJ4EThkugwfFsyrAO1/img.png&quot; data-phocus=&quot;https://blog.kakaocdn.net/dn/P6Ug1/btqFTT9qzqv/rAgWRJ4EThkugwfFsyrAO1/img.png&quot; data-alt=&quot;기존 master 3개에, worker 4개가 추가 되었다.&quot;&gt;&lt;img src=&quot;https://blog.kakaocdn.net/dn/P6Ug1/btqFTT9qzqv/rAgWRJ4EThkugwfFsyrAO1/img.png&quot; srcset=&quot;https://img1.daumcdn.net/thumb/R1280x0/?scode=mtistory2&amp;fname=https%3A%2F%2Fblog.kakaocdn.net%2Fdn%2FP6Ug1%2FbtqFTT9qzqv%2FrAgWRJ4EThkugwfFsyrAO1%2Fimg.png&quot; data-origin-width=&quot;0&quot; data-origin-height=&quot;0&quot; data-ke-mobilestyle=&quot;widthContent&quot; onerror=&quot;this.onerror=null; this.src='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png'; this.srcset='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png';&quot;/&gt;&lt;/span&gt;&lt;figcaption&gt;기존 master 3개에, worker 4개가 추가 되었다.&lt;/figcaption&gt;
&lt;/figure&gt;
&lt;/p&gt;
&lt;p&gt;이번에도 마찬가지로 주저리 설명이 길었지만, 실제로 worker에서 실행해야 하는 명령은 아래와 비슷한 단 한줄의 명령이다.&lt;/p&gt;
&lt;pre class=&quot;vim&quot;&gt;&lt;code&gt;curl -sfL https://get.k3s.io | K3S_URL=https://$(MASTER-1-IP):6443 K3S_TOKEN=$(MASTER-1-TOKEN) sh -&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;이로써 k3s를 이용해서 multi node cluster를 구성하는것까지 해 보았다. 이렇게 k3s는 kubernetes를 쉽게 설치하여 사용할 수 있는 방법을 제공한다. 심지어 multi node cluster 역시 쉽게 구성할 수 있도록 제공해 준다.&lt;/p&gt;
&lt;p&gt;이렇게 구성된 kubernetes cluster를 편리하게 관리할 수 있게 해 주는 Rancher라는 서비스도 있다. 다만 Rancher를 사용하려면 kubernetes 에 대하여 어느 정도는 알고 있어야 한다. 그렇기 때문에 Rancher를 사용하기 전에 Kubernetes를 사용하는것에 대해서 공부를 해야 한다. 편리하게 해 준다는거지, 모르는데도 잘 동작하게 해 주지는 않는다.&amp;nbsp;&lt;/p&gt;
&lt;p&gt;이 블로그에서 kubernetes 자료를 만들어 업데이트 할지는 모르겠다. 설명해야 할 게 너무 많고, 기반 지식도 어느정도는 필요하기 때문에 되도록이면, 책을 구매해서 보는것을 추천한다. 사실 내가 잘 모른다. ㅎㅎ. 혹시 docker 라는것도 잘 모른다면, docker와 kubernetes를 동시에 다루는 책도 있으니, 그런 책을 사서 보는것도 좋을 것이다. 보통 책에서는 minikube 로 kubernetes 환경을 만들어서 설명한다. 그 때 minikube가 아니라, k3s를 이용하여 구축해 보면 더 좋은 경험을 해 볼 수 있을것이라 생각한다.&lt;/p&gt;</description>
      <category>공부/컴퓨터</category>
      <category>Agent</category>
      <category>k3s</category>
      <category>k8s</category>
      <category>kubernetes</category>
      <category>MASTER</category>
      <category>server</category>
      <category>VULTR</category>
      <category>worker</category>
      <category>벌쳐</category>
      <category>쿠버네티스</category>
      <author>찬</author>
      <guid isPermaLink="true">https://blog.ggaman.com/1020</guid>
      <comments>https://blog.ggaman.com/1020#entry1020comment</comments>
      <pubDate>Thu, 23 Jul 2020 02:58:15 +0900</pubDate>
    </item>
    <item>
      <title>[Kubernetes] k3s를 이용해 single node 쿠버네티스 클러스터 구축하기</title>
      <link>https://blog.ggaman.com/1018</link>
      <description>&lt;p&gt;업무 관련으로 Kubernetes를 사용하려고 준비중이다. 하지만 쿠버네티스를 &quot;사용&quot;하는 것과 &quot;설치&quot;하는것은 많은 차이가 있다. 사용하면서 알아야 할 개념이 60이라면, 설치하면서 알아야 할 개념은 80, 운영까지 포함해야 100이 된다고 생각한다. 즉, 단순히 사용만 할 것인데, 나머지 40까지의 개념을 알아야 할까?&lt;/p&gt;
&lt;p&gt;그래서 Kubernetes 쪽 세상에서는 단순하게 사용할 수 있도록 하기 위해서 여러 쉬운 도구를 제공한다. 그 중에서 유명한게 &lt;code&gt;minikube&lt;/code&gt;, &lt;code&gt;k3d&lt;/code&gt;, &lt;code&gt;kind&lt;/code&gt;, &lt;code&gt;k3s&lt;/code&gt; 등이 있다. 4 개를 대충 사용해 봤고, 처음에는 &lt;code&gt;k3d&lt;/code&gt;로 클러스터를 구축해 봤었다. &lt;code&gt;k3d&lt;/code&gt;는 docker container에 &lt;code&gt;k3s&lt;/code&gt;가 설치되어 kubernetes를 구축하는 형태이다. 그러므로 반드시 docker를 따로 설치해야 했다. 그리고 가장 문제가 되었던것은 설명서가 너무 간단하게 되어 있어서, 무언가 궁금한것을 찾아 보려고 해도 찾을 방법이 없었다. 또한 테스트 해 봤던 시기에는 공식 홈페이지( &lt;a href=&quot;https://k3d.io/&quot;&gt;https://k3d.io/&lt;/a&gt; )의 설명은 3.x 설명이었는데, release된 버젼은 2.x 대였다. 오늘 확인해 보니 저번주에 3.0 이 release가 되었네?? 그래도 관심이 있는 사람이라면, k3d 홈페이지를 방문해서 확인해 보는것도 나쁘지는 않겠다.&lt;/p&gt;
&lt;p&gt;&lt;figure class=&quot;imageblock alignCenter&quot; data-origin-width=&quot;0&quot; data-origin-height=&quot;0&quot; data-ke-mobilestyle=&quot;widthContent&quot;&gt;&lt;span data-url=&quot;https://blog.kakaocdn.net/dn/osW8A/btqFXg2IG4Q/xNWXtITEmh6eC3FCqIdY70/img.png&quot; data-phocus=&quot;https://blog.kakaocdn.net/dn/osW8A/btqFXg2IG4Q/xNWXtITEmh6eC3FCqIdY70/img.png&quot;&gt;&lt;img src=&quot;https://blog.kakaocdn.net/dn/osW8A/btqFXg2IG4Q/xNWXtITEmh6eC3FCqIdY70/img.png&quot; srcset=&quot;https://img1.daumcdn.net/thumb/R1280x0/?scode=mtistory2&amp;fname=https%3A%2F%2Fblog.kakaocdn.net%2Fdn%2FosW8A%2FbtqFXg2IG4Q%2FxNWXtITEmh6eC3FCqIdY70%2Fimg.png&quot; data-origin-width=&quot;0&quot; data-origin-height=&quot;0&quot; data-ke-mobilestyle=&quot;widthContent&quot; onerror=&quot;this.onerror=null; this.src='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png'; this.srcset='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png';&quot;/&gt;&lt;/span&gt;&lt;/figure&gt;
&lt;/p&gt;
&lt;p&gt;&lt;code&gt;k3d&lt;/code&gt;를 찾아서 조사를 하다가, 원래 대상으로 생각하지 않았던 &lt;code&gt;k3s&lt;/code&gt;를 조사하게 되었고 설치 및 구성이 편하기 때문에 이 글에서는 &lt;code&gt;k3s&lt;/code&gt;를 이용하여 single node cluster(컴퓨터 1대)를 구축하거나, 혹은 multi node cluster(컴퓨터 여러대)를 구축하는 법에 대해서 적어 보도록 하겠다. ( 쿠버네티스쪽 세상에서 &quot;node&quot;라고 이야기 하는것들은 그냥 컴퓨터라고 생각하면 된다. )&lt;/p&gt;
&lt;p&gt;&lt;figure class=&quot;imageblock alignCenter&quot; data-origin-width=&quot;0&quot; data-origin-height=&quot;0&quot; data-ke-mobilestyle=&quot;widthContent&quot;&gt;&lt;span data-url=&quot;https://blog.kakaocdn.net/dn/oj2Mj/btqFUS25zxa/rd3KAXtNtOKLIPrF9NrurK/img.png&quot; data-phocus=&quot;https://blog.kakaocdn.net/dn/oj2Mj/btqFUS25zxa/rd3KAXtNtOKLIPrF9NrurK/img.png&quot; data-alt=&quot;k3s - 가벼운 쿠버네티스&quot;&gt;&lt;img src=&quot;https://blog.kakaocdn.net/dn/oj2Mj/btqFUS25zxa/rd3KAXtNtOKLIPrF9NrurK/img.png&quot; srcset=&quot;https://img1.daumcdn.net/thumb/R1280x0/?scode=mtistory2&amp;fname=https%3A%2F%2Fblog.kakaocdn.net%2Fdn%2Foj2Mj%2FbtqFUS25zxa%2Frd3KAXtNtOKLIPrF9NrurK%2Fimg.png&quot; data-origin-width=&quot;0&quot; data-origin-height=&quot;0&quot; data-ke-mobilestyle=&quot;widthContent&quot; onerror=&quot;this.onerror=null; this.src='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png'; this.srcset='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png';&quot;/&gt;&lt;/span&gt;&lt;figcaption&gt;k3s - 가벼운 쿠버네티스&lt;/figcaption&gt;
&lt;/figure&gt;
&lt;/p&gt;
&lt;p&gt;&lt;code&gt;k3s&lt;/code&gt;는 Rancher라는 회사에서 쿠버네티스를 경량화 시켜 둔 것이다. ( &lt;a href=&quot;https://rancher.com/docs/k3s/latest/en/&quot;&gt;https://rancher.com/docs/k3s/latest/en/&lt;/a&gt; ) 쿠버네티스를 영어로 쓰면 kubernetes 라고 쓰는데, 이걸 일일이 적기 힘드니깐 k와 s사이에 8글자가 있다고 해서 &lt;code&gt;k8s&lt;/code&gt; 라고 줄여서 적는다. 그렇다면 &lt;code&gt;k3s&lt;/code&gt;는 뭐냐? &lt;code&gt;k8s&lt;/code&gt; 보다 가볍게 만들었다고 &lt;code&gt;k3s&lt;/code&gt; 라는 이름을 지어 둔 것이다.&amp;nbsp;&lt;/p&gt;
&lt;p&gt;&lt;figure class=&quot;imageblock alignCenter&quot; data-origin-width=&quot;0&quot; data-origin-height=&quot;0&quot; data-ke-mobilestyle=&quot;widthContent&quot;&gt;&lt;span data-url=&quot;https://blog.kakaocdn.net/dn/m0J4Y/btqFVhOU4qU/MDPUBAhAnQf52m8yHhqmA0/img.png&quot; data-phocus=&quot;https://blog.kakaocdn.net/dn/m0J4Y/btqFVhOU4qU/MDPUBAhAnQf52m8yHhqmA0/img.png&quot;&gt;&lt;img src=&quot;https://blog.kakaocdn.net/dn/m0J4Y/btqFVhOU4qU/MDPUBAhAnQf52m8yHhqmA0/img.png&quot; srcset=&quot;https://img1.daumcdn.net/thumb/R1280x0/?scode=mtistory2&amp;fname=https%3A%2F%2Fblog.kakaocdn.net%2Fdn%2Fm0J4Y%2FbtqFVhOU4qU%2FMDPUBAhAnQf52m8yHhqmA0%2Fimg.png&quot; data-origin-width=&quot;0&quot; data-origin-height=&quot;0&quot; data-ke-mobilestyle=&quot;widthContent&quot; onerror=&quot;this.onerror=null; this.src='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png'; this.srcset='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png';&quot;/&gt;&lt;/span&gt;&lt;/figure&gt;
&lt;/p&gt;
&lt;p&gt;&lt;span style=&quot;color: #333333;&quot;&gt;어떻게 읽어야 하는지도 가이드를 해 주지 않았다. 그래서 나는 '케이쓰리에스'라고 읽고 있다. 우리나라에서는 이걸 보통 어떻게 읽어야 하는지 알면 좀 알려 주세요. ㅎㅎ.&lt;/span&gt;&lt;/p&gt;
&lt;p&gt;&lt;figure class=&quot;imageblock alignCenter&quot; data-origin-width=&quot;0&quot; data-origin-height=&quot;0&quot; data-ke-mobilestyle=&quot;widthContent&quot;&gt;&lt;span data-url=&quot;https://blog.kakaocdn.net/dn/bKU1pn/btqFTTuLnFl/h8qKrnh9lnoeve9JYtJMo1/img.png&quot; data-phocus=&quot;https://blog.kakaocdn.net/dn/bKU1pn/btqFTTuLnFl/h8qKrnh9lnoeve9JYtJMo1/img.png&quot; data-alt=&quot;https://github.com/rancher/k3s - 본인들도 공식 발음을 안 정했다는....&quot;&gt;&lt;img src=&quot;https://blog.kakaocdn.net/dn/bKU1pn/btqFTTuLnFl/h8qKrnh9lnoeve9JYtJMo1/img.png&quot; srcset=&quot;https://img1.daumcdn.net/thumb/R1280x0/?scode=mtistory2&amp;fname=https%3A%2F%2Fblog.kakaocdn.net%2Fdn%2FbKU1pn%2FbtqFTTuLnFl%2Fh8qKrnh9lnoeve9JYtJMo1%2Fimg.png&quot; data-origin-width=&quot;0&quot; data-origin-height=&quot;0&quot; data-ke-mobilestyle=&quot;widthContent&quot; onerror=&quot;this.onerror=null; this.src='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png'; this.srcset='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png';&quot;/&gt;&lt;/span&gt;&lt;figcaption&gt;https://github.com/rancher/k3s - 본인들도 공식 발음을 안 정했다는....&lt;/figcaption&gt;
&lt;/figure&gt;
&lt;/p&gt;
&lt;p&gt;Kubernetes에는 수 많은 기능들이 포함되어 있고, 또한 수많은 플러그인들이 따로 존재한다. 하지만, &lt;code&gt;k3s&lt;/code&gt;는 이를 단순화 시켜, 잘 사용하지 않는 기능들이나, 각종 실험적인 기능들을 제거 했고, 또한 몇가지 꼭 필요한 플러그인들을 기본적으로 포함시켜서 배포하고 있기 때문에, 단순히 설치하는것만으로도 어렵지 않게 쿠버네티스를 운영해 볼 수 있도록 해 두었다. &lt;code&gt;k3s&lt;/code&gt; 를 선택해 소개하는것도 이 때문이다.&lt;/p&gt;
&lt;p&gt;우선 간단하게 쿠버네티스 클러스터의 구조를 이야기 하고, 거기에 맞춰서 쿠버네티스 클러스터를 구축해 보는법에 대해서 알아 보자. 쿠버네티스는 관리를 위한 &lt;code&gt;master&lt;/code&gt;와 실제 서비스가 동작하는 &lt;code&gt;worker&lt;/code&gt;로 이루어진다. ( 더 자세히는 &lt;code&gt;etcd&lt;/code&gt;라던지, &lt;code&gt;api-server&lt;/code&gt;라던지 하는것들도 있는데 굳이 여기에서는 설명하지는 않는다. ) 당연히 single node cluster(컴퓨터 1대로 만드는 클러스터)의 경우, 한 대의 컴퓨터가 master와 worker의 역할을 모두 하는것이고, multi node cluster(컴퓨터를 여러개 묶어서 만드는 클러스터)의 경우, master와 worker가 분리되어 있는 형태다. master가 worker를 조종하는 역할이므로, master가 1대만 있고, 그 1대가 죽어버리면 쿠버네티스 클러스터 전체가 동작하지 않을테니 보통 3대를 둔다.&lt;/p&gt;
&lt;p&gt;&lt;figure class=&quot;imageblock alignCenter&quot; data-origin-width=&quot;0&quot; data-origin-height=&quot;0&quot; width=&quot;600&quot; data-ke-mobilestyle=&quot;widthContent&quot;&gt;&lt;span data-url=&quot;https://blog.kakaocdn.net/dn/cRsIKF/btqFUS21fiD/NrSFkY6Fxj6D1W4PkODzwk/img.png&quot; data-phocus=&quot;https://blog.kakaocdn.net/dn/cRsIKF/btqFUS21fiD/NrSFkY6Fxj6D1W4PkODzwk/img.png&quot; data-alt=&quot;https://rancher.com/learning-paths/introduction-to-kubernetes-architecture/&quot;&gt;&lt;img src=&quot;https://blog.kakaocdn.net/dn/cRsIKF/btqFUS21fiD/NrSFkY6Fxj6D1W4PkODzwk/img.png&quot; srcset=&quot;https://img1.daumcdn.net/thumb/R1280x0/?scode=mtistory2&amp;fname=https%3A%2F%2Fblog.kakaocdn.net%2Fdn%2FcRsIKF%2FbtqFUS21fiD%2FNrSFkY6Fxj6D1W4PkODzwk%2Fimg.png&quot; data-origin-width=&quot;0&quot; data-origin-height=&quot;0&quot; width=&quot;600&quot; data-ke-mobilestyle=&quot;widthContent&quot; onerror=&quot;this.onerror=null; this.src='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png'; this.srcset='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png';&quot;/&gt;&lt;/span&gt;&lt;figcaption&gt;https://rancher.com/learning-paths/introduction-to-kubernetes-architecture/&lt;/figcaption&gt;
&lt;/figure&gt;
&lt;/p&gt;
&lt;p&gt;우선 single node cluster를 구축하는 방법을 알아 보자.&lt;/p&gt;
&lt;p&gt;여기서는 vultr에서 VM Instance를 만들어서 테스트 하는 것을 설명하겠지만, 자신의 Ubuntu 서버가 있다면 그곳에 그냥 설치해도 된다. vultr에서 VM Instance 를 생성하는 방법은 이미 작성한 글이 있으니 그곳을 참고하도록 하자. ( &lt;a href=&quot;https://blog.ggaman.com/1019&quot;&gt;https://blog.ggaman.com/1019&lt;/a&gt; ) 링크가 클릭하기 귀찮을 수도 있으니 이곳에서도 대충 기록하도록 하겠다.&lt;/p&gt;
&lt;p&gt;Vultr ( &lt;a href=&quot;https://my.vultr.com/&quot;&gt;https://my.vultr.com/&lt;/a&gt; ) 에 로그인 한 뒤, &lt;code&gt;Products -&amp;gt; Deploy Instance&lt;/code&gt; 를 눌러 새로운 인스턴스를 생성하는 화면을 띄우자.&lt;/p&gt;
&lt;p&gt;&lt;figure class=&quot;imageblock alignCenter&quot; data-origin-width=&quot;0&quot; data-origin-height=&quot;0&quot; data-ke-mobilestyle=&quot;widthContent&quot;&gt;&lt;span data-url=&quot;https://blog.kakaocdn.net/dn/D550Z/btqFS0gyOK2/Pss29o2TFBrVhSMOGKAXY1/img.png&quot; data-phocus=&quot;https://blog.kakaocdn.net/dn/D550Z/btqFS0gyOK2/Pss29o2TFBrVhSMOGKAXY1/img.png&quot; data-alt=&quot;VM Instance를 만들기 위해서 Products -&amp;amp;amp;gt; Deploy Instance 를 누르자&quot;&gt;&lt;img src=&quot;https://blog.kakaocdn.net/dn/D550Z/btqFS0gyOK2/Pss29o2TFBrVhSMOGKAXY1/img.png&quot; srcset=&quot;https://img1.daumcdn.net/thumb/R1280x0/?scode=mtistory2&amp;fname=https%3A%2F%2Fblog.kakaocdn.net%2Fdn%2FD550Z%2FbtqFS0gyOK2%2FPss29o2TFBrVhSMOGKAXY1%2Fimg.png&quot; data-origin-width=&quot;0&quot; data-origin-height=&quot;0&quot; data-ke-mobilestyle=&quot;widthContent&quot; onerror=&quot;this.onerror=null; this.src='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png'; this.srcset='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png';&quot;/&gt;&lt;/span&gt;&lt;figcaption&gt;VM Instance를 만들기 위해서 Products -&amp;gt; Deploy Instance 를 누르자&lt;/figcaption&gt;
&lt;/figure&gt;
&lt;/p&gt;
&lt;p&gt;지금은 single node cluster를 만들어 볼 것이다. 즉, master와 worker가 같은 컴퓨터에 있을테니, 좀 넉넉한 크기의 인스턴스를 생성해 주도록 하자. &lt;code&gt;Cloud Computer -&amp;gt; Asia -&amp;gt; Seoul ( 서울이 Sold out이면 Tokyo ) -&amp;gt; Ubuntu 18.04&lt;/code&gt; 를 선택하고, VM Instance는 한달에 $40가 소모되는 인스턴스를 고르자. ( 4core, 8GB RAM, 4TB Traffic, 160GB SSD ) 비용이 매우 크게 느껴질 수 있는데, 1시간에 100원이 안되니깐, 크게 걱정할 필요는 없다.&lt;/p&gt;
&lt;p&gt;&lt;figure class=&quot;imageblock alignCenter&quot; data-origin-width=&quot;0&quot; data-origin-height=&quot;0&quot; data-ke-mobilestyle=&quot;widthContent&quot;&gt;&lt;span data-url=&quot;https://blog.kakaocdn.net/dn/6EH5O/btqFS1zJJ7M/utLSLOOg4uD5MK0q4AoZL1/img.png&quot; data-phocus=&quot;https://blog.kakaocdn.net/dn/6EH5O/btqFS1zJJ7M/utLSLOOg4uD5MK0q4AoZL1/img.png&quot;&gt;&lt;img src=&quot;https://blog.kakaocdn.net/dn/6EH5O/btqFS1zJJ7M/utLSLOOg4uD5MK0q4AoZL1/img.png&quot; srcset=&quot;https://img1.daumcdn.net/thumb/R1280x0/?scode=mtistory2&amp;fname=https%3A%2F%2Fblog.kakaocdn.net%2Fdn%2F6EH5O%2FbtqFS1zJJ7M%2FutLSLOOg4uD5MK0q4AoZL1%2Fimg.png&quot; data-origin-width=&quot;0&quot; data-origin-height=&quot;0&quot; data-ke-mobilestyle=&quot;widthContent&quot; onerror=&quot;this.onerror=null; this.src='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png'; this.srcset='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png';&quot;/&gt;&lt;/span&gt;&lt;/figure&gt;
&lt;/p&gt;
&lt;p&gt;1시간에 100원이 부담스럽다면, $20의 비용이 드는 인스턴스를 생성해도 상관없다. $20짜리 인스턴스는 1시간에 50원이 든다. 사실 k3s는 훨씬 더 작은 환경에서도 돌아 가지만, 그렇다고 하더라도 그 보다 작은 사이즈는 추천하지 않는다. 1 vCPU를 사용하기 때문에 속도가 좀 느리고, &quot;이렇게 쉽게 뭘 할 수 있다고?&quot; 라는것을 테스트 하기에는 사양이 딸린다. ( &lt;a href=&quot;https://rancher.com/docs/k3s/latest/en/installation/installation-requirements/&quot;&gt;https://rancher.com/docs/k3s/latest/en/installation/installation-requirements/&lt;/a&gt; )&lt;/p&gt;
&lt;p&gt;&lt;figure class=&quot;imageblock alignCenter&quot; data-origin-width=&quot;0&quot; data-origin-height=&quot;0&quot; data-ke-mobilestyle=&quot;widthContent&quot;&gt;&lt;span data-url=&quot;https://blog.kakaocdn.net/dn/dKIFlP/btqFXg2zWVj/lSuN1yEZBvOOERflQOiWtK/img.png&quot; data-phocus=&quot;https://blog.kakaocdn.net/dn/dKIFlP/btqFXg2zWVj/lSuN1yEZBvOOERflQOiWtK/img.png&quot;&gt;&lt;img src=&quot;https://blog.kakaocdn.net/dn/dKIFlP/btqFXg2zWVj/lSuN1yEZBvOOERflQOiWtK/img.png&quot; srcset=&quot;https://img1.daumcdn.net/thumb/R1280x0/?scode=mtistory2&amp;fname=https%3A%2F%2Fblog.kakaocdn.net%2Fdn%2FdKIFlP%2FbtqFXg2zWVj%2FlSuN1yEZBvOOERflQOiWtK%2Fimg.png&quot; data-origin-width=&quot;0&quot; data-origin-height=&quot;0&quot; data-ke-mobilestyle=&quot;widthContent&quot; onerror=&quot;this.onerror=null; this.src='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png'; this.srcset='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png';&quot;/&gt;&lt;/span&gt;&lt;/figure&gt;
&lt;/p&gt;
&lt;p&gt;VM Instance type을 고른 뒤에, 화면 아래쪽에 있는 Deploy now 버튼을 눌러 인스턴스를 생성하도록 하자.&lt;/p&gt;
&lt;p&gt;&lt;figure class=&quot;imageblock alignCenter&quot; data-origin-width=&quot;0&quot; data-origin-height=&quot;0&quot; data-ke-mobilestyle=&quot;widthContent&quot;&gt;&lt;span data-url=&quot;https://blog.kakaocdn.net/dn/dxyzhj/btqFT77iznp/1xEMZXyMeZreauU6mEzw8K/img.png&quot; data-phocus=&quot;https://blog.kakaocdn.net/dn/dxyzhj/btqFT77iznp/1xEMZXyMeZreauU6mEzw8K/img.png&quot;&gt;&lt;img src=&quot;https://blog.kakaocdn.net/dn/dxyzhj/btqFT77iznp/1xEMZXyMeZreauU6mEzw8K/img.png&quot; srcset=&quot;https://img1.daumcdn.net/thumb/R1280x0/?scode=mtistory2&amp;fname=https%3A%2F%2Fblog.kakaocdn.net%2Fdn%2Fdxyzhj%2FbtqFT77iznp%2F1xEMZXyMeZreauU6mEzw8K%2Fimg.png&quot; data-origin-width=&quot;0&quot; data-origin-height=&quot;0&quot; data-ke-mobilestyle=&quot;widthContent&quot; onerror=&quot;this.onerror=null; this.src='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png'; this.srcset='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png';&quot;/&gt;&lt;/span&gt;&lt;/figure&gt;
&lt;/p&gt;
&lt;p&gt;약 1분정도 기다리면 VM Instance가 생성된다. &lt;code&gt;Products -&amp;gt; Instances&lt;/code&gt; 를 누른 뒤에, VM 목록중에 생성된 &quot;Cloud Instance&quot;를 클릭하여 서버의 정보를 확인하자. 그리고 &lt;code&gt;IP Address&lt;/code&gt;와 &lt;code&gt;username&lt;/code&gt;, &lt;code&gt;password&lt;/code&gt;를 확인 한 뒤 ssh로 접속하자. password 옆에 있는 눈 모양 아이콘을 누르면 password를 볼 수 있고, 겹쳐진 종이 모양을 누르면 암호를 복사할 수 있다.&lt;/p&gt;
&lt;p&gt;&lt;figure class=&quot;imageblock alignCenter&quot; data-origin-width=&quot;0&quot; data-origin-height=&quot;0&quot; data-ke-mobilestyle=&quot;widthContent&quot;&gt;&lt;span data-url=&quot;https://blog.kakaocdn.net/dn/bJ5DL0/btqFWYA64u5/CrUhGFBTjv039pF3QLBKuK/img.png&quot; data-phocus=&quot;https://blog.kakaocdn.net/dn/bJ5DL0/btqFWYA64u5/CrUhGFBTjv039pF3QLBKuK/img.png&quot;&gt;&lt;img src=&quot;https://blog.kakaocdn.net/dn/bJ5DL0/btqFWYA64u5/CrUhGFBTjv039pF3QLBKuK/img.png&quot; srcset=&quot;https://img1.daumcdn.net/thumb/R1280x0/?scode=mtistory2&amp;fname=https%3A%2F%2Fblog.kakaocdn.net%2Fdn%2FbJ5DL0%2FbtqFWYA64u5%2FCrUhGFBTjv039pF3QLBKuK%2Fimg.png&quot; data-origin-width=&quot;0&quot; data-origin-height=&quot;0&quot; data-ke-mobilestyle=&quot;widthContent&quot; onerror=&quot;this.onerror=null; this.src='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png'; this.srcset='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png';&quot;/&gt;&lt;/span&gt;&lt;/figure&gt;
&lt;/p&gt;
&lt;p&gt;ssh에 접속하고 나면 명령어 딱 1개만 치면 k3s 가 설치 된다. 하지만 나중을 위해서 아래중 두번째 명령을 이용해서 설치하도록 하자.&lt;/p&gt;
&lt;p&gt;&lt;code&gt;curl -sfL https://get.k3s.io | sh -s -&lt;/code&gt;&lt;br /&gt;&lt;code&gt;curl -sfL https://get.k3s.io | sh -s - --write-kubeconfig-mode 644&lt;/code&gt;&lt;/p&gt;
&lt;p&gt;몇가지 shell script와 k3s 라는 바이너리를 다운받게 되는데, 용량은 약 50Mbyte 정도 된다. 일반적으로는 약 15초 ~ 30초 정도 지나면 설치가 완료되나, 다운로드 속도가 느린 경우에는 50Mbyte 정보를 다운 받는 시간을 기다려야 한다. 지금 이 문서를 작성하면서 테스트를 하고 있는데, 지금은.. 왜 그런지 느리네... 흠.. 원래는 이렇게 느리지 않았는데... 아무튼 좀 기다리면 곧 설치가 된다.&lt;/p&gt;
&lt;p&gt;&lt;figure class=&quot;imageblock alignCenter&quot; data-origin-width=&quot;0&quot; data-origin-height=&quot;0&quot; data-ke-mobilestyle=&quot;widthContent&quot;&gt;&lt;span data-url=&quot;https://blog.kakaocdn.net/dn/dHEmMZ/btqFWdFxFJi/q6GmuXy98z0hzERPoJOrZ1/img.png&quot; data-phocus=&quot;https://blog.kakaocdn.net/dn/dHEmMZ/btqFWdFxFJi/q6GmuXy98z0hzERPoJOrZ1/img.png&quot;&gt;&lt;img src=&quot;https://blog.kakaocdn.net/dn/dHEmMZ/btqFWdFxFJi/q6GmuXy98z0hzERPoJOrZ1/img.png&quot; srcset=&quot;https://img1.daumcdn.net/thumb/R1280x0/?scode=mtistory2&amp;fname=https%3A%2F%2Fblog.kakaocdn.net%2Fdn%2FdHEmMZ%2FbtqFWdFxFJi%2Fq6GmuXy98z0hzERPoJOrZ1%2Fimg.png&quot; data-origin-width=&quot;0&quot; data-origin-height=&quot;0&quot; data-ke-mobilestyle=&quot;widthContent&quot; onerror=&quot;this.onerror=null; this.src='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png'; this.srcset='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png';&quot;/&gt;&lt;/span&gt;&lt;/figure&gt;
&lt;/p&gt;
&lt;p&gt;잘 설치 되었는지 확인하자. 원래 kubernetes를 관리하기 위해선 &lt;code&gt;kubectl&lt;/code&gt; 이라는 프로그램을 설치해 사용한다. 하지만 k3s를 설치하면 굳이 &lt;code&gt;kubectl&lt;/code&gt;을 직접 설치하지 않아도 된다. &lt;code&gt;k3s kubectl&lt;/code&gt; 이라는 명령을 통해서 &lt;code&gt;kubectl&lt;/code&gt;을 실행 시킬 수 있다. &lt;code&gt;k3s kubectl nodes&lt;/code&gt; 명령을 이용해서 kubernetes cluster가 잘 설치 되었는지 확인해 보자. ( &lt;code&gt;kubectl get nodes&lt;/code&gt; 명령은 쿠버네티스에 몇개의 node(컴퓨터) 가 있는지 확인할 수 있는 명령이다. )&lt;/p&gt;
&lt;p&gt;&lt;figure class=&quot;imageblock alignCenter&quot; data-origin-width=&quot;0&quot; data-origin-height=&quot;0&quot; data-ke-mobilestyle=&quot;widthContent&quot;&gt;&lt;span data-url=&quot;https://blog.kakaocdn.net/dn/TahOR/btqFTSWRMp1/m6xxBMINHKfGuh8Qkyl4bK/img.png&quot; data-phocus=&quot;https://blog.kakaocdn.net/dn/TahOR/btqFTSWRMp1/m6xxBMINHKfGuh8Qkyl4bK/img.png&quot; data-alt=&quot;kubectl&amp;amp;amp;nbsp; get nodes 로 현재 node 갯수를 알 수 있다.&quot;&gt;&lt;img src=&quot;https://blog.kakaocdn.net/dn/TahOR/btqFTSWRMp1/m6xxBMINHKfGuh8Qkyl4bK/img.png&quot; srcset=&quot;https://img1.daumcdn.net/thumb/R1280x0/?scode=mtistory2&amp;fname=https%3A%2F%2Fblog.kakaocdn.net%2Fdn%2FTahOR%2FbtqFTSWRMp1%2Fm6xxBMINHKfGuh8Qkyl4bK%2Fimg.png&quot; data-origin-width=&quot;0&quot; data-origin-height=&quot;0&quot; data-ke-mobilestyle=&quot;widthContent&quot; onerror=&quot;this.onerror=null; this.src='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png'; this.srcset='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png';&quot;/&gt;&lt;/span&gt;&lt;figcaption&gt;kubectl&amp;nbsp; get nodes 로 현재 node 갯수를 알 수 있다.&lt;/figcaption&gt;
&lt;/figure&gt;
&lt;/p&gt;
&lt;p&gt;&lt;code&gt;k3s kubectl get all -A&lt;/code&gt; 명령을 이용해서 쿠버네티스에 pod나 service, loadbalancer 가 잘 설정되어 있는지도 확인해 보자.&lt;/p&gt;
&lt;p&gt;&lt;figure class=&quot;imageblock alignCenter&quot; data-origin-width=&quot;0&quot; data-origin-height=&quot;0&quot; data-ke-mobilestyle=&quot;widthContent&quot;&gt;&lt;span data-url=&quot;https://blog.kakaocdn.net/dn/2cytb/btqFWZ7Qxep/Lzo8sNthEw39rI8DSGUSI1/img.png&quot; data-phocus=&quot;https://blog.kakaocdn.net/dn/2cytb/btqFWZ7Qxep/Lzo8sNthEw39rI8DSGUSI1/img.png&quot;&gt;&lt;img src=&quot;https://blog.kakaocdn.net/dn/2cytb/btqFWZ7Qxep/Lzo8sNthEw39rI8DSGUSI1/img.png&quot; srcset=&quot;https://img1.daumcdn.net/thumb/R1280x0/?scode=mtistory2&amp;fname=https%3A%2F%2Fblog.kakaocdn.net%2Fdn%2F2cytb%2FbtqFWZ7Qxep%2FLzo8sNthEw39rI8DSGUSI1%2Fimg.png&quot; data-origin-width=&quot;0&quot; data-origin-height=&quot;0&quot; data-ke-mobilestyle=&quot;widthContent&quot; onerror=&quot;this.onerror=null; this.src='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png'; this.srcset='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png';&quot;/&gt;&lt;/span&gt;&lt;/figure&gt;
&lt;/p&gt;
&lt;p&gt;&lt;code&gt;kind&lt;/code&gt;에서는 &lt;code&gt;LoadBalancer&lt;/code&gt;를 사용하려고 하면 private IP 대역이 잡혀 서비스를 직접 돌려서 테스트하기 쉽지 않은데, k3s의 경우에는 &lt;code&gt;traefik&lt;/code&gt;이 같이 설치되고 &lt;code&gt;LoadBalancer&lt;/code&gt;의 external ip가, 지금 설치된 컴퓨터의 IP를 가지게 되므로 실제 서비스를 올린뒤 접속해서 테스트 해보기 좋다.&lt;/p&gt;
&lt;p&gt;&lt;code&gt;kubectl&lt;/code&gt; 명령이 필요할 때마다 &lt;code&gt;k3s kubectl&lt;/code&gt;이라고 치면 귀찮으니, 쉽게 alias를 만들 수도 있지만 그냥 &lt;code&gt;kubectl&lt;/code&gt;을 설치하도록 하자. 설치하는 방법도 여러가지가 있다. &lt;code&gt;snap&lt;/code&gt; 을 이용해서 설치하는게 제일 편하긴 하지만, &lt;code&gt;snap&lt;/code&gt;은 home directory 위치에 따라서 또 다른 설정이 필요할 수도 있으므로, 여기에서는 그냥 package manager 즉, &lt;code&gt;apt-get&lt;/code&gt;을 이용해서 설치하는 방법을 가이드 한다. 물론 이 내용은 공식 홈페이지에서도 찾을 수 있다. ( &lt;a href=&quot;https://kubernetes.io/docs/tasks/tools/install-kubectl/&quot;&gt;https://kubernetes.io/docs/tasks/tools/install-kubectl/&lt;/a&gt; )&lt;/p&gt;
&lt;pre class=&quot;vim&quot;&gt;&lt;code&gt;sudo apt-get update &amp;amp;&amp;amp; sudo apt-get install -y apt-transport-https gnupg2
curl -s https://packages.cloud.google.com/apt/doc/apt-key.gpg | sudo apt-key add -
echo &quot;deb https://apt.kubernetes.io/ kubernetes-xenial main&quot; | sudo tee -a /etc/apt/sources.list.d/kubernetes.list
sudo apt-get update
sudo apt-get install -y kubectl&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;kubectl을 설치한 직후에 바로 &lt;code&gt;kubectl get nodes&lt;/code&gt; 명령을 사용하면 예전과를 다르게 에러 메세지가 발생하는 경우가 있다. kubectl은 사용자 홈 디렉토리에 &lt;code&gt;.kube/config&lt;/code&gt; 파일을 읽어 kubernetes 환경 설정 정보로 사용하기 때문이다. 하지만 k3s를 설치하면 kubernetes 환경 설정 정보가 &lt;code&gt;/etc/rancher/k3s/k3s.yaml&lt;/code&gt; 위치에 있다. 그러므로 이를 복사해 주어야 한다. 아래 명령을 수행하려면 &lt;code&gt;k3s&lt;/code&gt;를 설치할때 &lt;code&gt;--write-kubeconfig-mode 644&lt;/code&gt; 명령을 줘야 root 권한이 아니더라도 별 문제 없이 수행 할 수 있다.&lt;/p&gt;
&lt;pre class=&quot;awk&quot;&gt;&lt;code&gt;mkdir .kube # 이미 디렉토리가 만들어져 있는 경우도 있다.
cp /etc/rancher/k3s/k3s.yaml ~/.kube/config&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;설명이 길고 이것저것 실행해 보느라 길어보이지만, 정리해 보면 아래 명령어 한줄이면 single node cluster kubernetes를 구축할 수 있다.&lt;/p&gt;
&lt;p&gt;&lt;code&gt;curl -sfL https://get.k3s.io | sh -s - --write-kubeconfig-mode 644&lt;/code&gt;&lt;/p&gt;
&lt;p&gt;모든 테스트가 끝나면 깔끔하게 삭제하고 싶을것이다. 아래의 명령을 이용하면 k3s를 삭제하면서 kubernetes의 모든 것들을 삭제할 수 있다. k3s를 설치할 때 각종 스크립트가 설치 된다는 정보가 있는데 그곳에 스크립트 파일의 위치를 보여 주고 있다. 궁금하다면 스크롤을 올려서 설치 스크린샷에 아래의 스크립트가 있는것을 확인해 보자.&lt;/p&gt;
&lt;p&gt;&lt;code&gt;/usr/local/bin/k3s-uninstall.sh&lt;/code&gt;&lt;/p&gt;
&lt;p&gt;글이 너무 길어지니 여기서 1차 정리를 하고, multi node cluster를 구축하는것은 다음글에서 다루도록 하겠다. 궁금하면 오백원..은 아니고, &lt;a href=&quot;https://k3s.io/&quot;&gt;https://k3s.io/&lt;/a&gt; 에 가면 agent 설치하는 설명이 있는데, 그곳을 먼저 봐도 되겠다. ㅋㅋ&lt;/p&gt;</description>
      <category>공부/컴퓨터</category>
      <category>k3s</category>
      <category>k8s</category>
      <category>kubectl</category>
      <category>kubernetes</category>
      <category>MASTER</category>
      <category>VULTR</category>
      <category>worker</category>
      <category>벌쳐</category>
      <category>쿠버네티스</category>
      <author>찬</author>
      <guid isPermaLink="true">https://blog.ggaman.com/1018</guid>
      <comments>https://blog.ggaman.com/1018#entry1018comment</comments>
      <pubDate>Thu, 23 Jul 2020 02:57:59 +0900</pubDate>
    </item>
    <item>
      <title>간단한 클라우드 서버(VPS) 만들기 -  Vultr - VPS</title>
      <link>https://blog.ggaman.com/1019</link>
      <description>&lt;p&gt;요즘에 회사에서 Kubernetes 관련 업무를 하고 있다. 실제로 내가하는건 별로 없고, 대부분 팀의 다른분들이 다 하지만, 그래도 뭔가를 좀 알아야 하니깐 집에서도 이것저것 해 보고 있다. Kubernetes를 하니깐 당연히 Cluster를 구축해 보는게 좋을것이다. GCP나 AWS에 모두 Kubernetes cluster를 제공해 주고 있으나, 이것저것 직접 해 보려고 일부러 VPS(Virtual Private Server)를 여러개 묶어서 해 보고 있다. Kubernetes를 모두 이해하는건 어려우니 지금은 k3s (&lt;a href=&quot;https://k3s.io/&quot;&gt;https://k3s.io/&lt;/a&gt;) 를 이용해서 해 보고 있다. 다음 기회에 k3s에 관해서 써 볼 수 있도록 하겠다.&lt;/p&gt;
&lt;p&gt;잡설은 그만. VPS를 제공해 주는데가 여러곳이 있는데, 이 중에서 내가 사용해 본 Vultr에서 VPS를 만드는것을 알아 보자.&lt;/p&gt;
&lt;h2 data-ke-size=&quot;size26&quot;&gt;Vultr&lt;/h2&gt;
&lt;p&gt;&lt;figure class=&quot;imageblock alignCenter&quot; data-origin-width=&quot;0&quot; data-origin-height=&quot;0&quot; width=&quot;550&quot; data-ke-mobilestyle=&quot;widthContent&quot;&gt;&lt;span data-url=&quot;https://blog.kakaocdn.net/dn/cgIV1E/btqFFUncdTg/9QOtKbsVjvfQ6YRKr1qe8k/img.png&quot; data-phocus=&quot;https://blog.kakaocdn.net/dn/cgIV1E/btqFFUncdTg/9QOtKbsVjvfQ6YRKr1qe8k/img.png&quot; data-alt=&quot;vultr.com&quot;&gt;&lt;img src=&quot;https://blog.kakaocdn.net/dn/cgIV1E/btqFFUncdTg/9QOtKbsVjvfQ6YRKr1qe8k/img.png&quot; srcset=&quot;https://img1.daumcdn.net/thumb/R1280x0/?scode=mtistory2&amp;fname=https%3A%2F%2Fblog.kakaocdn.net%2Fdn%2FcgIV1E%2FbtqFFUncdTg%2F9QOtKbsVjvfQ6YRKr1qe8k%2Fimg.png&quot; data-origin-width=&quot;0&quot; data-origin-height=&quot;0&quot; width=&quot;550&quot; data-ke-mobilestyle=&quot;widthContent&quot; onerror=&quot;this.onerror=null; this.src='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png'; this.srcset='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png';&quot;/&gt;&lt;/span&gt;&lt;figcaption&gt;vultr.com&lt;/figcaption&gt;
&lt;/figure&gt;
&lt;/p&gt;
&lt;p&gt;vultr(벌쳐-라고 읽는다)는 많은 VPS 서비스 제공자 중에 한명이다.&amp;nbsp; 처음 vultr을 선택한 이유는, 그 당시에는 가입후 $10 크레딧 충전시 무료 $20 크레딧을 추가로 제공했기 때문이었다. ( 지금은 스폰서 링크를 이용하면 $100을 더 준다. ) 벌써 $20~30 정도를 두 번 더 충전해서 쓰고 있다. 무엇보다 쉽게 VPS를 만들고 설정도 단순하기 때문에 대충 만들어서 테스트 해 보기 좋다는 점이 계속 사용하게 만들었다.&lt;/p&gt;
&lt;p&gt;그리고 아시아 지역에는 도쿄와 싱가폴 리전만 있었는데, 최근에 서울 리전에 새로 만들어져서 빠르게 접속해서 쓸 수 있다. 다만, 서울 리전은 가끔씩 Sold Out 되어 있는 상태가 자주 있고, 서울 리전에서 VPS를 만들어도, 정작 public IP를 확인 해 보면 일본 IP가 오는 경우도 있기도 한 점은 별로긴 하다.&lt;/p&gt;
&lt;p&gt;참고로 주소지를 한국으로 두면, VPS 사용료 이외에 부가세 10%가 더 발생한다고 경고가 나온다. 주소지를 미국으로 바꾸어 두었더니, 더 이상 그런 메세지가 안 나오긴 하는데 실제로 안 나올지는 좀 더 지켜 봐야 겠다.&lt;/p&gt;
&lt;p&gt;만약 vultr에 가입하실 거라면 아래 링크를 통해서 가입 부탁드립니다. 아래 링크로 가입을 하시고 $10 달러 이상 크레딧을 결재하시면, 가입하신분께 30일동안 사용 가능한 추가 $100 크레딧을 제공해, 총 $110 크레딧을 사용하실 수 있습니다. $25 크레딧을 충전하시면 30일동안 사용가능한 추가 $100 크레딧을 제공해, 총 $125 크레딧을 받으실 수 있으며, 저에게도 $25 크레딧이 제공됩니다. 이왕이면... $25 크레딧 충전 부탁드립니다. ^_^&lt;/p&gt;
&lt;p style=&quot;text-align: center;&quot;&gt;&lt;a href=&quot;https://www.vultr.com/?ref=7487482&quot;&gt;&lt;img src=&quot;https://www.vultr.com/media/banners/banner_728x90.png&quot; width=&quot;728&quot; height=&quot;90&quot; /&gt;&lt;/a&gt;&lt;/p&gt;
&lt;p style=&quot;text-align: center;&quot;&gt;&lt;a href=&quot;https://www.vultr.com/?ref=8582867-6G&quot; target=&quot;_blank&quot; rel=&quot;noopener&quot;&gt;www.vultr.com/?ref=8582867-6G&lt;/a&gt;&lt;/p&gt;
&lt;h2 data-ke-size=&quot;size26&quot;&gt;VPS를 만들어 보자&lt;/h2&gt;
&lt;h3 data-ke-size=&quot;size23&quot;&gt;Credit card 등록 및 결재&lt;/h3&gt;
&lt;p&gt;우선 돈 부터 채우자. 위 링크를 클릭하시어 가입을 하시고, 로그인 하면 Credit Card를 등록하는 화면이 나옵니다. 이때 $10 혹은 $25, 혹은 그 이상을 골라서 결재를 하게 되면, $100 크레딧을 추가로 받을 수 있습니다.&lt;/p&gt;
&lt;p&gt;&lt;figure class=&quot;imageblock alignCenter&quot; data-origin-width=&quot;0&quot; data-origin-height=&quot;0&quot; data-ke-mobilestyle=&quot;widthContent&quot;&gt;&lt;span data-url=&quot;https://blog.kakaocdn.net/dn/bQFZ1D/btqFJbm8vcD/ag6VWk4KNiz2TfYZDtyQ91/img.png&quot; data-phocus=&quot;https://blog.kakaocdn.net/dn/bQFZ1D/btqFJbm8vcD/ag6VWk4KNiz2TfYZDtyQ91/img.png&quot;&gt;&lt;img src=&quot;https://blog.kakaocdn.net/dn/bQFZ1D/btqFJbm8vcD/ag6VWk4KNiz2TfYZDtyQ91/img.png&quot; srcset=&quot;https://img1.daumcdn.net/thumb/R1280x0/?scode=mtistory2&amp;fname=https%3A%2F%2Fblog.kakaocdn.net%2Fdn%2FbQFZ1D%2FbtqFJbm8vcD%2Fag6VWk4KNiz2TfYZDtyQ91%2Fimg.png&quot; data-origin-width=&quot;0&quot; data-origin-height=&quot;0&quot; data-ke-mobilestyle=&quot;widthContent&quot; onerror=&quot;this.onerror=null; this.src='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png'; this.srcset='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png';&quot;/&gt;&lt;/span&gt;&lt;/figure&gt;
&lt;/p&gt;
&lt;h3 data-ke-size=&quot;size23&quot;&gt;인스턴스 생성&lt;/h3&gt;
&lt;p&gt;로그인 후 Products -&amp;gt; Deploy Instance 를 선택하자&lt;/p&gt;
&lt;p&gt;&lt;figure class=&quot;imageblock alignCenter&quot; data-origin-width=&quot;0&quot; data-origin-height=&quot;0&quot; width=&quot;550&quot; data-ke-mobilestyle=&quot;widthContent&quot;&gt;&lt;span data-url=&quot;https://blog.kakaocdn.net/dn/cfIyOI/btqFIGOFbk2/kW9D85BIpdAcZtEFFzRP3k/img.png&quot; data-phocus=&quot;https://blog.kakaocdn.net/dn/cfIyOI/btqFIGOFbk2/kW9D85BIpdAcZtEFFzRP3k/img.png&quot; data-alt=&quot;vultr products&quot;&gt;&lt;img src=&quot;https://blog.kakaocdn.net/dn/cfIyOI/btqFIGOFbk2/kW9D85BIpdAcZtEFFzRP3k/img.png&quot; srcset=&quot;https://img1.daumcdn.net/thumb/R1280x0/?scode=mtistory2&amp;fname=https%3A%2F%2Fblog.kakaocdn.net%2Fdn%2FcfIyOI%2FbtqFIGOFbk2%2FkW9D85BIpdAcZtEFFzRP3k%2Fimg.png&quot; data-origin-width=&quot;0&quot; data-origin-height=&quot;0&quot; width=&quot;550&quot; data-ke-mobilestyle=&quot;widthContent&quot; onerror=&quot;this.onerror=null; this.src='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png'; this.srcset='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png';&quot;/&gt;&lt;/span&gt;&lt;figcaption&gt;vultr products&lt;/figcaption&gt;
&lt;/figure&gt;
&lt;/p&gt;
&lt;h3 data-ke-size=&quot;size23&quot;&gt;VPS 스펙 정하기 - 1&lt;/h3&gt;
&lt;p&gt;Cloud Compute를 선택하고, Asia -&amp;gt; Seoul(Sold out이면 Tokyo)로 리전을 선택한뒤, OS 를 선택한다.&lt;/p&gt;
&lt;p&gt;&lt;figure class=&quot;imageblock alignCenter&quot; data-origin-width=&quot;0&quot; data-origin-height=&quot;0&quot; width=&quot;550&quot; data-ke-mobilestyle=&quot;widthContent&quot;&gt;&lt;span data-url=&quot;https://blog.kakaocdn.net/dn/tJpz2/btqFJrDeXFV/IqznuivhSaz87aVRSMcUlk/img.png&quot; data-phocus=&quot;https://blog.kakaocdn.net/dn/tJpz2/btqFJrDeXFV/IqznuivhSaz87aVRSMcUlk/img.png&quot; data-alt=&quot;vultr VPS spec&quot;&gt;&lt;img src=&quot;https://blog.kakaocdn.net/dn/tJpz2/btqFJrDeXFV/IqznuivhSaz87aVRSMcUlk/img.png&quot; srcset=&quot;https://img1.daumcdn.net/thumb/R1280x0/?scode=mtistory2&amp;fname=https%3A%2F%2Fblog.kakaocdn.net%2Fdn%2FtJpz2%2FbtqFJrDeXFV%2FIqznuivhSaz87aVRSMcUlk%2Fimg.png&quot; data-origin-width=&quot;0&quot; data-origin-height=&quot;0&quot; width=&quot;550&quot; data-ke-mobilestyle=&quot;widthContent&quot; onerror=&quot;this.onerror=null; this.src='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png'; this.srcset='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png';&quot;/&gt;&lt;/span&gt;&lt;figcaption&gt;vultr VPS spec&lt;/figcaption&gt;
&lt;/figure&gt;
&lt;/p&gt;
&lt;h3 data-ke-size=&quot;size23&quot;&gt;VPS 스펙 정하기 - 2&lt;/h3&gt;
&lt;p&gt;스크롤을 내려서 서버의 스펙을 선택한다. ( 클릭하면 크게 볼 수 있습니다. )&lt;/p&gt;
&lt;p&gt;&lt;figure class=&quot;imageblock alignCenter&quot; data-origin-width=&quot;0&quot; data-origin-height=&quot;0&quot; width=&quot;750&quot; data-ke-mobilestyle=&quot;widthContent&quot;&gt;&lt;span data-url=&quot;https://blog.kakaocdn.net/dn/Qt0Yb/btqFHb2T51b/8oLVYgKkX6HOCKK2KRYGfK/img.png&quot; data-phocus=&quot;https://blog.kakaocdn.net/dn/Qt0Yb/btqFHb2T51b/8oLVYgKkX6HOCKK2KRYGfK/img.png&quot; data-alt=&quot;vultr VPS price list&quot;&gt;&lt;img src=&quot;https://blog.kakaocdn.net/dn/Qt0Yb/btqFHb2T51b/8oLVYgKkX6HOCKK2KRYGfK/img.png&quot; srcset=&quot;https://img1.daumcdn.net/thumb/R1280x0/?scode=mtistory2&amp;fname=https%3A%2F%2Fblog.kakaocdn.net%2Fdn%2FQt0Yb%2FbtqFHb2T51b%2F8oLVYgKkX6HOCKK2KRYGfK%2Fimg.png&quot; data-origin-width=&quot;0&quot; data-origin-height=&quot;0&quot; width=&quot;750&quot; data-ke-mobilestyle=&quot;widthContent&quot; onerror=&quot;this.onerror=null; this.src='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png'; this.srcset='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png';&quot;/&gt;&lt;/span&gt;&lt;figcaption&gt;vultr VPS price list&lt;/figcaption&gt;
&lt;/figure&gt;
&lt;/p&gt;
&lt;h3 data-ke-size=&quot;size23&quot;&gt;금액 확인 및 계산&lt;/h3&gt;
&lt;p&gt;가격을 보면 알겠지만. 시간당 단위로 계산된다. 예를들면 위 그림에서 선택한 두번째 스펙인 1vCPU, 2G Memory, 50GB SSD, 2TB Bandwidth 를 선택하면 1달에는 $10, 그러니깐 1.2만원이고, 1시간은 $0.015, 약 20원 이다. 간단하게 테스트 해 보고 싶을때 좋다. 좀 쓸만한 서버 스펙은, 위 그림에서 4번째 스펙이다. 4vCPU, 8GB Memory, 4TB Bandwidth, 160GB SSD. 이것도 1시간 가격으로만 보면 72원 정도 된다. 미국의 일부 리전에는 IPv6 한정으로, 1vCPU, 512MB Memory, 500GB Bandwidth, 10GB SSD로, 시간당 5원 짜리도 있다. ( 하지만, IPv6는 아무도 안 쓰니깐... ㅎㅎ )&lt;/p&gt;
&lt;p&gt;Kubernetes Cluster 구축을 위해서 Master서버를 1번 스펙 3대, Worker서버를 3번 스펙 2대, 4번 스펙 1대를,&amp;nbsp; 4시간을 테스트 해 보았다고 하면 아래의 비용이 나온다.&lt;/p&gt;
&lt;p&gt;Master : 1vCPU, 1GB Memory, 1TB Bandwidth, 25GB SSD - 1시간 $0.007 * 4시간 * 3대 =&amp;nbsp; $0.084 = 약 100원&lt;br /&gt;Worker : 2vCPU, 4GB Memory, 3TB Bnadwidth, 80GB SSD - 1시간 $0.03 * 4시간 * 2대 = $0.24 = 약 290원&lt;br /&gt;Worker : 4vCPU, 8GB Memory, 4TB Bandwidth, 160GB SSD - 1시간 $0.06 * 4시간 * 1대 = $0.24 = 약 290원&lt;br /&gt;총 : 680원&lt;/p&gt;
&lt;p&gt;4시간동안 장비를 빌려서 공부를 하는데 700원 이하로 든다면 충분히 해 볼만 하다고 생각된다. 좀 고사양 장비를 가지고 필요할 때만 사용하면 $25만 충전해도 몇달은 거뜬했었다. 예전에 $25를 충전해서, 각 대륙별, 국가별 리전에서 네트워크 문제도 확인하고, 속도도 측정해 보고... 몇달동안 요긴하게 잘 써 먹었다.&lt;/p&gt;
&lt;p&gt;1시간 단위로 금액이 청구 되므로, 딱딱 끊어서 쓰고 싶다면 체크를 잘해야 한다. 하지만, 시간당 비용이 크게 비싸지 않기 때문에, 크게 무리가 없다. 트래픽도 시간당 단위로 나눠서 사용된다. 즉, 1시간을 사용하고 종료하면, 트래픽도 1시간치 이내로 써야, 1시간 비용만 나온다. 즉, 서버는 10일만 사용했는데, 트래픽을 한달치를 다 쓰게 되면 한 달 치 비용이 청구된다.&amp;nbsp;&lt;/p&gt;
&lt;h3 data-ke-size=&quot;size23&quot;&gt;스펙 확인 후 VPS 생성&lt;/h3&gt;
&lt;p&gt;화면 아래에 있는 Deploy Now 버튼을 클릭한다.&lt;/p&gt;
&lt;p&gt;&lt;figure class=&quot;imageblock alignCenter&quot; data-origin-width=&quot;0&quot; data-origin-height=&quot;0&quot; width=&quot;550&quot; data-ke-mobilestyle=&quot;widthContent&quot;&gt;&lt;span data-url=&quot;https://blog.kakaocdn.net/dn/10uWt/btqFGyqOek3/0ZcO6Q87qCEJUkSuUPWHa0/img.png&quot; data-phocus=&quot;https://blog.kakaocdn.net/dn/10uWt/btqFGyqOek3/0ZcO6Q87qCEJUkSuUPWHa0/img.png&quot; data-alt=&quot;vultr - depoly instance&quot;&gt;&lt;img src=&quot;https://blog.kakaocdn.net/dn/10uWt/btqFGyqOek3/0ZcO6Q87qCEJUkSuUPWHa0/img.png&quot; srcset=&quot;https://img1.daumcdn.net/thumb/R1280x0/?scode=mtistory2&amp;fname=https%3A%2F%2Fblog.kakaocdn.net%2Fdn%2F10uWt%2FbtqFGyqOek3%2F0ZcO6Q87qCEJUkSuUPWHa0%2Fimg.png&quot; data-origin-width=&quot;0&quot; data-origin-height=&quot;0&quot; width=&quot;550&quot; data-ke-mobilestyle=&quot;widthContent&quot; onerror=&quot;this.onerror=null; this.src='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png'; this.srcset='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png';&quot;/&gt;&lt;/span&gt;&lt;figcaption&gt;vultr - depoly instance&lt;/figcaption&gt;
&lt;/figure&gt;
&lt;/p&gt;
&lt;h3 data-ke-size=&quot;size23&quot;&gt;생성된 VPS 인스턴스 확인&lt;/h3&gt;
&lt;p&gt;서버를 만들고 나면 인스턴스의 목록을 볼 수 있다. 추후 이 화면을 보고 싶다면, 왼쪽의 Products 아이콘을 누르면 된다.&lt;/p&gt;
&lt;p&gt;&lt;figure class=&quot;imageblock alignCenter&quot; data-origin-width=&quot;0&quot; data-origin-height=&quot;0&quot; width=&quot;550&quot; data-ke-mobilestyle=&quot;widthContent&quot;&gt;&lt;span data-url=&quot;https://blog.kakaocdn.net/dn/bprp4E/btqFHqMidtM/93qYKJggL6CeFvOKcmd1iK/img.png&quot; data-phocus=&quot;https://blog.kakaocdn.net/dn/bprp4E/btqFHqMidtM/93qYKJggL6CeFvOKcmd1iK/img.png&quot; data-alt=&quot;vultr products - VPS instance list&quot;&gt;&lt;img src=&quot;https://blog.kakaocdn.net/dn/bprp4E/btqFHqMidtM/93qYKJggL6CeFvOKcmd1iK/img.png&quot; srcset=&quot;https://img1.daumcdn.net/thumb/R1280x0/?scode=mtistory2&amp;fname=https%3A%2F%2Fblog.kakaocdn.net%2Fdn%2Fbprp4E%2FbtqFHqMidtM%2F93qYKJggL6CeFvOKcmd1iK%2Fimg.png&quot; data-origin-width=&quot;0&quot; data-origin-height=&quot;0&quot; width=&quot;550&quot; data-ke-mobilestyle=&quot;widthContent&quot; onerror=&quot;this.onerror=null; this.src='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png'; this.srcset='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png';&quot;/&gt;&lt;/span&gt;&lt;figcaption&gt;vultr products - VPS instance list&lt;/figcaption&gt;
&lt;/figure&gt;
&lt;/p&gt;
&lt;h3 data-ke-size=&quot;size23&quot;&gt;생성된 VPS 정보 보기&lt;/h3&gt;
&lt;p&gt;만들어진 서버 (Cloud Instance)를 클릭하여 들어가보면, 아래와 같이 public IP 정보와 Username 그리고 password가 있다. ssh key를 등록하지 않고, password 만으로 접속할 수 있으니 매우 편리하다. 간단하게 shell 로 접속하고 싶다면, 화면의 상단 오른쪽에 있는 아이콘 중에서, 모니터 모양 안에 &quot;&amp;gt;&quot; 가 있는 버튼을 누르면, 바로 console 로 접근 가능하다. 서버를 삭제하고 싶다면 화면의 상단 오른쪽에 있는 아이콘 중에서 휴지통 모양의 아이콘을 누르면 된다.&lt;/p&gt;
&lt;p&gt;&lt;figure class=&quot;imageblock alignCenter&quot; data-origin-width=&quot;0&quot; data-origin-height=&quot;0&quot; width=&quot;750&quot; data-ke-mobilestyle=&quot;widthContent&quot;&gt;&lt;span data-url=&quot;https://blog.kakaocdn.net/dn/bfAgqz/btqFIiAxrhF/nUSJXXpkK9Y8k8YqCJrHlk/img.png&quot; data-phocus=&quot;https://blog.kakaocdn.net/dn/bfAgqz/btqFIiAxrhF/nUSJXXpkK9Y8k8YqCJrHlk/img.png&quot; data-alt=&quot;vultr server information&quot;&gt;&lt;img src=&quot;https://blog.kakaocdn.net/dn/bfAgqz/btqFIiAxrhF/nUSJXXpkK9Y8k8YqCJrHlk/img.png&quot; srcset=&quot;https://img1.daumcdn.net/thumb/R1280x0/?scode=mtistory2&amp;fname=https%3A%2F%2Fblog.kakaocdn.net%2Fdn%2FbfAgqz%2FbtqFIiAxrhF%2FnUSJXXpkK9Y8k8YqCJrHlk%2Fimg.png&quot; data-origin-width=&quot;0&quot; data-origin-height=&quot;0&quot; width=&quot;750&quot; data-ke-mobilestyle=&quot;widthContent&quot; onerror=&quot;this.onerror=null; this.src='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png'; this.srcset='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png';&quot;/&gt;&lt;/span&gt;&lt;figcaption&gt;vultr server information&lt;/figcaption&gt;
&lt;/figure&gt;
&lt;/p&gt;
&lt;p&gt;이로써 Vultr에서 VPS를 생성하는 방법을 알아 보았다. 다음에는 기회가 되면 vultr에서 VPS를 발급 받고, k3s를 이용해서 kubernetes cluster를 구축하는 법을 알아 보겠다.&lt;/p&gt;
&lt;p&gt;&lt;span style=&quot;color: #333333;&quot;&gt;만약 vultr에 가입하실 거라면 아래 링크를 통해서 가입 부탁드립니다. 아래 링크로 가입을 하시고 $10 달러 이상 크레딧을 결재하시면, 가입하신분께 30일동안 사용 가능한 추가 $100 크레딧을 제공해, 총 $110 크레딧을 사용하실 수 있습니다. $25 크레딧을 충전하시면 30일동안 사용가능한 추가 $100 크레딧을 제공해, 총 $125 크레딧을 받으실 수 있으며, 저에게도 $25 크레딧이 제공됩니다. 이왕이면... $25 크레딧 충전 부탁드립니다. ^_^&lt;/span&gt;&lt;/p&gt;
&lt;p style=&quot;text-align: center;&quot;&gt;&lt;a href=&quot;https://www.vultr.com/?ref=8582867-6G&quot;&gt;www.vultr.com/?ref=8582867-6G&lt;/a&gt;&lt;/p&gt;
&lt;p style=&quot;text-align: center;&quot;&gt;&lt;a href=&quot;https://www.vultr.com/?ref=7487482&quot;&gt;&lt;img src=&quot;https://www.vultr.com/media/banners/banner_728x90.png&quot; width=&quot;728&quot; height=&quot;90&quot; /&gt;&lt;/a&gt;&lt;/p&gt;</description>
      <category>공부/컴퓨터</category>
      <category>server</category>
      <category>VPS</category>
      <category>VULTR</category>
      <category>서버</category>
      <author>찬</author>
      <guid isPermaLink="true">https://blog.ggaman.com/1019</guid>
      <comments>https://blog.ggaman.com/1019#entry1019comment</comments>
      <pubDate>Thu, 16 Jul 2020 00:17:58 +0900</pubDate>
    </item>
    <item>
      <title>[책읽기] 엔지니어를 위한 인터넷 전화와 SIP의 이해 - 우병수, 부크크</title>
      <link>https://blog.ggaman.com/1017</link>
      <description>&lt;h1&gt;공부 겸.. 업무 겸(?)...&lt;/h1&gt;
&lt;ul&gt;
&lt;li&gt;전화 시스템에 대해서 알아야 할 필요가 있기도 했고, 궁금하기도 했다.&lt;/li&gt;
&lt;li&gt;온라인에서 이것저것을 찾아서 볼 수 있지만... 참... 온라인 자료는 정리가 잘 되어 있지 않다.&lt;/li&gt;
&lt;li&gt;그러다 한 블로그를 찾았는데 ( &lt;a href=&quot;https://www.nexpert.net/715&quot;&gt;https://www.nexpert.net/715&lt;/a&gt; ) 글이 잘 정리 되어 있었다.&lt;/li&gt;
&lt;li&gt;마침 책이 나왔다는 글까지 봐서 책을 구매.&lt;/li&gt;
&lt;li&gt;가격은 좀 비싼 편(2.8만원)이다. 부크크( &lt;a href=&quot;http://www.bookk.co.kr/&quot;&gt;http://www.bookk.co.kr/&lt;/a&gt; ) 라는 플랫폼을 이용하는데, 자가 출판 플랫폼이다.&lt;/li&gt;
&lt;li&gt;그러니.. 좀 비쌀 수 밖에... 하지만 책 쓴 사람에게 많이 돌아 간다면 이해 할 수 있다. ( 회사 돈이기도 했고 ㅎ )&lt;/li&gt;
&lt;/ul&gt;
&lt;h1&gt;책 읽기 전&lt;/h1&gt;
&lt;ul&gt;
&lt;li&gt;이 책에서는 전화망에 관련되는 정보가 있다.&lt;/li&gt;
&lt;li&gt;그 중에서도 SIP 관련 내용이 대부분이나, &quot;전화망 그 자체&quot;에 대한 정보도 많기 때문에, 전화망에 대해서 궁금하다면 읽으면 괜찮을거라 생각한다.&lt;/li&gt;
&lt;li&gt;다만, 원래 관심이 있었던 IP-PBX 에 대한 내용은 단어 정도만 나오긴 하지만...&lt;/li&gt;
&lt;li&gt;그래도, 많은 도움이 되었다.&lt;/li&gt;
&lt;/ul&gt;
&lt;h1&gt;책 정리&lt;/h1&gt;
&lt;ul&gt;
&lt;li&gt;이 책의 주요 내용을 모두 요약하는건 너무 어렵다. 특히 SIP 프로토콜 부분은 RFC 문서를 설명하고 있는거라서...&lt;/li&gt;
&lt;li&gt;그러므로, 전화망에 대한 몇가지 지식이나, 용어들에 대해서만 정리 하도록 하겠다.&lt;/li&gt;
&lt;/ul&gt;
&lt;h1&gt;정리&lt;/h1&gt;
&lt;h2&gt;주요 용어&lt;/h2&gt;
&lt;ul&gt;
&lt;li&gt;
&lt;p&gt;IDF : Intermediate Distribution Frame, 중간 단자함. 각층에 전화선이 모여 있는 곳. 아파트에서는 각층의 계단실 같은곳에 있다.&lt;/p&gt;
&lt;/li&gt;
&lt;li&gt;&lt;figure class=&quot;imageblock alignCenter&quot; width=&quot;100%&quot;&gt;&lt;span data-url=&quot;https://blog.kakaocdn.net/dn/Qzqh1/btqxqK0D4OP/qCgUKryrV20u3liADeZ8jk/img.png&quot; data-phocus=&quot;https://blog.kakaocdn.net/dn/Qzqh1/btqxqK0D4OP/qCgUKryrV20u3liADeZ8jk/img.png&quot;&gt;&lt;img src=&quot;https://blog.kakaocdn.net/dn/Qzqh1/btqxqK0D4OP/qCgUKryrV20u3liADeZ8jk/img.png&quot; srcset=&quot;https://img1.daumcdn.net/thumb/R1280x0/?scode=mtistory2&amp;amp;fname=https%3A%2F%2Fblog.kakaocdn.net%2Fdn%2FQzqh1%2FbtqxqK0D4OP%2FqCgUKryrV20u3liADeZ8jk%2Fimg.png&quot; width=&quot;100%&quot; onerror=&quot;this.onerror=null; this.src='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png'; this.srcset='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png';&quot;/&gt;&lt;/span&gt;&lt;/figure&gt;
&lt;/li&gt;
&lt;li&gt;
&lt;p&gt;MDF : Main Distribution Frame. 주 배선반. 국선 단자함. 건물의 지하나 1층에 모든 전화선이 모이는곳.&lt;/p&gt;
&lt;/li&gt;
&lt;li&gt;&lt;figure class=&quot;imageblock alignCenter&quot; width=&quot;100%&quot;&gt;&lt;span data-url=&quot;https://blog.kakaocdn.net/dn/O7P7o/btqxsBVOJjA/w31m0Qm6kylomb8kmG5Ju1/img.png&quot; data-phocus=&quot;https://blog.kakaocdn.net/dn/O7P7o/btqxsBVOJjA/w31m0Qm6kylomb8kmG5Ju1/img.png&quot;&gt;&lt;img src=&quot;https://blog.kakaocdn.net/dn/O7P7o/btqxsBVOJjA/w31m0Qm6kylomb8kmG5Ju1/img.png&quot; srcset=&quot;https://img1.daumcdn.net/thumb/R1280x0/?scode=mtistory2&amp;amp;fname=https%3A%2F%2Fblog.kakaocdn.net%2Fdn%2FO7P7o%2FbtqxsBVOJjA%2Fw31m0Qm6kylomb8kmG5Ju1%2Fimg.png&quot; width=&quot;100%&quot; onerror=&quot;this.onerror=null; this.src='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png'; this.srcset='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png';&quot;/&gt;&lt;/span&gt;&lt;/figure&gt;
&lt;/li&gt;
&lt;li&gt;&lt;figure class=&quot;imageblock alignCenter&quot; width=&quot;100%&quot;&gt;&lt;span data-url=&quot;https://blog.kakaocdn.net/dn/dCYHry/btqxqJ1MTkz/kflxKzKuTVSWKGXfNz9850/img.png&quot; data-phocus=&quot;https://blog.kakaocdn.net/dn/dCYHry/btqxqJ1MTkz/kflxKzKuTVSWKGXfNz9850/img.png&quot;&gt;&lt;img src=&quot;https://blog.kakaocdn.net/dn/dCYHry/btqxqJ1MTkz/kflxKzKuTVSWKGXfNz9850/img.png&quot; srcset=&quot;https://img1.daumcdn.net/thumb/R1280x0/?scode=mtistory2&amp;amp;fname=https%3A%2F%2Fblog.kakaocdn.net%2Fdn%2FdCYHry%2FbtqxqJ1MTkz%2FkflxKzKuTVSWKGXfNz9850%2Fimg.png&quot; width=&quot;100%&quot; onerror=&quot;this.onerror=null; this.src='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png'; this.srcset='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png';&quot;/&gt;&lt;/span&gt;&lt;/figure&gt;
&lt;/li&gt;
&lt;li&gt;
&lt;p&gt;PBX : Private Branch Exchange, 사설 전화를 처리하는 장비. 내선은 외부로 나가지 않고, PBX에서 내선끼리 연결된다. 작은것은 전화 2개 정도만 처리하는것도 있고, 많은것은 100개 가까이 처리하는것도 있는듯 하다.&lt;/p&gt;
&lt;/li&gt;
&lt;li&gt;
&lt;p&gt;PSTN : Public Switched Telephone Network. 일반 전화망. 전화선이 모인 PBX 끼리 연결된 전화망이라고 생각하면 된다.&lt;/p&gt;
&lt;/li&gt;
&lt;li&gt;
&lt;p&gt;DTMF : Dual Tone Multiple Frequency. 전화 번호를 누르면 나오는 &quot;띠 띠&quot; 소리가, 2가지 이상의 톤을 같이 소리를 내는것이다. 이 정보를 이용해서 전화 번호를 전달한다.&lt;/p&gt;
&lt;/li&gt;
&lt;li&gt;
&lt;p&gt;FXO : 전화기 포트 중, 전화국에서 오는쪽의 포트. 즉 벽에 설치 되어 있는 포트를 FXO 라고 부른다.&lt;/p&gt;
&lt;/li&gt;
&lt;li&gt;
&lt;p&gt;FXS : 전화기 포트 중, 전화기에 연결되는 쪽의 포트. 즉 전화가에 연결되는 포트를 FXS 라고 부른다.&lt;/p&gt;
&lt;/li&gt;
&lt;li&gt;
&lt;p&gt;IP-BPX : Internet Protocol Private Branch Exchange. PBX와 동일하지만, 전화선이 아니라, 인터넷을 이용해서 전화를 할 수 있도록 하는 장비&lt;/p&gt;
&lt;/li&gt;
&lt;li&gt;
&lt;p&gt;VoIP : Voice over Internet Protocol. 인터넷을 이용하는 전화. 일반적으로는 인터넷을 이용해서 전화를 걸고 이를 PSTN(일반전화선)으로 전화를 받거나, 그 반대의 경우에 대해서 이야기 한다. 아날로그 음성을 디지털 패킷으로, 디지털 패킷을 아날로그 음성으로 변환해야 하는데, VoIP GateWay가 이 역할을 한다.&lt;/p&gt;
&lt;/li&gt;
&lt;li&gt;
&lt;p&gt;SIP : 일반전화선이 아니라, 인터넷을 이용해서 전화를 할 수 있는 프로토콜. 인터넷을 이용해서 전화를 할 수 있는 몇가지 프로토콜이 있긴한데, 현재는 다들 SIP를 쓰고 있음.&lt;/p&gt;
&lt;/li&gt;
&lt;li&gt;
&lt;p&gt;아.. 귀찮으니깐 나머지는 생략... ㅎㅎ.&lt;/p&gt;
&lt;/li&gt;
&lt;/ul&gt;</description>
      <category>공부/컴퓨터</category>
      <category>sip</category>
      <category>VoIP</category>
      <category>부크크</category>
      <category>엔지니어를 위한 인터넷 전화와 SIP의 이해</category>
      <category>우병수</category>
      <category>인터넷전화</category>
      <category>책읽기</category>
      <author>찬</author>
      <guid isPermaLink="true">https://blog.ggaman.com/1017</guid>
      <comments>https://blog.ggaman.com/1017#entry1017comment</comments>
      <pubDate>Tue, 13 Aug 2019 23:52:41 +0900</pubDate>
    </item>
    <item>
      <title>크롬에서 팝업으로 광고창이 뜰때... 분명 팝업창이 막혔는데... - 4탄</title>
      <link>https://blog.ggaman.com/1016</link>
      <description>&lt;h1&gt;허허 참.... 또 뜬다..&lt;/h1&gt;
&lt;ul&gt;
&lt;li&gt;근데 좀 다른 점을 발견해서 기록으로...&lt;/li&gt;
&lt;li&gt;테스트 주소 : &lt;a href=&quot;http://woollimcoop.org/?m=bbs&amp;amp;bid=contact&quot;&gt;http://woollimcoop.org/?m=bbs&amp;amp;bid=contact&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;재현 방법 : 위 주소에 접속 한 뒤, F5를 빠른 시간에 여러번 누른다.&lt;/li&gt;
&lt;/ul&gt;
&lt;h1&gt;문제 추측 1 : 팝업을 허용한 곳이 있어서 생기는 문제 아닐까?&lt;/h1&gt;
&lt;ul&gt;
&lt;li&gt;웹브라우져를 사용하다보면 팝업을 허용하게 되는곳이 한 두 곳 생긴다.&lt;/li&gt;
&lt;li&gt;팝업을 허용한 A 사이트가 있고, B 사이트에서는 팝업이 막혀 있을때, B 사이트에서 A 사이트의 url을 iframe 등으로 loading 한 뒤, popup을 띄우면 가능하지 않을까? 라는 생각이 들었다.&lt;/li&gt;
&lt;li&gt;그래서 팝업이 허용되어있는 모든 사이트를 삭제 했지만, 팝업이 뚫고 나오는 문제 발생&lt;/li&gt;
&lt;li&gt;재현 동영상&lt;/li&gt;
&lt;li&gt;&amp;nbsp;&lt;/li&gt;
&lt;/ul&gt;

            &lt;figure class=&quot;unsupported component-kakaotv&quot; contenteditable=&quot;false&quot; style=&quot;background:#000;margin:16px 0;min-height:72px;padding:10px 16px;display:flex;align-items:center;justify-content:center;text-align:center;box-sizing:border-box;width:100%;max-width:100%;&quot;&gt;
                &lt;p contenteditable=&quot;false&quot; style=&quot;margin:0;color:#8a8a8a;font-size:13px;line-height:1.6;user-select:none;pointer-events:none;&quot;&gt;동영상 서비스가 종료되어 해당 콘텐츠를 재생할 수 없습니다.&lt;/p&gt;
            &lt;/figure&gt;
        
&lt;h1&gt;문제 추측 2 : 익스텐션(확장) 문제가 아닐까?&lt;/h1&gt;
&lt;ul&gt;
&lt;li&gt;설치된 익스텐션 중 일부의 기능이나, 버그를 우회해서 팝업을 띄우는게 아닐까 생각을 해 보았다.&lt;/li&gt;
&lt;li&gt;그래서 모든 익스텐션을 disable 시키고 테스트 해 보았으나, 실패...&lt;/li&gt;
&lt;li&gt;그래서 팝업 블로커 익스텐션만 따로 설치해서 테스트 해도, 팝업이 뚫고 나오는 문제 발생&lt;/li&gt;
&lt;li&gt;재현 동영상&lt;/li&gt;
&lt;li&gt;&amp;nbsp;&lt;/li&gt;
&lt;/ul&gt;

            &lt;figure class=&quot;unsupported component-kakaotv&quot; contenteditable=&quot;false&quot; style=&quot;background:#000;margin:16px 0;min-height:72px;padding:10px 16px;display:flex;align-items:center;justify-content:center;text-align:center;box-sizing:border-box;width:100%;max-width:100%;&quot;&gt;
                &lt;p contenteditable=&quot;false&quot; style=&quot;margin:0;color:#8a8a8a;font-size:13px;line-height:1.6;user-select:none;pointer-events:none;&quot;&gt;동영상 서비스가 종료되어 해당 콘텐츠를 재생할 수 없습니다.&lt;/p&gt;
            &lt;/figure&gt;
        
&lt;h1&gt;그래서..&lt;/h1&gt;
&lt;ul&gt;
&lt;li&gt;걍 이쯤에서 포기하고 크롬을 새로 깔까 생각중...;;;&lt;/li&gt;
&lt;/ul&gt;</description>
      <category>공부/컴퓨터</category>
      <category>popup</category>
      <category>블로커</category>
      <category>크롬</category>
      <category>팝업</category>
      <category>팝업블로커</category>
      <author>찬</author>
      <guid isPermaLink="true">https://blog.ggaman.com/1016</guid>
      <comments>https://blog.ggaman.com/1016#entry1016comment</comments>
      <pubDate>Mon, 22 Jul 2019 09:00:00 +0900</pubDate>
    </item>
    <item>
      <title>크롬에서 팝업으로 광고창이 뜰때... 분명 팝업창이 막혔는데... - 3탄</title>
      <link>https://blog.ggaman.com/1015</link>
      <description>&lt;h1&gt;오늘 또.. 광고가...&lt;/h1&gt;
&lt;ul&gt;
&lt;li&gt;광고 팝업 관련해서 댓글을 달아 주신 내용을 참고삼아 #_enliple 을 검색해 보았더니.. 세상에. 링크의 끝에 #_enliple 이 붙어 있는게 너무 많더라...&lt;/li&gt;
&lt;li&gt;&lt;figure class=&quot;imageblock alignCenter&quot; width=&quot;100%&quot;&gt;&lt;span data-url=&quot;https://blog.kakaocdn.net/dn/cKVbOU/btqwk90D8Kc/EqHFUPaAmQ9pzumIxzS0z0/img.png&quot; data-phocus=&quot;https://blog.kakaocdn.net/dn/cKVbOU/btqwk90D8Kc/EqHFUPaAmQ9pzumIxzS0z0/img.png&quot;&gt;&lt;img src=&quot;https://blog.kakaocdn.net/dn/cKVbOU/btqwk90D8Kc/EqHFUPaAmQ9pzumIxzS0z0/img.png&quot; srcset=&quot;https://img1.daumcdn.net/thumb/R1280x0/?scode=mtistory2&amp;amp;fname=https%3A%2F%2Fblog.kakaocdn.net%2Fdn%2FcKVbOU%2Fbtqwk90D8Kc%2FEqHFUPaAmQ9pzumIxzS0z0%2Fimg.png&quot; width=&quot;100%&quot; onerror=&quot;this.onerror=null; this.src='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png'; this.srcset='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png';&quot;/&gt;&lt;/span&gt;&lt;/figure&gt;
&lt;/li&gt;
&lt;/ul&gt;
&lt;h2&gt;어떻게 저 링크가 만들어 질까?&lt;/h2&gt;
&lt;ul&gt;
&lt;li&gt;궁금해서 링크가 있는것들을 찾아서 소스코드를 확인해 보니 자주 보였던 mobon.net 이 보인다. 그리고 dreamsearch.or.kr 도 보인다.&lt;/li&gt;
&lt;li&gt;&lt;figure class=&quot;imageblock alignCenter&quot; width=&quot;100%&quot;&gt;&lt;span data-url=&quot;https://blog.kakaocdn.net/dn/bpIEUK/btqwmuiSy8y/J0D4JXwx88kO3WLR5MhAPK/img.png&quot; data-phocus=&quot;https://blog.kakaocdn.net/dn/bpIEUK/btqwmuiSy8y/J0D4JXwx88kO3WLR5MhAPK/img.png&quot;&gt;&lt;img src=&quot;https://blog.kakaocdn.net/dn/bpIEUK/btqwmuiSy8y/J0D4JXwx88kO3WLR5MhAPK/img.png&quot; srcset=&quot;https://img1.daumcdn.net/thumb/R1280x0/?scode=mtistory2&amp;amp;fname=https%3A%2F%2Fblog.kakaocdn.net%2Fdn%2FbpIEUK%2FbtqwmuiSy8y%2FJ0D4JXwx88kO3WLR5MhAPK%2Fimg.png&quot; width=&quot;100%&quot; onerror=&quot;this.onerror=null; this.src='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png'; this.srcset='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png';&quot;/&gt;&lt;/span&gt;&lt;/figure&gt;
&lt;/li&gt;
&lt;li&gt;mobon.net에서 사용되는 javascript는 아래와 같다.
&lt;ul&gt;
&lt;li&gt;&lt;figure class=&quot;imageblock alignCenter&quot; width=&quot;100%&quot;&gt;&lt;span data-url=&quot;https://blog.kakaocdn.net/dn/nsYIS/btqwky0QGPG/zsmgJTwSJlVCMo42Ni6Ki0/img.png&quot; data-phocus=&quot;https://blog.kakaocdn.net/dn/nsYIS/btqwky0QGPG/zsmgJTwSJlVCMo42Ni6Ki0/img.png&quot;&gt;&lt;img src=&quot;https://blog.kakaocdn.net/dn/nsYIS/btqwky0QGPG/zsmgJTwSJlVCMo42Ni6Ki0/img.png&quot; srcset=&quot;https://img1.daumcdn.net/thumb/R1280x0/?scode=mtistory2&amp;amp;fname=https%3A%2F%2Fblog.kakaocdn.net%2Fdn%2FnsYIS%2Fbtqwky0QGPG%2FzsmgJTwSJlVCMo42Ni6Ki0%2Fimg.png&quot; width=&quot;100%&quot; onerror=&quot;this.onerror=null; this.src='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png'; this.srcset='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png';&quot;/&gt;&lt;/span&gt;&lt;/figure&gt;
&lt;/li&gt;
&lt;li&gt;코드를 간단히 설명하면,
&lt;ul&gt;
&lt;li&gt;해당 사이트에 접근해서 url에 #_enplie라는 hash가 없으면, url 주소의 끝에 #_enplie를 붙여 주고&lt;/li&gt;
&lt;li&gt;hash가 변경되면(위에서 변경했음), 광고로(adUrl)로 이동한다.&lt;/li&gt;
&lt;/ul&gt;
&lt;/li&gt;
&lt;li&gt;아래의 동영상 확인&lt;/li&gt;
&lt;/ul&gt;
&lt;/li&gt;
&lt;/ul&gt;

            &lt;figure class=&quot;unsupported component-kakaotv&quot; contenteditable=&quot;false&quot; style=&quot;background:#000;margin:16px 0;min-height:72px;padding:10px 16px;display:flex;align-items:center;justify-content:center;text-align:center;box-sizing:border-box;width:100%;max-width:100%;&quot;&gt;
                &lt;p contenteditable=&quot;false&quot; style=&quot;margin:0;color:#8a8a8a;font-size:13px;line-height:1.6;user-select:none;pointer-events:none;&quot;&gt;동영상 서비스가 종료되어 해당 콘텐츠를 재생할 수 없습니다.&lt;/p&gt;
            &lt;/figure&gt;
        
&lt;ul&gt;
&lt;li&gt;즉, 걍 저 스크립트가 페이지에 있으면 무조건 url의 hash 부분을 변경하게 된다.&lt;/li&gt;
&lt;li&gt;이후, 사용자들은 페이지를 facebook등에 공유하게 되면 url의 끝에 #_enplie가 붙어 있는 url을 공유하게 되는것이다.&lt;/li&gt;
&lt;li&gt;&amp;nbsp;얼마나 링크들이 많이 퍼졌는지를 트래킹하기 위한 정보로 사용하는것인지, 아니면 광고를 잘 처리 했다는 의미로 사용하는것인지는 모르겠다.&lt;/li&gt;
&lt;/ul&gt;
&lt;p&gt;오늘은 여기까지. ㅎ.&lt;/p&gt;</description>
      <category>공부/컴퓨터</category>
      <category>AD</category>
      <category>dreamsearch</category>
      <category>enplie</category>
      <category>mediacategory</category>
      <category>mobon</category>
      <category>popup</category>
      <category>광고</category>
      <category>엔플라이</category>
      <category>인플라이</category>
      <category>팝업</category>
      <author>찬</author>
      <guid isPermaLink="true">https://blog.ggaman.com/1015</guid>
      <comments>https://blog.ggaman.com/1015#entry1015comment</comments>
      <pubDate>Tue, 2 Jul 2019 12:55:00 +0900</pubDate>
    </item>
    <item>
      <title>크롬에서 팝업으로 광고창이 뜰때... 분명 팝업창이 막혔는데... - 2탄</title>
      <link>https://blog.ggaman.com/1014</link>
      <description>&lt;h1&gt;이런이런.. 또 뜨네?&lt;/h1&gt;
&lt;ul&gt;
&lt;li&gt;그래서 이제는 코드까지 좀 뜯어 보기로&lt;/li&gt;
&lt;/ul&gt;
&lt;h1&gt;코드는?&lt;/h1&gt;
&lt;ul&gt;
&lt;li&gt;YTN에 페이지에 접근했는데 광고가 또 떴다.&lt;/li&gt;
&lt;li&gt;그래서 이제는 코드를 뜯어 보기로...&lt;/li&gt;
&lt;li&gt;개발자 도구를 띄웠는데.. 에러가 나있네?&lt;/li&gt;
&lt;/ul&gt;
&lt;h2&gt;에러난쪽 코드 분석...&lt;/h2&gt;
&lt;ul&gt;
&lt;li&gt;에러는 다음의 jquery에서 시작된 것이다.&lt;ul&gt;
&lt;li&gt;&lt;img src=&quot;https://t1.daumcdn.net/cfile/tistory/9912523C5CF7EC4117&quot; alt=&quot;&quot;&gt;&lt;/li&gt;
&lt;li&gt;이 파일을 열어 보면, mobon.net 이라는 사이트에서 jquery를 다시 긁어 오는것을 알 수 있다.&lt;/li&gt;
&lt;li&gt;&lt;img src=&quot;https://t1.daumcdn.net/cfile/tistory/995FD7455CF7ECE52D&quot; alt=&quot;&quot;&gt;&lt;/li&gt;
&lt;li&gt;mobon.net 사이트에 접근해 보면, 이전에 조사한 사이트인 인라이플라는 회사가 연관되어 있는것을 알 수 있다.&lt;ul&gt;
&lt;li&gt;&lt;img src=&quot;https://t1.daumcdn.net/cfile/tistory/99D153455CF7E4B628&quot; alt=&quot;&quot;&gt;&lt;/li&gt;
&lt;li&gt;또한, mediacategory 사이트에 접근할때, megadata.co.kr 이라는 사이트에서 javascript를 다운 받는다. 해당 자바스크립트에는 인플라이 광고주 랜딩처리 한다고 적혀 있다.&lt;/li&gt;
&lt;li&gt;&lt;img src=&quot;https://t1.daumcdn.net/cfile/tistory/9936113C5CF7F7C122&quot; alt=&quot;&quot;&gt;&lt;/li&gt;
&lt;/ul&gt;
&lt;/li&gt;
&lt;/ul&gt;
&lt;/li&gt;
&lt;li&gt;또한 해당 코드를 확인해보면 의심스러운 몇가지 행동을 하는 코드를 발견할 수 있다.&lt;ul&gt;
&lt;li&gt;HTML Document에 a tag를 만들어 추가하고, click event를 발생시키는 코드. 이후 추가한 a tag를 제거하고 있다.&lt;ul&gt;
&lt;li&gt;&lt;img src=&quot;https://t1.daumcdn.net/cfile/tistory/997A0C3C5CF7E57216&quot; alt=&quot;&quot;&gt;&lt;/li&gt;
&lt;/ul&gt;
&lt;/li&gt;
&lt;li&gt;빈 새창을 띄우고, focus를 보낸뒤, 창을 닫는 동작&lt;ul&gt;
&lt;li&gt;&lt;img src=&quot;https://t1.daumcdn.net/cfile/tistory/99584D365CF7E5D92A&quot; alt=&quot;&quot;&gt;&lt;/li&gt;
&lt;/ul&gt;
&lt;/li&gt;
&lt;li&gt;모바일 웹 브라우져면 동작하지 않도록&lt;ul&gt;
&lt;li&gt;&lt;img src=&quot;https://t1.daumcdn.net/cfile/tistory/9917F2345CF7E77E18&quot; alt=&quot;&quot;&gt;&lt;/li&gt;
&lt;/ul&gt;
&lt;/li&gt;
&lt;li&gt;Safari면 동작하지 않도록&lt;ul&gt;
&lt;li&gt;&lt;img src=&quot;https://t1.daumcdn.net/cfile/tistory/998C34405CF7E7FF1B&quot; alt=&quot;&quot;&gt;&lt;/li&gt;
&lt;/ul&gt;
&lt;/li&gt;
&lt;li&gt;볼만한 부분은 아래 부분이다. 계속해서 about:blank popup을 만들고 focus를 주고, close 시키는것을 0.5초마다 반복하도록 한다.&lt;ul&gt;
&lt;li&gt;&lt;img src=&quot;https://t1.daumcdn.net/cfile/tistory/99C1BF425CF7E97327&quot; alt=&quot;&quot;&gt;&lt;/li&gt;
&lt;/ul&gt;
&lt;/li&gt;
&lt;/ul&gt;
&lt;/li&gt;
&lt;/ul&gt;
&lt;h1&gt;엇...&lt;/h1&gt;
&lt;ul&gt;
&lt;li&gt;실수로 창을 닫아 버렸다. -_-;;;&lt;/li&gt;
&lt;li&gt;오늘은 여기까지... ;;;&lt;/li&gt;
&lt;/ul&gt;</description>
      <category>공부/컴퓨터</category>
      <category>appier.net</category>
      <category>chrome</category>
      <category>enliple.com</category>
      <category>mediacatory.com</category>
      <category>mobon.net</category>
      <category>popup</category>
      <category>광고</category>
      <category>크롬</category>
      <category>팝업</category>
      <author>찬</author>
      <guid isPermaLink="true">https://blog.ggaman.com/1014</guid>
      <comments>https://blog.ggaman.com/1014#entry1014comment</comments>
      <pubDate>Thu, 6 Jun 2019 01:42:12 +0900</pubDate>
    </item>
  </channel>
</rss>