레이블이 golang인 게시물을 표시합니다. 모든 게시물 표시
레이블이 golang인 게시물을 표시합니다. 모든 게시물 표시

gofumpt

neovim(nvim) gopls 설정을 업데이트한 후 .go 파일을 저장하면
var 로 선언된 변수가 := 로 간단하게 변경된다.

찾아보니 gopls 설정에 gofumpt(https://github.com/mvdan/gofumpt) 활성화되어 있었다.

gofumpt 는 gofmt, goimports 기능을 포함하면서 더 엄격한 포맷팅 규칙을 적용하는 확장된 포맷팅 방식이다.
그래서 gofumpt 포맷팅된 코드는 gofmt, goimports 검사를 통과한다고 한다.
요런 규칙들이 있다.
- 복합 리터럴에서 선행 또는 후행 빈 줄 제거
- 할당 연산자 다음의 불필요한 빈 줄 제거
- 인터페이스에서 불필요한 빈 줄 제거
- 가독성을 위해 함수에서 빈 줄 대신 ) { 라인 사용

# 바이너리 설치
go install mvdan.cc/gofumpt@latest

# 이전의 .go 파일들에 대해 포맷팅을 해봤는데 개인적으로 깔끔해져서 가독성에 더 좋은것 같다.
gofumpt -w -l $(fd --type file .go)

# 참고, gopls 체크사항 확인
gopls check $(fd --type file .go)

argocd go module invalid version error

# go.mod 에 argo-cd 패키지가 됐는데 다음과 같이 -m(모든 종속성 패키지 출력) 로 모듈 리스트 출력시 에러가 발생한다.
# (재현을 위해 sudo rm -rf ~/workspace/gopath/pkg/mod/k8s.io/)

# 다음 가이드로 go.mod 에 replace 추가해야 한다.

golang pthread_create failed

# github action 과 비슷한 사내 환경에서 golang 이미지 기반의 빌드로 docker build 를 수행하면 다음과 같은 에러가 발생한다.
RUN go install github.com/swaggo/swag/cmd/swag@latest && swag init
go: downloading golang.org/x/sys v0.18.0
runtime/cgo: pthread_create failed: Operation not permitted
SIGABRT: abort

# 이 현상으로 이미 이슈가 등록되어 있지만 아직 해결되진 않았다.

근본적인 원인은 libseccomp 라는 Debian Debian Bookworm 패키지/라이브러리에 사용되는 새로운 syscall 이 차단되었기 때문이라고 한다. 

golang:1.23.3 (태그 버전에 suffix 가 없으면 현재 debian bookworm 버전임)
이미지를 사용할때 발생한 문제로
위 글중에 bullseye 를 사용하면 된다고 하는 커멘트가 있어 
golang:1.23.3-bullseye 로 변경해서 시도하니 된다.

# 참고로 로컬에 이미지 다운 받아 버전 확인해보면 debian 버전이 다르다.
docker run -it golang:1.23.3

docker run -it golang:1.23.3-bullseye

# 참고 golang 이미지

gup

go install 로 $GOPATH/bin 설치한 툴을 업데이트는 요렇게 한다.
ex) go install github.com/vektra/mockery/v2@v2.49.0

$GOPATH/bin 에 많은 툴들을 한번에 업데이트가 되면 좋을 텐데, 현재는 공식적으론 안되고 각각 위와 같이 업데이트를 해줘야 된다.
그래서 찾아보니 reddit 형들이 요런 툴을 알려주네~

# gup 설치
go install github.com/nao1215/gup@latest

# go install 로 설치된 패키지 모두 업데이트
gup update

참고로 이번에 brew 와 go 중복으로 설치된 패키지들을 파악해서 한쪽은 제거하자.

golang gomaxprocs in k8s

golang garbage collector(GC) 는 데이터 무결성을 위해 stop-the-world(일시정지)가 필요하다.

