1. 程式人生 > 程式設計 >使用 Go 執行與部署

使用 Go 執行與部署

簡介

到了最後,測試和檔案都已經完成了,只剩下部署了.

平常測試的時候可以直接使用 go run 執行,但到了部署階段,對於編譯型語言來說,肯定是要使用 go build 生成二進位制檔案的.

在 docker 中構建

因為整個系統都是基於 docker-compose 的,所以需要寫一個 Dockerfile,將整個專案在 docker 中構建為一個映象.

這樣,就可以直接在 docker 中運行了. 每次的本地構建生成二進位制檔案的過程,就轉變為了重新構建 docker 映象.

Dockerfile 如下:

FROM golang:1.13 as build

ENV GOPROXY="https://goproxy.io"
# https://stackoverflow.com/questions/36279253/go-compiled-binary-wont-run-in-an-alpine-docker-container-on-ubuntu-host
# build for static link
ENV CGO_ENABLED=0
WORKDIR /app
COPY . /app
RUN make build

# production stage
FROM alpine as production

WORKDIR
/app COPY ./conf/ /app/conf COPY --from=build /app/web /app EXPOSE 8081 ENTRYPOINT ["/app/web"] CMD [ "-c","./conf/config_docker.yaml" ] 複製程式碼

構建的時候用到了二階段構建,首先在普通的 golang 映象中構建二進位制檔案,然後複製到 alpine 映象中使用,以減少構建完成後的映象體積.

構建的時候需要設定環境變數 CGO_ENABLED=0,以禁止使用 CGO 動態連結,具體請參考 stackoverflow.

整合在 docker-compose 中

當 Dockerfile 寫完後,可以直接構建映象,並執行一下測試是否正常.

docker build -t go_web .
docker run -p 8081:8081 go_web
複製程式碼

一切順利之後,就可以將它整合到 docker-compose.yaml 中,命名為一個服務了.

app:
  build:
    context: .
  depends_on:
    - mysql
複製程式碼

有個依賴,畢竟 mysql 要先啟動. 至於為什麼沒有暴露埠,是因為要使用 nginx 反向代理.

使用 nginx 反向代理

docker-compose 可以將一個 SERVICE 手動縮放至多個例項.

Usage: up [options] [--scale SERVICE=NUM...] [SERVICE...]
複製程式碼

雖然在 kubernetes 出來之後,docker-compose scale 已經不再流行了,但還是實現一下. 這裡只關注 app 的擴容,即當前專案,而不管其他的依賴,比如資料庫等.

修改 API

首先,改造一下 /check/health API,返回 hostname,以便可以觀察到效果.

var hostname string

func init() {
	name,err := os.Hostname()
	if err != nil {
		name = "unknow"
	}
	hostname = name
}

// HealthCheck 返回心跳響應
func HealthCheck(ctx *gin.Context) {
	message := fmt.Sprintf("OK from %s",hostname)
	ctx.String(http.StatusOK,message)
}
複製程式碼

修改完成後,注意重新構建映象,以便改動生效.

建立 nginx service

在 docker-compose 中配置 nginx.

nginx:
  image: nginx:stable-alpine
  ports:
    - 80:80
  depends_on:
    - app
  volumes:
    - ./conf/nginx_web.conf:/etc/nginx/conf.d/default.conf
  command: nginx -g 'daemon off;'
複製程式碼

然後是編寫 nginx 的配置檔案:

upstream web {
  server app:8081;
}

server {
  listen 80;
  server_name localhost;

  location / {
    # https://stackoverflow.com/questions/42720618/docker-nginx-stopped-emerg-11-host-not-found-in-upstream
    resolver 127.0.0.1;

    proxy_set_header Host $http_host;
    proxy_set_header X-Forwarded-Host $http_host;
    proxy_set_header X-Forwarded-For $proxy_add_x_forwarded_for;
    proxy_set_header X-Real-IP $remote_addr;

    client_max_body_size 5m;

    proxy_pass http://web;
  }

}
複製程式碼

這裡設定了反向代理,將所有的請求都轉向了 app:8081,就是應用伺服器暴露的埠,

注意,設定了 resolver 127.0.0.1;,否則 nginx 會在一開始無法連通 app:8081 時直接崩潰.

那麼,為什麼不再準備好的時候才啟動 nginx 呢? 這是因為 docker-compose 中的 depends_on 只是保證了啟動順序,而無法確認是否已經準備好了.

更新資料庫

資料庫也是同理,我們需要設定一定的重試機制來保證資料庫是否已經啟動完成了.

func openDB(username,password,addr,name string) *gorm.DB {
	config := fmt.Sprintf(
		"%s:%s@tcp(%s)/%s?charset=utf8mb4&parseTime=%t&loc=%s&timeout=10s",username,name,true,// "Asia%2FShanghai",// 必須是 url.QueryEscape 的
		"Local",)
	var db *gorm.DB
	var err error
	for i := 0; i < 10; i++ {
		db,err = gorm.Open("mysql",config)
		if err == nil {
			break
		}
		time.Sleep(time.Second * 3)
	}
	if db == nil {
		logrus.Fatalf("資料庫連線失敗. 資料庫名字: %s. 錯誤資訊: %s",err)
	}
	logrus.Infof("資料庫連線成功,資料庫名字: %s",name)

	setupDB(db)
	return db
}
複製程式碼

另外,在資料庫啟動的時候,設定一個初始化指令碼,這樣就不用手動建立資料庫了.

mysql:
  image: mysql:8
  command: --default-authentication-plugin=mysql_native_password --init-file /data/application/init.sql
  environment:
    MYSQL_ROOT_PASSWORD: "1234"
  ports:
    - 3306:3306
  volumes:
    - ./script/db.sql:/data/application/init.sql
複製程式碼

資料庫初始化指令碼很簡單,只是檢查特定資料庫是否存在,不存在就先建立.

CREATE DATABASE IF NOT EXISTS `db_apiserver`;
複製程式碼

啟動

當一切改動都完成之後,就可以啟動並嘗試了.

docker-compose up --scale app=3 nginx
複製程式碼

這會啟動三個 app 的例項.

如果你不斷訪問 http://127.0.0.1:80/v1/check/health,應該會得到三個結果,類似於下面這樣:

OK from 5f8a835b6797
OK from b6dbb50cecd5
OK from 87e98121950d
複製程式碼

前幾次訪問可能返回 502 錯誤,這是因為 app 還在連線資料庫中,沒有啟動起來.

然後,你可以修改 nginx 的配置,體驗 nginx 內建的各種負載均衡機制. 建議配合前面的 使用 Go 新增 Nginx 代理 一起使用.

總結

Go 部署方式有很多,選擇適合的就好. 如果有需求,還可以交叉編譯各平臺的二進位制檔案.

當前部分的程式碼

作為版本 v0.17.0