在Docker容器中部署Go應用程序

介紹

如果您從未聽說過Docker,但不太可能,那麼您應該知道的第一件事是,Docker允許您獨立運行應用程序,並且可以將關注點分離開來,但仍允許它們與外部世界進行通信和交互。

而且您應該知道每個人都使用它,並且每個主要的雲提供商都提供專用於運行容器的解決方案,因此您應該學習它!

安裝

安裝會因您的系統而異,因此請檢查https://www.docker.com/get-docker

我假設您已經安裝了Docker並擁有docker您的Shell中可用的命令。

Go官方圖片

Docker維護了許多不同語言的官方映像列表,Go也不例外,因為它是原始語言的一部分官方圖片發布 back in 2014.

官方圖片庫可以在以下位置找到https://hub.docker.com/_/golang/。有很多標記既可以標識Go版本,也可以標識您要獲取的操作系統。

一個示例應用

例如,我將在Docker容器中部署一個小Go應用程序。它偵聽端口8000,將網頁作為q查詢參數,將其提取並打印找到的鏈接:

findlinks.go

package main

import ( “fmt” “log” “net/http”

<span style="color:#e6db74">"golang.org/x/net/html"</span>

)

func main() { http.HandleFunc("/", handler) log.Fatal(http.ListenAndServe(“0.0.0.0:8000”, nil)) }

