从零开始写一个运行在Kubernetes上的服务程序

这是一篇对于Go语言和Kubernetes新手来说再适合不过的文章了。文中详细介绍了从代码编写到用容器的方式在Kubernetes集群中发布,一步一步,一行一行,有例子有说明,解释透彻,贯穿始末,值得每一个容器爱好者和Go语言程序员去阅读和学习。

也许你已经尝试过了Go语言,也许你已经知道了可以很容易的用Go语言去写一个服务程序。没错!我们仅仅需要几行代码[1]就可以用Go语言写出一个http的服务程序。但是如果我们想把它放到生产环境里,我们还需要准备些什么呢?让我用一个准备放在Kubernetes上的服务程序来举例说明一下。

你可以从这里[2]找到这篇章中使用的,跟随我们一步一步[3]地进行。

第1步 最简单的http服务程序

下面就是这个程序:

如果是第一次运行,仅仅执行go run main.go就可以了。如果你想知道它是怎么工作的,你可以用下面这个命令:curl -i http://127.0.0.1:8000/home。但是当我们运行这个应用的时候,我们找不到任何关于状态的信息。

第2步 增加日志

首先,增加日志功能可以帮助我们了解程序现在处于一个什么样的状态,并记录错误(译者注:如果有错误的话)等其他一些重要信息。在这个例子里我们使用Go语言标准库里最简单的日志模块,但是如果是跑在Kubernetes上的服务程序,你可能还需要一些额外的库,比如glog[4]或者logrus[5]。

比如,如果我们想记录3种情况:当程序启动的时候,当程序启动完成,可以对外提供服务的时候,当http.listenAndServe 返回出错的时候。所以我们程序如下:

第3步 增加一个路由

现在,如果我们写一个真正实用的程序,我们也许需要增加一个路由,根据规则去响应不同的URL和处理HTTP的方法。在Go语言的标准库中没有路由,所以我们需要引用gorilla/mux[6],它们兼容Go语言的标准库net/http。

如果你的服务程序需要处理大量的不同路由规则,你可以把所有相关的路由放在各自的函数中,甚至是package里。现在我们就在例子中,把路由的初始化和规则放到handlers package里(点这里[7]有所有的更改)。

现在我们增加一个Router函数,它返回一个配置好的路由和能够处理/home 的home函数。就我个人习惯,我把它们分成两个文件:

然后我们稍微修改一下:

第四步 测试

现在是时候增加一些测试了。我选择httptest ,对于Router函数,我们需要增加如下修改:

在这里我们会监测如果GET方法返回200。另一方面,如果我们发出POST,我们期待返回405。最后,增加一个如果访问错误的404。实际上,这个例子有有一点“冗余”了,因为路由作为 gorilla/mux的一部分已经处理好了,所以其实你不需要处理这么多情况。

对于home合理的检查一下响应码和返回值:

现在我们运行go tests来检查代码的正确性:

第5步 配置

下一个问题就是如何去配置我们的服务程序。因为现在它只能监听8000端口,如果能配置这个端口,我们的服务程序会更有价值。Twelve-Factor App manifesto,为服务程序提供了一个很好的方法,让我们用环境变量去存储配置信息。所以我们做了如下修改:

在这个例子里,如果没有设置端口,应用程序会退出并返回一个错误。因为如果配置错误了,就没有必要再继续执行了。

第6步 Makefile

几天以前有一篇文章[8]介绍make工具,如果你有一些重复性比较强的工作,那么使用它就大有帮助。现在我们来看一看我们的应用程序如何使用它。当前,我们有两个操作,测试和编译并运行。我们对Makefile文件进行了如下修改[9]。但是我们用go build代替了go run,并且运行那个编译出来的二进制程序,因为这样修改更适合为我们的生产环境做准备:

这个例子里,为了省去重复性操作,我们把程序命名为变量app的值。

这里,为了运行应用程序,我们需要删除掉旧的程序(如果它存在的话),编译代码并用环境变量代表的参数运行新编译出的程序,做这些操作,我们仅仅需要执行make run。

第7步 版本控制

下一步,我们将为我们的程序加入版本控制。因为有的时候,它对我们知道正在生产环境中运行和编译的代码非常有帮助。(译者注:就是说,我们在生产环境中运行的代码,有的时候我们自己都不知道对这个代码进行和什么样的提交和修改,有了版本控制,就可以显示出这个版本的变化和历史记录)。

为了存储这些信息,我们增加一个新的package -version:

我们可以在程序启动时,用日志记录这些版本信息:

现在我们给home和test也增加上版本控制信息:

我们用Go linker在编译中去设置BuildTime、Commit和Release变量。

为Makefile增加一些变量:

这里面的COMMIT和RELEASE可以在命令行中提供,也可以用semantic version设置RELEASE`。

现在我们为了那些变量重写build那段:

我也在Makefile文件的开始部分定义了PROJECT变量去避免做一些重复性的事。

所有的变化都可以在这里[10]找到,现在可以用make run去运行它了。

第8步 减少一些依赖

这里有一些代码里我不喜欢的地方:handlepakcage依赖于versionpackage。这个很容易修改:我们需要让home 处理变得可以配置。

别忘了同时去修改测试和必须的环境变量。

第9步 健康检查

在某些情况下,我们需要经常对运行在Kubernetes里的服务程序进行健康检查:liveness and readiness probes[11]。这么做的目的是为了知道容器里的应用程序是否还在运行。如果liveness探测失败,这个服务程序将会被重启,如果readness探测失败,说明服务还没有准备好。

为了支持readness探测,我们需要实现一个简单的处理函数,去返回 200:

readness探测方法一般和上面类似,但是我们需要经常去增加一些等待的事件(比如我们的应用已经连上了数据库)等:

在上面的例子里,如果变量isReady被设置为true就返回200。

现在我们看看怎么使用:

在这里,我们想在10秒后把服务程序标记成可用,当然在真正的环境里,不可能会等待10秒,我这么做仅仅是为了报出警报去模拟程序要等待一个时间完成之后才能可用。

所有的修改都可以从这个GitHub[12]找到。

第10步 程序优雅的关闭

当服务需要被关闭的停止的时候,最好不要立刻就断开所有的链接和终止当前的操作,而是尽可能的去完成它们。Go语言自从1.8版本开始http.Server支持程序以优雅的方式退出。下面我们看看如何使用这种方式:

这里,我们会捕获系统信号,如果发现有SIGKILL,SIGINT或者SIGTERM,我们将优雅的关闭程序。

第11步 Dockerfile

我们的应用程序马上就以运行在Kubernetes里了,现在我们把它容器化。

下面是一个最简单的Dockerfile:

我们创建了一个最简单的容器,复制程序并且运行它(当然不会忘记设置PORT这个环境变量)。

我们再对Makefile进行一下修改,让他能够产生容器镜像,并且运行一个容器。在这里为了交叉编译,定义环境变量GOOS 和GOARCH在build段。

我们还增加了container段去产生一个容器的镜像,并且在run段运去以容器的方式运行我们的程序。所有的变化可以从这里[13]找到。

现在我们终于可以用make run去检验一下整个过程了。

第12步 发布

在我们的项目里,我们还依赖一个外部的包(github.com/gorilla/mux)。而且,我们需要为生产环境里的readness安装依赖管理。所以我们用了dep之后我们唯一要做的就是运行dep init:

这个工具会创建两个文件Gopkg.toml和Gopkg.lock,还有一个目录vendor,个人认为,我会把vendor放到git上去,特别是对与那些比较重要的项目来说。

第13步 Kubernetes

这也是最后一步了。运行一个应用程序到Kubernetes上。最简单的方法就是在本地去安装和配置一个minikube(这是一个单点的kubernetes测试环境)。

Kubernetes从容器仓库拉去镜像。在我们的例子里,我们会用公共容器仓库——Docker Hub。在这一步里,我们增加一些变量和执行一些命令。

现在我们试一下make push:

现在你看它可以工作了,从这里[14]可以找到这个镜像。

现在我们来定义一些Kubernetes里需要的配置文件。通常情况下,对于一个简单的服务程序,我们需要定一个deployment,一个service和一个ingress。默认情况下所有的配置都是静态的,即配置文件里不能使用变量。希望以后可以使用helm来创建一份灵活的配置。

在这个例子里,我们不会使用helm,虽然这个工具可以定义一些变量ServiceName和Release,它给我们的部署带来了很多灵活性。以后,我们会使用sed命令去替换一些事先定好的值,以达到“变量”目的。

现在我们看一下deployment的配置:

我们需要用另外一篇文章来讨论Kubernetes的配置,但是现在你看见了,我们这里所有定义的信息里包括了容器的名称, liveness和readness探针。

一个典型的service看起来更简单:

最后是ingress,这里我们定义了一个规则来能从Kubernetes外面访问到里面。假设,你想要访问的域名是advent.test(这当然是一个假的域名)。

现在为了检查它是否能够工作,我们需要安装一个minikube,它的官方文档在[这里[15]。我们还需要kubectl这个工具去把我们的配置文件应用到上面,并且去检查服务是否正常启动。

运行minikube,需要开启ingress并且准备好kubectl,我们要用它运行一些命令:

我们在Makefile里加一个minikube段,让它去安装我们的服务:

这个命令会把所有的yaml文件的配置信息都合并成一个临时文件,然后替换变量Release和ServiceName(这里要注意一下,我使用的gsed而不是sed)并且运行kubectl apply进行安装的Kubernetes。

现在我们来看一下我的工作成果:

现在我们可以发送一个http的请求到我们的服务上,但是首先还是要把域名adventtest增加到/etc/host文件里:

现在,我们终于可以使用我们的服务了:

看,它工作了!

从这里[16]你可找到所有的步骤,这里[17]是提交的历史,这里[18]是最后的结果。如果你还有任何的疑问,请创建一个issue或者通过twitter:@webdeva或者是留一条comment。

创建一个在生产环境中灵活的服务程序对你来说也许很有意思。在这个例子里可以去看一下takama/k8sapp[19],一个用Go语言写的能够运行在Kubernetes上面的应用程序模版。

本文来自企鹅号 - Docker媒体

发表于

我来说两句

0 条评论
登录 后参与评论

相关文章

来自专栏张戈的专栏

Linux系统防CC攻击自动拉黑IP增强版Shell脚本

最新更新:张戈博客已推出功能更强大的轻量级 CC 攻击防御脚本工具 CCKiller==>传送门 前天没事写了一个防 CC 攻击的 Shell 脚本,没想到这么...

6505
来自专栏Netkiller

Tomcat 安全配置与性能优化

Tomcat 安全配置与性能优化 摘要 我的系列文档 Netkiller Architect 手札Netkiller Developer 手札Netkiller...

3526
来自专栏happyJared

Docker + Elasticsearch 集群环境搭建

无论是安装包形式还是基于Docker,搭建Elasticsearch集群环境还是较为简单的,实操的时候还遇到过一丢小问题,本文用于记录下操作过程。

3393
来自专栏WindCoder

tomcat基础小结

bin:可执行文件,包含启动脚本 conf:配置文件 lib:tomcat的依赖库 logs:日志 temp:临时文件 webapp:默认的应用部署目...

821
来自专栏飞雪无情的博客

使用Nginx搭建PHP服务器

一般我们都是采用Apache 作为PHP的解析服务器,这次则是采用Nginx这个强大的反向代理服务器来搭建PHP服务器。下面就以Linux发行版Ubuntu为例...

2413
来自专栏运维小白

Linux基础(day57)

14.4 exportfs命令 exportfs命令 常用选项 -a 全部挂载或者全部卸载 -r 重新挂载 -u 卸载某一个目录 -v 显示共享目录 以下操作在...

2457
来自专栏小车博客

短网址程序YOURLS安装及配置教程与设置中文

3254
来自专栏python3

gitlab 注册runner

向GitLab-CI注册一个Runner需要两样东西:GitLab-CI的url和注册token。 其中,token是为了确定你这个Runner是所有工程都能够...

1241
来自专栏Laoqi's Linux运维专列

Redis慢日志+扩展模块+存储session+主从配置

4115
来自专栏Netkiller

Tomcat 安全配置与性能优化

Tomcat 安全配置与性能优化 摘要 我的系列文档 Netkiller Architect 手札Netkiller Developer 手札Netkiller...

3595

扫码关注云+社区