# Golang运行环境

# 运行环境说明

# 工作流程

Golang运行环境是新浪云推出的本地可写的Golang语言运行环境,通过Git hook触发自动构建逻辑,将提交的代码打包为Docker镜像,然后由新浪云的容器管理引擎启动生成的镜像实例运行。

# 工作流程示意图

容器运行环境示意图

总体的流程分为以下步骤:

  1. 从本地通过Git客户端提交代码到仓库,如何部署请参考容器环境Git管理示例
  2. 新浪云的Git仓库通过触发器启动一个同步构建镜像的任务,解析代码中指定的需要安装的Golang版本、下载依赖的包等,制作一个Docker的镜像;
  3. 如果以上过程没有错误,则将镜像推送到新浪云的镜像中心,并通知托管服务管理系统;如果出错,从Git的回显通知具体的错误;
  4. 调度新建或者替换正在运行的镜像实例,启动后将更新后的运行环境更新到负载均衡;
  5. 所有的实例更新或则启动完成后,通过Git回显告知提交成功。

# 创建应用

  1. 登录云应用管理平台https://sae.sinacloud.com (opens new window),如果没有账号请先注册;

  2. 点击“+创建应用”进入应用的创建页面; 点击创建应用进入应用管理页面

  3. 开发语言选择“Go”;

  4. 运行环境选择“容器环境”;

  5. 选择单实例的配置; 选择Go应用配置

  6. 选择实例个数,开发阶段可以选择1,可以随时调整;

  7. 输入应用的二级域名前缀,二级域名的组成规则请参考应用名,应用名称(应用的中文介绍); 输入应用名、应用描述等

  8. 点击“确认创建”完成创建。

说明

  • 每个实例中将会拥有相同的代码,每个实例的资源配额为创建时指定的单实例配置
  • 实例的个数可以随时修改;
  • 计费的规则为:单实例价格 * 实例总数 * 实例的运行时长,按分钟计费。

# 计费说明

# 计费规则

计费的规则为:单实例价格 * 实例总数 * 实例的运行时长,按分钟计费。

其中单实例的价格可以从价格中心 (opens new window)查询。

例如单实例的配置为标准型,单价为0.06元每实例每小时,开启的实例个数为2,则一天的价格为:

0.06 * 2 * 24 = 2.88

# 流量计费

当前Golang运行环境的流量不计费。

# 构建和运行说明

# 如何识别应用是Go应用

Go通过应用根目录下的 Godeps/Godeps.json 文件来判断一个应用是否是Go应用,所以即使应用没有任何依赖,您也必须执行 godep save来生成这个文件,该命令也会记录下本地使用的Go版本,服务端会使用对应版本的Go来完成应用的构建。

# 指定Golang版本

您可以使用 Godeps/Godeps.json 中的 GoVersion 来指定服务端构建和运行的时候使用的Go版本。

{
  "ImportPath": "...",
  "GoVersion": "go1.6"
}

注意

建议使用 1.5.3 以上的版本。

# 构建说明

服务端使用下面的命令来编译你的Go应用程序:

godep go install ./...

# 静态文件处理

所有通过git提交的文件在应用运行的时候都可以直接访问,您可以直接使用Go的 http.FileServer 来处理这些静态文件的访问请求。

# Hello world

# 创建Golang应用

先在新浪云创建一个Go类型的应用,如果不知道如何创建,请参考创建应用章节。

# 准备工作

从Github上clone一个go示例程序https://github.com/sinacloud/go-getting-started (opens new window)

