理論上來講Docker這東西,把Application包成image在跑container,應該行為要跟原生跑的一樣才對。即使不同,我們也會利用-v跟-p把volume以及port掛上去,讓他能直接對應到host裡面的某些東西。

然而,某些東西,尤其是API Server,我們總會碰到一些跑原生以及跑Container行為大不相同的地方,我把我開發中碰到的一些例子跟大家分享一下。

Docker + Proxy後拿到的Source IP會不對

我們以gin為例子,最常見的拿source IP的方法大概長得像這樣:

func(c *gin.Context) {
    ipAddr := c.ClientIP()
}

這個在原生裡面跑起來應該沒太大問題,問題是Docker來講就會有問題。Docker通常我們application都是在container裡面,用-p去map到外面的port,然後再利用諸如nginx之類的Load Balancer把外面expose的port forwarding到docker裡面。那這種拿法很顯然的,docker port收到的ip通常就是127.0.0.1(IPv4)或者::1(IPv6) — 也就是程式在container bind的位置。

這個是有標準解法的,標準解法是,以nginx為例,在site-enabled/default(或者自己的設定檔)設定proxy時,多夾帶一個X-Forwarded-For的header在http request裡面 :

        location / {
                # First attempt to serve request as file, then
                # as directory, then fall back to displaying a 404.
                # try_files $uri $uri/ =404;
                proxy_pass http://127.0.0.1:8800;
                proxy_set_header X-Real-IP $remote_addr;
                proxy_set_header X-Forwarded-For $proxy_add_x_forwarded_for;

        }

這樣X-Forwarded-For就會夾帶真實IP了,最後在go裡面用這方法就可以輕鬆拿到:

func(c *gin.Context) {
	ipAddr := c.GetHeader("X-Forwarded-For")
	//記得這段,不然原生跑起來會抓不到ip
	if ipAddr == "" {
		ipAddr = c.ClientIP()
	}
}

跑在Docker裡面的時候Docker SDK失效啦

Docker container裡面並沒有跑起來docker daemon(通常啦),所以裡所當然的Docker SDK的client拿得到但是沒辦法使用。

dc, err := client.NewEnvClient()
if err != nil {
    //其實沒有docker下,他也不會err....
    return err
}
//但是Ping下去就原形畢露了,err就跑出來了,這也是通常我們偵測有沒有docker的方法
_, err = dc.Ping(context.TODO()) 

這做法其實有兩種,分別是dind(Docker in Docker)跟較為簡單的掛載法(詳細可以參考這裡)。dind的做法問題很多,所以我就不贅述,而掛載法極簡單。Docker是靠/var/run/docker.sock這個socket來跟docker host溝通,所以其實只要把這個東西用-v參數,掛到docker裡面同樣位置不就好了嗎?

docker run -p 8800:8080 -v /var/run/docker.sock:/var/run/docker.sock --hostname $(hostname) --rm -d rayer/someawesomeimage'

這樣你的docker sdk就能正確地在docker container操作外面的docker,拿到外面container目前狀態以及其他騷操作了。

使用multi-stage build造成SSL Trust Root缺失,造成https操作錯誤

有另外一個問題是,明明一個很簡單的https call,比方說http.GET(“https://www.hinet.net”),原生條件下跑得很順利,在Docker裡面卻會出現X509的錯誤:x509: certificate signed by unknown authority。

這個原因是,multi-stage build我們通常使用的是scratch image當作最後的載體,而scratch就是什麼都沒有,包括trust root, CA什麼都沒有!那當然,當這個image跑container時,所有的https call都沒辦法認證對方的certificate — 你沒有trusted root,你要怎麼認証?

這個解法其實也不困難,我是參考了這裡得到的答案。通常來講multi-stage的build image是golang:alpine,所以我拿這個當例子,修改自己的Dockerfile:

FROM golang:alpine as build-env
# 安裝ca-certificate
RUN apk add -U --no-cache ca-certificates
#...
#...
#...做你剩下的building

FROM scratch
# 把剛剛安裝的ca-certificate拷貝過來
COPY --from=build-env /etc/ssl/certs/ca-certificates.crt /etc/ssl/certs/
ENV TZ=Asia/Taipei
EXPOSE 8080
ENTRYPOINT ["/app/server.app"]

好啦,這樣build出來的image就有trust root了,不再會有X.509的錯誤了。

發佈留言

發佈留言必須填寫的電子郵件地址不會公開。 必填欄位標示為 *