在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容器经过尼克·高蒂尔

我们告诉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: