Triển khai ứng dụng Go trong Docker Container

Giới thiệu

Nếu bạn chưa bao giờ nghe nói về Docker, nhưng điều đó khó xảy ra, điều đầu tiên bạn nên biết là Docker cho phép bạn chạy các ứng dụng một cách riêng biệt và tách biệt các mối quan tâm, nhưng vẫn cho phép chúng giao tiếp và tương tác với thế giới bên ngoài.

Và bạn nên biết rằng mọi người đều sử dụng nó và mọi nhà cung cấp dịch vụ đám mây lớn đều có một giải pháp dành riêng cho việc chạy các vùng chứa, vì vậy bạn nên tìm hiểu nó!

Tải về

Cài đặt thay đổi tùy thuộc vào hệ thống của bạn, vì vậy hãy kiểm trahttps://www.docker.com/get-docker.

Tôi giả sử bạn đã cài đặt Docker và códockerlệnh có sẵn trong trình bao của bạn.

Hình ảnh chính thức của Go

Docker duy trì một danh sách các hình ảnh chính thức cho nhiều ngôn ngữ khác nhau và Go không phải là ngoại lệ, nó là một phần của bản gốcra mắt hình ảnh chính thức back in 2014.

Kho hình ảnh chính thức có thể được tìm thấy tạihttps://hub.docker.com/_/golang/. Có nhiều thẻ xác định cả phiên bản Go và hệ điều hành bạn muốn tìm nạp.

Một ứng dụng mẫu

Ví dụ, tôi sẽ triển khai một ứng dụng Go nhỏ trong vùng chứa Docker. Nó lắng nghe trên cổng 8000, nhận một trang web dưới dạngqtham số truy vấn, tìm nạp nó và in các liên kết mà nó tìm thấy:

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 }

Ví dụ sử dụng:

Di chuyển ứng dụng sang Docker

Tôi đã đặt ứng dụng trênhttps://github.com/flaviocopes/findlinks.

Sử dụnggo getTôi có thể dễ dàng tải xuống và cài đặt nó bằng cách sử dụnggo get github.com/flaviocopes/findlinks.

Đang chạy

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

đầu tiên sẽ tải xuốnggolangHình ảnh Docker, nếu bạn chưa có, thì nó sẽ tìm nạp kho lưu trữ và sẽ quét các phần phụ thuộc bổ sung không có trong thư viện chuẩn. Trong trường hợp này,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

Lệnh này tạo một vùng chứa và chạy nó. Chúng tôi có thể kiểm tra nó bằng cách sử dụngdocker ps -l(các-ltùy chọn yêu cầu Docker liệt kê vùng chứa mới nhất đã chạy):

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

Vùng chứa đã thoát khigo getlệnh đã hoàn thành.

Docker chỉ xây dựng một hình ảnh theo yêu cầu và chạy nó; để chạy lại, chúng ta sẽ cần lặp lại quá trình, nhưng hình ảnh sẽ giúp chúng ta: bây giờ chúng ta hãy tạo một hình ảnh từ vùng chứa này, để chúng ta có thể chạy nó sau:

docker commit $(docker ps -lq) findlinks

Lệnh trên lấy ID vùng chứa cuối cùng bằng cách sử dụngdocker ps -lq, và cam kết hình ảnh. Hình ảnh hiện đã được cài đặt, bạn có thể kiểm tra bằng cách sử dụngdocker images findlinks:

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

Chúng tôi có thể chạyfindlinkslệnh của chúng tôifindlinkshình ảnh với

docker run -p 8000:8000 findlinks findlinks

Đó là nó! Ứng dụng của chúng tôi bây giờ sẽ phản hồi trênhttp://192.168.99.100:8000/, Ở đâu192.168.99.100là địa chỉ IP của vùng chứa Docker.

Bạn có thể kiểm tra tính năng gọi điệnhttp://192.168.99.100:8000/?q=flaviocopes.com, sẽ in ra cùng một đầu ra mà chúng tôi đã có khi chạy ứng dụng cục bộ:

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/"

Cắt hình ảnh Docker

Chiến lược này hiện không được dùng nữa để thay thế choxây dựng nhiều giai đoạn

Vấn đề với kết quả trên là,hình ảnh rất lớn: 720MB cho chương trình đơn giản này là không thực sự chấp nhận được, hãy nhớ rằng đây là một kịch bản rất đơn giản. Chúng tôi có thể muốn triển khai hàng nghìn phiên bản của ứng dụng và kích thước này sẽ không hoạt động.

Tại sao hình ảnh lại lớn như vậy? Bởi vì những gì xảy ra là ứng dụng Go được biên dịch bên trong vùng chứa. Vì vậy hình ảnh cần được cài đặt trình biên dịch Go. Và tất nhiên mọi thứ cần thiết của trình biên dịch, GCC và toàn bộ bản phân phối Linux (Debian Jessie). Nó tải về Go và cài đặt nó, biên dịch ứng dụng và chạy nó.

Tất cả diễn ra quá nhanh mà chúng tôi thậm chí không nhận ra. Nhưng chúng ta có thể làm tốt hơn. Làm sao? Tôi áp dụng những gì tôi đã học đượcXây dựng vùng chứa Docker tối thiểu cho các ứng dụng GobởiNick Gauthier

Chúng tôi yêu cầu Docker chạygolang:1.8.3hình ảnh vàbiên dịch tĩnhứng dụng của chúng tôi, vô hiệu hóa CGO, có nghĩa là hình ảnh thậm chí không cần các thư viện C mà nó thường cần để truy cập khi được liên kết động, bằng cách sử dụng:

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'

Bây giờ chúng tôi có mộtfindlinksnhị phân trong thư mục:

$ 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

Lưu ý rằng tất nhiên đây không phải là tệp mà tôi nhận được nếu tôigo buildtrên OSX, đó là một tệp nhị phân sẵn sàng cho Linux:

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

Sau đó, chúng tôi tạo một Dockerfile, yêu cầu Docker sử dụngsắt / đế, một hình ảnh rất nhẹ nhàng:

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

Bây giờ chúng tôi có thể xây dựng hình ảnh, gắn thẻ nóflaviocopes/golang-docker-example-findlinks:

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

và chạy nó:

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

Đầu ra vẫn giống như trước, nhưng lần này hình ảnh không phải là 720MB mà chỉ là 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

Bản dựng nhiều giai đoạn

Phần này đã được thêm vào nhờ các nhận xét trên Reddit chỉ tôi đến các bản dựng nhiều giai đoạn, một bổ sung gần đây choDocker 17.05

Bản dựng nhiều giai đoạncho phép chúng ta có một hình ảnh nhẹ rất dễ dàng mà không cần phải biên dịch tệp nhị phân và sau đó chạy nó một cách riêng biệt. Đây là Dockerfile để đưa vào ứng dụng:

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"]

Chạy

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

xây dựng một hình ảnh nhẹ (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: