簡介
如果你從沒聽過 Docker,不過這很不可能,第一件你應該知道的事情是 Docker 允許你以隔離方式執行應用程式,高度關注於分離的問題,但同時允許它們與外部世界通訊和互動。
還有你應該知道的是,每個人都在使用它,每個主要的雲端服務提供商也都有專用於運行容器的解決方案,所以你應該學習它!
安裝
安裝方法隨你的系統而異,所以請查看 https://www.docker.com/get-docker。
我假設你已經安裝了 Docker,並在你的 shell 中可以使用 docker
命令。
Go 官方鏡像
Docker 維護著許多不同語言的官方鏡像清單,Go 也不例外,它是 2014 年原始 官方鏡像推出 的一部分。
官方鏡像庫可以在這裡找到: https://hub.docker.com/_/golang/。有許多標籤可以識別所需的 Go 版本,以及想要提取的操作系統。
一個範例應用
作為範例,我將在 Docker 容器中部署一個小型的 Go 應用程式。它在 8000 port 監聽,以 q
作為查詢參數來獲得網頁,然後取回並印出找到的連結:
package main
import (
"fmt"
"log"
"net/http"
"golang.org/x/net/html"
)
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
如果你還沒有 golang
的 Docker 映像檔,這個命令將首先下載該映像檔,然後下載存儲庫並掃描標準庫中未包含的額外依賴,這裡是 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
以上命令使用 docker ps -lq
獲取最後一個容器的 ID,並提交該映像檔。
你可以使用 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]](/cdn-cgi/l/email-protection)"
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,編譯應用程式並運行,這一切都是如此快速,我們甚至都沒有注意到。但我們可以做得更好。我們該怎麼做?我們使用了 Nick Gauthier 在 為 Go 應用程式構建最小的 Docker 容器 文章學到的東西。
我們告訴 Docker 執行 golang:1.8.3
映像檔並使用 CGO
禁用 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
請注意,這當然不是我在 OSX 上編譯所得到的檔案,它是一個準備好在 Linux 上運行的二進位檔:
$ file findlinks
findlinks: Mach-O 64-bit executable x86_64
然後,我們創建一個 Dockerfile,告訴 Docker 使用 iron/base,一個非常輕量的映像檔:
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
多階段建置
多階段建置 讓我們能夠簡單地獲得輕量級映像檔,而不需要分別編譯二進位檔和運行它。這是我們在應用程式中的 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
運行映像檔:
$ docker run --rm -it -p 8000:8000 flaviocopes/golang-docker-example-findlinks