func handler(w http.ResponseWriter, r *http.Request) { url := r.URL.Query().Get(“q”) fmt.Fprintf(w, “Page = %q\n”, url) if len(url) == 0 { return } page, err := parse(“https://” + url) if err != nil { fmt.Printf(“Error getting page %s %s\n”, url, err) return } links := pageLinks(nil, page) for _, link := range links { fmt.Fprintf(w, “Link = %q\n”, link) } }

func parse(url string) (*html.Node, error) { fmt.Println(url) r, err := http.Get(url) if err != nil { return nil, fmt.Errorf(“Cannot get page”) } b, err := html.Parse(r.Body) if err != nil { return nil, fmt.Errorf(“Cannot parse page”) } return b, err }

func pageLinks(links []string, n *html.Node) []string { if n.Type == html.ElementNode && n.Data == “a” { for _, a := range n.Attr { if a.Key == “href” { links = append(links, a.Val) } } } for c := n.FirstChild; c != nil; c = c.NextSibling { links = pageLinks(links, c) } return links }

用法示例:

將應用程序移至Docker

我把應用程序放在https://github.com/flaviocopes/findlinks

使用go get我可以輕鬆地下載並安裝它,使用go get github.com/flaviocopes/findlinks

跑步

docker run golang go get -v github.com/flaviocopes/findlinks

將首先下載golangDocker映像(如果尚未安裝),它將獲取存儲庫並掃描標準庫中未包含的其他依賴項。在這種情況下,golang.org/x/net/html

$ docker run golang go get -v github.com/flaviocopes/findlinks
github.com/flaviocopes/findlinks (download)
Fetching https://golang.org/x/net/html?go-get=1
Parsing meta tags from https://golang.org/x/net/html?go-get=1 (status code 200)
get "golang.org/x/net/html": found meta tag main.metaImport{Prefix:"golang.org/x/net", VCS:"git", RepoRoot:"https://go.googlesource.com/net"} at https://golang.org/x/net/html?go-get=1
get "golang.org/x/net/html": verifying non-authoritative meta tag
Fetching https://golang.org/x/net?go-get=1
Parsing meta tags from https://golang.org/x/net?go-get=1 (status code 200)
golang.org/x/net (download)
golang.org/x/net/html/atom
golang.org/x/net/html
github.com/flaviocopes/findlinks

此命令創建一個容器並運行它。我們可以使用檢查docker ps -l(這-l選項告訴Docker列出運行的最新容器):

CONTAINER ID        IMAGE               COMMAND                  CREATED             STATUS                     PORTS               NAMES
343d96441f16        golang              "go get -v github...."   3 minutes ago       Exited (0) 2 minutes ago                       mystifying_swanson

容器退出時,go get命令完成。

Docker只是構建了一個按需映像並運行了它。要再次運行它,我們將需要重複該過程,但是圖像可以幫助我們:讓我們現在從該容器中創建一個圖像,以便以後可以運行它:

docker commit $(docker ps -lq) findlinks

上面的命令使用以下命令獲取最後一個容器IDdocker ps -lq,並提交圖像。該映像已安裝完畢,您可以使用docker images findlinks

$ docker images findlinks
REPOSITORY          TAG                 IMAGE ID            CREATED             SIZE
findlinks           latest              4e7ebb87d02e        11 seconds ago      720MB

我們可以運行findlinks在我們的命令findlinks與圖像

docker run -p 8000:8000 findlinks findlinks

而已!我們的應用程序現在將響應http://192.168.99.100:8000/, 在哪裡192.168.99.100是Docker容器的IP地址。

您可以測試通話http://192.168.99.100:8000/?q=flaviocopes.com,它將輸出與在本地運行應用程序時相同的輸出:

Page = "flaviocopes.com"
Link = "https://flaviocopes.com/index.xml"
Link = "https://twitter.com/flaviocopes"
Link = "https://github.com/flaviocopes"
Link = "https://stackoverflow.com/users/205039/flaviocopes"
Link = "https://linkedin.com/in/flaviocopes/"
Link = "mailto:[email protected]"
Link = "/"
Link = "/page/contact/"
Link = "/page/about/"
Link = "https://flaviocopes.com/golang-tutorial-rest-api/"
Link = "https://flaviocopes.com/golang-environment-variables/"
Link = "https://flaviocopes.com/golang-sql-database/"
Link = "https://flaviocopes.com/golang-is-go-object-oriented/"
Link = "https://flaviocopes.com/golang-comparing-values/"
Link = "https://flaviocopes.com/golang-data-structures/"
Link = "https://flaviocopes.com/golang-data-structure-binary-search-tree/"
Link = "https://flaviocopes.com/golang-data-structure-graph/"
Link = "https://flaviocopes.com/golang-data-structure-linked-list/"
Link = "https://flaviocopes.com/golang-data-structure-queue/"
Link = "https://flaviocopes.com/golang-data-structure-stack/"
Link = "https://flaviocopes.com/golang-event-listeners/"

修剪Docker映像

現在不贊成使用此策略,而建議多階段構建

以上結果的問題是,圖像很大:720MB對於這個簡單的程序並不是真正可以接受的,請記住,這是一個非常簡單的方案。我們可能要部署數千個應用程序實例,而這種大小將無法正常工作。

為什麼圖像這麼大?因為發生的事情是Go應用程序是在容器內編譯的。因此,該映像需要安裝Go編譯器。當然還有編譯器,GCC和整個Linux發行版(Debian Jessie)所需的一切。它下載Go並安裝,編譯該應用程序並運行它。

速度如此之快,我們甚至都沒有意識到。但是我們可以做得更好。如何?我運用我學到的東西為Go應用程序構建最小的Docker容器經過尼克·高錫(Nick Gauthier)

我們告訴Docker運行golang:1.8.3圖像和靜態編譯我們的應用程序禁用了CGO,這意味著圖像甚至不需要動態鏈接時通常需要訪問的C庫,可以使用:

docker run --rm -it -v "$GOPATH":/gopath -v "$(pwd)":/app -e "GOPATH=/gopath" -w /app golang:1.8.3 sh -c 'CGO_ENABLED=0 go build -a --installsuffix cgo --ldflags="-s" -o findlinks'

我們現在有一個findlinks文件夾中的二進製文件:

$ ll
.rw-r--r--   77 flavio 17 Aug 18:57 Dockerfile
.rwxr-xr-x 4.2M flavio 17 Aug 19:13 findlinks
.rw-r--r-- 1.1k flavio 12 Aug 18:10 findlinks.go

$ file findlinks findlinks: ELF 64-bit LSB executable, x86-64, version 1 (SYSV), statically linked, stripped

請注意,這當然不是我得到的同一文件go build在OSX上,它是Linux就緒的二進製文件:

$ file findlinks
findlinks: Mach-O 64-bit executable x86_64

然後,我們創建一個Dockerfile,告訴Docker使用鐵/底座,非常輕的圖像:

FROM iron/base
WORKDIR /app
COPY findlinks /app/
ENTRYPOINT ["./findlinks"]

現在我們可以構建圖像,對其進行標記flaviocopes/golang-docker-example-findlinks

docker build -t flaviocopes/golang-docker-example-findlinks .

並運行它:

docker run --rm -it -p 8000:8000 flaviocopes/golang-docker-example-findlinks

輸出與以前相同,但是這次圖像不是720MB,而是11.1MB

REPOSITORY                                    TAG                 IMAGE ID            CREATED             SIZE
flaviocopes/golang-docker-example-findlinks   latest              f32d2fd74638        14 minutes ago      11.1MB
findlinks                                     latest              c60f6792b9f3        20 minutes ago      720MB

多階段構建

添加此部分是由於Reddit的評論將我指向多階段構建,這是最近的新增內容。Docker 17.05

多階段構建使我們能夠非常輕鬆地擁有輕量級映像,而無需編譯二進製文件然後單獨運行它。這是要放入應用程序中的Dockerfile:

FROM golang:1.8.3 as builder
WORKDIR /go/src/github.com/flaviocopes/findlinks
RUN go get -d -v golang.org/x/net/html
COPY findlinks.go  .
RUN CGO_ENABLED=0 GOOS=linux go build -a -installsuffix cgo -o findlinks .

FROM alpine:latest RUN apk --no-cache add ca-certificates WORKDIR /root/ COPY --from=builder /go/src/github.com/flaviocopes/findlinks/findlinks . CMD ["./findlinks"]

$ docker build -t flaviocopes/golang-docker-example-findlinks .

生成一個輕量級(10.8MB)的圖像:

$ docker images
REPOSITORY                                    TAG                 IMAGE ID            CREATED             SIZE
flaviocopes/golang-docker-example-findlinks   latest              aa2081ca7016        12 seconds ago      10.8MB

Run the image with

$ docker run --rm -it -p 8000:8000 flaviocopes/golang-docker-example-findlinks  

More go tutorials: