Go / Client-go

문학적인유사성·2024년 6월 1일
0

language

목록 보기
20/24

go 키워드 정리

c, c++를 대학교때 연구실폐관수련했더니, 새로 알게된 것만 적고 넘어가야겠다. 교수님... 빅피쳐가 있으셨군요...

Go 개발환경 설정 (go SDK 설치, go mod, go mod vendor, Package 선언 및 활용)

공식 홈페이지에서 환경에 맞게 다운로드 받으면 됨.

https://go.dev/dl/
go mod init ${module} #모듈 초기화
go mod tidy #사용하지 않는 모듈 제거 
go mod vendor # vendor하위에 모듈 종속성 붙혀넣기 / vcs에 포함시킴 / 폐쇄망에 미리 보관하는 느낌  
go mod download # go.mod 모듈 모두 다운로드 
go mod graph # 종속성 출력

go 언어는 패키지 선언으로 시작되어야한다.
package main은 프로그램 시작점이 있는 패키지다..
fmt패키지는 표준 입출력을 다루는 내장 패키지
대부분 c랑 비슷한 것같은데, 다른 점이 좀 있긴함..

go build를 하려면 반드시 go 모듈 루트 폴더에 go.mod파일이 있어야함.
go.mod + go.sum(외부저장소 패키지 버전 정보) => 실행파일

goproject % tree
.
├── go.mod
└── usepkg
    ├── custompkg
    │   └── custompkg.go
    ├── go.mod
    ├── go.sum
    └── usepkg.go
cd usepkg
go mod init goproject/usepkg
go mod tidy # 패키지 다운로드, go.mod , go.sum 파일 적어줌  
go build usepkg.go
./usepkg
package main

import (
	"fmt"
	"goproject/usepkg/custompkg" // 이렇게 다른 위치 모듈 가져오는 것도 가능
	"github.com/guptarohit/asciigraph" // 깃허브 모듈 가져오는 것도 가능
)

func main(){
	custompkg.PrintCustom()

	data := []float64{3,4,5,6,7}
	graph := asciigraph.Plot(data)
	fmt.Println(graph)
}

패키지에서 전역으로 선언된 첫 글자가 대문자로 시작하면 외부로 공개됨.
_ 를 쓰면 패키지에서 함수만 쓰는게 가능함.
go mod vendor를 쓰면 pkg 컴파일에 필요한 파일들이 vendor directory가 생기면서 복사 할수있음.
https://stackoverflow.com/questions/68544611/what-is-the-purpose-of-go-mod-vendor-command
https://doitnow-man.tistory.com/entry/go-go-mod-vendor-사용법

변수 선언: var, Short Variable, Declaration Operator(:=)

go build, go run
선언대입문 := 을 통해서 var키워드와 타입을 생략해서 선언가능
:= is for declaration + assignment, whereas = is for assignment only.
For example, var foo int = 10 is the same as foo := 10.

자동변환 없는거 기억하기.
scan()으로 받을 때, &로 받는거. c랑 똑같네..
대학교때 포인터 폐관수련덕분에 포인터는 쉽게 넘어갈수있을듯.

slice, map, pointer는 nil로 비교를 하게됨.
nil -> 정의되지 않은 메모리 주소를 나타내는 go 키워드

package main

import (
	"bufio"  // io 담당 패키지
	"fmt" // 표준 입출력
	"os" // 표준 입출력 등을 가지고 있는 패키지 
)

func main() {
	stdin := bufio.NewReader(os.Stdin) // 선언대입문

	var a int
	var b int

	n, err := fmt.Scanln(&a, &b)

	if err != nil { 
		fmt.Println(err)
		stdin.ReadString('\n') // 표준 입출력 스트림 지우는 것
	}else {
		fmt.Println(n, a, b)
	}

	n,err = fmt.Scanln(&a, &b)
	if err != nil {
		fmt.Println(err)
	}else{
		fmt.Println(n, a, b)
	}

}

