From a59d31e27cc5ae67e4f3bb9d9c1b3907c7b0bc30 Mon Sep 17 00:00:00 2001 From: myeunee Date: Sun, 3 Nov 2024 20:25:23 +0900 Subject: [PATCH] =?UTF-8?q?=EB=8F=84=EC=BB=A4=ED=8C=8C=EC=9D=BC=20?= =?UTF-8?q?=EB=B2=84=EC=A0=84=20=EC=B5=9C=EC=8B=A0=EC=9C=BC=EB=A1=9C=20?= =?UTF-8?q?=EC=88=98=EC=A0=95?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- chapter15/section57/Dockerfile | 2 +- chapter16/section60/Dockerfile | 2 +- chapter16/section60/Makefile | 2 +- chapter16/section61/.dockerignore | 2 + chapter16/section61/Dockerfile | 35 ++++++++++++ chapter16/section61/Makefile | 28 ++++++++++ chapter16/section61/air.toml | 33 +++++++++++ chapter16/section61/config/config.go | 18 ++++++ chapter16/section61/config/config_test.go | 23 ++++++++ chapter16/section61/docker-compose.yml | 14 +++++ chapter16/section61/go.mod | 8 +++ chapter16/section61/go.sum | 4 ++ chapter16/section61/main.go | 68 +++++++++++++++++++++++ chapter16/section61/main_test.go | 50 +++++++++++++++++ 14 files changed, 286 insertions(+), 3 deletions(-) create mode 100644 chapter16/section61/.dockerignore create mode 100644 chapter16/section61/Dockerfile create mode 100644 chapter16/section61/Makefile create mode 100644 chapter16/section61/air.toml create mode 100644 chapter16/section61/config/config.go create mode 100644 chapter16/section61/config/config_test.go create mode 100644 chapter16/section61/docker-compose.yml create mode 100644 chapter16/section61/go.mod create mode 100644 chapter16/section61/go.sum create mode 100644 chapter16/section61/main.go create mode 100644 chapter16/section61/main_test.go diff --git a/chapter15/section57/Dockerfile b/chapter15/section57/Dockerfile index 3f30900..d40733c 100644 --- a/chapter15/section57/Dockerfile +++ b/chapter15/section57/Dockerfile @@ -1,6 +1,6 @@ # Build stage # 최신 버전으로 수정 ****** -FROM golang:1.22-bullseye as deploy-builder +FROM golang:1.23.1 AS deploy-builder WORKDIR /app diff --git a/chapter16/section60/Dockerfile b/chapter16/section60/Dockerfile index 3f30900..d40733c 100644 --- a/chapter16/section60/Dockerfile +++ b/chapter16/section60/Dockerfile @@ -1,6 +1,6 @@ # Build stage # 최신 버전으로 수정 ****** -FROM golang:1.22-bullseye as deploy-builder +FROM golang:1.23.1 AS deploy-builder WORKDIR /app diff --git a/chapter16/section60/Makefile b/chapter16/section60/Makefile index d579fa4..7fe2dd9 100644 --- a/chapter16/section60/Makefile +++ b/chapter16/section60/Makefile @@ -3,7 +3,7 @@ DOCKER_TAG := latest build: ## 배포용 도커 이미지 빌드 + 내 모듈 이름에 맞게 수정 ********** - docker build -t myeunee/golangstudy-chapter15-section57:${DOCKER_TAG} --target deploy ./ + docker build -t myeunee/golangstudy-chapter16-section60:${DOCKER_TAG} --target deploy ./ build-local: ## 로컬 환경용 도커 이미지 빌드 docker compose build --no-cache diff --git a/chapter16/section61/.dockerignore b/chapter16/section61/.dockerignore new file mode 100644 index 0000000..a09d331 --- /dev/null +++ b/chapter16/section61/.dockerignore @@ -0,0 +1,2 @@ +.git +.DS_Store \ No newline at end of file diff --git a/chapter16/section61/Dockerfile b/chapter16/section61/Dockerfile new file mode 100644 index 0000000..d40733c --- /dev/null +++ b/chapter16/section61/Dockerfile @@ -0,0 +1,35 @@ +# Build stage +# 최신 버전으로 수정 ****** +FROM golang:1.23.1 AS deploy-builder + +WORKDIR /app + +# go.mod, go.sum 파일 복사 및 의존성 다운로드 +COPY go.mod go.sum ./ +RUN go mod download + +COPY . . +# 바이너리 빌드 +RUN go build -trimpath -ldflags "-w -s" -o app + +#-------------------------------- +# Deployment stage + +FROM debian:bullseye-slim as deploy + +RUN apt-get update + +# 빌드된 바이너리를 복사 +COPY --from=deploy-builder /app/app . + +# 실행 명령 +CMD ["./app"] + +#-------------------------------- +# Development stage with Air for live reloading + +# ******** 최신 버전으로 수정 *********** +FROM golang:1.23.1 as dev +WORKDIR /app +RUN go install github.com/air-verse/air@latest +CMD ["air"] diff --git a/chapter16/section61/Makefile b/chapter16/section61/Makefile new file mode 100644 index 0000000..5237d7f --- /dev/null +++ b/chapter16/section61/Makefile @@ -0,0 +1,28 @@ +.PHONY: help build build-local up down logs ps test +.DEFAULT_GOAL := help + +DOCKER_TAG := latest +build: ## 배포용 도커 이미지 빌드 + 내 모듈 이름에 맞게 수정 ********** + docker build -t myeunee/golangstudy-chapter16-section61:${DOCKER_TAG} --target deploy ./ + +build-local: ## 로컬 환경용 도커 이미지 빌드 + docker compose build --no-cache + +up: ## 자동 새로고침을 사용한 도커 컴포즈 실행 + docker compose up -d + +down: ## 도커 컴포즈 종료 + docker compose down + +logs: ## 도커 컴포즈 로그 출력 + docker compose logs -f + +ps: ## 컨테이너 상태 확인 + docker compose ps + +test: ## 테스트 실행 + go test -race -shuffle=on ./... + +help: ## 옵션 보기 + @grep -E '^[a-zA-Z_-]+:.*?## .*$$' $(MAKEFILE_LIST) | \ + awk 'BEGIN {FS = ":.*?## "}; {printf "\033[36m%-20s\033[0m %s\n", $$1, $$2}' \ No newline at end of file diff --git a/chapter16/section61/air.toml b/chapter16/section61/air.toml new file mode 100644 index 0000000..d87ffdf --- /dev/null +++ b/chapter16/section61/air.toml @@ -0,0 +1,33 @@ +root = "." +tmp_dir = "tmp" + +[build] +cmd = "go build -o ./tmp/main ." +# 'cmd'에서 바이너리 파일 지정 +bin = "tmp/main" + +# 80번 포트를 사용하도록 실행 시 인수를 지정 +full_bin = "APP_ENV=dev APP_USER=air ./tmp/main 80" + +include_ext = ["go", "tpl", "tmpl", "html"] +exclude_dir = ["assets", "tmp", "vendor"] +exclude_regex = ["_test.go"] +exclude_unchanged = true +follow_symlink = true +log = "air.log" +delay = 1000 +stop_on_error = true +send_interrupt = false +kill_delay = 500 + +[log] +time = false + +[color] +main = "magenta" +watcher = "cyan" +build = "yellow" +runner = "green" + +[misc] +clean_on_exit = true diff --git a/chapter16/section61/config/config.go b/chapter16/section61/config/config.go new file mode 100644 index 0000000..de1fab7 --- /dev/null +++ b/chapter16/section61/config/config.go @@ -0,0 +1,18 @@ +package config + +import ( + "github.com/caarlos0/env/v6" +) + +type Config struct { + Env string `env:"TODO_ENV" envDefault:"dev"` + Port int `env:"PORT" envDefault:"80"` +} + +func New() (*Config, error) { + cfg := &Config{} + if err := env.Parse(cfg); err != nil { + return nil, err + } + return cfg, nil +} diff --git a/chapter16/section61/config/config_test.go b/chapter16/section61/config/config_test.go new file mode 100644 index 0000000..0cac651 --- /dev/null +++ b/chapter16/section61/config/config_test.go @@ -0,0 +1,23 @@ +package config + +import ( + "fmt" + "testing" +) + +func TestNew(t *testing.T) { + wantPort := 3333 + t.Setenv("PORT", fmt.Sprint(wantPort)) + + got, err := New() + if err != nil { + t.Fatalf("cannot create config: %v", err) + } + if got.Port != wantPort { + t.Errorf("want %d, but %d", wantPort, got.Port) + } + wantEnv := "dev" + if got.Env != wantEnv { + t.Errorf("want %s, but %s", wantEnv, got.Env) + } +} diff --git a/chapter16/section61/docker-compose.yml b/chapter16/section61/docker-compose.yml new file mode 100644 index 0000000..521ffd7 --- /dev/null +++ b/chapter16/section61/docker-compose.yml @@ -0,0 +1,14 @@ +version: "3.9" +services: + app: + image: gotodo + build: + args: + - target=dev + environment: + TODO_ENV: dev + PORT: 8080 + volumes: + - .:/app + ports: + - "18000:8080" \ No newline at end of file diff --git a/chapter16/section61/go.mod b/chapter16/section61/go.mod new file mode 100644 index 0000000..0841182 --- /dev/null +++ b/chapter16/section61/go.mod @@ -0,0 +1,8 @@ +module github.com/myeunee/GolangStudy/chapter16/section61 + +go 1.23.1 + +require ( + github.com/caarlos0/env/v6 v6.10.1 + golang.org/x/sync v0.8.0 +) diff --git a/chapter16/section61/go.sum b/chapter16/section61/go.sum new file mode 100644 index 0000000..d2ef449 --- /dev/null +++ b/chapter16/section61/go.sum @@ -0,0 +1,4 @@ +github.com/caarlos0/env/v6 v6.10.1 h1:t1mPSxNpei6M5yAeu1qtRdPAK29Nbcf/n3G7x+b3/II= +github.com/caarlos0/env/v6 v6.10.1/go.mod h1:hvp/ryKXKipEkcuYjs9mI4bBCg+UI0Yhgm5Zu0ddvwc= +golang.org/x/sync v0.8.0 h1:3NFvSEYkUoMifnESzZl15y791HH1qU2xm6eCJU5ZPXQ= +golang.org/x/sync v0.8.0/go.mod h1:Czt+wKu1gCyEFDUtn0jG5QVvpJ6rzVqr5aXyt9drQfk= diff --git a/chapter16/section61/main.go b/chapter16/section61/main.go new file mode 100644 index 0000000..e308fcb --- /dev/null +++ b/chapter16/section61/main.go @@ -0,0 +1,68 @@ +package main + +import ( + "context" + "fmt" + "log" + "net" + "net/http" + "os" + "os/signal" + "syscall" + "time" + + "github.com/myeunee/GolangStudy/chapter16/section61/config" + "golang.org/x/sync/errgroup" +) + +// run 함수만 호출하도록 +func main() { + if err := run(context.Background()); err != nil { + log.Printf("failed to terminated server: %v", err) + os.Exit(1) + } +} + +func run(ctx context.Context) error { + ctx, stop := signal.NotifyContext(ctx, os.Interrupt, syscall.SIGTERM) + defer stop() + cfg, err := config.New() + if err != nil { + return err + } + l, err := net.Listen("tcp", fmt.Sprintf(":%d", cfg.Port)) + if err != nil { + log.Fatalf("failed to listen port %d: %v", cfg.Port, err) + } + url := fmt.Sprintf("http://%s", l.Addr().String()) + log.Printf("start with: %v", url) + s := &http.Server{ + // 인수로 받은 net.Listener를 이용 -> Addr 필드 지정 X + Handler: http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) { + // 명령줄에서 테스트하기 위한 로직 + time.Sleep(5 * time.Second) + fmt.Fprintf(w, "Hello, %s!", r.URL.Path[1:]) + }), + } + eg, ctx := errgroup.WithContext(ctx) + // 다른 고루틴에서 http 서버 실행 + eg.Go(func() error { + // ListenAndServe 가 아니라 Serve 메서드로 변경 + if err := s.Serve(l); err != nil && + // http.ErrServerClosed는 + // http.Server.Shutdown()가 정상 종료되었다고 표시하므로 문제 X。 + err != http.ErrServerClosed { + log.Printf("failed to close: %+v", err) + return err + } + return nil + }) + + // 채널로부터 알림(종료 알림)을 기다림 + <-ctx.Done() + if err := s.Shutdown(context.Background()); err != nil { + log.Printf("failed to shutdown: %+v", err) + } + // Go메서드로 실행한 다른 고루틴의 종료를 기다림 + return eg.Wait() +} diff --git a/chapter16/section61/main_test.go b/chapter16/section61/main_test.go new file mode 100644 index 0000000..f28ee8a --- /dev/null +++ b/chapter16/section61/main_test.go @@ -0,0 +1,50 @@ +package main + +import ( + "context" + "fmt" + "io" + "net" + "net/http" + "testing" + + "golang.org/x/sync/errgroup" +) + +func TestRun(t *testing.T) { + t.Skip("리팩토링 중") + + l, err := net.Listen("tcp", "localhost:0") + if err != nil { + t.Fatalf("failed to listen port %v", err) + } + ctx, cancel := context.WithCancel(context.Background()) + eg, ctx := errgroup.WithContext(ctx) + eg.Go(func() error { + return run(ctx) + }) + in := "message" + url := fmt.Sprintf("http://%s/%s", l.Addr().String(), in) + // // 어떤 포트로 리슨하고 있는지 확인 + t.Logf("try request to %q", url) + rsp, err := http.Get(url) + if err != nil { + t.Errorf("failed to get: %+v", err) + } + defer rsp.Body.Close() + got, err := io.ReadAll(rsp.Body) + if err != nil { + t.Fatalf("failed to read body: %v", err) + } + // http 서버의 반환값을 검증 + want := fmt.Sprintf("Hello, %s!", in) + if string(got) != want { + t.Errorf("want %q, but got %q", want, got) + } + // run함수에 종료 알림을 전송 + cancel() + // run함수의 반환값을 검증 + if err := eg.Wait(); err != nil { + t.Fatal(err) + } +}