使用 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

相关文章
相关标签/搜索