실수 오차 : 가장 작은 오차를 엡실론이라고 가정할때, 정확하게 표현하지 못하는 수가 있는 경우 엡실론만큼 오차가 생기더라도 같은 걸로 생각할수있음. 2진수 컴퓨터 한계
iota 키워드 : iota는 0부터 시작해서 1씩 증가함. 소괄호를 벗어나면 초기화됨
쇼트서킷

조건문, loop문

if초기문, 선언문
go는 if문에서 선언하는 경우가 가끔씩있는데, try, catch가 없음
error를 알기위해서 if문 안에 함수에 error라는 변수를 줘서 (error는 포인터) error가 nil이 아니면 에러 처리를 하게 됨.

result, err := samplefunction()
	if err != nil {
		fmt.Println(err.Error())
	} else {
		fmt.Println("result")
	}
package main

import "fmt"

func getMyAge() (int, bool) {
	return 33, true
}

func main() {
	if age, ok := getMyAge(); ok && age < 20 { // 초기문을 넣을 수 있음. 초기문에서 선언한 범위는 if문으로 한정됨.
		fmt.Println("You are young", age)
	} else if ok && age < 30 {
		fmt.Println("Nice age", age)
	} else if ok {
		fmt.Println("You are beautiful", age)
	} else {
		fmt.Println("Error")
	}
//밑에서는 오류가 생기는걸 확인할수있음..! 
	// fmt.Println("your are is", age)
}

case초기문,선언문

package main

import "fmt" 

func getMyAge() int{
	return 33
}

func main() {
	switch age := getMyAge(); age { // switch문에서도 초기문을 넣을 수 있음. 
	case 10:
		fmt.Println("Teenage")
	case 33:
		fmt.Println("3 two")
		fallthrough // 다음 case문까지 실행
	default:
		fmt.Println("My age is", age)
	}
	// fmt.Println("age is", age)
}

배열 문법

package main
import "fmt"

func main(){
	var t [5]float64 = [5]float64{24.0, 25.9, 27.8, 20.2, 1.1}
	// var nums [5]int
	// days := [3]string{"m", "t", "w"}
	// var s = [5]int{1:10, 3:30}
	// x := [...]int{10, 20,30}

	for i :=0; i<5; i++ {
		fmt.Println(t[i])
	}

	for i, v := range t { // index, 변수값
		fmt.Println(i, v)
	}
	for _, v := range t { // index무효화가능, 변수값
		fmt.Println(v)
	}	

	// 행 열
	a := [2][5]int {
		{1,2,3,4,5},
		{6,7,8,9,10},
	} // }가  같은줄에 있으면 , 없어도됨

	for _, arr := range a {
		for _, v := range arr {
			fmt.Print(v, " ")
		}
	}
}

Struct

구조체 복사시 :=가능 (대입 연산자가 우변 값을 좌변 메모리 공가에 복사할때, 복하되는 크기는 타입 크기와 같음..! )
구조체 생성시 메모리 패딩을 고려할것
구조체에서 sort.Sort(구조체)를 쓰려면, Len(), Less(), Swap()을 정의해주면됨.

package main

import "fmt"

type User struct {
	Name string
	ID string
	Age int
	Level int
}

type VIPuser struct {
	User 
	Price int
	Level int
}

func main(){
	user := User{"Kyoin", "Kyo", 27, 10}
	vip := VIPuser{
		User{"In", "INN", 26, 12},
		200,
		3, // 쉼표 필수
	}
	fmt.Printf("유저: %s ID: %s 나이 %d\n", user.Name, user.ID, user.Age)
	fmt.Printf("VIP 유저: %s ID: %s 나이 %d VIP 레벨: %d 유저 레벨:%d\n",
		vip.Name,
		vip.ID,
		vip.Age,
		vip.Level,      
		vip.User.Level, 
	)
}

slice, map

slice : 동적 배열
append() <- 이거 내부적으로 다시 만들어서 리턴하는 구조로 되어있음
make() <- 널널하게 만들어두고, append를 다시하면 주소가 바뀌지않는다.
copy(),
따로 중간에 자르는 거 없음!
reflect패키지의 sliceHeader 구조체를 이용해서 내부 구현 확인

type SliceHeader struct{
	Data uintprt // 실제 배열을 가르키는 포인터
    Len int // 요소 개수
    Cap int // 실제 배열 길이
package main

import "fmt"

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


	for i, v := range slice1 {  // 각각 복사해줘야함
		slice2[i] = v
	}

	slice1[1] = 100 
	fmt.Println(slice1)
	fmt.Println(slice2)

	slice3 := make([]int, 3, 10) 
	slice4 := make([]int, 10) 

	cnt1 := copy(slice3, slice1) // copy() slice1 -> slice3
	cnt2 := copy(slice4, slice1)

	fmt.Println(cnt1, slice3)
	fmt.Println(cnt2, slice4)

	slice := []int{1,2,3,4,5,6,7}
	idx := 2 
	// for i := idx+1, i< len(slice);i++ {
	// 	slice[i-1] = slice[i]
	// }
	// slice = slice[:len(slice)-1] // 마지막 자르기

	slice = append(slice[:idx], slice[idx+1:]...) 
	fmt.Println(slice)

	slice7 := []int{1, 2, 3, 4, 5, 6}
	slice7 = append(slice7, 0)
	// for i := len(slice7) - 2; i >= idx; i-- {
	// 	slice7[i+1] = slice7[i]
	// }
	copy(slice7[idx+1:], slice7[idx:])
	slice7[idx] = 100
	fmt.Println(slice7)

}
package main

import "fmt"

type Vertex struct {
	Lat, Long float64
}

var m = map[string]Vertex{
	"Bell Labs": Vertex{
		40.68433, -74.39967,
	},
	"Google": Vertex{
		37.42202, -122.08408,
	},
}

func main() {
	fmt.Println(m)
}

Receiver function

리시버가 포인터인 경우 / 메서드에서 리시버의 값을 변경할 경우
포인터로 하는게, 복제 작업 성능 작업을 피할수있음
리시버가 값이 같아야될때는 불변성을 보장해야하기때문에, 기본타입의 경우에는 그냥 넘길때가 있다고 함.
리시버가 있으면 메소드, 없으면 일반함수
메서드를 이용해서 데이터와 기능을 묶을수있음=> 객체!
go는 클래스 상속 지원X
메소드와 인터페이스만 지원함

package main

import "fmt"

type account struct {
	balance   int
	firstName string
	lastName  string
}

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

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

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

func main() {
	var mainA *account = &account{100, "Kyoin", "Goyo"}
	mainA.withdrawPointer(30)
	fmt.Println(mainA.balance)

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

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

	mainB.withdrawPointer(30)
	fmt.Println(mainB.balance) 
}
package main

import (
	"fmt"
	"math"
)

type Vertex struct {
	X, Y float64
}

func (v Vertex) Abs() float64 {
	return math.Sqrt(v.X*v.X + v.Y*v.Y)
}

func (v *Vertex) Scale(f float64) {
	v.X = v.X * f
	v.Y = v.Y * f
}

func main() {
	v := Vertex{3, 4}
	v.Scale(10)
	fmt.Println(v.Abs())
}
package main

import "fmt"

type Data struct {
	value int
	data  [200]int
}

func ChangeData(arg *Data) { 
	arg.value = 999 
	arg.data[100] = 999
}


func NewUser(name string, age int) *User {
	var u = User{name, age}
	return &u  // 탈출 검사를 통해서, 외부로 공개되는 것 확인 - 힙 메모리에 할당하게됨.
}

func main() {
	var data Data

	ChangeData(&data)                      
	fmt.Printf("value = %d\n", data.value) 
	fmt.Printf("data[100] = %d\n", data.data[100])

	var data1 Data
	var p *Data = &data1

	var p *Data = &Data{} // 구조체 생성 초기화

	// 인스턴스 : 메모리에 할당된 데이터의 실체
	p1 := &Data{}
	var p2 = new(Data)

	userPointer := NewUser("AAA", 23)
	fmt.Println(userPointer)
}

len(),[]rune
string은 필드가 2개인 구조체

type StringHeader struct {
	Data uinptr // 문자열의 데이터가 있는 메모리 주소를 나타내는 구조체 
    Len int // 문자열 길이
}

Interface 선언 및 활용

