新浪容器云入门教程(1)-如何部署一个go web应用

新浪容器云介绍

如果您熟知云计算,也知道云计算有几个分类,那么PaaS你也一定不陌生。但是在docker这种技术出来之前主要还是依赖进程之间的隔离来完成PaaS平台的设计的。举一个通俗的例子,如果我想实现两个网站的隔离,传统的做法肯定是不同的用户启动一个httpd进程来隔离。但是这么整还是得借助cgroup去限制各个进程的cpu使用、内存使用等等,不能让一个用户就把一个服务器的CPU、内存耗尽了。但是SAE的实现是基于请求之间的隔离实现。但是这种方式的好处是统一调度、我们对各种软件的修改例如操作系统的优化、httpd的调优、php的改进等等都能被所用的用户“快速的享受到”我们改进的好处。但是凡事有利就有弊,害处就是牺牲了很多的用户体验,例如:

  • 本地不能写了,直接导致的恶果是诸如discuz等传统的软件没法直接安装了
  • 好多的php函数、Apache的配置文件没法写了
  • 只能使用平台提供的软件版本,例如php的版本、jdk的版本、Python的版本等等

总结起来一句话,大家觉得在享受到极快的部署体验下(可能注册个应用立马就能访问了),还是丧失了一些灵活性。当然这也成为好多用户吐槽的原因。其实各家做相同模式业务的也都知道有这个问题,都会面临用户通过各种渠道质问为啥这个在我本地跑的好好地到你们那就不能用的问题。也是在这种大背景下,我们几经波折的推出了“容器云平台”。那么容器云到底是个什么东西呢?

啥是容器云

其实容器云,顾名思义就是能运行很多很多容器的云平台。而容器既不是大家熟知的jetty容器也不是也不是大家用来装双氧水的容器,就是docker容器。也有很多人将它称为轻量级虚拟机,我觉得这个称谓也是比较合理的,我们不妨叫他“昙花一现的虚拟机”,为什么这么说呢,因为docker这个东西没有“重启”,“关闭”这一说,当你关闭一个启动的docker容器或者重启时,其实是docker daemon又把相同的镜像又启动了一个新的。(以上概念在新浪云容器如此,其他家可能有一些技术能保证上次写的文件还在等等可能不一致。)所以说到这里大家应该知道容器云是个什么东西了,其实就是帮你管理启动好多“一次性虚拟机”的云平台。所以为了达成以上几点,又衍生出了一个概念那就是“镜像”。

什么是镜像

其实好多东西都叫镜像,不论是虚拟机的快照,还是大家安装各种虚拟机时下载的ISO文件等等,都被称为“镜像”,docker的镜像也叫镜像,可以狭隘的理解为docker容器的系统快照,只是这个快照不包含系统运行时产生的文件。

在新浪云容器创建几个容器

讲了以上这么多概念终于能切入到本文介绍的正文了,那就是如何在新浪云容器(以下为了方便,简称为我们自己给自己定的缩写SC2了,应该是Sina Cloud Containers的缩写,为了防止和上海某著名跑车协会撞名,就叫SC2了)。登录http://sc2.sinacloud.com,选择“应用”tab就能看到“创建应用”了。大致可以看到类似以下的界面(我们还在频繁的开发修改中,后来的读者看到的界面可能不一致):

先开始创建一个实例就可以了。这时候就创建好了一个容器,但是为了给大家省钱,默认是不会启动任何容器的。因为容器的计费方式和SAE平台的并不一致,是按容器的启动时间和你选的配置的计价单位计算扣除费用的。

上传我们的代码

为了方便大家先体验,我们在github给大家准备好了入门的示例代码,看这里:https://github.com/sinacloud/go-getting-started。我们将代码先下载到本地来,然后用git上传到我们的应用中,注意:我们的git不知道用户上传证书,所以需要大家自动输入安全邮箱和密码才能提交,但是从我的实际测试中看,需要git 客户端的版本在1.8以上才能弹出来让人输入邮箱和密码,如果你是从centos6默认的yum仓库安装的git,那么你的git版本是有问题的。我们可以执行

