使用 Go 運行與部署

簡介

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

日常測試的時候能夠直接使用 go run 運行, 但到了部署階段, 對於編譯型語言來講, 確定是要使用 go build 生成二進制文件的.nginx

在 docker 中構建

由於整個系統都是基於 docker-compose 的, 因此須要寫一個 Dockerfile, 將整個項目在 docker 中構建爲一個鏡像.git

這樣, 就能夠直接在 docker 中運行了. 每次的本地構建生成二進制文件的過程, 就轉變爲了從新構建 docker 鏡像.github

Dockerfile 以下:golang

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 鏡像中使用, 以減小構建完成後的鏡像體積.web

構建的時候須要設置環境變量 CGO_ENABLED=0, 以禁止使用 CGO 動態連接, 具體請參考 stackoverflow.sql

集成在 docker-compose 中

當 Dockerfile 寫完後, 能夠直接構建鏡像, 並運行一下測試是否正常.docker

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

一切順利以後, 就能夠將它集成到 docker-compose.yaml 中, 命名爲一個服務了.數據庫

app:
 build:
 context: .
 depends_on:
 - mysql
複製代碼

有個依賴, 畢竟 mysql 要先啓動. 至於爲何沒有暴露端口, 是由於要使用 nginx 反向代理.ubuntu

使用 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,
		password,
		addr,
		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", name, 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

相關文章
相關標籤/搜索