구현을 포함하지 않는 메서드 집합
인터페이스만 가지고 메서드를 호출할수있음, 프로그램 변경시 유연하게 대처 가능
객체의 동작을 표현하는 수단을 제공함.

package main

import "fmt"

type Stringer interface { 
	String() string
}

type Student struct {
	Age int
}

func (s *Student) String() string { 
	return fmt.Sprintf("Student Age:%d", s.Age)
}

func PrintAge(stringer Stringer) { 
	s := stringer.(*Student) // 구체화된 다른 타입으로 변환이 가능함. 
	fmt.Printf("Age: %d\n", s.Age) 
}

func main() {
	student := Student{27} 
	var stringer Stringer 
	stringer = &student
	fmt.Printf("%s\n", stringer.String())

	s := &Student{15}

	PrintAge(s) 
}

Json marshal, unmarshal

Marshal : go object -> []byte, string 변환
unmarshal : []byte, string 변환 -> go object

Go 객체(예: struct, map 등)를 다른 형식으로 변환또는 그 반대로 변환하는 과정을 의미
일반적으로 JSON 형식으로 변환하는 것이 가장 흔함.
encoding/json 패키지를 사용

package main

import (
 "encoding/json"
 "fmt"
 "strings"
)

type Email string

func (e Email) MarshalJSON() ([]byte, error) {
 return json.Marshal(strings.ToLower(string(e)))
}

func (e *Email) UnmarshalJSON(data []byte) error {
 var s string
 err := json.Unmarshal(data, &s)
 if err != nil {
  return err
 }
 *e = Email(strings.ToLower(s))
 return nil
}

type Person struct {
 Name  string
 Email Email
}

func main() {
 p := Person{
  Name:  "Kyoin GU",
  Email: "kyoin@goyo.com",
 }

 jsonData, err := json.Marshal(p)
 if err != nil {
  fmt.Println("Error:", err)
  return
 }

 fmt.Println(string(jsonData))

 var decodedPerson Person
 err = json.Unmarshal(jsonData, &decodedPerson)
 if err != nil {
  fmt.Println("Error:", err)
  return
 }

 fmt.Printf("%+v\n", decodedPerson)
}

go.work

import를 했어도, 프로젝트 디렉토리 내 로컬에 있는 걸 바로 사용하게 하는 경우에 유용함
모듈 중복 문제도 해결할수있고, 디렉토리 별로 다르게 설정할수도있음.


Client-go, controller-runtime 활용

참고 : https://pkg.go.dev/k8s.io/client-go@v0.30.1/examples/create-update-delete-deployment#section-readme
https://blog.dsb.dev/posts/creating-dynamic-informers/
https://kubernetes.io/blog/2018/01/introducing-client-go-version-6/

minikube start --driver=docker --memory=4096
git clone https://github.com/kubernetes/client-go.git
cd examples/create-update-delete-deployment
kubectl get nodes
go build -o ./app
./app -kubeconfig=$HOME/.kube/config

예시로 만든 pod list
deploymentsClient.Delete(context.TODO(), "demo-deployment", metav1.DeleteOptions{
PropagationPolicy: &deletePolicy,
});

clientset.CoreV1().Pods("").List(context.TODO(), metav1.ListOptions{})

clientset.CoreV1().Pods(namespace).Delete(ctx, podName, metav1.DeleteOptions{
PropagationPolicy: &deletePolicy,
})

package main

import (
 "context"
 "flag"
 "fmt"
 "path/filepath"
 "k8s.io/client-go/kubernetes"
 "k8s.io/client-go/tools/clientcmd"
 "k8s.io/client-go/util/homedir"
 metav1 "k8s.io/apimachinery/pkg/apis/meta/v1"
)