[root@vm237147/tmp/f2p2xu8381/src/cow-master]#git version
git version 1.8.3.1

看到当前环境的git版本,当然windows下也是有git的,推荐使用这个:http://pan.baidu.com/s/1geaPw4r

准备好git客户端后,我们需要做一个“选择”了,到底是通过git提交的时候自动就把代码构建为docker的镜像呢,这种比较适合我会在本地一次修改,测试通过后再提交,还有我们也提供异步的镜像构建。意思就是说你可以把git只当一个代码管理的工具使用,从sc2的在线管理平台处就能设定。在这个地方的“勾”:

以下是我通过自动部署时候的命令行样子:

如果git只是提交代码,我们需要去到sc2的在线管理平台部署我们的应用,其实过程都是一样的,只是触发的地方不一样,如果是在界面上完成的,大概是长这个样子的(当然我这部署失败了大家不要在意):

部署后的样子

这时候我们就能看到启动好的容器了,从sc2的管理面板上就能看到容器使用的CPU、内存等,还能看到各种日志。容器的实时状态:

访问日志、错误日志、运维日志的样子:

Godep是啥

写go的都知道我们我们在代码里面会这么写

import "github.com/xx/xx"

其实go在编译的时候又是要求你必须提前go get github.com/xx/xx把代码下载到本地的src的,但是从代码管理的角度说呢,第三方的代码不算是我们工程的代码,不应该被提交到我们的代码管理仓库中。那这种咋办,有了go的包管理工具,新浪云要求必须要有Godep这个东西,可以去:https://github.com/tools/godep 这里下载godep,也可以直接用heroku编译好的版本,加入直接放到/usr/local/sbin/.

wget https://raw.githubusercontent.com/kr/heroku-buildpack-go/master/linux-amd64/bin/godep -O /usr/local/sbin/dodep

如果使用godep自动生成依赖

在你的代码路径下执行:

/usr/local/sbin/godep save -r

就可以看到自动生成了Godeps目录,它下面的文件大概是这样的:

[root@vm237147/tmp/f2p2xu8381/src/cow-master]#ls Godeps
Godeps.json  Readme  _workspace

看看那个json文件吧大概长这样:

[root@vm237147/tmp/f2p2xu8381/src/cow-master]#cat Godeps/Godeps.json
{
        "ImportPath": "cow",
        "GoVersion": "go1.5.1",
        "Deps": [
                {
                        "ImportPath": "github.com/codahale/chacha20",
                        "Rev": "ec07b4f69a3f70b1dd2a8ad77230deb1ba5d6953"
                },
                {
                        "ImportPath": "github.com/cyfdecyf/bufio",
                        "Rev": "9601756e2a6b5fa8ca6749ce4f73f6afdd83030d"
                },
                {
                        "ImportPath": "github.com/cyfdecyf/color",
                        "Rev": "31d518c963d22b95d500ab628c1d1d1b8eff2ab9"
                },
                {
                        "ImportPath": "github.com/cyfdecyf/leakybuf",
                        "Comment": "1.0",
                        "Rev": "ffae040843bee2891b6306d1d085c25ca822e72c"
                },
                {
                        "ImportPath": "github.com/shadowsocks/shadowsocks-go/shadowsocks",
                        "Comment": "1.1.4-4-g2b4d9d7",
                        "Rev": "2b4d9d7c839f2939ec6a96cc423dea44319e848b"
                },
                {
                        "ImportPath": "golang.org/x/crypto/blowfish",
                        "Rev": "f18420efc3b4f8e9f3d51f6bd2476e92c46260e9"
                },
                {
                        "ImportPath": "golang.org/x/crypto/cast5",
                        "Rev": "f18420efc3b4f8e9f3d51f6bd2476e92c46260e9"
                },
                {
                        "ImportPath": "golang.org/x/crypto/salsa20/salsa",
                        "Rev": "f18420efc3b4f8e9f3d51f6bd2476e92c46260e9"
                }
        ]
}

关于Procfile

我们的文档中也简单描述了这个东西,应用可以通过代码根目录下的 Procfile 文件指定在容器中运行的程序命令。Procfile 文件每一行声明一条需要运行的命令,格式如下:

type: command

目前type仅支持 web , 也就是web进程,前端负载均衡的请求会被转发给运行这些命令的容器。假如我们的go编译完后二进制名字叫cow那么就可以这么启动

web: cow

如果要在启动的时候带上参数也是可以的

web: cow -rc=/app/rc

我们上传完的代码在哪儿

因为大家暂时还是不能登录到docker的容器中的,所以难免会有这些疑问,我们的代码传上去到底在哪个路径呢,我应用程序启动的时候要指定配置文件去哪弄?我们应用上传完之后默认会在/app这个目录下,这个目录也是docker容器当前用户的home目录,为了印证这个问题,我写了一个http的文件探针,来列一下我们的文件到底是啥样的,主要的代码在这:

r.POST("/ls", func(c *gin.Context) {
        // check dir exist
        dir := c.PostForm("dir")
        dirList, err := ioutil.ReadDir(dir)
        if err != nil {
            c.String(http.StatusOK, "dir not exist")
        } else {
            var ret string = ""
            for _, v := range dirList {
                ret += v.Name() + "\n"
            }  
            c.String(http.StatusOK, ret)
        }  
    })

大家讲这个方法加到我们github那个实例应用的main.go中重新提交部署就可以了。

先看看我本地的目录:

[root@vm237147/tmp/test2/git]#ls -al
total 48
drwxr-xr-x 7 root      root      4096 2015/12/29 16:46:13 .
drwxrwxr-x 4 mingming6 mingming6 4096 2015/12/28 12:16:58 ..
drwxr-xr-x 2 root      root      4096 2015/12/29 13:45:17 .cow
drwxr-xr-x 8 root      root      4096 2015/12/29 14:53:36 .git
drwxr-xr-x 3 root      root      4096 2015/11/26 19:22:52 Godeps
-rw-r--r-- 1 root      root      3426 2015/12/29 14:52:51 main.go
-rw-r--r-- 1 root      root      1305 2015/12/28 12:17:20 memcache.js
-rw-r--r-- 1 root      root      1661 2015/12/28 12:17:20 mysql.js
-rw-r--r-- 1 root      root        24 2015/11/26 19:22:52 Procfile
-rw-r--r-- 1 root      root       726 2015/11/26 19:22:52 README.md
drwxr-xr-x 2 root      root      4096 2015/11/26 19:22:52 static
drwxr-xr-x 4 root      root      4096 2015/12/28 12:18:35 templates

再来发一个请求看看docker中把我们提交的代码放到哪儿去了。

[root@vm237147/tmp/test2/git]#curl 'http://hmh69m3229.sinaapp.com/ls' -d "dir=/app"
.basher
.cow
.profile.d
.release
Godeps
Procfile
README.md
bin
main.go
memcache.js
mysql.js
static
templates

大家可以看到这个app目录就是我们代码提交后保存的目录,其中还有一个bin就是我们go程序编译完之后的目录,我们列一下看看:

[root@vm237147/tmp/test2/git]#curl 'http://hmh69m3229.sinaapp.com/ls' -d "dir=/app/bin"
go-getting-started
templates

果不其然,我们的Procfile是这么写的

[root@vm237147/tmp/test2/git]#cat Procfile
web: go-getting-started

从bin中的go-getting-started大家应该就能发现了吧,这里的Procfile中的go-getting-started启动的就是这样,所以类比的,大家如果程序是其他名字的就将Procfile改成其他名字,如果要启动配置文件的可以将文件放在代码中提交,然后用go的flag传入,在启动时指定到/app/路径就可以了。

结束语

相信看过这个文章再加上实际的操练应该可以初步搞清楚“云容器”是个什么东西,怎么运行起一个go web,后续的文章将会给大家介绍如果在我们的云容器中使用共享存储,如果使用SAE中MySQL、memcache等服务。祝大家2016新年快乐!