iT邦幫忙

第 11 屆 iT 邦幫忙鐵人賽

DAY 29
1

Go的交叉編譯

交叉編譯就是指在自己的OS上, 編譯出另一個OS可以執行的程式.
現在執行環境不外乎就是三類WindowsLinuxMac

在Windows上編譯

To MacOs

SET CGO_ENABLED=0
SET GOOS=darwin
SET GOARCH=amd64
go build main.go

To Linux

SET CGO_ENABLED=0
SET GOOS=linux
SET GOARCH=amd64
go build main.go

To Windows ???

go build main.go

在Linux上編譯

To MacOs

CGO_ENABLED=0 GOOS=darwin GOARCH=amd64 go build main.go

To Linux ???

CGO_ENABLED=0 GOOS=linux GOARCH=amd64 go build main.go

To Windows

CGO_ENABLED=0 GOOS=windows GOARCH=amd64 go build main.go

Windows的編譯出來一定是xxx.exe喔!!
會講這個是因為 公司還是給Windows做開發機器.
執行環境才是Linux.

Docker


小弟第一次聽到Docker大概是2年前.
一年多前自己慢慢在自己的小專案上摸索使用,
因為我之前任職的公司都還在傳統的人工管理跟實機VM.

Docker直至今天, 我也只有在公司的測試開發環境有使用.
自己的專案作品就是全docker化.
怎麼入門Docker就不在這範圍了, 我也是在鐵人賽文章中爬文學的.
還把Windows砍掉改裝Elementary os (斷捨離)
之後想把IDE砍掉, 來熟悉VIM.

我覺得Docker最方便的就是快速創建一個用完可拋的測試環境.
且都可以納入板控.
像我前面在寫DAO層的測試部份, 怎測試自己寫的sql是否正確?
這部份mock意義不大, 我就是搭配docker快速建立一組讀寫分離的mysql.
用完就刪除.

來替之前寫的寫一個Dockerfile
Dockerfile

ARG GO_VERSION
ARG PORT
FROM golang:${GO_VERSION}
WORKDIR /go/src/github.com/tedmax100/docker

COPY . .
ENV GO111MODULE=on
RUN go get -u -m

RUN   CGO_ENABLED=0 GOOS=linux go build -o main
EXPOSE ${PORT}

ENTRYPOINT ./main

build出docker image

docker build  --build-arg GO_VERSION=1.12.9 --build-arg PORT=8080 -t go-docker . 

build完成...1.7GB...
先跑看看

docker run -d --name gin-web -p 8080:8080 go-docker   


好!!! 很棒, 完成了docker佈署.
首先docker file會指定arg是因為我想docker跟我本機目前開發版本一致, 但我又不想寫死在dockerfile內, 就透過ARG把值透過--build-arg給指定進去.
Port也是一樣道理. 我可能一台機器起多台一樣的專案只是吃不同port.

來看看image吃了啥為什麼大到1.7G.
這樣跟我用node寫一份專案去佈署好像差異不大(都很肥)

Dive

Dive能幫助我們解析image中的每一個layer.

安裝dive

wget https://github.com/wagoodman/dive/releases/download/v0.8.1/dive_0.8.1_linux_amd64.deb
sudo apt install ./dive_0.8.1_linux_amd64.deb

來分析剛剛的docker image

dive go-docker


大部分情況下,我們所需求的僅是最終編譯出之二進制檔或執行檔
而編譯環境與第三方套件,都只是為了前面這個目的而準備
那來看看docker中的一個技巧叫做multi-stage build

ARG GO_VERSION 
ARG PORT
FROM golang:${GO_VERSION} AS build-env
WORKDIR /go/src/github.com/tedmax100/docker

COPY . .
ENV GO111MODULE=on
RUN go get -u -m

RUN   CGO_ENABLED=0 GOOS=linux go build -o main

FROM alpine 
WORKDIR /app
COPY --from=build-env  /go/src/github.com/tedmax100/docker /app
EXPOSE ${PORT}

ENTRYPOINT ./main


magic! 只剩下358MB
這樣的檔案拿去佈版就會快很多了.

Multi-stage build

適用在需要編譯環境的應用上(GO, C, JAVA...)
至少都會需要兩個環境的Docker image:

  • 編譯環境鏡像
    • 包含完整編譯引擎, 依賴庫等等
  • 運行環境鏡像
    • 包含編譯好的二進制檔, 用來執行app, 沒有編譯環境, 體積會小上很多
      透過multi-stage build的方式, 可以僅僅使用單個dockerfile, 降低維護複雜度.

Node用這技巧意義就不太大了, 那些依賴包它在執行環境還是需要的.
它最肥的就是那包, 沒法再分離.

來講一下上面那段Dockerfile
編譯所需的環境
就跟前面single build的dockerfile一樣.

ARG GO_VERSION 
ARG PORT
FROM golang:${GO_VERSION} AS build-env
WORKDIR /go/src/github.com/tedmax100/docker
COPY . .
ENV GO111MODULE=on
RUN go get -u -m
RUN   CGO_ENABLED=0 GOOS=linux go build -o main

產出執行所需的image
docker並非真的不用OS,實際運作時依然會啟動一個基底OS,app開發通常會選alpine或是scratch.
我們指定alpine, 然後把編譯階段的產出複製過來XD
就這樣.

也能在第二段產出測試環境並且跑測試.
測試成功才來這第三階段的產出.

FROM alpine 
WORKDIR /app
COPY --from=build-env  /go/src/github.com/tedmax100/docker /app
EXPOSE ${PORT}

ENTRYPOINT ./main

More Example

時區錯亂

  • main.go
package main

import (
	"fmt"
	"time"
)

func main() {

	location, err := time.LoadLocation("Europe/Berlin")
	if err != nil {
		fmt.Println(err)
	}

	t := time.Now().In(location)

	fmt.Println("Time in Berlin:", t.Format("02.01.2006 15:04"))
}

build 完之後執行會出錯

搜尋該錯誤 panic: time: missing Location in call to Time.In
搜尋Google後得知, 原來時區位置是從本地文件讀取出的.
可以透過安裝tzdata, 在/usr/share/zoneinfo產生各時區的資訊; 或者複製機器上的
修改Dockefile

# build stage
FROM golang:alpine AS build-env
ADD . /src
WORKDIR /src
RUN go build -o goapp

#final stage
FROM alpine
WORKDIR /app
# RUN apk add --no-cache tzdata
# COPY --from=build-env /etc/ssl/certs/ca-certificates.crt /etc/ssl/certs/
COPY --from=build-env /usr/share/zoneinfo /usr/share/zoneinfo
COPY --from=build-env /src/goapp /app/
ENTRYPOINT ./goapp

如果docker內需要SSL/TLC來訪問, 那就把ca-certificates 給複製進去吧.


上一篇
Go 鍊結參數 LDFLAGS
下一篇
CI with Go & Docker on Gitlab
系列文
下班加減學點Golang與Docker30

尚未有邦友留言

立即登入留言