linux 스케줄러 Completely Fair Scheduler(CFS) 에서 프로세스를 cpu(core) 시간에 할당한다.
golang 은 container(linux cfs 기반)의 cpu (시간)제한을 인지하지 못해 일시정지가 발생할 수 있다.

GOMAXPROCS 를 k8s cpu limit 와 일치 시키면 gc 로 인한 일시정지를 줄 일 수 있다.
GOMAXPROCS 를 k8s cpu limit 와 일치시키기
ubuer automaxprocs 를 golang main 에 import 하면 되지만 GOMEMLIMIT 는 지원하지 않는다.

대신 환경변수로 GOMAXPROCS, GOMEMLIMIT 로 pod > resource > limits 를 설정하면 된다.

golang main() 에 다음을 추가해 프로그램 시작시 GOMAXPROCS, GOMEMLIMIT 값을 찍어보자.
기본적으로 최대 core 개수가 찍힌다.
fmt.Printf("GOMAXPROCS: %d\n", runtime.GOMAXPROCS(0))
fmt.Printf("GOMEMLIMIT: %d\n", debug.SetMemoryLimit(-1))

이제 deployment 에 다음과 같이 GOMAXPROCS, GOMEMLIMIT 환경변수를 적용하면
pod(container)가 새로 시작되고 golang 프로그램의 GOMAXPROCS, GOMEMLIMIT 에 반영된다.
spec:
  template:
    spec:
      containers:
      - name: ysoftman-app
        resources:
          requests:
            cpu: "2000m"
            memory: "2048Mi"
          limits:
            cpu: "2000m"
            memory: "2048Mi"
        env:
        - name: GOMEMLIMIT
          valueFrom:
            resourceFieldRef:
              resource: limits.memory
        - name: GOMAXPROCS
          valueFrom:
            resourceFieldRef:
              resource: limits.cpu

golang disassembly

# golang binary disassembly
go tool objdump -S exec_file_path

# 위 방식은 수백메가 이상의 파일을 생성하며 보기도 쉽지 않다.
# lensm 을 사용해보자.
# 설치
go install loov.dev/lensm@main

# 사용하기
# -watch  auto reload executable
# -filter filter the functions by regexp
# main(hello_world.go) 바이너리 assembly 보기
lensm -watch -filter main main

# 요렇게 창이 뜨고 소스 라인 별로 어셈블리 코드를 편하게 확인할 수 있다.

golang package name

# golang 에서 package(module)명은 지을 때 소문자로 모두 붙여쓰라고 한다.
# camel_case, snake_case 등은 사용하지 말고
computeServiceClient
priority_queue

# 다음과 같이 소문자로만 의미있게 축약하는것이 좋다고 한다.
strconv (string conversion)
syscall (system call)
fmt (formatted I/O)

# k8s 소스에서 package 를 검색해봤다.(정규식에서 길이 조건을 쓸수 없다.)
# syscall 처럼 의미 있게 축약되면 좋지만 client, pod 이런 단어가 조합되는 경우 축약이 힘들어 보인다.

# k8s 소스를 다운받아 package 이름 긴것들을 확인해 보면 이렇다.
# 소문자까지는 좋은데 넘 길면 갠적으로 snake_case 가 더 눈에 잘 들어온다.
rg -IN "^package [[:alpha:]]{10,50}$" | awk '{print length, $0}' | sort -r | uniq | head -10
40 package validatingadmissionpolicybinding
39 package validatingadmissionpolicystatus
38 package validatingwebhookconfiguration
37 package prereleaselifecyclegenerators
37 package externalaccountauthorizeduser
36 package storageobjectinuseprotection
36 package mutatingwebhookconfiguration
34 package prioritylevelconfiguration
34 package extendedresourcetoleration
34 package clusterauthenticationtrust

keep swag fmt

# .go 파일에서 swag comment 작성 후 swag fmt 하면 다음과 같이 주석을 보기 좋게 간격을 띄워 준다.

# 그런데 vim, vscode 등에서 저장하면 gofmt 이 동작하며 다음과 같이 주석 앞쪽을 공백으로 바꾸면서 간격이 흐트러진다.
# 이상태에선 swag fmt 해도 포맷팅이 되지 않는다.
# 이상태에서 탭 부분을 수정 후 swag fmt 하면 포맷팅돼 위 화면처럼 된다.

# vim 등에서 저장(gofmt 으로 주석이 포맷팅)시 swag fmt 부분을 유지하려면
# 다음과 같이 맨위 주석과 공백 주석을 추가하면 gofmt 해도 이 swag 주석 부분이 변경되지 않는다.

#####

# default, enum 등의 속성 사용 예시
// @Param   enumstring  query     string     false  "string enums"       Enums(A, B, C)
// @Param   enumint     query     int        false  "int enums"          Enums(1, 2, 3)
// @Param   enumnumber  query     number     false  "int enums"          Enums(1.1, 1.2, 1.3)
// @Param   string      query     string     false  "string valid"       minlength(5)  maxlength(10)
// @Param   int         query     int        false  "int valid"          minimum(1)    maximum(10)
// @Param   default     query     string     false  "string default"     default(A)
// @Param   example     query     string     false  "string example"     example(string)
// @Param   collection  query     []string   false  "string collection"  collectionFormat(multi)
// @Param   extensions  query     []string   false  "string collection"  extensions(x-example=test,x-nullable)

golangci-lint

# vim 에서 golang generic(1.18)를 사용하는데 lint 에러가 많이 발생한다.
# vim > vim-go > golint 를 사용하고 있고 golint 바이너리는 업데이트 해도 다음과 같은 메시지가 발생한다.
golint ./...
main.go:12:8: expected ';', found '|' (and 4 more errors)

# vim > ale > gofmt 도 다음과 같은 에러 메시지를 발생한다.
gofmt .
main.go:12:8: expected ';', found '|'
main.go:12:10: illegal character U+007E '~'
main.go:16:2: expected '}', found 'return'
main.go:20:2: expected declaration, found result

https://github.com/golang/lint 가보니 2021년에 deprecated 돼 관리가 안되고 있었다.
# gofmt 바이너리는 2018년에 생성된것을 사용하고 있었다.
type gofmt
gofmt is /Users/ysoftman/workspace/gopath/bin/gofmt --> 2018년도
gofmt is /opt/homebrew/bin/gofmt --> ../Cellar/go/1.22.1/bin/gofmt

# golint 는 삭제하고
rm -f $(which golint)

# 2018년도 gofmt 삭제
rm -f /Users/ysoftman/workspace/gopath/bin/gofmt

# 대안으로 https://github.com/golangci/golangci-lint 를 사용하면 된다.
# 설치
brew install golangci-lint

# 실행
golangci-lint run ./...

# revive 사용시 설치
go install github.com/mgechev/revive@latest

# vim-go 사용시
":GoMetaLinter 명령실행시 동작할 커맨드
let g:go_metalinter_command = "golangci-lint"
"최신 golangci-lint 에서 --deadline 옵션이 --timeout 으로 변경됨
"vim-go 에서는 아직 deadline 을 사용하고 있어 주석처리함
"let g:go_metalinter_deadline = "5s"
"golangci-lint 에서 활성화할 항목
"vet -> govet 으로 바뀜
let g:go_metalinter_enabled = ['govet', 'revive', 'errcheck']

# ale 사용시
"vim-go 와 달리 linter 커맨드를 입력하지 않아도 golangci-lint 결과가 코드에 자동으로 표시된다
let g:ale_linters = {
\ 'python': ['flake8', 'pylint'],
\ 'javascript': ['eslint'],
\ 'go': ['golangci-lint', 'gofmt']
\}


gorm join table

# 다음과 department:member 가 M:N 관계인 테이블이 있을때
[departments table]
id uint
name string

[members table]
id uint
name string

[departments_members table](관계 매핑테이블/join table)
id uint
department_key uint
member_key uint
val1 string
val2 string