$ git clone https://github.com/sinacloud/go-getting-started.git
$ cd go-getting-started
$ $ ls -al
total 19
drwxr-xr-x 1 37708 197609   0 1012 13:25 ./
drwxr-xr-x 1 37708 197609   0 928 14:09 ../
drwxr-xr-x 1 37708 197609   0 1012 13:28 .git/
-rw-r--r-- 1 37708 197609   7 1012 13:24 1.txt
drwxr-xr-x 1 37708 197609   0 1012 13:25 files/
drwxr-xr-x 1 37708 197609   0 1117  2017 Godeps/
-rw-r--r-- 1 37708 197609 491 1012 13:27 main.go
-rw-r--r-- 1 37708 197609  24 1117  2017 Procfile
-rw-r--r-- 1 37708 197609 733 1117  2017 README.md
drwxr-xr-x 1 37708 197609   0 1117  2017 static/
drwxr-xr-x 1 37708 197609   0 1117  2017 templates/
drwxr-xr-x 1 37708 197609   0 1117  2017 vendor/

这个示例程序是一个使用Gin Web (opens new window)框架写的HTTP Web服务器。我们依次来看下每个文件的作用,首先,main.go文件:

package main

import (
	"net/http"
	"os"

	"github.com/gin-gonic/gin"
)

func main() {
	port := os.Getenv("PORT")

	if port == "" {
		port = "5050"
	}

	r := gin.Default()
	r.StaticFile("/favicon.ico", "./static/favicon.ico")
	r.Static("/files", "./files")
	r.LoadHTMLGlob("templates/*.tmpl.html")
	r.GET("/", func(c *gin.Context) {
		c.String(http.StatusOK, "Hello, World")
	})
	r.GET("/ping", func(c *gin.Context) {
		c.HTML(http.StatusOK, "ping.tmpl.html", nil)
	})

	r.Run(":" + port)
}

这段程序就是设置了一些HTTP Handler,最后启动一个Web服务器,监听在环境变量 PORT 指定的端口上。

上面这段程序依赖于 Gin 这个包, Godeps 目录下就是通过 godep save -r ./... 保存下来的所有依赖包。

最后通过 Procfile 文件指定了如何启动这个应用的程序:

web: go-getting-started

# 通过Git提交代码

通过Git将代码提交到应用的仓库中,系统会自动构建运行环境并创建或者重启应用的容器,如何部署应用请参考容器环境git管理示例

# 访问应用

部署完成后即可从浏览器访问应用:

从浏览器访问Golang应用

# 支持的Golang版本

当前支持1.11及以下的所有版本,建议使用1.6及以上的版本。

# 使用gomod

Golang 1.11加入了gomod,可以使用其更好的管理依赖,目前,我们已经支持。

# 在本地的非$GOPATH路径初始化项目

go mod init github.com/you/hello

# 编写main.go

package main

import (
	"net/http"
	"os"

	"github.com/gin-gonic/gin"
)

func main() {
	port := os.Getenv("PORT")

	if port == "" {
		port = "5050"
	}

	r := gin.Default()
	r.StaticFile("/favicon.ico", "./static/favicon.ico")
	r.Static("/files", "./files")
	r.LoadHTMLGlob("templates/*.tmpl.html")
	r.GET("/", func(c *gin.Context) {
		c.String(http.StatusOK, "Hello, World")
	})
	r.GET("/ping", func(c *gin.Context) {
		c.HTML(http.StatusOK, "ping.tmpl.html", nil)
	})

	r.Run(":" + port)
}

# 编译

go build

看到输出如下:

root@38930c89bb89:/tmp/test/src# go build main.go 
go: finding github.com/gin-gonic/gin v1.3.0
go: downloading github.com/gin-gonic/gin v1.3.0
go: finding github.com/mattn/go-isatty v0.0.4
go: downloading github.com/mattn/go-isatty v0.0.4
go: finding github.com/gin-contrib/sse latest
go: finding github.com/golang/protobuf/proto latest
go: finding github.com/ugorji/go/codec latest
go: finding gopkg.in/yaml.v2 v2.2.1
go: finding gopkg.in/go-playground/validator.v8 v8.18.2
go: downloading github.com/gin-contrib/sse v0.0.0-20170109093832-22d885f9ecc7
go: downloading gopkg.in/go-playground/validator.v8 v8.18.2
go: downloading github.com/ugorji/go/codec v0.0.0-20181012064053-8333dd449516
go: downloading gopkg.in/yaml.v2 v2.2.1


go: finding github.com/golang/protobuf v1.2.0
go: downloading github.com/golang/protobuf v1.2.0
go: finding gopkg.in/check.v1 v0.0.0-20161208181325-20d25e280405

其中go.mod的文件中会自动加入依赖的信息:

root@38930c89bb89:/tmp/test/src# cat go.mod 
module github.com/you/hello

require (
        github.com/gin-contrib/sse v0.0.0-20170109093832-22d885f9ecc7 // indirect
        github.com/gin-gonic/gin v1.3.0 // indirect
        github.com/golang/protobuf v1.2.0 // indirect
        github.com/mattn/go-isatty v0.0.4 // indirect
        github.com/ugorji/go/codec v0.0.0-20181012064053-8333dd449516 // indirect
        gopkg.in/go-playground/validator.v8 v8.18.2 // indirect
        gopkg.in/yaml.v2 v2.2.1 // indirect
)

可以看到版本和依赖已经自动写入了。

同时多了一个go.sum文件:

root@38930c89bb89:/tmp/test/src# cat go.sum 
github.com/gin-contrib/sse v0.0.0-20170109093832-22d885f9ecc7 h1:AzN37oI0cOS+cougNAV9szl6CVoj2RYwzS3DpUQNtlY=
github.com/gin-contrib/sse v0.0.0-20170109093832-22d885f9ecc7/go.mod h1:VJ0WA2NBN22VlZ2dKZQPAPnyWw5XTlK1KymzLKsr59s=
github.com/gin-gonic/gin v1.3.0 h1:kCmZyPklC0gVdL728E6Aj20uYBJV93nj/TkwBTKhFbs=
github.com/gin-gonic/gin v1.3.0/go.mod h1:7cKuhb5qV2ggCFctp2fJQ+ErvciLZrIeoOSOm6mUr7Y=
github.com/golang/protobuf v1.2.0 h1:P3YflyNX/ehuJFLhxviNdFxQPkGK5cDcApsge1SqnvM=
github.com/golang/protobuf v1.2.0/go.mod h1:6lQm79b+lXiMfvg/cZm0SGofjICqVBUtrP5yJMmIC1U=
github.com/mattn/go-isatty v0.0.4 h1:bnP0vzxcAdeI1zdubAl5PjU6zsERjGZb7raWodagDYs=
github.com/mattn/go-isatty v0.0.4/go.mod h1:M+lRXTBqGeGNdLjl/ufCoiOlB5xdOkqRJdNxMWT7Zi4=
github.com/ugorji/go/codec v0.0.0-20181012064053-8333dd449516 h1:tYsnVMTj4SrtarTPEquseLh3QgR7mEY3WSPW7x2c9hk=
github.com/ugorji/go/codec v0.0.0-20181012064053-8333dd449516/go.mod h1:VFNgLljTbGfSG7qAOspJ7OScBnGdDN/yBr0sguwnwf0=
gopkg.in/check.v1 v0.0.0-20161208181325-20d25e280405/go.mod h1:Co6ibVJAznAaIkqp8huTwlJQCZ016jof/cbN4VW5Yz0=
gopkg.in/go-playground/validator.v8 v8.18.2 h1:lFB4DoMU6B626w8ny76MV7VX6W2VHct2GVOI3xgiMrQ=
gopkg.in/go-playground/validator.v8 v8.18.2/go.mod h1:RX2a/7Ha8BgOhfk7j780h4/u/RRjR0eouCJSH80/M2Y=
gopkg.in/yaml.v2 v2.2.1 h1:mUhvW9EsL+naU5Q3cakzfE91YhliOondGd6ZrsDBHQE=
gopkg.in/yaml.v2 v2.2.1/go.mod h1:hI93XBmqTisBFMUTm0b8Fm+jr3Dg1NNxqwp+5A1VGuI=

# 编译完成后的文件列表

go build完成后的文件里列表如下:

root@38930c89bb89:/tmp/test/src# ls -al
total 15044
drwxr-xr-x 3 root root     4096 Oct 15 02:26 .
drwxr-xr-x 3 root root     4096 Oct 15 02:09 ..
-rw-r--r-- 1 root root      424 Oct 15 02:20 go.mod
-rw-r--r-- 1 root root     1413 Oct 15 02:20 go.sum
-rwxr-xr-x 1 root root 15358854 Oct 15 02:20 main
-rw-r--r-- 1 root root      491 Oct 15 02:10 main.go
drwxr-xr-x 2 root root     4096 Oct 15 02:27 templates

注意:加了一个template/ping.tmpl.html文件,内容是:

<strong>{{ "Pong!" }}</strong>

# 启动进程

root@38930c89bb89:/tmp/test/src# ./main 
[GIN-debug] [WARNING] Now Gin requires Go 1.6 or later and Go 1.7 will be required soon.

[GIN-debug] [WARNING] Creating an Engine instance with the Logger and Recovery middleware already attached.

[GIN-debug] [WARNING] Running in "debug" mode. Switch to "release" mode in production.
 - using env:   export GIN_MODE=release
 - using code:  gin.SetMode(gin.ReleaseMode)

[GIN-debug] GET    /favicon.ico              --> github.com/gin-gonic/gin.(*RouterGroup).StaticFile.func1 (3 handlers)
[GIN-debug] HEAD   /favicon.ico              --> github.com/gin-gonic/gin.(*RouterGroup).StaticFile.func1 (3 handlers)
[GIN-debug] GET    /files/*filepath          --> github.com/gin-gonic/gin.(*RouterGroup).createStaticHandler.func1 (3 handlers)
[GIN-debug] HEAD   /files/*filepath          --> github.com/gin-gonic/gin.(*RouterGroup).createStaticHandler.func1 (3 handlers)
[GIN-debug] Loaded HTML Templates (2): 
        - ping.tmpl.html
        - 

[GIN-debug] GET    /                         --> main.main.func1 (3 handlers)
[GIN-debug] GET    /ping                     --> main.main.func2 (3 handlers)
[GIN-debug] Listening and serving HTTP on :5050

可以看到进程已经启动在5050端口。

说明

更多关于Go mod的说明请参考Go 1.11 Modules (opens new window)

# 提交代码

提交非编译后的文件到Git仓库即可,代码部署请参考通过Git提交代码

# 协议支持

应用支持http、https和websocket协议(ws、wss)。

# 端口映射

# 工作原理

应用的负载均衡(新浪云提供)监听80和443端口,然后将请求转发到容器中的5050端口,示意图如下:

协议及端口示意

# 支持的端口

容器中可以启动多个端口,但是仅5050端口会映射到负载均衡上,其他的端口只能在本地访问。可以从环境变量中获取支持的端口,环境变量的key为:

PORT 

可以通过:

port := os.Getenv("PORT")

请注意

  • 另外HTTP等服务器应该监听在0.0.0.0,不要监听在127.0.0.1,如果监听在127.0.0.1,仅在容器上运行的程序才可以访问,外部无法访问;
  • 如果使用HTTPS访问,在负载均衡到容器间会转变为HTTP协议,可以从HTTP的header头中判断是否有HTTP-X-PROTO头,如果这个header头的值为SSL表示本次请求为HTTPS访问;
  • 如上所述,HTTPS证书应该传到负载均衡处(从管理面板的域名绑定处上传),不应该放到容器中。

# 查询容器stdout日志

  1. 进入应用的列表页面https://sae.sinacloud.com (opens new window)
  2. 点击操作处的管理进入应用的管理中心
  3. 左侧导航选择“日志及监控”,选择“日志中心”进入日志管理
  4. 选择“容器日志”的“stdout”分类 查询stdout分类日志

# 查询容器stderr日志

  1. 进入应用的列表页面https://sae.sinacloud.com (opens new window)
  2. 点击操作处的管理进入应用的管理中心
  3. 左侧导航选择“日志及监控”,选择“日志中心”进入日志管理
  4. 选择“容器日志”的“stderr”分类 查询stderr分类日志

# SSH登录

# 说明

Golang容器支持通过在线SSH终端登录到容器中查看进程的状态和系统的信息。

# 如何登录

  1. 进入应用的列表页面https://sae.sinacloud.com (opens new window)
  2. 点击操作处的管理进入应用的管理中心
  3. 左侧导航选择“运行环境管理”,选择“容器管理”进入应用的容器列表页面
  4. 点击需要管理的容器示例的“操作”区域的“终端”即可进入web版本的终端 进入web terminal
  5. 输入Linux命令可以查询进程的状态、执行其他的操作 在web terminal执行ps -ef命令

# 文件系统

# 本地文件系统

实例在创建时将会分配5GB的本地磁盘,用于存放程序的代码和系统文件等,你可以从程序往本地写文件,但本地的文件不是持久化存储,在重启后文件会删除

警告

  • 如果你需要持久化存储文件,请不要将文件写到本地文件系统,可以通过以下几种方式存储文件:

# 挂载共享存储

共享存储服务是为容器云提供的分布式存储服务,利用多副本写入实现数据高可靠性,并提升读取性能,可以提供数十M/S的峰值读写性能。

您可以通过挂载,将对应的共享存储卷映射进容器中,是容器获得本地文件读写和数据持久化功能。

通过共享存储,还可以在不同的容器之间共享数据,实现跨容器的数据共享和交互。

有关共享存储的更多介绍,请参考共享存储章节,请参考以下步骤挂载共享存储:

  1. 共享存储 (opens new window)管理面板创建一个共享存储;
  2. 云应用管理平台 (opens new window)应用列表页面点击“管理”进入应用的管理中心;
  3. 左侧导航选择“运行环境管理”,“容器管理”进入容器管理页面; 如何进入容器管理页面
  4. 点击“挂载管理”进入挂载管理页面; 进入磁盘挂载的页面
  5. 点击“+新增挂载”,存储的分类选择“共享存储”,从下拉列表选择刚刚创建的存储,输入一个挂载路径。 填写挂载的信息

    说明

    挂载路径是系统的绝对路径,如果你挂载的路径在本地文件系统已经存在,则挂载后本地的文件系统将被覆盖。

  6. 选择“立即重启容器”后挂载会立即生效,挂载完成后可以看到挂载的详情: 挂载详情

挂载完成后,从容器的实例中可以看到挂载的路径: 在容器中查看挂载的路径

共享存储中的文件支持FTP管理,详情请参考共享存储的FTP管理章节。

挂载完共享存储到本地的路径后,就可以在程序中将需要存储的文件写到挂载的路径下了。

# 挂载本地存储

本地存储服务是为容器云提供的本地问年间存储服务,可以和本地磁盘读写一样速度的磁盘。

您可以通过挂载,将对应的本地存储卷映射进容器中,是容器获得本地文件读写和数据持久化功能。

有关共享存储的更多介绍,请参考本地存储章节,请参考以下步骤挂载本地存储:

  1. 本地存储 (opens new window)管理面板创建一个本地存储;
  2. 云应用管理平台 (opens new window)应用列表页面点击“管理”进入应用的管理中心;
  3. 左侧导航选择“运行环境管理”,“容器管理”进入容器管理页面; 如何进入容器管理页面
  4. 点击“挂载管理”进入挂载管理页面; 进入磁盘挂载的页面
  5. 点击“+新增挂载”,存储的分类选择“本地存储”,从下拉列表选择刚刚创建的存储,输入一个挂载路径。 填写挂载的信息

    说明

    挂载路径是系统的绝对路径,如果你挂载的路径在本地文件系统已经存在,则挂载后本地的文件系统将被覆盖。

  6. 选择“立即重启容器”后挂载会立即生效,挂载完成后可以看到挂载的详情: 挂载详情

挂载完成后,从容器的实例中可以看到挂载的路径: 在容器中查看挂载的路径

本地存储中的文件支持FTP管理,详情请参考本地存储的FTP管理章节。

挂载完地存储到本地的路径后,就可以在程序中将需要存储的文件写到挂载的路径了。