func main() {
	var kubeconfig *string
	if home := homedir.HomeDir(); home != "" {
		kubeconfig = flag.String("kubeconfig", filepath.Join(home, ".kube", "config"), "(optional) absolute path to the kubeconfig file")
	} else {
		kubeconfig = flag.String("kubeconfig", "", "absolute path to the kubeconfig file")
	}
	flag.Parse()

	config, err := clientcmd.BuildConfigFromFlags("", *kubeconfig)
	if err != nil {
		panic(err)
	}
	clientset, err := kubernetes.NewForConfig(config)
	if err != nil {
		panic(err)
	}


  pods, _ := clientset.CoreV1().Pods("").List(context.TODO(), metav1.ListOptions{})
  fmt.Printf("There are %d pods in the cluster\n", len(pods.Items))

}

  • Generic Dynamic Client 가 필요한 이유와 사용 방법
    컴파일 시점에 리소스 타입을 알 수 없거나, 변경될 수 있는 커스텀 리소스(CRD)와 작업할 때 특히 유용
go build -o generic generic.go
kubectl run nginx --image=nginx
./generic --kubeconfig $HOME/.kube/config -name nginx -resource pods
package main

import (
    "context"
    "flag"
    "fmt"
    "os"

    "k8s.io/client-go/dynamic"
    "k8s.io/client-go/tools/clientcmd"
    metav1 "k8s.io/apimachinery/pkg/apis/meta/v1"
    "k8s.io/apimachinery/pkg/runtime/schema"
)

func main() {
    kubeconfig := flag.String("kubeconfig", "", "path to the kubeconfig file")
    namespace := flag.String("namespace", "default", "namespace to interact with")
    resource := flag.String("resource", "", "resource type to interact with (e.g., pods, deployments)")
    name := flag.String("name", "", "name of the resource instance")
    flag.Parse()

    if *resource == "" || *name == "" {
        fmt.Fprintln(os.Stderr, "Please provide both resource type and resource name.")
        flag.Usage()
        os.Exit(1)
    }

    config, err := clientcmd.BuildConfigFromFlags("", *kubeconfig)
    if err != nil {
        fmt.Fprintf(os.Stderr, "Error creating Kubernetes client configuration: %v\n", err)
        os.Exit(1)
    }

    dynamicClient, err := dynamic.NewForConfig(config)
    if err != nil {
        fmt.Fprintf(os.Stderr, "Error creating dynamic client: %v\n", err)
        os.Exit(1)
    }

    gvr := schema.GroupVersionResource{Group: "", Version: "v1", Resource: *resource}
    resourceInterface := dynamicClient.Resource(gvr).Namespace(*namespace)

    ctx := context.Background()
    resourceInstance, err := resourceInterface.Get(ctx, *name, metav1.GetOptions{})
    if err != nil {
        fmt.Fprintf(os.Stderr, "Error getting resource: %v\n", err)
        os.Exit(1)
    }

    fmt.Printf("Resource details: %v\n", resourceInstance)
}

고루틴, 뮤텍스, 리터럴, 에러 핸들링, 채널, 컨텍스트 쪽 알아두면 더 좋을 꺼같음.
gorilla/mux로 crwd만들어보는것도 좋은듯?

오퍼레이터 패턴

admissioncontroller의 한 기능
ex) crd를 배포하면, api서버가 새로운 타입이 들어왔을때 validation을 crd에 정의한대로 pass해줄게. GVR, GVK
==> 프로그램의 확장이 가능해진다. !!!

참고

https://codecollector.tistory.com/1552
https://mehranjnf.medium.com/delete-kubernetes-pods-with-golang-and-a-dash-of-fun-97929e18d029
https://codecollector.tistory.com/1552
https://soyoung-new-challenge.tistory.com/130
https://comdoc.tistory.com/entry/golang-array-slice-map-함께-쓰기
https://forum.golangbridge.org/t/how-to-cast-interface-to-a-given-interface/13997
https://medium.com/cloud-native-daily/working-with-kubernetes-using-golang-a3069d51dfd6
https://github.com/kubernetes/client-go/tree/v0.29.3/examples/create-update-delete-deployment
https://medium.com/@chaewonkong/go-and-json-a-comprehensive-guide-to-working-with-json-in-golang-143fa2dfa897
https://etloveguitar.tistory.com/44
https://go.dev/tour/basics/1
https://pkg.go.dev/k8s.io/client-go@v0.30.1/examples/create-update-delete-deployment#section-readme
https://github.com/kubernetes/client-go
tucker go programming
Learning Go

profile
Are you nervous? Don't be

0개의 댓글