èè
ããã GopherAcademyããã°ã®12æ5æ¥ã®é£ç¶ã§ãGoã³ãã¥ããã£ã®æã倿§ãªä»£è¡šè
ããã¯ãªã¹ãã¹åã®ç¹å¥ãªäžé£ã®æçš¿ã§çµéšãå
±æããŠããŸãã ä»å¹Žã ãã€ã¯ããµãŒãã¹ã¯ãŒã¯ã·ã§ããã®æåã®éšåã«åºã¥ããŠæžãããèšäºãIgor DolzhikovãšæäŸããããšã決ããŸããã Habréã«ã€ããŠã¯ã ãã§ã«ãã®ã¬ã€ãã®äžéšããã§ã«æ€èšããŸãã ã
Goã詊ããããšããã人ãªããGoã§ã®ãµãŒãã¹ã®äœæãç°¡åã§ããããšããåç¥ã§ãããã httpãµãŒãã¹ãéå§ã§ããããã«ãã»ãã®æ°è¡ã®ã³ãŒããå¿
èŠã§ãã ãããããã®ãããªã¢ããªã±ãŒã·ã§ã³ãå®çšŒåç°å¢ã§æºåããå Žåãäœã远å ããå¿
èŠããããŸããïŒ Kubernetesã§å®è¡ããæºåãã§ããŠãããµãŒãã¹ã®äŸãèŠãŠã¿ãŸãããã
ãã®èšäºã®ãã¹ãŠã®ã¹ãããã¯1ã€ã®ã¿ã°ã§èŠã€ããããšãã§ããŸãããŸãã¯ãcommitã§èšäºã®ã³ãããã®äŸã«åŸãããšãã§ããŸãã
ã¹ããã1.æãåçŽãªãµãŒãã¹
ãããã£ãŠãéåžžã«åçŽãªã¢ããªã±ãŒã·ã§ã³ããããŸãã
main.gopackage main import ( "fmt" "net/http" ) func main() { http.HandleFunc("/home", func(w http.ResponseWriter, _ *http.Request) { fmt.Fprint(w, "Hello! Your request was processed.") }, ) http.ListenAndServe(":8000", nil) }
å®è¡ããŠã¿ããå Žåã¯ã go run main.go
ã§ååã§ãã curlã䜿çšãããšããã®ãµãŒãã¹ã®åäœã確èªã§ãcurl -i http://127.0.0.1:8000/home
ïŒ curl -i http://127.0.0.1:8000/home
ãããããã®ã¢ããªã±ãŒã·ã§ã³ãèµ·åãããšã端æ«ã«ã¯ãã®ç¶æ
ã«é¢ããæ
å ±ããªãããšãããããŸãã
ã¹ããã2.ãã®ã³ã°ã远å ãã
ãŸãããµãŒãã¹ã§äœãèµ·ãã£ãŠããã®ããçè§£ãããšã©ãŒããã®ä»ã®éèŠãªç¶æ³ãèšé²ã§ããããã«ããããã«ããã®ã³ã°ã远å ããŸãããã ãã®äŸã§ã¯ãGoæšæºã©ã€ãã©ãªã®æãåçŽãªãã¬ãŒã䜿çšããŸãããå®çšŒåã§éå§ãããå®éã®ãµãŒãã¹ã«ã¯ã glogãlogrusãªã©ã®ããè€éãªè峿·±ããœãªã¥ãŒã·ã§ã³ãååšããå ŽåããããŸãã
3ã€ã®ç¶æ³ã«èå³ããããããããŸããïŒãµãŒãã¹ãéå§ãããšãããµãŒãã¹ãèŠæ±ãåŠçããæºåãã§ãããšãããããŠhttp.ListenAndServe
ããšã©ãŒãè¿ããšãã çµæã¯æ¬¡ã®ããã«ãªããŸã ã
main.go func main() { log.Print("Starting the service...") http.HandleFunc("/home", func(w http.ResponseWriter, _ *http.Request) { fmt.Fprint(w, "Hello! Your request was processed.") }, ) log.Print("The service is ready to listen and serve.") log.Fatal(http.ListenAndServe(":8000", nil)) }
ããããïŒ
ã¹ããã3.ã«ãŒã¿ãŒã远å ãã
ãã®ã¢ããªã±ãŒã·ã§ã³ã§ã¯ãã»ãšãã©ã®å ŽåãããŸããŸãªURIãHTTPã¡ãœããããŸãã¯ãã®ä»ã®ã«ãŒã«ã®åŠçãç°¡çŽ åããããã«ã«ãŒã¿ãŒã䜿çšããŸãã Goæšæºã©ã€ãã©ãªã«ã¯ã«ãŒã¿ãŒããªãã®ã§ãæšæºã®net/http
ã©ã€ãã©ãªãšå®å
šã«äºææ§ã®ããgorilla / muxã詊ããŠã¿ãŸãããã
ãµãŒãã¹ã«é¡èãªæ°ã®ã«ãŒãã£ã³ã°ã«ãŒã«ãå¿
èŠãªå Žåã¯ãã«ãŒãã£ã³ã°ã«é¢é£ãããã¹ãŠãå¥ã®ããã±ãŒãžã«å
¥ããã®ãçã«ããªã£ãŠããŸãã ãã³ãã©ãŒããã±ãŒãžå
ã®ãã³ãã©ãŒé¢æ°ãšåæ§ã«ãã«ãŒãã£ã³ã°ã«ãŒã«ã®åæåãšèšå®ãè¡ããŸãããïŒå®å
šãªå€æŽã«ã€ããŠã¯ã ãã¡ããã芧ãã ãã ïŒã
æ§æãããã«ãŒã¿ãŒãè¿ãRouter
颿°ãšã /home
ãã¹ã®ã«ãŒã«ãåŠçããhome
颿°ã远å ããŸãã ç§ã¯ãã®ãããªæ©èœãå¥ã
ã®ãã¡ã€ã«ã«åããããšã奜ã¿ãŸãïŒ
ãã³ãã©ãŒ/handlers.go package handlers import ( "github.com/gorilla/mux" )
ãã³ãã©ãŒ/ home.go package handlers import ( "fmt" "net/http" )
ããã«ã main.go
ãã¡ã€ã«ã«å°ããªå€æŽãå¿
èŠã§ãã
main.go package main import ( "log" "net/http" "github.com/rumyantseva/advent-2017/handlers" )
ã¹ããã4.ãã¹ã
ããã€ãã®ãã¹ãã远å ããŸã ã httptest
ã¯æšæºã®httptest
ããã±ãŒãžã䜿çšã§ããŸãã Router
æ©èœã®å Žåãæ¬¡ã®ããã«èšè¿°ã§ããŸãã
ãã³ãã©ãŒ/handlers_test.go package handlers import ( "net/http" "net/http/httptest" "testing" ) func TestRouter(t *testing.T) { r := Router() ts := httptest.NewServer(r) defer ts.Close() res, err := http.Get(ts.URL + "/home") if err != nil { t.Fatal(err) } if res.StatusCode != http.StatusOK { t.Errorf("Status code for /home is wrong. Have: %d, want: %d.", res.StatusCode, http.StatusOK) } res, err = http.Post(ts.URL+"/home", "text/plain", nil) if err != nil { t.Fatal(err) } if res.StatusCode != http.StatusMethodNotAllowed { t.Errorf("Status code for /home is wrong. Have: %d, want: %d.", res.StatusCode, http.StatusMethodNotAllowed) } res, err = http.Get(ts.URL + "/not-exists") if err != nil { t.Fatal(err) } if res.StatusCode != http.StatusNotFound { t.Errorf("Status code for /home is wrong. Have: %d, want: %d.", res.StatusCode, http.StatusNotFound) } }
ããã§ã¯ã /home
GET
ã¡ãœãããåŒã³åºããšã³ãŒã200
ãè¿ãããããšã確èªããŸãã ãããŠã POST
ãéä¿¡ããããšããPOST
äºæ³ãããå¿çã¯ãã§ã«405
ãŸãã æåŸã«ãååšããªããã¹ã®å Žåã 404
äºæ³ãããŸãã äžè¬ã«ããã®ãã¹ãã¯ããåé·ã«ãªãå¯èœæ§ããããŸããããã¯ãã«ãŒã¿ãŒãgorilla/mux
å
ã®ãã¹ãã§æ¢ã«ã«ããŒãããŠãããããããã§ç¢ºèªã§ããã±ãŒã¹ã¯ããã«å°ãªãããã§ãã
home
颿°ã®å Žåãã³ãŒãã ãã§ãªãå¿çæ¬æã確èªããã®ãçã«ããªã£ãŠããŸãã
ãã³ãã©ãŒ/ home_test.go package handlers import ( "io/ioutil" "net/http" "net/http/httptest" "testing" ) func TestHome(t *testing.T) { w := httptest.NewRecorder() home(w, nil) resp := w.Result() if have, want := resp.StatusCode, http.StatusOK; have != want { t.Errorf("Status code is wrong. Have: %d, want: %d.", have, want) } greeting, err := ioutil.ReadAll(resp.Body) resp.Body.Close() if err != nil { t.Fatal(err) } if have, want := string(greeting), "Hello! Your request was processed."; have != want { t.Errorf("The greeting is wrong. Have: %s, want: %s.", have, want) } }
go test
ãå®è¡ãããã¹ããæ©èœããããšã確èªããŸã
$ go test -v ./... ? github.com/rumyantseva/advent-2017 [no test files] === RUN TestRouter --- PASS: TestRouter (0.00s) === RUN TestHome --- PASS: TestHome (0.00s) PASS ok github.com/rumyantseva/advent-2017/handlers 0.018s
ã¹ããã5.æ§æ
次ã®éèŠãªã¹ãããã¯ããµãŒãã¹ãæ§æããæ©èœã§ãã çŸåšãèµ·åæã«ããµãŒãã¹ã¯åžžã«ããŒã8000
ã§ãªãã¹ã³ãããã®å€ãæ§æããæ©èœã圹ç«ã€å ŽåããããŸãã ãµãŒãã¹ãèšè¿°ããããã®éåžžã«è峿·±ãã¢ãããŒãã§ãã12èŠçŽ ã¢ããªã±ãŒã·ã§ã³ã®ãããã§ã¹ãã§ã¯ãç°å¢ã«åºã¥ããŠæ§æãä¿åããããšãæšå¥šããŠããŸãã ããã§ã¯ã ç°å¢å€æ°ã䜿çšããŠããŒãã®æ§æãèšå®ããŸãããïŒ
main.go package main import ( "log" "net/http" "os" "github.com/rumyantseva/advent-2017/handlers" )
ãã®äŸã§ã¯ãããŒããæå®ãããŠããªãå Žåãã¢ããªã±ãŒã·ã§ã³ã¯ããã«å€±æããŸãã æ§æãæ£ããèšå®ãããŠããªãå Žåãäœæ¥ãç¶è¡ããããšããããšã¯æå³ããããŸããã
ã¹ããã6. Makefile
æ°æ¥åã make
ãŠãŒãã£ãªãã£ã«é¢ããèšäºãGopherAcademyããã°ã«å
¬éãããŸãããããã¯ãç¹°ãè¿ãã®ã¢ã¯ã·ã§ã³ã«å¯ŸåŠããå¿
èŠãããå Žåã«éåžžã«åœ¹ç«ã¡ãŸãã ãããžã§ã¯ãã§ãããã©ã®ããã«äœ¿çšã§ããããèŠãŠã¿ãŸãããã çŸåšããã¹ãã®å®è¡ãšãµãŒãã¹ã®ã³ã³ãã€ã«ãšéå§ãšãã2ã€ã®å埩ã¢ã¯ã·ã§ã³ããããŸãã ãããã®ã¢ã¯ã·ã§ã³ãMakefileã«è¿œå ããŸãããåã«go run
代ããã«ã go build
ã䜿çšããŠã³ã³ãã€ã«æžã¿ã®ãã€ããªãå®è¡ããŸãããã®ãªãã·ã§ã³ã¯ãå°æ¥ã¢ããªã±ãŒã·ã§ã³ãæ¬çªçšã«æºåããå Žåã«é©ããŠããŸãã
ã¡ã€ã¯ãã¡ã€ã« APP?=advent PORT?=8000 clean: rm -f ${APP} build: clean go build -o ${APP} run: build PORT=${PORT} ./${APP} test: go test -v -race ./...
ãã®äŸã§ã¯ããã€ããªåãå¥ã®APP
倿°ã«å
¥ããŠãæ°åç¹°ãè¿ããªãããã«ããŸãã
ããã«ã説æããããã«ã¢ããªã±ãŒã·ã§ã³ãå®è¡ããå Žåã¯ããŸãå€ããã€ããªãåé€ããå¿
èŠããããŸãïŒååšããå ŽåïŒã ãããã£ãŠã make build
ãå®è¡make build
ãšãæåã«clean
ãåŒã³åºãããŸãã
ã¹ããã7.ããŒãžã§ã³ç®¡ç
ãµãŒãã¹ã«è¿œå ããæ¬¡ã®ãã©ã¯ãã£ã¹ã¯ãããŒãžã§ã³ç®¡çã§ãã ç¹å®ã®ãã«ããç¹å®ããæ¬çªç°å¢ã§äœ¿çšããŠããã³ããããããã«ã¯ãã€ããªãæ£ç¢ºã«ãã€ãã«ããããããç¥ãããšã圹ç«ã€å ŽåããããŸãã
ãã®æ
å ±ãä¿åããã«ã¯ãæ°ããããã±ãŒãž-versionã远å ããŸãã
ããŒãžã§ã³/ version.go ã¢ããªã±ãŒã·ã§ã³ã®èµ·åæã«ãããã®å€æ°ãèšé²ã§ããŸãã
main.go ... func main() { log.Printf( "Starting the service...\ncommit: %s, build time: %s, release: %s", version.Commit, version.BuildTime, version.Release, ) ... }
ãŸããããããhome
远å ããããšãã§ããŸãïŒãã¹ããä¿®æ£ããããšãå¿ããªãã§ãã ããïŒïŒïŒ
ãã³ãã©ãŒ/ home.go package handlers import ( "encoding/json" "log" "net/http" "github.com/rumyantseva/advent-2017/version" )
ã³ã³ãã€ã«æã«å€æ°BuildTime
ã Commit
ã Release
ãèšå®ããããã«ãªã³ã«ãŒã䜿çšããŸãã
Makefile
ã«æ°ãã倿°ã远å ããŸãã
Makefile
RELEASE?=0.0.1 COMMIT?=$(shell git rev-parse
ããã§ã COMMIT
ããã³BUILD_TIME
æå®ãããã³ãã³ãã«ãã£ãŠå®çŸ©ããã RELEASE
ã«ã¯ãããšãã°ãã¢ã»ã³ããªã®ã»ãã³ãã£ãã¯ããŒãžã§ãã³ã°ãŸãã¯åãªãã€ã³ã¯ãªã¡ã³ã¿ã«ããŒãžã§ã³ã䜿çšã§ããŸãã
ãããã®å€æ°ã®å€ã䜿çšã§ããããã«ã build
ã¿ãŒã²ãããæžãæããŸãã
Makefile
build: clean go build \ -ldflags "-s -w -X ${PROJECT}/version.Release=${RELEASE} \ -X ${PROJECT}/version.Commit=${COMMIT} -X ${PROJECT}/version.BuildTime=${BUILD_TIME}" \ -o ${APP}
ãŸããåãããšãæ°åç¹°ãè¿ããªãããã«ã PROJECT
倿°ãMakefile
ã®å
é ã«è¿œå ããŸããã
Makefile
PROJECT?=github.com/rumyantseva/advent-2017
ãã®ã¹ãããã§è¡ããããã¹ãŠã®å€æŽã¯ã ããã«ãããŸã ã make run
ã詊ããŠmake run
ã確èªããŠãã ããã
ã¹ããã8.äŸåé¢ä¿ãæžããïŒ
ã³ãŒãã«ã€ããŠæ°ã«å
¥ããªãããšã1ã€ãããŸãã handler
ããã±ãŒãžã¯version
ããã±ãŒãžã«äŸåããŸãã ããã倿Žããã®ã¯ç°¡åã§ãã home
æ©èœãèšå®å¯èœã«ããå¿
èŠããããŸãã
handlers/home.go
ãŸãããã¹ããä¿®æ£ãã å¿
èŠãªå€æŽãå ããããšãå¿ããªãã§ãã ããã
ã¹ããã9. Helscheki
Kubernetesã§ãµãŒãã¹ãèµ·åããå Žåãéåžžã2ã€ã®helchecksã远å ããå¿
èŠããããŸããliveness ããã³readiness probeã§ãã æŽ»æ§ãã¹ãã®ç®çã¯ããµãŒãã¹ãéå§ãããããšãæç¢ºã«ããããšã§ãã æŽ»æ§ãããŒãã倱æãããšããµãŒãã¹ãåèµ·åãããŸãã æºåãã¹ãã®ç®çã¯ãã¢ããªã±ãŒã·ã§ã³ããã©ãã£ãã¯ãåä¿¡ããæºåãã§ããŠããããšãçè§£ããããšã§ãã æºåãããŒãã倱æãããšãã³ã³ããã¯ãµãŒãã¹ããŒããã©ã³ãµãŒããåé€ãããŸãã
掻æ§ãããŒããæ±ºå®ããã«ã¯ãåžžã«200
è¿ãåçŽãªãã³ãã©ãŒãäœæã§ããŸãã
ãã³ãã©ãŒ/ healthz.go æºåãµã³ãã«ã®å Žåãå€ãã®å Žåãåæ§ã®ãœãªã¥ãŒã·ã§ã³ã§ååã§ããããã©ãã£ãã¯ã®åŠçãéå§ããããã«ãäœããã®ã€ãã³ãïŒããšãã°ãããŒã¿ããŒã¹ã®æºåãã§ããŠããïŒãåŸ
ã€å¿
èŠãããå ŽåããããŸãã
ãã³ãã©ãŒ/ readyz.go ãã®äŸã§ã¯ã isReady
倿°isReady
true
èšå®ãããŠããå Žåã«ã®ã¿200
ãè¿ãtrue
ã
ãããã©ã®ããã«äœ¿çšã§ããããèŠãŠã¿ãŸãããïŒ
handlers.go func Router(buildTime, commit, release string) *mux.Router { isReady := &atomic.Value{} isReady.Store(false) go func() { log.Printf("Readyz probe is negative by default...") time.Sleep(10 * time.Second) isReady.Store(true) log.Printf("Readyz probe is positive.") }() r := mux.NewRouter() r.HandleFunc("/home", home(buildTime, commit, release)).Methods("GET") r.HandleFunc("/healthz", healthz) r.HandleFunc("/readyz", readyz(isReady)) return r }
ããã§ã¯ãã¢ããªã±ãŒã·ã§ã³ã¯èµ·ååŸ10ç§ã§ãã©ãã£ãã¯ãåŠçããæºåãã§ããŠãããšèšããŸãã ãã¡ãããå®éã«ã¯10ç§åŸ
ã€ããšã¯æå³ããããŸãããããã£ãã·ã¥ãŠã©ãŒãã³ã°ãªã©ã远å ããããšãã§ããŸãã
ãã€ãã®ããã«ãå®å
šãªå€æŽã¯GitHubã§èŠã€ããããšãã§ããŸãã
ãæ³šæ ã¢ããªã±ãŒã·ã§ã³ã倧éã®ãã©ãã£ãã¯ãåä¿¡ãããšãäžå®å®ãªå¿çãéå§ããŸãã ããšãã°ãã¿ã€ã ã¢ãŠãã®ããã«æŽ»æ§ãããŒãã倱æããã³ã³ããããªããŒããããŸãã ãã®ãããäžéšã®ãšã³ãžãã¢ã¯ã掻æ§ãµã³ãã«ããŸã£ãã䜿çšããªãããšã奜ã¿ãŸãã å人çã«ã¯ãããå€ãã®ãªã¯ãšã¹ãããµãŒãã¹ã«æ¥ãŠããããšã«æ°ä»ãããããªãœãŒã¹ãã¹ã±ãŒãªã³ã°ããæ¹ãè¯ããšæããŸãã ããšãã°ã HPAã䜿çšããŠçã®èªåã¹ã±ãŒãªã³ã°ã詊ãããšãã§ããŸãã
ã¹ããã10.æ£åžžãªã·ã£ããããŠã³
ãµãŒãã¹ã忢ããå¿
èŠãããå Žåãæ¥ç¶ãèŠæ±ããã®ä»ã®æäœãããã«åæããã«ãããããæ£ããåŠçããããšããå§ãããŸãã Goã¯ãããŒãžã§ã³1.8以éã®http.Server
ãæ£åžžãªã·ã£ããããŠã³ãããµããŒãããŠããŸãã ãããã©ã®ããã«äœ¿çšã§ããããæ€èšããŠãã ããã
main.go func main() { ... r := handlers.Router(version.BuildTime, version.Commit, version.Release) interrupt := make(chan os.Signal, 1) signal.Notify(interrupt, os.Interrupt, syscall.SIGTERM) srv := &http.Server{ Addr: ":" + port, Handler: r, } go func() { log.Fatal(srv.ListenAndServe()) }() log.Print("The service is ready to listen and serve.") killSignal := <-interrupt switch killSignal { case os.Interrupt: log.Print("Got SIGINT...") case syscall.SIGTERM: log.Print("Got SIGTERM...") } log.Print("The service is shutting down...") srv.Shutdown(context.Background()) log.Print("Done") }
ãã®äŸã§ã¯ãã·ã¹ãã ä¿¡å·SIGINT
ããã³SIGTERM
ãã€ã³ã¿ãŒã»ãããããããã®ããããããã£ãããããå ŽåããµãŒãã¹ãæ£ãã忢ããŸãã
ãæ³šæ ãã®ã³ãŒããæžãããšããããã§ãSIGKILL
ååããããšããŸããã ç§ã¯ãã®ã¢ãããŒããç°ãªãã©ã€ãã©ãªãŒã§äœåºŠãèŠãŸãããã確å®ã«æ©èœããããšã確信ããŸããã ããããSandorSzÃŒcsãææããããã«ã SIGKILL
ååããããšSIGKILL
ã§ããŸããã SIGKILL
å ŽåSIGKILL
ã¢ããªã±ãŒã·ã§ã³ã¯ããã«åæ¢ããŸãã
ã¹ããã11. Dockerfile
ã¢ããªã±ãŒã·ã§ã³ã¯ãKubernetesã§å®è¡ããæºåãã»ãŒæŽããŸãããä»åºŠã¯ã³ã³ããåãããšãã§ãã
å¿
èŠãªæãåçŽãªDockerfile
ãæ¬¡ã®ããã«ãªããŸãã
Dockerfile
FROM scratch ENV PORT 8000 EXPOSE $PORT COPY advent / CMD ["/advent"]
å¯èœãªéãæå°ã®ã³ã³ãããäœæããããã«ãã€ããªãã³ããŒããŠå®è¡ããŸãïŒããã«ã PORT
倿°ã転éããããšãå¿ããŸããã§ããïŒã
ããã§ã Makefile
å°ã倿ŽããŠãã€ã¡ãŒãžã¢ã»ã³ããªãšã³ã³ãããŒèµ·åã远å ããŸãã ããã§ã¯ã build
ã¿ãŒã²ããã®äžéšãšããŠã¯ãã¹ã³ã³ãã€ã«ã«äœ¿çšãã2ã€ã®æ°ãã倿°GOOS
ãšGOARCH
ã䟿å©ã«ãªããŸãã
ã¡ã€ã¯ãã¡ã€ã« ... GOOS?=linux GOARCH?=amd64 ... build: clean CGO_ENABLED=0 GOOS=${GOOS} GOARCH=${GOARCH} go build \ -ldflags "-s -w -X ${PROJECT}/version.Release=${RELEASE} \ -X ${PROJECT}/version.Commit=${COMMIT} -X ${PROJECT}/version.BuildTime=${BUILD_TIME}" \ -o ${APP} container: build docker build -t $(APP):$(RELEASE) . run: container docker stop $(APP):$(RELEASE) || true && docker rm $(APP):$(RELEASE) || true docker run --name ${APP} -p ${PORT}:${PORT} --rm \ -e "PORT=${PORT}" \ $(APP):$(RELEASE) ...
ããã§ã container
ã¿ãŒã²ããã远å ããŠã€ã¡ãŒãžãæ§ç¯ãã run
ã¿ãŒã²ããã調æŽããŠããã€ããªãèµ·åãã代ããã«ã³ã³ãããèµ·åããããã«ããŸããã ãã¹ãŠã®å€æŽã¯ããããå
¥æã§ããŸã ã
ããã§ã make run
ãmake run
ããŠããã»ã¹å
šäœããã¹ãã§ããŸãã
ã¹ããã12.äŸåé¢ä¿ç®¡ç
ãããžã§ã¯ãã«ã¯å€éšäŸåé¢ä¿ã1ã€ãããŸãgithub.com/gorilla/mux
ã ãããã£ãŠãå®éã«æ¬çªçšã®æºåãæŽã£ãã¢ããªã±ãŒã·ã§ã³ã®å Žåã äŸåé¢ä¿ç®¡çã远å ããå¿
èŠããããŸãã depãŠãŒãã£ãªãã£ã䜿çšããå Žåã dep init
ã³ãã³ããåŒã³åºãã ãã§ãã
$ dep init Using ^1.6.0 as constraint for direct dep github.com/gorilla/mux Locking in v1.6.0 (7f08801) for direct dep github.com/gorilla/mux Locking in v1.1 (1ea2538) for transitive dep github.com/gorilla/context
ãã®çµæã Gopkg.lock
ãšGopkg.lock
ãããã³äœ¿çšããããã¹ãŠã®äŸåé¢ä¿ãå«ãvendor
ãã£ã¬ã¯ããªãäœæãããŸããã å人çã«ã¯ãç¹ã«éèŠãªãããžã§ã¯ãã®å Žåã vendor
ã«gitãããã·ã¥ããããšã奜ã¿ãŸãã
ã¹ããã13. Kubernetes
æåŸã«ã æçµã¹ããã ïŒKubernetesã§ã¢ããªã±ãŒã·ã§ã³ãèµ·åããŸãã Kubernetesã詊ãæãç°¡åãªæ¹æ³ã¯ãããŒã«ã«ç°å¢ã«minikubeãã€ã³ã¹ããŒã«ããŠæ§æããããšã§ãã
Kubernetesã¯ãã¬ãžã¹ããªïŒDockerã¬ãžã¹ããªïŒããç»åãããŠã³ããŒãããŸãã ç§ãã¡ã®å Žåããããªãã¯ã¬ãžã¹ããªã§ååã§ã-Docker Hub ã Makefile
å¥ã®å€æ°ãšå¥ã®ã³ãã³ããå¿
èŠã§ãã
ã¡ã€ã¯ãã¡ã€ã« CONTAINER_IMAGE?=docker.io/webdeva/${APP} ... container: build docker build -t $(CONTAINER_IMAGE):$(RELEASE) . ... push: container docker push $(CONTAINER_IMAGE):$(RELEASE)
ããã§ã CONTAINER_IMAGE
倿°ã¯ãéä¿¡å
ããã³ã³ã³ããã€ã¡ãŒãžã®ããŠã³ããŒãå
ã®ã¬ãžã¹ããªãªããžããªãèšå®ããŸãã ã芧ã®ãšããããã®äŸã§ã¯ããŠãŒã¶ãŒåïŒ webdeva
ïŒãã¬ãžã¹ããªãã¹ã§äœ¿çšãããŠããŸãã hub.docker.comã«ã¢ã«ãŠã³ãããªãå Žåã¯ãã¢ã«ãŠã³ããäœæããŠããdocker docker login
ã䜿çšããŠdocker login
ã ãã®åŸãã¬ãžã¹ããªã«ç»åãéä¿¡ã§ããŸãã
make push
詊ããŠã¿ãŸãããïŒ
$ make push ... docker build -t docker.io/webdeva/advent:0.0.1 . Sending build context to Docker daemon 5.25MB ... Successfully built d3cc8f4121fe Successfully tagged webdeva/advent:0.0.1 docker push docker.io/webdeva/advent:0.0.1 The push refers to a repository [docker.io/webdeva/advent] ee1f0f98199f: Pushed 0.0.1: digest: sha256:fb3a25b19946787e291f32f45931ffd95a933100c7e55ab975e523a02810b04c size: 528
ããŸãããïŒ ããã§ãäœæãããã€ã¡ãŒãžãã¬ãžã¹ããªã«èŠã€ãããŸã ã
Kubernetesã«å¿
èŠãªæ§æïŒãããã§ã¹ãïŒãå®çŸ©ããŸãã ãããã¯JSONãŸãã¯YAML圢åŒã®éçãã¡ã€ã«ã§ãããããã倿°ãã®ä»£ããã«sed
ãŠãŒãã£ãªãã£ã䜿çšããå¿
èŠããããŸãã ãã®äŸã§ã¯ã deployment ã serviceãããã³ingressã® 3ã€ã®ã¿ã€ãã®ãªãœãŒã¹ãèŠãŠãããŸãã
ãæ³šæ helmãããžã§ã¯ãã¯ãäžè¬ã«Kubernetesã®æ§æãªãªãŒã¹ã管çããåé¡ã解決ããç¹ã«æè»ãªæ§æã®äœæãæ€èšããŸãã ãããã£ãŠã sed
ã ãsed
ã¯ååã§ãªãå Žåsed
ãHelmãç¥ãããšã¯çã«ããªã£ãŠããŸãã
å±éã®æ§æãæ€èšããŸãã
deployment.yaml apiVersion: extensions/v1beta1 kind: Deployment metadata: name: {{ .ServiceName }} labels: app: {{ .ServiceName }} spec: replicas: 3 strategy: type: RollingUpdate rollingUpdate: maxUnavailable: 50% maxSurge: 1 template: metadata: labels: app: {{ .ServiceName }} spec: containers: - name: {{ .ServiceName }} image: docker.io/webdeva/{{ .ServiceName }}:{{ .Release }} imagePullPolicy: Always ports: - containerPort: 8000 livenessProbe: httpGet: path: /healthz port: 8000 readinessProbe: httpGet: path: /readyz port: 8000 resources: limits: cpu: 10m memory: 30Mi requests: cpu: 10m memory: 30Mi terminationGracePeriodSeconds: 30
Kubernetesã®æ§æã®åé¡ã«ã€ããŠã¯ãå¥ã®èšäºã§å¯ŸåŠããã®ãæé©ã§ãããã芧ã®ãšãããç¹ã«ã³ã³ãããŒã®ã¬ãžã¹ããªãšã€ã¡ãŒãžãããã§å®çŸ©ãããæŽ»æ§ãšæºåãµã³ãã«ã®ã«ãŒã«ãå®çŸ©ãããŠããŸãã
ãµãŒãã¹ã®äžè¬çãªæ§æã¯ããã·ã³ãã«ã«èŠããŸãã
service.yaml apiVersion: v1 kind: Service metadata: name: {{ .ServiceName }} labels: app: {{ .ServiceName }} spec: ports: - port: 80 targetPort: 8000 protocol: TCP name: http selector: app: {{ .ServiceName }}
ãããŠæåŸã«ãã€ã³ã°ã¬ã¹ã ããã§ã¯ãããšãã°ãKubernetesã®å€éšãããµãŒãã¹ã«ã¢ã¯ã»ã¹ããã®ã«åœ¹ç«ã€ãã€ã³ã°ã¬ã¹ã³ã³ãããŒã©ãŒã®æ§æãå®çŸ©ããŸãã ãã¡ã€ã³advent.test
ã«ã¢ã¯ã»ã¹ãããšãã«ãµãŒãã¹ã«ãªã¯ãšã¹ããéä¿¡ãããšããŸãïŒå®éã«ã¯ååšããŸããïŒã
ingress.yaml apiVersion: extensions/v1beta1 kind: Ingress metadata: annotations: kubernetes.io/ingress.class: nginx ingress.kubernetes.io/rewrite-target: / labels: app: {{ .ServiceName }} name: {{ .ServiceName }} spec: backend: serviceName: {{ .ServiceName }} servicePort: 80 rules: - host: advent.test http: paths: - path: / backend: serviceName: {{ .ServiceName }} servicePort: 80
èšå®ã®åäœã確èªããã«ã¯ã å
¬åŒããã¥ã¡ã³ãã䜿çšããŠminikube
ãã€ã³ã¹ããŒã«ããŸã ã ããã«ãæ§æãé©çšããŠãµãŒãã¹ãæ€èšŒããã«ã¯ã kubectlãŠãŒãã£ãªãã£ãå¿
èŠã«ãªããŸãã
minikube
ãèµ·åããã€ã³ã°ã¬ã¹ãæå¹ã«ããŠkubectl
ãæºåããã«ã¯ã次ã®ã³ãã³ãkubectl
å¿
èŠkubectl
ã
minikube start minikube addons enable ingress kubectl config use-context minikube
Makefile
ã«å¥ã®ç®æšã远å ããŠã minikube
ãµãŒãã¹ãã€ã³ã¹ããŒã«ããŸãã
Makefile
minikube: push for t in $(shell find ./kubernetes/advent -type f -name "*.yaml"); do \ cat $$t | \ gsed -E "s/\{\{(\s*)\.Release(\s*)\}\}/$(RELEASE)/g" | \ gsed -E "s/\{\{(\s*)\.ServiceName(\s*)\}\}/$(APP)/g"; \ echo ---; \ done > tmp.yaml kubectl apply -f tmp.yaml
ãããã®ã³ãã³ãã¯ããã¹ãŠã®*.yaml
æ§æãåäžãã¡ã€ã«ã«ãã³ã³ãã€ã«ãããã倿°ã Release
ããã³ServiceName
å®éã®å€ã«çœ®ãæãïŒéåžžã®sed
ã§ã¯ãªãgsed
ã䜿çšïŒã kubectl apply
ãå®è¡ããŠKubernetesã«ã¢ããªã±ãŒã·ã§ã³ãã€ã³ã¹ããŒã«ããŸãã
èšå®ãã©ã®ããã«é©çšããããã確èªããŸãããã
$ kubectl get deployment NAME DESIRED CURRENT UP-TO-DATE AVAILABLE AGE advent 3 3 3 3 1d $ kubectl get service NAME CLUSTER-IP EXTERNAL-IP PORT(S) AGE advent 10.109.133.147 <none> 80/TCP 1d $ kubectl get ingress NAME HOSTS ADDRESS PORTS AGE advent advent.test 192.168.64.2 80 1d
ããã§ãæå®ããããã¡ã€ã³ãä»ããŠãµãŒãã¹ã«ãªã¯ãšã¹ããéä¿¡ããŠã¿ãŸãããã ãŸããããŒã«ã«ã®/etc/hosts
advent.test
ãã¡ã€ã³ã远å ããå¿
èŠããã/etc/hosts
ïŒWindowsã®å Žå- %SystemRoot%\System32\drivers\etc\hosts
ïŒïŒ
echo "$(minikube ip) advent.test" | sudo tee -a /etc/hosts
ãããŠä»ãããªãã¯ãµãŒãã¹ã®åäœã確èªããããšãã§ããŸãïŒ
curl -i http://advent.test/home HTTP/1.1 200 OK Server: nginx/1.13.6 Date: Sun, 10 Dec 2017 20:40:37 GMT Content-Type: application/json Content-Length: 72 Connection: keep-alive Vary: Accept-Encoding {"buildTime":"2017-12-10_11:29:59","commit":"020a181","release":"0.0.5"}%
ããããïŒ
ããã¥ã¢ã«ã®ãã¹ãŠã®ã¹ãããã¯ããã«ãããŸãã2ã€ã®ãªãã·ã§ã³ããããŸãïŒ commit-by-commitãš1ã€ã®ãã£ã¬ã¯ããªå
ã®ãã¹ãŠã®ã¹ãããã§ã ã 質åãããå Žåã¯ãåé¡ãäœæããããTwitterã§@webdevaãããã¯ããããããã«ã³ã¡ã³ããæ®ãããã§ããŸãã
çç£æºåãæŽã£ãçã®ããæè»ãªãµãŒãã¹ãã©ã®ããã«èŠãããã«ã€ããŠèå³ãããå Žåã¯ã kubernetesã®èŠä»¶ãæºããGoã¢ããªã±ãŒã·ã§ã³ãã³ãã¬ãŒãã§ããtakama / k8sappãããžã§ã¯ããã芧ãã ããã
PSã¬ãã¥ãŒãšã³ã¡ã³ããå¯ããŠããããã¿ãªãŒã»ãã¹ããããã ã ããŒã« ã»ãã«ãœãŒ ã ãµã³ããŒã« ã» ã·ã¥ãã ã ããã·ã ã»ãã£ã©ãã ããã®ä»ã®ã³ãã¥ããã£ã®ä»²éã«æè¬ããŸãã