# 조인 테이블은 다음과 같이 FK 를 설정했다.
`FK_MEMBERS` FOREIGN KEY (`member_key`) REFERENCES `members` (`id`)
`FK_DEPARTMENTS` FOREIGN KEY (`department_key`) REFERENCES `departments` (`id`),

# golang gorm 사용시
# 사용 테이블 이름 설정
type Tabler interface { TableName() string}
func (Department) TableName() string  { return "departments" }
func (Member) TableName() string  { return "members" }

# 부서 조회시 부서에 속한 멤버들을 조회를 한다고 했을때
# many2many 로 조인테이블 departments_members 을 참고하도록 했고
type Member struct {
  ID   uint64  `gorm:"primary_key;auto_increment" json:"id"`
  Name string  `gorm:"unique;size:100;not null" json:"name"`
  Departments []Department `gorm:"many2many:departments_members;" json:"departments,omitempty"` 
}
type Department struct {
  ID   uint64  `gorm:"primary_key;auto_increment" json:"id"`
  Name string  `gorm:"unique;size:100;not null" json:"name"`
  Members []Member `gorm:"many2many:departments_members; "json:"members,omitempty"`
}

# 다음과 preload 로 멤버가 포함된 부서를 조회를 하면 
err := ysoftmanMysql.Db.Model(&db.Department{}).Preload("Members").Find(&department, "name = ?", name).Error

# 다음과 같이 department_id 를 알 수 없다고 나온다.
Error 1054 (42S22): Unknown column 'departments_members.department_id' in 'where clause'

# 기본적으로 foreignkey로 모델명(struct)+pk 를 붙인 이름(department_id)필드를 사용한다.
# departments_members 의 필드명을 다음과 같이 바꾸면 된다.
department_key -> department_id
member_key -> member_id

# 참고
# github.com/jinzhu/gorm -> 1.x
# gorm.io/gorm -> 2.x

#####

# departments_members 테이블 자체에 있는 val1,val2 필드고 같이 조회 하기
func (DepartmentsMembers) TableName() string { return "departments_members" }

type Member struct {
  ID   uint64  `gorm:"primary_key;auto_increment" json:"id"`
  Name string  `gorm:"unique;size:100;not null" json:"name"`
  # DepartmentMembers 에서 MemberID 로 조회 할 수 있도록 한다.
  DepartmentMembers []DepartmentMembers `gorm:"foreignkey:MemberID" json:"users,omitempty"`
}

type Department struct {
  ID   uint64  `gorm:"primary_key;auto_increment" json:"id"`
  Name string  `gorm:"unique;size:100;not null" json:"name"`
  # DepartmentMembers 에서 DepartmentID 로 조회 할 수 있도록 한다.
  DepartmentMembers []DepartmentMembers `gorm:"foreignkey:DepartmentID" json:"users,omitempty"`
}

# DepartmentsMembers 데이터추가고 이곳에서 Member, Department 를 담을 수 있도록 한다.
type DepartmentsMembers struct {
  ID       uint64  `gorm:"primary_key;auto_increment" json:"id"`
  MemberID   uint64  `gorm:"";auto_increment" json:"user_id,omitempty"`
  DepartmentID uint64  `gorm:"" json:"user_id,omitempty"`
  Member      *Member  `json:"member,omitempty"`
  Department  *Department  `json:"department,omitempty"`
}

# Department 조회시 멤버 정보가 포함해서 조회
# DepartmentsMember.Member 를 한번 더 프리로드 한다.
err := ysoftmanMysql.Db.Preload("DepartmentsMember").Preload("DepartmentsMember.Member").Find(&project, "project_id = ?", id).Error

golangci-lint cpu usage

# vscode 로 golang 을 오픈하면 갑자기 컴이 버벅거린다.
# 아래 프로세스가 CPU 를 모두 사용하고 있었다.
golangci-lint run --print-issued-lines=false --out-format=colored-line-number --issues-exit-code=0

# golangci-lint 커맨드 설명을 보면 기본 16core 를 사용하고 있다.
-j, --concurrency int           Concurrency (default NumCPU) (default 16)

# vscode 설정을 요렇게 변경해보자.
// file 저장시 lint 수행, lint 결과 problems 에 표시
"go.lintOnSave": "file",
"go.lintTool": "golangci-lint",
"go.lintFlags": [
  "--allow-parallel-runners",
  "--concurrency",
  "4"
],

docker build image apt-update error

# 잘 되던 docker 이미지 빌드 중 Dockerfile 에서 패키지 업데이트시
RUN apt-get update
...
#6 10.23 Reading package lists...
#6 10.24 W: The repository 'http://security.debian.org/debian-security stretch/updates Release' does not have a Release file.
#6 10.24 W: The repository 'http://deb.debian.org/debian stretch Release' does not have a Release file.
#6 10.24 W: The repository 'http://deb.debian.org/debian stretch-updates Release' does not have a Release file.
#6 10.24 E: Failed to fetch http://security.debian.org/debian-security/dists/stretch/updates/main/binary-amd64/Packages  404  Not Found
#6 10.24 E: Failed to fetch http://deb.debian.org/debian/dists/stretch/main/binary-amd64/Packages  404  Not Found
#6 10.24 E: Failed to fetch http://deb.debian.org/debian/dists/stretch-updates/main/binary-amd64/Packages  404  Not Found
#6 10.24 E: Some index files failed to download. They have been ignored, or old ones used instead.
------
executor failed running [/bin/sh -c apt-get update && apt-get install -y net-tools htop lsof wget curl rsync vim tar man-db traceroute]: exit code: 100

# 기본 이미지를 변경하면 잘 동작한다. 
FROM golang:1.17-stretch  -> FROM golang:1.19

# 참고로 데이안 계열 이미지 suffix 로 붙는 이름(코드네임)
-없는경우 debian latest
-bullseye: debian 11 
-buster: debian 10.4
-stretch: debian 9
-jessie: debian 8

mongodb-go-driver cursor not found error

# mongo go client v1.9.1 (https://github.com/mongodb/mongo-go-driver) 로 documents 조회(find)를 다음과 같이 구현했다.
cursor, err := c.collection.Find(context.TODO(), filter)
if err != nil {
    return err
}
defer cursor.Close(context.TODO())
return cursor.All(context.TODO(),  results)

# 여러개의 조회 요청이 동시에 들어오면 일부 요청 결과에 다음 에러가 발생했다.
(CursorNotFound) Cursor not found (namespace:

# 에러를 발생하는 mongo-go-driver 부분
# https://github.com/mongodb/mongo-go-driver/blob/4f06ad2489b73cd1dbcebb5e7df09b77cb643be1/mongo/cursor.go#L266

# document 수가 50개로 적은 컬렉션에 대해서 조회시 발생하지 않고,
# document 수가 156개인 컬렉션에서 발생했다.
 
# 커서를 읽기(cursor.All)전에 현재 커서에 있는 document 를 찍어보면 101로 보인다.
cursor.RemainingBatchLength()

# 이유는 default batchSize=101 이기 때문이다.
# 인덱스 없는 정렬시에는 전체 document 를 로딩한다고 한다.
```
find() and aggregate() operations have an initial batch size of 101 documents by default. Subsequent getMore operations issued against the resulting cursor have no default batch size, so they are limited only by the 16 megabyte message size.

For queries that include a sort operation without an index, the server must load all the documents in memory to perform the sort before returning any results.
```

# Find 옵션으로 batch 크기를 다음과 같이 설정했다.
opts := options.Find()
opts = opts.SetBatchSize(500)
cursor, err := c.collection.Find(context.TODO(), filter, opts)

# cursor.RemainingBatchLength()를 출력해보면 156 이다.
# 위와 같이 한번에 전체 documents 를 가져오니 cursor not found 에러가 발생하지 않았다.
# batchSize 가 전체 documents 보다 작으면 계속 batchSize 만큼 조회해서 가져오는것으로 보인다.(실제 batchSize 101일때 1번 find 최종 조회 결과는 156개의데이터를 모두 가져왔다.)
# 하지만 이 과정에서 동시에 조회가 실행될 경우 서버로 부터 커서를 찾을 수 없다는 응답(헤더)를 받는것으로 보인다.

# 참고
# default batchSize 로 인한 지연
# batchSize 설정값에 따른 성능 결과

add timezone in alpine

# golang 에서 타임존이 변경된 time 사용을 위해 time.LoadLocation 를 사용했다.
loc, _ := time.LoadLocation("Asia/Seoul")
t = t.In(loc)

# 로컬에서는 동작했지만,
# 배포된 도커에서는 loc 를 찾을 수 없어 panic 이 발생하고 있었다.
[PANIC RECOVER] time: missing Location in call to Time.

# 컨테이너 사이즈를 줄이기 위해 alpine 이미지를 사용하고 있었는데,
# LoadLocation 가 참조하는 zoneinfo 데이터가 존재하지 않아 발생한 문제였다.

# dockerfile 에 tzdata 를 설치를 추가하자.
RUN apk add tzdata

# 이제 다음과 같은 위치에 timezone 데이터 값이 추가되고,
# time.LoadLocation 도 사용할 수 있다.
/usr/share/zoneinfo/Asia/Seoul

golang vim-go GOBIN error

# go 1.19.2 버전 설치 후 .go 파일을 열때 다음과 같은 에러가 발생했다.
# :messages 로 확인해보면
vim-go: 'go env GOBIN' failed
vim-go: could not determine appropriate working directory for gopls
FileNotFoundError: [Errno 2] No such file or directory: '/Users/ysoftman/.vim/plugged/youcompleteme/third_party/ycmd/third_party/go/bin/gopls'

# vim plug 를 업데이트해도 에러가 발생한다.
:PlugUpdate

# 참고로 vim-go 최신 소스를 보면 go env gobin > $GOBIN > GOPATH./bin 순으로 찾고 있다.

# 다음과 같이 설정해도 에러가 발생한다.
# GOBIN (go install 시 실행 binary 설치 경로)
go env -w GOBIN=/Users/ysoftman/workspace/gopath/gopath/bin
go env GOBIN

# 원인은 go 실행 자체가 안되는것이 문제였다.
# go 신규 버전 설치시 macOS(darwin)환경인데 linux 바이너리파일을 설치한것이 문제였다.
# go1.19.2.linux-amd64.tar.gz -> go1.19.2.darwin-amd64.tar.gz 로 변경해서 설치해고 go 가 실행된다.
# 이제 GOBIN 을 설정하지 않아도(GOPATH/bin 로 찾는다.) 에러가 없다.
go env -w GOBIN=

# vim-plug 라면 go binary 업데이트도 해주자.
:GoUpdateBinaries

# ycmd 에러 관련해서는 다음과 같이 설치하면 된다.
cd ~/.vim/plugged/youcompleteme/third_party/ycmd/
git checkout master
git pull
git submodule update --init --recursive
./build.py --go-completer

mongodb find pagination

# mongodb collection 의 document 조회(find)시 pagination
// document _id 다음 포맷으로 구성되어 있어, _id 크기비교로 범위를 탐색할 수 있다.
// https://www.mongodb.com/docs/manual/reference/method/ObjectId/#objectid
// A 4-byte timestamp + A 5-byte random value + A 3-byte incrementing counter
// 가장 오래된 document 파악
filter = bson.D{{Key: "number", Value: bson.D{{Key: "$gt", Value: 0}}}}
docs = make([]samepleDoc1, 0)
// 가장 오래된 document 부터 2개 문서 가져오기
c.FindByPageSize(filter, &docs, 2)
page := 0
lastObjID := docs[len(docs)-1].ObjId
PrintDoc(docs)
page++
fmt.Printf("lastObjID %v page %v\n", lastObjID, page)
// 페이지당 2개 documents 로 계속 조회
for len(docs) > 0 {
lastObjID := docs[len(docs)-1].ObjId
filter = bson.D{{Key: "_id", Value: bson.D{{Key: "$gt", Value: lastObjID}}}}
docs = make([]samepleDoc1, 0)
c.FindByPageSize(filter, &docs, 2)
PrintDoc(docs)
page++
fmt.Printf("lastObjID %v page %v\n", lastObjID, page)
}

# 조회시 위 설정한 objid(_id) 크기로 비교로 다음 document를 조회한다.
func (mc *MongoDBClient) FindByPageSize(filter interface{}, r interface{}, pageSize int) {
// ascending sort by document _id
opt1 := options.Find().SetSort(bson.D{{Key: "_id", Value: 1}}).SetLimit(int64(pageSize))
cursor, err := mc.Client.Database("my_db").Collection("my_coll").Find(context.TODO(), filter, opt1)
if err != nil {
log.Println("failed to find document", err.Error())
}
defer cursor.Close(context.TODO())
// decode each document into r
if err := cursor.All(context.TODO(), r); err != nil {
log.Println("failed to decode document", err.Error())
}
log.Println("---Find result---")
}

# 테스트 코드
# https://github.com/ysoftman/test_code/tree/master/mongodb_golang

echo reverse proxy ingress localhost 404 error

/*
echo golang web framework 의 proxy 사용시 upstream 을 ingress domain 을 사용하면 404 not found 응답 이슈가 발생했다.

echo proxy 사용 예시 참고
https://echo.labstack.com/cookbook/reverse-proxy/

같은 localhost 로 업스트림 설정하면 문제가 없다.
http://localhost:8080 -> http://localhost:8081 (upstream local)

ingress domain 으로 업스트림을 설정하면 404 응답을 받는다.
http://localhost:8080 -> http://ysoftman.dev:8081 (upstream ingress)
404 Not Found

nginx pod 에 로그를 보면 다음과 같은 메시지가 기록된다.
호스트가 ysoftman.dev 로 되어야 할것 같은데, localhost 로 파악돼 pod 까지 요청이 전달되지 않는다.
Skipping metric for host not being served" host="localhost"

해결 방법
프록시 설정전 다음과 같이 핸들러를 등록하고 request host 를 ysoftman.dev 로 설정하면 ysoftman.dev 로 부터 200 OK 응답을 받는다.
*/

package main

import (
"net/http"
"net/url"

// ModifyResponse 사용을 위해선 echo v4 가 필요
"github.com/labstack/echo/v4"
"github.com/labstack/echo/v4/middleware"
)

func main() {
url, _ := url.Parse("http://ysoftman.dev:8081")
e := echo.New()
g := e.Group("/test")

// 프록시 설정 전 request host 를 upstream host 로 변경
g.Use(func(next echo.HandlerFunc) echo.HandlerFunc {
return func(c echo.Context) error {
c.Request().Host = url.Host
return next(c)
}
})

// set proxy upstream
proxyTargets := []*middleware.ProxyTarget{
{
URL: url,
},
}
g.Use(middleware.ProxyWithConfig(middleware.ProxyConfig{
Balancer: middleware.NewRoundRobinBalancer(proxyTargets),
ModifyResponse: func(resp *http.Response) error {
return nil
},
}))
}

golang xor bitwise 사용하기

// golang 에는 not 비트연산자(C 에서는 ~)가 없어 XOR 를 이용해 구한다.
// XOR 를 단항연산으로 사용시 보수(not)를 구할 수 있다.
// https://go.dev/ref/spec#Arithmetic_operators
// +x                          is 0 + x
// -x    negation              is 0 - x
// ^x    bitwise complement    is m ^ x  with m = "all bits set to 1" for unsigned x
//                                       and  m = -1 for signed x
// 위 설명대로 ^x 는
// x 가 unsigned 라면 0xfffff..(모든비트1) ^ x 로 동작하고
// x 가 signed 라면 -1 ^ x 로 동작한다

// not 구하기
var aa uint = 5 // 0101
fmt.Printf("%3d(%10b)\n", aa, aa)
// 모든 비트가 1로 설정된 m 과 xor 되어 not(비트 반전, 1의보수)결과를 구할 수 있다.
fmt.Printf("%3d(%10b)\n", ^aa, ^aa)
// 0xf 등으로 xor 해서 비트 범위를 줄여서 not 결과를 만들 수도 있다.
fmt.Printf("%3d(%10b)\n", 0xf^aa, 0xf^aa)

bb := 5 // 0101
// singed 형이라면 위 설명대로 -1 ^ bb 가 된다.
fmt.Printf("%3d(%10b)\n", ^bb, ^bb)

// 양수 -> 음수, MSB(Most Significant Bit) 는 음수가 된다.
// bb 는 signed 라서 위 설명처럼 ^bb 하면 -1 과 xor 된다.
// 여기에 +1(2의보수)를 하면 음수가 된다.
fmt.Printf("%3d(%8b) -> %3d(%8b) -> %3d(%8b)\n", bb, bb, ^bb, ^bb, ^bb+1, ^bb+1)

bb = -5
// 음수 -> 양수, MSB(Most Significant Bit) 는 양수가 된다.
// bb 는 signed 라서 위 설명처럼 ^bb 하면 -1 과 xor 된다.
// 여기에 +1(2의보수)를 하면 양수가 된다.
fmt.Printf("%3d(%8b) -> %3d(%8b) -> %3d(%8b)\n", bb, bb, ^bb, ^bb, ^bb+1, ^bb+1)

# 참고

golang filename rules

# golang 파일을 생성할때 파일리스트 특정 파일을 가장 위쪽에 보려고 
# _aaa.go 와 같이 _로 시작하는 파일을 만들고 go build(run) 하면
# 다음과 같이 파일을 찾지 못한다.
package command-line-arguments: no Go files in /xxxx

# 찾아보니 .go .c 등의 파일은 _ 나 . 로 시작하면 대상에서 제외된다.
// Import returns details about the Go package named by the import path,
// interpreting local import paths relative to the srcDir directory.
// If the path is a local import path naming a package that can be imported
// using a standard import path, the returned package will set p.ImportPath
// to that path.
//
// In the directory containing the package, .go, .c, .h, and .s files are
// considered part of the package except for:
//
// - .go files in package documentation
// - files starting with _ or . (likely editor temporary files)
// - files with build constraints not satisfied by the context
//
# 출처

# 추가로 패키지 이름은 _ 나 대소문자구분 없이 모두 소문자로 사용한다.
Good package names are short and clear. They are lower case, with no under_scores or mixedCaps.

k8s deployment or pod serialization

# golang 환경에서 k8s deployment(k8s.io/api/apps/v1 -> Deployment struct) 를 manifest(yaml)로 serialization 할때
# 일반적인 gopkg.in/yaml.v2 패키지를 사용시
yamlDeployment, err := yaml.Marshal(deployment)

# string(yamlDeployment) 결과를 보면
# 다음과 같이 cpu 리소스등의 값이 DecimalSI 포맷이라고만 나오고 정확한 값이 표시되지 않는다.
spec -> template -> spec -> containers -> resources
  limits:
    cpu:
      format: DecimalSI
  requests:
    cpu:
      format: DecimalSI

# pod 나 deployment 는 k8s apimachinery 패키지의 NewYAMLSerializer 를 사용하면 된다.
import k8sJson "k8s.io/apimachinery/pkg/runtime/serializer/json"

e := k8sJson.NewYAMLSerializer(k8sJson.DefaultMetaFactory, nil, nil)
yamlDeployment := new(bytes.Buffer)
err = e.Encode(deployment, yamlDeployment)

# yamlDeployment.String() 결과를 보면 다음과 같이 값이 잘 표시된다.
  limits:
    cpu: 2500m
  requests:
    cpu: "1"