Go

Go

Go版微服务开发框架Micro及标准2019年大整合

cleverlzc 发表了文章 • 0 个评论 • 299 次浏览 • 2019-06-16 08:38 • 来自相关话题

【编者的话】Micro是构建微服务的最简单方式,并逐渐成为云计算中基于Go的微服务开发的实际标准。通过将我们的努力整合到一个开发框架和运行时中,我们使这个过程更加简单。 Micro作为[go-micro](https://gith ...查看全部

【编者的话】Micro是构建微服务的最简单方式,并逐渐成为云计算中基于Go的微服务开发的实际标准。通过将我们的努力整合到一个开发框架和运行时中,我们使这个过程更加简单。 

Micro作为[go-micro](https://github.com/micro/go-micro)——一个微服务框架开始了它的旅程,专注于提供微服务开发的核心需求。它通过抽象出分布式系统的复杂性,为构建微服务创造了更简单的体验。

随着时间的推移,我们已经从go-micro扩展到其他工具、库和插件。这导致了我们解决问题的方式和开发人员使用微服务工具的方式的分散化。我们现在正在整合所有这些工具,以简化开发人员的体验。

从本质上来说,Micro已经成为一个独立的开发框架和微服务开发的运行时。

在讨论整合之前,让我们回顾一下迄今为止的历程。

### 主要关注点

Go-micro最初主要专注于微服务的通信方面,我们一直努力做到这一点。到目前为止,这种固执己见的方法和关注点是驱动框架成功的真正驱动力。多年来,我们已经收到了无数的请求,要求解决第二天在go-micro中构建生产就绪软件的问题。其中大部分都与可伸缩性、安全性、同步和配置有关。


虽然增加所要求的额外特性是有好处的,但我们确实希望一开始就非常专注于很好地解决一个问题。所以我们采取了一种不同的方式来促进社区这样做。

### 生态系统和插件

投入生产所涉及的不仅仅是普通的服务发现、消息编码和请求-响应。我们真正明白这一点,希望使用户能够通过可插拔和可扩展的接口选择更广泛的平台需求。通过[资源管理器](https://micro.mu/explore/)促进生态系统,资源管理器聚合了GitHub上的基于微服务的开源项目,并通过[go-plugins](https://github.com/micro/go-plugins)扩展插件。


一般来说,Go插件已经取得了巨大的成功,因为它允许开发人员将大量的复杂性转移到为这些需求构建的系统上。例如用于度量的Prometheus、用于分布式跟踪的Zipkin和用于持久消息传递的Kafka。

### 交互点

Go Micro确实是微服务开发的核心,但是随着服务的编写,接下来的问题就转移到了:我如何查询它们,如何与它们交互,如何通过传统方式为它们服务。

鉴于go-micro使用了一个基于RPC/Protobuf的协议,该协议既可插拔又不依赖于运行时,我们需要某种方法来解决这个问题。这导致了微服务工具包[Micro](https://github.com/micro/micro)的产生。Micro提供了API网关、网络仪表板、cli命令行工具、slack bot机器人程序、服务代理等等。


Micro工具包通过http api、浏览器、slack命令和命令行接口充当交互点。这些是我们查询和构建应用程序的常见方式,对于我们来说,提供一个真正支持这一点的运行时非常重要。然而,但它仍然把重点放在通信上。

###其他工具

虽然插件和工具包极大地帮助了使用了Micro的用户,但在关键领域仍然缺乏。很明显,我们的社区希望我们能够围绕产品开发的平台工具来解决更多的问题,而不是必须在他们各自的公司中单独完成。我们需要为动态配置、分布式同步和为Kubernetes这样的系统提供更广泛的解决方案等方面提供相同类型的抽象。

于是我们创建了以下项目:


- [micro/go-config](https://github.com/micro/go-config): 一个动态配置库

- [micro/go-sync](https://github.com/asim/go-sync):一个分布式同步库

- [micro/kubernetes](https://github.com/micro/kubernetes):在Kubernetes平台上的初始化

- [examples](https://github.com/micro/examples):使用举例

- [microhq](https://github.com/microhq):微服务预构建


这些是一部分repos、库和工具,用于尝试解决我们社区更广泛的需求。在过去的四年里,repos的数量不断增长,新用户的入门体验也变得更加困难。进入壁垒急剧增加,我们意识到需要做出一些改变。

在过去的几周里,我们意识到[go-micro](https://github.com/micro/go-micro)确实是大多数用户开发微服务的焦点。很明显,他们想要额外的功能,作为这个库的一部分以及一个自我描述的框架,我们真的需要通过解决那些第二天的问题来实现这一点,而不要求开发人员寻求其他方法。

本质上,go-micro将成为微服务开发的全面和独立框架。

我们通过将所有库迁移到go-micro开始了整合过程,在接下来的几周里,我们将继续进行重构,以提供更简单的默认入门体验,同时还为日志记录、跟踪、度量、身份验证等添加更多功能。


不过,我们也没有忘记Micro。在我们看来,当构建了微服务之后,仍然需要一种查询、运行和管理它们的方法。所有人都认为Micro将是微服务开发的运行时。我们正在致力于提供一种更简单的方法来管理微服务开发的端到端流程,并且应该很快会有更多消息发布。

### 总结

Micro是构建微服务的最简单方式,并逐渐成为云计算中基于Go的微服务开发的实际标准。通过将我们的努力整合到一个开发框架和运行时中,我们使这个过程更加简单。

原文链接:Micro - The great consolidation of 2019


**译者**:Mr.lzc,软件工程师、DevOpsDays深圳核心组织者,目前供职于华为,从事云存储工作,以Cloud Native方式构建云文件系统服务,专注于Kubernetes、微服务领域。


容器化 Go 开发环境的尝试

齐达内 发表了文章 • 0 个评论 • 928 次浏览 • 2019-04-08 13:08 • 来自相关话题

【编者的话】本文是我在思考“如何组建团队”时候的一个小尝试,旨在通过容器技术(Docker)降低探索 Golang 技术开发的门槛。目前的效果还不是很明显,不过作为一种新思路,非常值得大家了解。 #容器化 Go 开发环境 ##容器化的价 ...查看全部
【编者的话】本文是我在思考“如何组建团队”时候的一个小尝试,旨在通过容器技术(Docker)降低探索 Golang 技术开发的门槛。目前的效果还不是很明显,不过作为一种新思路,非常值得大家了解。
#容器化 Go 开发环境
##容器化的价值
搭建开发环境往往是一个啰嗦繁杂的过程。对职业开发者如此,对知识学习者和探索者亦如此。

职业编码工作中,代码编辑测试完成后部署到生产环境,需要按照自己本地的开发环境重新配置生产环境的机器。由于本地开发环境的搭建比较随性,往往,本地能够跑起来的代码部署到生产环境后跑不起来,或并未达到预期的运行效果。

对于一个刚刚开始学习 《C 语言程序设计》课程的大学生来说,编译出自己的 “Hello World” 往往意味着很多事先的准备工作(至少先把课堂上老师三言两语带过的开发环境搭建起来)。

之前因为项目的需要我魔改过日志收集工具 fluent/fluent-bit,这是一个主要由 C 语言进行开发的项目,而我对 C 语言的认识还停留在大学课堂的水平,更何况我本地没有搭建过开发 C 的环境。

容器化技术能很好地解决上面的问题。职业开发者使用 Docker(容器化技术的一种)把环境搭建的过程封装到容器里,并以镜像的形式复制到生产环境得以“复现”相同的环境。作为知识学习者,完全可以利用相似的技术“复现”老师课堂上使用的环境。而作为知识探索者,在修改了 fluent-bit 的源码后,我利用其源码中提供的 Dockerfile 很方便地实现了定制化源码的编译,快速验证了思路可行性及定制化功能的可用性。

如果读者未使用过 Docker,可以参考《如何用一个例子上手 Docker》这篇博客的内容及其参考中列出的地址了解并尝试一下,应该会被甜到。
##容器化的 Go 开发环境
为了说明问题并方便读者能容易地在自己机器上验证,我在《Go 反序列化 JSON 字符串的两种常见用法》和 《浅谈 Go 标准库对 JSON 的处理效率》两篇博客里刻意贴了完整而冗长的源码内容。虽说 package 和 import 语句对博客的内容并没有任何作用,但是如果因为多这样几句内容就能让代码成为完整可运行的源码,从而节省读者自己构造完整源代码的时间,我认为是值得且必要的。

可以把思考更进一步,如果读者朋友没有 Go 开发环境(或者与作者本地的开发环境不一致),如何才能以一种低成本的方式开始这一切呢?不知不觉就想到了 Docker 技术。

定制化 Go 开发环境镜像

想要低成本获取 Go 开发环境,思路很简单,把 Go 开发环境打包到容器里(其实官方已经存在这种镜像),大家只需要拉取相应的镜像然后运行就可以了。如下面的源码所示,为了方便编辑并调试 Go 源码,我在 Go 官方镜像的基础上安装并简单配置了 vim 和 delve,并把镜像推送到了 Docker Hub 仓库中。更详尽的内容可以参考 GitHub - chalvern/smile
# cat https://github.com/chalvern/smile/blob/master/docker/Dockerfile
FROM golang:1.12

[size=16] vim[/size]
RUN apt-get update \
&& apt-get install -y vim \
&& rm -rf /var/lib/apt/lists/*

# vim setting
COPY vimrc /root/.vimrc

RUN go get -u github.com/derekparker/delve/cmd/dlv

WORKDIR $GOPATH

运行 Go 开发环境镜像

  1. 拉取镜像:docker pull chalvern/golang:1.12
  2. 以 privileged 方式运行镜像:docker run -it --privileged chalvern/golang:1.12 bash
  3. 此时便有了一个 Go 开发环境。

##环境(上下文)一致的必要性
我在学生时代发现一个很有趣的现象,国外的教材往往页码很足整本书很厚,而中文的教材页码比较少相对要薄一些。排除一部分语言表达力的因素,主要是因为国外的教材喜欢包含比较多知识之外的细节。

以《C 语言程序设计》类似的书籍来说,是直接从 Hello World 讲起好呢?还是从详细的环境搭建步骤讲起好呢?我记得当年在学习 C 语言编程的时候,为了搭建开发环境到图书馆找了很多资料,最终也未“复现”教科书上一模一样的开发环境,导致在学习过程中产生非常多的疑惑。有的同学在疑惑面前退缩了,渐渐失去了编码的兴趣,最终的成绩自然也不如人意。

国外教材比较厚重的另一个原因,是国外教材中喜欢包含比较详细的参考文献。那么,书籍或者博客中,是否应该把参考文献放进正文呢?我认为是必要的。把参考文献列出来,一方面可以表达对相关论点提出者的尊重,另一方面则方便让读者能够进一步了解论点的渊源或者进一步考证“真相”。书里或博客里所论述的是“集百家之长的一家之言”呢?还是纯碎个人思考得出来的“一家之言”呢?不同的分类,其说服力以及可采纳率其实是不一样的;如果混淆在一起使人不可分辨,容易让人忽视共识的力量,
#小结
本文尝试通过容器技术(Docker)降低探索 Golang 技术开发的门槛。相比于把开发环境直接安装到自己的电脑上“尝鲜”,容器化技术能够很好地避免 Go 开发环境及其依赖项(比如 $GOPATH、$GOROOT 等变量)对电脑的污染,同时容器化技术能够很好地“复现”一致可用的开发环境,避免引入其他变量,从而降低技术探索的难度。
#参考

* 以认真的态度做完美的事情(2018年总结) - 敬维 之前写的 2018 年的总结
* Docker基本原理简析 - 敬维 简单介绍了 Docker 涉及到的三种技术:Namespace、CGroup与AUFS
* 如何用一个例子上手Docker - 敬维 用一个例子来上手使用 docker。
* GitHub - fluent/fluent-bit 轻量级日志收集应用
* Go 反序列化 JSON 字符串的两种常见用法 - 敬维 两种反序列化 JSON 字符串的方法,包含了复制黏贴即可运行的源码
* 浅谈 Go 标准库对 JSON 的处理效率 - 敬维 探究 Go 标准库对 JSON 的处理效率,包含了复制黏贴即可运行的源码

原文链接:容器化 Go 开发环境的尝试

如何为你的Go应用创建轻量级Docker镜像?

ScofieldDM 发表了文章 • 0 个评论 • 4572 次浏览 • 2018-09-03 21:46 • 来自相关话题

恭喜!你已经创建了一个出色的go应用程序,现在你想创建一个Docker容器来分发你的应用。 但是,如何尽可能为你的Golang应用程序打造一个最小的镜像呢?提示:我们将会使用多阶段构建(自从Docker 17.05版本提供的方法)来完 ...查看全部
恭喜!你已经创建了一个出色的go应用程序,现在你想创建一个Docker容器来分发你的应用。

但是,如何尽可能为你的Golang应用程序打造一个最小的镜像呢?提示:我们将会使用多阶段构建(自从Docker 17.05版本提供的方法)来完成这个目标。
# 介绍
## 多什么?
简单来讲,多阶段。

多阶段允许在创建Dockerfile时使用多个from,它非常有用,因为它使我们能够使用所有必需的工具构建应用程序。举个例子,首先我们使用Golang的基础镜像,然后在第二阶段的时候使用构建好的镜像的二进制文件,最后阶段构建出来的镜像用于发布到我们自己的仓库或者是用于上线发布。

在上述的案例中,我们总共有三个阶段:

  • build编译阶段
  • certs(可选,可有可无)证书认证阶段
  • prod生产阶段
在build阶段主要是编译我们的应用程序,证书认证阶段将会安装我们所需要的CA证书,最后的生产发布阶段会将我们构建好的镜像推到镜像仓库中。而且发布阶段将会使用build阶段编译完毕的二进制文件和certs阶段安装的证书。
1.png
项目发布的多个build阶段# 示例工程对于这个方法,我们将使用一个非常简单的项目。它只是一个运行在8080端口的HTTP服务,并且返回结果为传递过去的URL的内容结果。## 举例GET http://localhost:8080?url=https://google.com 返回结果为goole页面内容展示。你也可以在这里找到代码仓库。在master分支上只包含了应用程序,final分支上还包含本篇教程中使用的Dockerfile文件如果你想跟着本教程来做,只需要拉下master上的代码并且跟着我来创建Dockerfile。# 步骤1 - 编译阶段第一阶段主要是使用Golang基础镜像来将我们的应用程序打包为二进制文件。这个基础镜像包含了将我们的应用程序编译成可执行二进制文件的所有工具。下面是我们最原始的Dockerfile:
1 #2 # BUILD 阶段3 # 4 FROM golang:1.10 AS build56 # 设置我们应用程序的工作目录7 WORKDIR /go/src/github.com/scboffspring/blog-multistage-go89 # 添加所有需要编译的应用代码10 ADD . .1112 # 编译一个静态的go应用(在二进制构建中包含C语言依赖库)13 RUN CGO_ENABLED=0 GOOS=linux go build -a -installsuffix cgo .1415 # 设置我们应用程序的启动命令16 CMD ["./blog-multistage-go"]
  • 第4行:使用的基础镜像(golang:1.10)并且我们使用as给当前阶段一个别名,也可以使用阶段索引来引用前一阶段,但这使得它更清晰。
  • 第7行:我们将工作目录设置为Golang基础镜像的默认$GOPATH中的应用程序目录。
  • 第10行:添加我们的应用程序源文件。
  • 第13行:编译二进制文件。使用不同的参数来创建一个完整的静态库,因为在生产环境拉取镜像时可能不一定需要所有的Golang VM以及C语言库。
  • 第16行:使用设定的命令来启动应用程序。
现在我们进行编译并使用Docker容器,我们的应用程序如我们预期正常运行:
docker build -t scboffspring/blog-multistage-go .docker run --rm -ti -p 8080:8080 \scboffspring/blog-multistage-go
我们可以使用curl命令来请求,并且它会返回http://google.com页面内容。在终端运行`curl localhost:8080`。
1 2  3  5  Google7 ....
让我们使用`docker images`,来看看镜像的大小:
REPOSITORY                                       ... SIZEscboffspring/blog-multistage-go                  ... 818MB
荒唐,太荒唐了,一个这么小的应用居然占了磁盘818M内存空间。推送到镜像仓库后,镜像大小被压缩到309M。
2.png
docker hub 占用309M接下来我们来改善这种情况,把镜像的大小降低到10M!# 步骤2 - 生产阶段上面提供的镜像是完全可以进行部署使用的,但是它真的是太大了。每次在Kubernetes上启动你的容器时需要拉取309M的镜像?真的是太浪费时间和带宽。让我们来为我们的镜像构建一个生产阶段,正如上面解释的,这个阶段只是从build阶段拷贝二进制文件到容器中。我们新的Dockerfile将会如下所示:
1 #2 # BUILD 阶段3 # 4 FROM golang:1.10 AS build56 # 设置我们应用程序的工作目录7 WORKDIR /go/src/github.com/scboffspring/blog-multistage-go89 # 添加所有需要编译的应用代码10 ADD . .1112 # 编译一个静态的go应用(在二进制构建中包含C语言依赖库)13 RUN CGO_ENABLED=0 GOOS=linux go build -a -installsuffix cgo .1415 # 设置我们应用程序的启动命令16 CMD ["./blog-multistage-go"]17181920 #21 # 生产阶段22 # 23 FROM scratch AS prod24 25 # 从buil阶段拷贝二进制文件26 COPY --from=build /go/src/github.com/scboffspring/blog-multistage-go/blog-multistage-go .27 CMD ["./blog-multistage-go"]
如你所见,同一个Dockerfile文件中我们添加了第二个FROM语句。这次,我们直接拉取二进制文件,不需要添加任何其他依赖。
  • 第23行:拉取基础镜像
  • 第26行:从`/go/src/github.com/scboffspring/blog-multistage-go/blog-multistage-go`拷贝build阶段编译的文件
  • 第27行:使用设定的命令来启动应用程序
简单吧。让我们像之前一样编译并使用Docker容器:
docker build -t scboffspring/blog-multistage-go . docker run --rm -ti -p 8080:8080 \scboffspring/blog-multistage-go
我们可以看到服务正常启动,也就是意味着它正确的启动了!我们完成了!让我们使用`docker images`,来看看镜像的大小:
REPOSITORY                                       ... SIZEscboffspring/blog-multistage-go                  ... 6.65MB
如我们之前所说,镜像的大小变为10MB以下。而且镜像被推送到镜像仓库后,它只有2MB。当你启动容器时,只需下载2MB即可,相比于之前节省了大量的时间和带宽呢。
3.png
使用prod阶段编译的容器仅2MB但是,它在我们的例子中不起作用。 如果运行`curl localhost:8080`,你看到的返回的结果为500。
curl localhost:8080500 - Something bad happened
如果你查看容器的日志,你可以找到如下错误:

发生了一个错误:Get http://google.com:X509:加载系统根目录失败并且没有根目录可以使用。

我们尝试使用https来连接Goole服务器,但是我们没有用于验证Google的SSL证书的CA(证书颁发机构)证书或是其他网站的CA证书。如果你的应用不需要使用SSL的话,可以选择跳到下一节,否则,让我们来改善我们的软件使得其可以进行访问。# 阶段3 - (可选)认证阶段
1 #2 # BUILD 阶段3 # 4 FROM golang:1.10 AS build56 # 设置我们应用程序的工作目录7 WORKDIR /go/src/github.com/scboffspring/blog-multistage-go89 # 添加所有需要编译的应用代码10 ADD . .1112 # 编译一个静态的go应用(在二进制构建中包含C语言依赖库)13 RUN CGO_ENABLED=0 GOOS=linux go build -a -installsuffix cgo .1415 # 设置我们应用程序的启动命令16 CMD ["./blog-multistage-go"]171819 # 20 # CERTS Stage21 #22 FROM alpine:latest as certs23 24 # Install the CA certificates25 RUN apk --update add ca-certificates26 27 #28 # PRODUCTION STAGE29 # 30 FROM scratch AS prod31 32 # 从certs阶段拷贝CA证书33 COPY --from=certs /etc/ssl/certs/ca-certificates.crt /etc/ssl/certs/ca-certificates.crt34 # 从buil阶段拷贝二进制文件35 COPY --from=build /go/src/github.com/scboffspring/blog-multistage-go/blog-multistage-go .36 CMD ["./blog-multistage-go"]
  • 第23行:我们新的certs阶段,使用alpine镜像
  • 第25行:安装最新版的CA证书
  • 第33行:从certs层拷贝证书,并保存为`/etc/ssl/certs/ca-certificates.crt`

让我们再次编译并使用Docker容器:
docker build -t scboffspring/blog-multistage-go . 
docker run --rm -ti -p 8080:8080 \
scboffspring/blog-multistage-go

现在,`curl localhost:8080`将会返回真实的页面!它真的奏效了!

使用`docker images`查看,镜像依然还是非常小的:
REPOSITORY                                       ... SIZE
scboffspring/blog-multistage-go ... 6.89MB

#额外福利:在指定的阶段为镜像添加tag
有时候我们可能会在各个阶段为镜像创建一个tag,在我们的示例中,我们可能也会将build阶段产生的结果发布到Docker,因为它对开发真的十分有用。

要想这样做的话,只需要在build镜像的时候简单的使用`--target=NAMEOFTHESTAGE`。

举个例子:
docker build -t scboffspring/blog-multistage-go:build . --target=build

# 总结
现在你已经能够为你的Golang应用程序创建一个非常轻量级的应用程序。阶段构建的概念对其他许多案例也是非常有用的。

我在NodeJS世界中的一个用法是第一阶段编译TypeScript项目。然后第一个阶段编译以便使得该镜像可以运行测试。此镜像也能够用于开发环境,因为它包含了所有开发环境所需的依赖。

当第一阶段测试通过后,第二阶段只是简单的安装项目中的`package.json`中的依赖(并不是测试环境依赖)。它只将编译和缩小的代码复制到镜像中,然后将该镜像推送并部署到生产中。

原文链接:HOW-TO: Create lightweight docker images for your Go applications using multi-stage builds(翻译:刘明)

如何在GO语言中使用Kubernetes API?

Rancher 发表了文章 • 1 个评论 • 2067 次浏览 • 2018-03-02 14:01 • 来自相关话题

Rancher Labs首席软件工程师Alena Prokharchyk受邀在2017年12月6-8日的CNCF主办的Kubernetes领域顶级盛会KubeCon + CloudNativeCon 2017北美峰会上进行演讲,本文由演讲内容整理而成。 ...查看全部
Rancher Labs首席软件工程师Alena Prokharchyk受邀在2017年12月6-8日的CNCF主办的Kubernetes领域顶级盛会KubeCon + CloudNativeCon 2017北美峰会上进行演讲,本文由演讲内容整理而成。

----------

随着Kubernetes越来越受欢迎,围绕它的集成和监控服务的数量也在不断增长。Golang编写的所有此类服务的关键组件是kubernetes / client-go——一个用于与Kubernetes集群API通信的软件包。在本文中,我们将讨论client-go使用的基本知识,以及如何为开发人员节约编写实际应用程序逻辑所需的时间。我们还将展示使用该软件包的最佳实践,并从每天与Kubernetes进行集成工作的开发人员的角度,分享我们已有的经验。内容将包括:

- 集群中的客户端认证 vs. 集群外的客户端认证
- 基本列表,使用client-go去创建和删除Kubernetes对象的操作
- 如何使用ListWatch和Informers监视K8s事件并做出反应
- 如何管理软件包依赖

Kubernetes是一个平台
Kubernetes有很多受欢迎的地方。用户喜欢它的丰富功能、稳定性和性能。对贡献者来说,Kubernetes开源社区不仅规模庞大,还易于上手、反馈迅速。而真正让Kubernetes吸引了第三方开发者的是它的可扩展性。该项目提供了很多方式来添加新功能、扩展现有功能而且不会中断主代码库。正是这些,使得Kubernetes发展成为了一个平台。
这里有一些方式来扩展Kubernetes:



上图所示,你可以发现每个Kubernetes集群组件无论是Kubelet还是API服务器,都可以以某种方式进行扩展。今天我们将重点介绍一种“自定义控制器”的方式,从现在起我将它称为Kubernetes控制器(Kubernetes Controller),或者简单地称为控制器(Controller)。
Kubernetes控制器究竟是什么?
控制器最常见的定义是:使得系统的当前状态达到所期望的状态的代码。但这究竟是什么意思呢?我们以Ingress控制器为例。Ingress是一个Kubernetes资源,它能够对集群中服务的外部访问进行定义。通常采用HTTP并且有负载均衡支持。然而Kubernetes的核心代码中并没有ingress的实现。第三方控制器的实现将包含:
1.监控ingress/services/endpoint 资源的事件(创建、更新、删除)
2.程序内部或外部的负载均衡器
3.使用负载均衡器的地址来更新Ingress
“所期望的状态”在Ingress这里指的是IP地址指向运行着的负载均衡器,该均衡器由用户根据ingress规范定义的规则实现。并且由外部Ingress控制器负责将ingress资源转移到这一状态。
对相同的资源,控制器的实现以及部署他们的方式也可能会有所不同。你可以选择nginx控制器并将其部署到集群中的每个节点上作为守护进程集(Daemon Set),也可以选择在Kubernetes集群外部运行ingress控制器并且对F5编程作为负载均衡器。这里没有严格的规定,Kubernetes就是如此灵活。
Client-go
这里有几种获得Kubernetes集群及其资源相关信息的方法,你可以使用Dashboard、kubectl或者使用对Kubernetes API的编程式访问来实现。Client-go所有用Go语言编写的工具中使用最为广泛的库,还有许多其他语言的版本(java、python等)。如果你还没自己写过控制器,我推荐你首先去尝试go/client-go。Kubernetes是用Go编写的,而且我发现使用和主项目相同的语言来开发插件会更加方便。
我们来搭建吧…
要熟悉相关的平台和工具,最好的办法就是去实践,去实现一些东西。我们从简单入手,先实现一个如下的控制器:
1.监控Kubernetes节点
2.当节点上的镜像占用存储空间时进行警报,并且可以更改
这部分的实现,源码可以在这里找到:https://github.com/alena1108/kubecon2017
基本流程
#配置项目
作为一名开发者,我和Rancher Labs的同事们更愿意使用轻便简易的工具,在这里我将分享3个我最喜欢的工具,它们将帮助我们完成第一个项目。
1.go-skel – Go语言的微服务skeleton,只需执行run ./skel.sh test123即可,它会为新的go项目test123创建skeleton。
2.trash – Go语言的供应商管理工具。实际上这儿有很多依赖项管理工具,但是在临时依赖项管理方面,trash使用起来非常出色,而且简单。
3.dapper – 在一致性环境中对任何现有构建工具进行封装的一种工具
#添加client-go作为一个依赖项
为了方便使用client-go的代码,我们必须要将其设置为项目的依赖项。将它添加到vendor.conf文件中:



接着运行trash。它会将vendor.conf中定义的所有依赖项都拉到项目的vendor文件夹中。在这里需要确保client-go与你集群对应的Kubernetes版本是兼容的。
#创建一个客户端
在创建与Kubernetes API通信的客户端之前,我们必须要先决定如何运行我们的工具:是在Kubetnetes集群内部还是外部。当应用程序在集群内部运行时,对它进行容器化,部署成为Kubernetes pod。它还提供了一些额外的功能:你可以选择部署它的方式(Deamon set运行在每个节点上,或者作为n个副本的部署),配置针对它的健康检查等等。当应用程序在集群外部运行时,就需要自己来管理它。下面的配置可以让我们的工具变得更灵活,并且支持基于config flag定义客户端的两种方式:



我们将在调试应用程序时使用集群外部运行的方式,这样你不需要每次都构建镜像并且将其重新部署成Kubernetes pod。在测试好应用程序后,我们就可以构建镜像并将其部署到集群中。
正如在截图中看到的那样,正在构建配置,并将其传递到kubernetes.NewForConfig来生成客户端。
#使用基本的CRUDs
我们的工具需要监控节点。在实现逻辑流程之前,我们先来熟悉使用client-go执行CRUD操作:



上面的截图展示了:
1.List节点minikube,是经过FieldSelector过滤器实现的
2.用新的标注来更新节点
3.使用gracePerios=10秒指令删除节点—意思是从该命令执行后10秒才会执行删除操作
上面所有的步骤都是使用我们之前创建的用户集(clientset)进行的。
我们还需要节点上镜像的相关信息;它可以通过访问相应的字段来检索:



#使用Informer来进行监控/通知
现在我们知道了如何从Kubernetes APIs中获取节点并从中得到镜像信息。那么我们该如何监控镜像大小的变化呢?最简单的方法是周期性轮询节点,计算当前的镜像存储容量,并将其和先前轮询的结果比较。这里的不足之处在于:无论节点是否发生变化,我们执行的列表调用都会获取所有的节点,这可能会很费资源——特别是当轮询间隔很短的时候。而我们真正想要实现的是—在节点发生变化时得到通知,只有在这之后才执行我们的逻辑流程。这些就是client-go的Informer来做的。



在这个例子中,我们经过watchList指令为节点对象创建Informer来监控节点,设置对象类型为api.Node和30秒的同步周期来周期性地轮询节点,无论节点是否发生改变——这种方式在更新事件出于某种原因发生终止时可以很好的进行撤回。在最后一个参数,我们传递了2个回调函数——handleNodeAdd和handleNodeUpdate。这些回调函数具有实际的逻辑,并且在节点上的镜像占用存储发生改变时触发。NewInformer返回2个对象——controller和store。一旦controller启动,将会开始对node.update和node.add的监控,并且调用回调函数。这部分代码的存储区位于内存缓存中,由informer负责更新,另外你可以在缓存区中获取节点对象而不用直接调用Kubernetes APIs:



我们的项目中只有一个控制器,使用常规的Informer就足够了。不过,如果未来你的项目最终同一个对象拥有了多个控制器,我建议你使用SharedInformer。这样一来你不用再一个一个为每个控制器配上Informer,只需要注册一个Shared informer即可,并且让每个控制器注册自己的一组回调函数,返回共享缓存,这可以减少内存占用:



部署时间
是时候来部署和测试代码了!对于第一次运行,我们只需要创建一个go的二进制文件并且在集群外模式下运行它即可:



如要更改消息输出,那么使用镜像部署一个pod,该镜像是没有在当前节点显示的镜像。
在基本的功能通过测试之后,接下来就是按照集群模式尝试运行它了。为此我们必须先创建镜像,定义它的Dockerfile:



并使用docker build创建一个镜像,该命令将生成一个可用在Kubernetes中部署pod的镜像。现在你的应用程序可以作为一个pod运行在Kubernetes集群上了。这里是一个部署定义的例子,在之前的截图中,我使用了该例部署我们的应用程序:



在本文中我们做了如下工作:
1.创建go项目
2.为项目添加client-go包的依赖项
3.创建用于和Kubernetes api通信的客户端
4.定义一个用于监控节点对象改变,并且一旦发生就执行回调函数的Informer
5.在回调函数中实现一个实际的逻辑
6.在集群外运行二进制文件来测试代码,并把它部署到集群中

推荐阅读
http://mp.weixin.qq.com/s/4-cFeRsa0J4tr6GE91Grag
http://mp.weixin.qq.com/s/2qZHVM-JrJ4kaKt4qBhq5w
http://mp.weixin.qq.com/s/prP9pFTXG6EHoDGoh_nyEA
http://mp.weixin.qq.com/s/FJ2D34ewH_bL8kSM-eUE1g

如若转载,请注明出处谢谢!
微信号:RancherLabs

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

alex_wang2 发表了文章 • 0 个评论 • 4686 次浏览 • 2017-12-19 22:11 • 来自相关话题

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

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

你可以从这里找到这篇章中使用的例子,跟随我们一步一步的进行。
#第1步 最简单的http服务程序
下面就是这个程序:

`main.go`
package main

import (
"fmt"
"net/http"
)

func main() {
http.HandleFunc("/home", func(w http.ResponseWriter, _ *http.Request) {
fmt.Fprint(w, "Hello! Your request was processed.")
},
)
http.ListenAndServe(":8000", nil)
}

如果是第一次运行,仅仅执行`go run main.go`就可以了。如果你想知道它是怎么工作的,你可以用下面这个命令:`curl -i http://127.0.0.1:8000/home`。但是当我们运行这个应用的时候,我们找不到任何关于状态的信息。
#第2步 增加日志
首先,增加日志功能可以帮助我们了解程序现在处于一个什么样的状态,并记录错误(译者注:如果有错误的话)等其他一些重要信息。在这个例子里我们使用Go语言标准库里最简单的日志模块,但是如果是跑在Kubernetes上的服务程序,你可能还需要一些额外的库,比如glog或者logrus

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

`main.go`
func main() {
log.Print("Starting the service...")

http.HandleFunc("/home", func(w http.ResponseWriter, _ *http.Request) {
fmt.Fprint(w, "Hello! Your request was processed.")
},
)

log.Print("The service is ready to listen and serve.")
log.Fatal(http.ListenAndServe(":8000", nil))
}

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

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

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

`handler/handers.go`
package handlers

import (
"github.com/gorilla/mux"
)

// Router register necessary routes and returns an instance of a router.
func Router() *mux.Router {
r := mux.NewRouter()
r.HandleFunc("/home", home).Methods("GET")
return r
}

`handlers/home.go`
package handlers

import (
"fmt"
"net/http"
)

// home is a simple HTTP handler function which writes a response.
func home(w http.ResponseWriter, _ *http.Request) {
fmt.Fprint(w, "Hello! Your request was processed.")
}

然后我们稍微修改一下`main.go`:
package main

import (
"log"
"net/http"

"github.com/rumyantseva/advent-2017/handlers"
)

// How to try it: go run main.go
func main() {
log.Print("Starting the service...")
router := handlers.Router()
log.Print("The service is ready to listen and serve.")
log.Fatal(http.ListenAndServe(":8000", router))
}

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

`handlers/handles_test.go`
package handlers

import (
"net/http"
"net/http/httptest"
"testing"
)

func TestRouter(t *testing.T) {
r := Router()
ts := httptest.NewServer(r)
defer ts.Close()

res, err := http.Get(ts.URL + "/home")
if err != nil {
t.Fatal(err)
}
if res.StatusCode != http.StatusOK {
t.Errorf("Status code for /home is wrong. Have: %d, want: %d.", res.StatusCode, http.StatusOK)
}

res, err = http.Post(ts.URL+"/home", "text/plain", nil)
if err != nil {
t.Fatal(err)
}
if res.StatusCode != http.StatusMethodNotAllowed {
t.Errorf("Status code for /home is wrong. Have: %d, want: %d.", res.StatusCode, http.StatusMethodNotAllowed)
}

res, err = http.Get(ts.URL + "/not-exists")
if err != nil {
t.Fatal(err)
}
if res.StatusCode != http.StatusNotFound {
t.Errorf("Status code for /home is wrong. Have: %d, want: %d.", res.StatusCode, http.StatusNotFound)
}
}

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

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

`handlers/home_test.go`
package handlers

import (
"io/ioutil"
"net/http"
"net/http/httptest"
"testing"
)

func TestHome(t *testing.T) {
w := httptest.NewRecorder()
home(w, nil)

resp := w.Result()
if have, want := resp.StatusCode, http.StatusOK; have != want {
t.Errorf("Status code is wrong. Have: %d, want: %d.", have, want)
}

greeting, err := ioutil.ReadAll(resp.Body)
resp.Body.Close()
if err != nil {
t.Fatal(err)
}
if have, want := string(greeting), "Hello! Your request was processed."; have != want {
t.Errorf("The greeting is wrong. Have: %s, want: %s.", have, want)
}
}

现在我们运行`go tests`来检查代码的正确性:
$ go test -v ./...
? github.com/rumyantseva/advent-2017 [no test files]
=== RUN TestRouter
--- PASS: TestRouter (0.00s)
=== RUN TestHome
--- PASS: TestHome (0.00s)
PASS
ok github.com/rumyantseva/advent-2017/handlers 0.018s

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

`main.go`
package main

import (
"log"
"net/http"
"os"

"github.com/rumyantseva/advent-2017/handlers"
)

// How to try it: PORT=8000 go run main.go
func main() {
log.Print("Starting the service...")

port := os.Getenv("PORT")
if port == "" {
log.Fatal("Port is not set.")
}

r := handlers.Router()
log.Print("The service is ready to listen and serve.")
log.Fatal(http.ListenAndServe(":"+port, r))
}

在这个例子里,如果没有设置端口,应用程序会退出并返回一个错误。因为如果配置错误了,就没有必要再继续执行了。
#第6步 Makefile
几天以前有一篇文章介绍`make`工具,如果你有一些重复性比较强的工作,那么使用它就大有帮助。现在我们来看一看我们的应用程序如何使用它。当前,我们有两个操作,测试和编译并运行。我们对Makefile文件进行了如下修改。但是我们用`go build`代替了`go run`,并且运行那个编译出来的二进制程序,因为这样修改更适合为我们的生产环境做准备:

`Makefile`
APP?=advent
PORT?=8000

clean:
rm -f ${APP}

build: clean
go build -o ${APP}

run: build
PORT=${PORT} ./${APP}

test:
go test -v -race ./...

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

这里,为了运行应用程序,我们需要删除掉旧的程序(如果它存在的话),编译代码并用环境变量代表的参数运行新编译出的程序,做这些操作,我们仅仅需要执行`make run`。
#第7步 版本控制
下一步,我们将为我们的程序加入版本控制。因为有的时候,它对我们知道正在生产环境中运行和编译的代码非常有帮助。(译者注:就是说,我们在生产环境中运行的代码,有的时候我们自己都不知道对这个代码进行和什么样的提交和修改,有了版本控制,就可以显示出这个版本的变化和历史记录)。

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

`version/version.go`
package version

var (
// BuildTime is a time label of the moment when the binary was built
BuildTime = "unset"
// Commit is a last commit hash at the moment when the binary was built
Commit = "unset"
// Release is a semantic version of current build
Release = "unset"
)

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

`main.go`
...
func main() {
log.Printf(
"Starting the service...\ncommit: %s, build time: %s, release: %s",
version.Commit, version.BuildTime, version.Release,
)
...
}

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

`handlers/home.go`
package handlers

import (
"encoding/json"
"log"
"net/http"

"github.com/rumyantseva/advent-2017/version"
)

// home is a simple HTTP handler function which writes a response.
func home(w http.ResponseWriter, _ *http.Request) {
info := struct {
BuildTime string `json:"buildTime"`
Commit string `json:"commit"`
Release string `json:"release"`
}{
version.BuildTime, version.Commit, version.Release,
}

body, err := json.Marshal(info)
if err != nil {
log.Printf("Could not encode info data: %v", err)
http.Error(w, http.StatusText(http.StatusServiceUnavailable), http.StatusServiceUnavailable)
return
}
w.Header().Set("Content-Type", "application/json")
w.Write(body)
}

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

为`Makefile`增加一些变量:

`Makefile`
RELEASE?=0.0.1
COMMIT?=$(shell git rev-parse --short HEAD)
BUILD_TIME?=$(shell date -u '+%Y-%m-%d_%H:%M:%S')

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

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

`Makefile`
build: clean
go build \
-ldflags "-s -w -X ${PROJECT}/version.Release=${RELEASE} \
-X ${PROJECT}/version.Commit=${COMMIT} -X ${PROJECT}/version.BuildTime=${BUILD_TIME}" \
-o ${APP}

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

`Makefile`
PROJECT?=github.com/rumyantseva/advent-2017


所有的变化都可以在这里找到,现在可以用`make run`去运行它了。
#第8步 减少一些依赖
这里有一些代码里我不喜欢的地方:`handle`pakcage依赖于`version`package。这个很容易修改:我们需要让`home` 处理变得可以配置。

`handler/home.go`
// home returns a simple HTTP handler function which writes a response.
func home(buildTime, commit, release string) http.HandlerFunc {
return func(w http.ResponseWriter, _ *http.Request) {
...
}
}

别忘了同时去修改测试和必须的环境变量。
#第9步 健康检查
在某些情况下,我们需要经常对运行在Kubernetes里的服务程序进行健康检查:liveness and readiness probes。这么做的目的是为了知道容器里的应用程序是否还在运行。如果liveness探测失败,这个服务程序将会被重启,如果readness探测失败,说明服务还没有准备好。

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

`handlers/healthz.go`
// healthz is a liveness probe.
func healthz(w http.ResponseWriter, _ *http.Request) {
w.WriteHeader(http.StatusOK)
}

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

`handlers/readyz.go`
// readyz is a readiness probe.
func readyz(isReady *atomic.Value) http.HandlerFunc {
return func(w http.ResponseWriter, _ *http.Request) {
if isReady == nil || !isReady.Load().(bool) {
http.Error(w, http.StatusText(http.StatusServiceUnavailable), http.StatusServiceUnavailable)
return
}
w.WriteHeader(http.StatusOK)
}
}

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

现在我们看看怎么使用:

`handles.go`
func Router(buildTime, commit, release string) *mux.Router {
isReady := &atomic.Value{}
isReady.Store(false)
go func() {
log.Printf("Readyz probe is negative by default...")
time.Sleep(10 * time.Second)
isReady.Store(true)
log.Printf("Readyz probe is positive.")
}()

r := mux.NewRouter()
r.HandleFunc("/home", home(buildTime, commit, release)).Methods("GET")
r.HandleFunc("/healthz", healthz)
r.HandleFunc("/readyz", readyz(isReady))
return r
}

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

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

`main.go`
func main() {
...
r := handlers.Router(version.BuildTime, version.Commit, version.Release)

interrupt := make(chan os.Signal, 1)
signal.Notify(interrupt, os.Interrupt, os.Kill, syscall.SIGTERM)

srv := &http.Server{
Addr: ":" + port,
Handler: r,
}
go func() {
log.Fatal(srv.ListenAndServe())
}()
log.Print("The service is ready to listen and serve.")

killSignal := <-interrupt
switch killSignal {
case os.Kill:
log.Print("Got SIGKILL...")
case os.Interrupt:
log.Print("Got SIGINT...")
case syscall.SIGTERM:
log.Print("Got SIGTERM...")
}

log.Print("The service is shutting down...")
srv.Shutdown(context.Background())
log.Print("Done")
}

这里,我们会捕获系统信号,如果发现有`SIGKILL`,`SIGINT`或者`SIGTERM`,我们将优雅的关闭程序。
#第11步 Dockerfile
我们的应用程序马上就以运行在Kubernetes里了,现在我们把它容器化。

下面是一个最简单的Dockerfile:

`Dockerfile`
FROM scratch

ENV PORT 8000
EXPOSE $PORT

COPY advent /
CMD ["/advent"]

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

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

`Makefile`
...

GOOS?=linux
GOARCH?=amd64

...

build: clean
CGO_ENABLED=0 GOOS=${GOOS} GOARCH=${GOARCH} go build \
-ldflags "-s -w -X ${PROJECT}/version.Release=${RELEASE} \
-X ${PROJECT}/version.Commit=${COMMIT} -X ${PROJECT}/version.BuildTime=${BUILD_TIME}" \
-o ${APP}

container: build
docker build -t $(APP):$(RELEASE) .

run: container
docker stop $(APP):$(RELEASE) || true && docker rm $(APP):$(RELEASE) || true
docker run --name ${APP} -p ${PORT}:${PORT} --rm \
-e "PORT=${PORT}" \
$(APP):$(RELEASE)

...

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

现在我们终于可以用`make run`去检验一下整个过程了。
#第12步 发布
在我们的项目里,我们还依赖一个外部的包(github.com/gorilla/mux)。而且,我们需要为生产环境里的
readness安装依赖管理。所以我们用了dep之后我们唯一要做的就是运行`dep init`:
$ dep init
Using ^1.6.0 as constraint for direct dep github.com/gorilla/mux
Locking in v1.6.0 (7f08801) for direct dep github.com/gorilla/mux
Locking in v1.1 (1ea2538) for transitive dep github.com/gorilla/context

这个工具会创建两个文件`Gopkg.toml`和`Gopkg.lock`,还有一个目录`vendor`,个人认为,我会把`vendor`放到git上去,特别是对与那些比较重要的项目来说。
#第13步 Kubernetes
这也是最后一步了。运行一个应用程序到Kubernetes上。最简单的方法就是在本地去安装和配置一个minikube(这是一个单点的kubernetes测试环境)。

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

`Makefile:`
CONTAINER_IMAGE?=docker.io/webdeva/${APP}

...

container: build
docker build -t $(CONTAINER_IMAGE):$(RELEASE) .

...

push: container
docker push $(CONTAINER_IMAGE):$(RELEASE)

这个`CONTAINER_IMAGE`变量用来定义一个镜像的名字,我们用这个镜像存放我们的服务程序。如你所见,在这个例子里包含了我的用户名(`webdeva`)。如果你在hub.docker.com上没有账户,那你就先得创建一个,然后用`docker login`命令登陆,这个时候你就可以推送你的镜像了。

现在我们试一下`make push`:
$ make push
...
docker build -t docker.io/webdeva/advent:0.0.1 .
Sending build context to Docker daemon 5.25MB
...
Successfully built d3cc8f4121fe
Successfully tagged webdeva/advent:0.0.1
docker push docker.io/webdeva/advent:0.0.1
The push refers to a repository [docker.io/webdeva/advent]
ee1f0f98199f: Pushed
0.0.1: digest: sha256:fb3a25b19946787e291f32f45931ffd95a933100c7e55ab975e523a02810b04c size: 528

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

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

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

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

`deployment.yaml`
apiVersion: extensions/v1beta1
kind: Deployment
metadata:
name: {{ .ServiceName }}
labels:
app: {{ .ServiceName }}
spec:
replicas: 3
strategy:
type: RollingUpdate
rollingUpdate:
maxUnavailable: 50%
maxSurge: 1
template:
metadata:
labels:
app: {{ .ServiceName }}
spec:
containers:
- name: {{ .ServiceName }}
image: docker.io/webdeva/{{ .ServiceName }}:{{ .Release }}
imagePullPolicy: Always
ports:
- containerPort: 8000
livenessProbe:
httpGet:
path: /healthz
port: 8000
readinessProbe:
httpGet:
path: /readyz
port: 8000
resources:
limits:
cpu: 10m
memory: 30Mi
requests:
cpu: 10m
memory: 30Mi
terminationGracePeriodSeconds: 30

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

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

`service.yaml`
apiVersion: v1
kind: Service
metadata:
name: {{ .ServiceName }}
labels:
app: {{ .ServiceName }}
spec:
ports:
- port: 80
targetPort: 8000
protocol: TCP
name: http
selector:
app: {{ .ServiceName }}

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

`ingress.yaml`
apiVersion: extensions/v1beta1
kind: Ingress
metadata:
annotations:
kubernetes.io/ingress.class: nginx
ingress.kubernetes.io/rewrite-target: /
labels:
app: {{ .ServiceName }}
name: {{ .ServiceName }}
spec:
backend:
serviceName: {{ .ServiceName }}
servicePort: 80
rules:
- host: advent.test
http:
paths:
- path: /
backend:
serviceName: {{ .ServiceName }}
servicePort: 80

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

运行`minikube`,需要开启ingress并且准备好`kubectl`,我们要用它运行一些命令:
minikube start
minikube addons enable ingress
kubectl config use-context minikube

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

`Makefile`
minikube: push
for t in $(shell find ./kubernetes/advent -type f -name "*.yaml"); do \
cat $$t | \
gsed -E "s/\{\{(\s[i])\.Release(\s[/i])\}\}/$(RELEASE)/g" | \
gsed -E "s/\{\{(\s[i])\.ServiceName(\s[/i])\}\}/$(APP)/g"; \
echo ---; \
done > tmp.yaml
kubectl apply -f tmp.yaml

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

现在我们来看一下我的工作成果:
$ kubectl get deployment
NAME DESIRED CURRENT UP-TO-DATE AVAILABLE AGE
advent 3 3 3 3 1d

$ kubectl get service
NAME CLUSTER-IP EXTERNAL-IP PORT(S) AGE
advent 10.109.133.147 80/TCP 1d

$ kubectl get ingress
NAME HOSTS ADDRESS PORTS AGE
advent advent.test 192.168.64.2 80 1d

现在我们可以发送一个http的请求到我们的服务上,但是首先还是要把域名`adventtest`增加到`/etc/host`文件里:
echo "$(minikube ip) advent.test" | sudo tee -a /etc/hosts 

现在,我们终于可以使用我们的服务了:
curl -i http://advent.test/home
HTTP/1.1 200 OK
Server: nginx/1.13.6
Date: Sun, 10 Dec 2017 20:40:37 GMT
Content-Type: application/json
Content-Length: 72
Connection: keep-alive
Vary: Accept-Encoding

{"buildTime":"2017-12-10_11:29:59","commit":"020a181","release":"0.0.5"}%

看,它工作了!

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

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

非常感谢 Natalie PistunovichPaul BrousseauSandor Szücs等人的的建议和审核。

原文链接:Write a Kubernetes-ready service from zero step-by-step(翻译:王晓轩)

基于Go技术栈的微服务构建

ucloud 发表了文章 • 0 个评论 • 2204 次浏览 • 2017-11-29 11:21 • 来自相关话题

在大型系统的微服务化构建中,一个系统会被拆分成许多模块。这些模块负责不同的功能,组合成系统,最终可以提供丰富的功能。在这种构建形式中,开发者一般会聚焦于最大程度解耦模块的功能以减少模块间耦合带来的额外开发成本。同时,微服务面临着如何部署这些大量的服务系统、如何 ...查看全部
在大型系统的微服务化构建中,一个系统会被拆分成许多模块。这些模块负责不同的功能,组合成系统,最终可以提供丰富的功能。在这种构建形式中,开发者一般会聚焦于最大程度解耦模块的功能以减少模块间耦合带来的额外开发成本。同时,微服务面临着如何部署这些大量的服务系统、如何运维这些系统等新问题。

本文的素材来源于我们在开发中的一些最佳实践案例,从开发、监控、日志等角度介绍了一些我们基于Go技术栈的微服务构建经验。

开 发

微服务的开发过程中,不同模块由不同的开发者负责,明确定义的接口有助于确定开发者的工作任务。最终的系统中,一个业务请求可能会涉及到多次接口调用,如何准确清晰的调用远端接口,这也是一大挑战。对于这些问题,我们使用了gRPC来负责协议的制订和调用。

传统的微服务通常基于http协议来进行模块间的调用,而在我们的微服务构建中,选用了Google推出的gRPC框架来进行调用。下面这张简表比较了http rpc框架与gRPC的特性:


gRPC的接口需要使用Protobuf3定义,通过静态编译后才能成功调用。这一特性减少了由于接口改变带来的沟通成本。如果使用http rpc,接口改变就需要先改接口文档,然后周知到调用者,如果调用者没有及时修改,很可能会到服务运行时才能发现错误。而gRPC的这种模式,接口变动引起的错误保证在编译时期就能消除。

在性能方面,gRPC相比传统的http rpc协议有非常大的改善(根据这个评测,gRPC要快10倍)。gRPC使用http 2协议进行传输,相比较http 1.1, http 2复用tcp连接,减少了每次请求建立tcp连接的开销。需要指出的是,如果单纯追求性能,之前业界一般会选用构建在tcp协议上的rpc协议(thrift等),但四层协议无法方便的做一些传输控制。相比而言,gRPC可以在http header中放入控制字段,配合nginx等代理服务器,可以很方便的实现转发/灰度等功能。

接下来着重谈谈我们在实践中如何使用gRPC的一些特性来简化相关开发流程。

1. 使用context来控制请求的生命周期

在gRPC的go语言实现中,每个rpc请求的第一个参数都是context。http2协议会将context放在HEADER中,随着链路传递下去,因此可以为每个请求设置过期时间,一旦遇到超时的情况,发起方就会结束等待,返回错误。

ctx := context.Background() // blank context

ctx, cancel = context.WithTimeout(ctx, 5*time.Second)

defer cancel( )

grpc.CallServiveX(ctx, arg1)

上述这段代码,发起方设置了大约5s的等待时间,只要远端的调用在5s内没有返回,发起方就会报错。

除了能加入超时时间,context还能加入其他内容,下文我们还会见到context的另一个妙用。

2.使用TLS实现访问权限控制

gRPC集成了TLS证书功能,为我们提供了很完善的权限控制方案。在实践中,假设我们的系统中存在服务A,由于它负责操作用户的敏感内容,因此需要保证A不被系统内的其他服务滥用。为了避免滥用,我们设计了一套自签名的二级证书系统,服务A掌握了自签名的根证书,同时为每个调用A的服务颁发一个二级证书。这样,所有调用A的服务必须经过A的授权,A也可以鉴别每个请求的调用方,这样可以很方便的做一些记录日志、流量控制等操作。

3. 使用trace在线追踪请求

gRPC内置了一套追踪请求的trace系统,既可以追踪最近10个请求的详细日志信息,也可以记录所有请求的统计信息。

当我们为请求加入了trace日志后,trace系统会为我们记录下最近10个请求的日志,下图中所示的例子就是在trace日志中加入了对业务数据的追踪。


在宏观上,trace系统为我们记录下请求的统计信息,比如请求数目、按照不同请求时间统计的分布等。



需要说明的是,这套系统暴露了一个http服务,我们可以通过debug开关在运行时按需打开或者关闭,以减少资源消耗。

监控

1.确定监控指标

在接到为整个系统搭建监控系统这个任务时,我们面对的第一个问题是要监控什么内容。针对这个问题,GoogleSRE这本书提供了很详细的回答,我们可以监控四大黄金指标,分别是延时、流量、错误和饱和度。

延时衡量了请求花费的时间。需要注意的,考虑到长尾效应,使用平均延时作为延时方面的单一指标是远远不够的。相应的,我们需要延时的中位数90%、95%、99%值来帮助我们了解延时的分布,有一种更好的办法是使用直方图来统计延时分布。

流量衡量了服务面临的请求压力。针对每个API的流量统计能让我们知道系统的热点路径,帮助优化。
错误监控是指对错误的请求结果的统计。同样的,每个请求有不同的错误码,我们需要针对不同的错误码进行统计。配合上告警系统,这类监控能让我们尽早感知错误,进行干预。

饱和度主要指对系统CPU和内存的负载监控。这类监控能为我们的扩容决策提供依据。

2.监控选型

选择监控方案时,我们面临的选择主要有两个,一是公司自建的监控系统,二是使用开源Prometheus系统搭建。这两个系统的区别列在下表中。



考虑到我们的整个系统大约有100个容器分布在30台虚拟机上,Prometheus的单机存储对我们并不是瓶颈。我们不需要完整保留历史数据,自建系统的最大优势也不足以吸引我们使用。相反,由于希望能够统计四大黄金指标延生出的诸多指标,Prometheus方便的DSL能够很大程度上简化我们的指标设计。

最终,我们选择了Prometheus搭建监控系统。整个监控系统的框架如下图所示。


各服务将自己的地址注册到consul中,Prometheus会自动从consul中拉取需要监控的目标地址,然后从这些服务中拉取监控数据,存放到本地存储中。在Prometheus自带的Web UI中可以快捷的使用PromQL查询语句获取统计信息,同时,还可以将查询语句输入grafana,固定监控指标用于监控。


此外,配合插件AlertManager,我们能够编写告警规则,当系统出现异常时,将告警发送到手机/邮件/信箱。

日志

1.日志格式

一个经常被忽略的问题是如何选择日志记录的格式。良好的日志格式有利于后续工具对日志内容的切割,便于日志存储的索引。我们使用logrus来打印日志到文件,logrus工具支持的日志格式包裹以空格分隔的单行文本格式、json格式等等。

文本格式

time=”2015-03-26T01:27:38-04:00″ level=debug g=”Started observing beach” animal=walrus number=8

time=”2015-03-26T01:27:38-04:00″ level=info msg=”A group of walrus emerges from the ocean” animal=walrus size=10Json格式

{“animal”:”walrus”,”level”:”info”,”msg”:”A group of walrus emerges from theocean”,”size”:10,”time”:”2014-03-10 19:57:38.562264131 -0400 EDT”}

{“level”:”warning”,”msg”:”The group’s number increased tremendously!”,”number”:122,”omg”:true,”time”:”2014-03-10 19:57:38.562471297 -0400 EDT”}

2.端到端链路上的调用日志收集

在微服务架构中,一个业务请求会经历多个服务,收集端到端链路上的日志能够帮助我们判断错误发生的具体位置。在这个系统中,我们在请求入口处,生成了全局ID,通过gRPC中的context将ID在链路中传递。将不同服务的日志收集到graylog中,查询时就能通过一个ID,将整个链路上的日志查询出来。


上图中,使用session-id来作为整个调用链的ID可以进行全链路检索。

小结

微服务构建的系统中,在部署、调度、服务发现、一致性等其他方面都有挑战,Go技术栈在这些方面都有最佳实践(docker,k8s,consul,etcd等等)。具体内容在网上已经有很完善的教程,在此不用班门弄斧,有需要的可以自行查阅。

Docker 学习笔记 ( 一 ): 简介以及构架剖析

腾讯云技术社区 发表了文章 • 0 个评论 • 18292 次浏览 • 2017-10-20 16:53 • 来自相关话题

欢迎大家前往腾讯云技术社区,获取更多腾讯海量技术实践干货哦~ 作者:陈云 导语 之前一直忙于项目工作,一直没有好好去系统学习,完善自己的知识体系,以致于在腾讯两年知识体系都没 ...查看全部
欢迎大家前往腾讯云技术社区,获取更多腾讯海量技术实践干货哦~

作者:陈云



导语

之前一直忙于项目工作,一直没有好好去系统学习,完善自己的知识体系,以致于在腾讯两年知识体系都没有深度发展,还是停留在原来的领域,所以从8月份开始学习docker。中间穿插的学习了linux内核和python爬虫。

Docker简介

Docker是2013发起的一个项目,早在2013年,Docker自诞生起,就是整个技术界的明星项目,当时我还在上海实习,就在各种技术媒体上看到了Docker的介绍文章,很多技术媒体宣称docker是一项技术突破,并且是一次技术革命,可惜当时由于本身是一个Android Framework开发者,眼界很低,对于这种OS虚拟化技术有点不屑一顾,而今转后台后才发现这项技术的重要性

Docker的特征

Docker是一个云开源项目,托管在github,任何人都可以通过 git clone 或者fork参与进来,本身是基于linux的容器技术,采用当时如日中天google新推出的Go语言实现。采用apache 2.0协议开源。

docker镜像地址

Go语言与Docker

相比Go语言与其它语言的对比,国内外很多技术媒体都有列举,在Docker领域,Go语言相比其它语言的优势在于

  • 相对于C/C 开发难度低,支持向前兼容,运维维护成本小
  • 相对于python,生成的是静态文件,有效的避免的低级错误,并且性能高一个等级
  • 并发性好,内存占用低
  • 部署简单,毕竟生成的静态文件,有glibc的地方就能运行
一门语言当然也有自己的缺点,比如,内存回收延迟久,图片处理库有bug,对包版本要求严格等一些问题,但是瑕不掩瑜,一个开发成本极其简单,性能优良,部署简单的语言与Docker简直就是 天作之合至于Go语言的优势,在Go的社区中都有非常详尽的讨论,这里不多讲 Docker的目标 Docker的是一个轻量级的操作系统虚拟化解决方案。 主要目标,用官网的概括来说就是“Build,Ship and Run Any App,Anywhere”:编译,装载任何App,在任何地方都可以运行,我们大概理解就是一个容器,实现了对应用的封装,部署,运行等生命周期管理,只要在glibc的环境下,到处都可以运行。这点在企业的云服务部署是有非常广泛的应用前景。后面我们将详细讨论。 Docker的引擎 Docker的是基于Linux自带的(Linux。 Containers,LXC)技术,在LXC上,Docker进行了近一步封装。正因为如此,Docker只能在Linux环境下运行,当然,前段时间docker终于支持OSX和Windows了,虽然还是体验尝鲜版,但更加方便开发者去开发了! Docker的原理 其实前面讲了这么多,Docker的原理已经不言而喻,这里用IBM的解释就是容器有效的将单个操作系统管理的资源划分到孤立的组中,以便更好的在孤立的组之间平衡有冲突的资源使用需求。与虚拟化相比,这样既不需要指令级模拟,也不需要即时编译。容器可以在核心CPU本地运行指令,而不需要任何专门的解释机制。此外,也避免了准虚拟化(paravirtualization)和系统调用替换中的复杂性。简而言之就是,Docker是一个盒子,一个盒子装一个玩具,无论你丢在哪里,你给他通电(glibc),他就能运行。你的玩具大就用大盒子,小玩具就用小盒子。两个应用之间的环境是环境是完全隔离的,建立通信机制来互相调用。容器的创建和停止都十分快速(秒级),容器自身对资源的需求十分有限,远比虚拟机本身占用的资源少。 Docker VS VM Docker与虚拟机(虚拟机)的区别可以看:
1.jpg
左图是虚拟机的工作原理图,对资源进行抽象,着重体现在硬件层面的虚拟化上,这种方式增加了两场调用链,对性能的损耗比较大,而且还会占用大量的内存资源有图是Docker的工作原理图,属于OS级别的虚拟化,kernel通过创建多个镜像来隔离不同的app进程,由于kernel是是共享,而且本身linux image也不大,性能损耗几乎可以不计,而且内存占用也不大,大大节约了设备成本。 Docker架构总览
2.png
最核心的是 Docker Daemon我们称之为Docker守护进程,也就是Server端,Server端可以部署在远程,也可以部署在本地,因为Server端与客户端(Docker Client)是通过Rest API进行通信。docker CLI 实现容器和镜像的管理,为用户提供统一的操作界面,这个 客户端提供一个只读的镜像,然后通过镜像可以创建一个或者多个容器(container),这些容器可以只是一个RFS(Root File System),也可以是一个包含了用户应用的RFS。容器在docker Client中只是一个进程,两个进程是互不可见的。用户不能与server直接交互,但可以通过与容器这个桥梁来交互,由于是操作系统级别的虚拟技术,中间的损耗几乎可以不计注:CLI:command line interface。命令行接口.RFS:Root File System 根文件系统. Image & Container 在docker中,我们重点关注的就是镜像和容器了。因为在实际应用中,我们封装好镜像,然后通过镜像来创建容器,在容器运行我们的应用就好了。而server端掌控网络和磁盘,我们不用去关心,启动docker sever 和 docker client都是一条命令的事情。后面会详细讲docker的启动过程。Image: 一个只读的镜像模板。可以自己创建一个镜像也可以从网站上下载镜像供自己使用。镜像包含了一个RFS.一个镜像可以创建很多容器。Container:由docker client通过镜像创建的实例,用户在容器中运行应用,一旦创建后就可以看做是一个简单的RFS,每个应用运行在隔离的容器中,享用独自的权限,用户,网络。确保安全与互相干扰两者在创建后,都是一堆layer的统一视角,唯一的却别是镜像最上面那一层是只读的,不可以修改,但是容器最上面一层是rw的,提供给用户操作repository:仓库,这个东西没有单独介绍不是因为它不重要,而是因为之前做个比较多的Android源码编译,所以这里就没有仔细往下看,大概就是一个镜像库,最大的是docker hub,类似于google 的aosp,当然也可以本地搭,比如mig事业群就有自己的repo。 Docker的应用 最后,这里讲一下docker的应用作为本文的终结。A:为什么会想起来学习docker技术2016年年中的时候,我转做后台,经历了一段时间的时候痛苦转型后,中学摸到了门槛,年底赶上事业群的服务器docker化,那段时间非常痛苦,因为相对实体机或者虚拟机,各种问题频出,因为虚拟机或者实体机是不会迁移的,我们部署一套服务后会有一些依赖库需要安装,但是那段时间docker经常迁移,之前也没有接触过docker,导致问题频出。到2017年4月的时候,docker基本稳定下来,我们也开始享受docker带来的种种便利,比如:
  • 发布服务再也不用care服务器的运行环境了,所有的服务器都是自动分配docker,自动部署,自动安装,自动运行
  • 再也不用担心其他服务引起的磁盘问题,cpu问题,系统问题了。之前我们固定在一台idc上发布我们所有的服务,导致后面这台idc上挂了200多个服务,日志文件经常导致磁盘爆满,一旦磁盘爆满,200多个服务就要挂掉一半服务。
  • 更好的资源利用,因为今年还没有数据出来,但个人预计是会给公司节省一半的服务器资源,既避免了资源浪费的同时,又保证了服务的稳定运行
  • 自动迁移,学历了docker后,我们可以自己制作镜像,后面服务迁移的时候,只要使用我们自己的镜像,无论怎么迁移都不会出现任何问题
  • 对于运维来说,管理更加方便了。
目前MIG事业群已经全面接入了docker,也证明了docker容器技术的成功性。PS:
  • edhat 已经集中支持 Docker
  • Google 在PaaS 产品中广泛应用。

后记

看完docker整体架构后,内心真心感谢那些为了推动事业群docker化力排众议者,后面有时间自己也会去封装一些docker image,毕竟很多项目需要使用特有的环境,以往每次迁移都要去安装一次软件。

这篇文章是8月中开始写,写写断断,持续了一个半月,这一个半月项目组一直很忙,连续加了好几个周末。知道国庆才有时间好好整理一下所学。有时间再去写docker的启动流程。

纯手打的文章,写辣么多,路过就点个赞吧


相关阅读

码农小马与 Docker 不得不说的故事
Docker 入门实践
5 种 Docker 日志最佳实践

此文已由作者授权腾讯云技术社区发布,转载请注明文章出处
原文链接:https://cloud.tencent.com/community/article/739560

Mesos+ZooKeeper+Marathon+Docker分布式部署打造PaaS云平台实践(一)

赵英俊 发表了文章 • 5 个评论 • 20419 次浏览 • 2015-07-21 16:39 • 来自相关话题

【编者的话】本文先给出一个分布式部署的过程,在完成这种分布式部署的过程花费了我一个周末的时间,因为国内几乎没有找到分布式部署的实践过程记录,希望我的实践过程能够给有兴趣的小伙伴在进行分布式部署中提供一定的帮助。 最近开始对Mesos非 ...查看全部
【编者的话】本文先给出一个分布式部署的过程,在完成这种分布式部署的过程花费了我一个周末的时间,因为国内几乎没有找到分布式部署的实践过程记录,希望我的实践过程能够给有兴趣的小伙伴在进行分布式部署中提供一定的帮助。

最近开始对Mesos非常的感兴趣,Mesos和Docker一样是一个使用Go语言编写的新兴且具有活力的项目,因为最近痴迷学习Go语言,所以对使用Go语言编写的项目都比较看好。本文先给出一个分布式部署的过程,在完成这种分布式部署的过程花费了我一个周末的时间,因为国内几乎没有找到分布式部署的实践过程记录,希望我的实践过程能够给有兴趣的小伙伴在进行分布式部署中提供一定的帮助。之后我会在本系列博文的后半部分综合阐述一下当前Docker的一些应用场景,以及未来基于Mesos+ZooKeeper+Marathon+Docker分布式部署打造PaaS云平台的可行性。
#本文中用到的项目组件介绍(全部摘自网络)
Mesos:Mesos采用与Linux Kernel相同的机制,只是运行在不同的抽象层次上。Mesos Kernel利用资源管理和调度的API在整个数据中心或云环境中运行和提供引用(例如,Hadoop、Spark、Kafaka、Elastic Search)。

ZooKeeper:ZooKeeper是一个分布式的,开放源码的分布式应用程序协调服务,是Google的Chubby一个开源的实现,是Hadoop和HBase的重要组件。它是一个为分布式应用提供一致性服务的软件,提供的功能包括:配置维护、名字服务、分布式同步、组服务等。

Marathon:Marathon是一个Mesos框架,能够支持运行长服务,比如Web应用等。它是集群的分布式Init.d,能够原样运行任何Linux二进制发布版本,如Tomcat、Play等等。它也是一种私有的PaSS,实现服务的发现,为部署提供提供REST API服务,有授权和SSL、配置约束,通过HAProxy实现服务发现和负载平衡。

Docker:Docker 是一个开源的应用容器引擎,让开发者可以打包他们的应用以及依赖包到一个可移植的容器中,然后发布到任何流行的 Linux 机器上,也可以实现虚拟化。
#Mesos+ZooKeeper+Marathon+Docker分布式部署过程记录
Mesos为了管理的简单也采用了master-slave的架构,因此我本次使用了6个Ubuntu 14.04的虚拟机作为部署节点。其中3个mastser节点,3个slave节点。为直观的理解,我简单的画了一张架构图:
3AD0EBE4-D449-4E1F-B5FA-AB8CDC8F265B.png

如图所示其中master节点都需要运行ZooKeeper、Mesos-master、Marathon,在slave节点上只需要运行master-slave就可以了,但是需要修改ZooKeeper的内容来保证slave能够被master发现和管理。为了节约时间和搞错掉,我在公司内部云平台上开一个虚拟机把所有的软件都安装上去,做成快照进行批量的创建,这样只需要在slave节点上关闭ZooKeeper、Mesos-master服务器就可以了,在文中我是通过制定系统启动规则来实现的。希望我交代清楚了,现在开始部署。
##一、准备部署环境

* 在Ubuntu 14.04的虚拟机上安装所有用到软件,虚拟机可以上互联网。

* 安装Python依赖
apt-get install curl python-setuptools python-pip python-dev python-protobuf


* 安装配置ZooKeeper
apt-get install ZooKeeperd
echo 1 | sudo dd of=/var/lib/ZooKeeper/myid


* 安装Docker
echo deb http://get.Docker.io/ubuntu Docker main | sudo tee /etc/apt/sources.list.d/Docker.list
sudo apt-key adv --keyserver keyserver.ubuntu.com --recv-keys 36A1D7869245C8950F966E92D8576A8BA88D21E9
apt-get update && apt-get install lxc-Docker


* 安装配置Mesos-master和Mesos-slave
 curl -fL http://downloads.Mesosphere.io/master/ubuntu/14.04/Mesos_0.19.0~ubuntu14.04%2B1_amd64.deb -o /tmp/Mesos.deb 
dpkg -i /tmp/Mesos.deb
mkdir -p /etc/Mesos-master
echo in_memory | sudo dd of=/etc/Mesos-master/registry


* 安装配置Mesos的Python框架
 curl -fL http://downloads.Mesosphere.io/master/ubuntu/14.04/Mesos-0.19.0_rc2-py2.7-linux-x86_64.egg -o /tmp/Mesos.egg
easy_install /tmp/Mesos.egg


* 下载Marathon
curl -O http://downloads.Mesosphere.io/marathon/marathon-0.6.0/marathon-0.6.0.tgz  
tar xvzf marathon-0.6.0.tgz


* 下载安装Mesos管理Docker的代理组件Deimos
 pip install deimos


* 配置Mesos使用Deimos
mkdir -p /etc/mesos-slave
echo /usr/local/bin/deimos | sudo dd of=/etc/Mesos-slave/containerizer_path
echo external | sudo dd of=/etc/Mesos-slave/isolation


好,至此在一个虚拟机上就完成了所有组件的安装部署,下面就是对虚拟机打快照,然后快速的复制出6个一样的虚拟机,按照上图的ip配置进行配置之后就可以进入下个阶段,当然为了保险你可以测试一下上处组件是否安装成功和配置正确。如果你没有使用云平台,或者不具备快照功能,那就只能在6个虚拟机上重复6遍上处过程了。
##二、在所有的节点上配置ZooKeeper
在配置maser节点和slave节点之前,需要先在所有的6个节点上配置一下ZooKeeper,配置步骤如下:

  • 修改zk的内容
sudo vi /etc/Mesos/zk} 
  • 将zk的内容修改为如下:
zk://10.162..2.91:2181,10.162.2.92:2181,10.162.2.93:2181/Mesos
##三、 配置所有的master节点在所有的master节点上都要进行如下操作:
  • 修改ZooKeeper的myid的内容
sudo vi /etc/ZooKeeper/conf/myid
将三个master节点的myid按照顺序修改为1,2,3。
  • 修改ZooKeeper的zoo.cfg
sudo vi/etc/ZooKeeper/conf/zoo.cfg
server.1=10.162.2.91:2888:3888server.2=10.162.2.92:2888:3888server.3=10.162.2.93:2888:3888
  • 修改Mesos的quorum
sudo vi /etc/Mesos-master/quorum
将值修改为2。
  • 配置master节点的Mesos 识别ip和和hostname(以在master1上的配置为例)
echo 10.162.2.91 | sudo tee /etc/Mesos-master/ip 
sudo cp /etc/Mesos-master/ip /etc/Mesos-master/hostname
  • 配置master节点服务启动规则
sudo stop Mesos-slaveecho manual | sudo tee /etc/init/Mesos-slave.override
四、配置所有的slave节点
  • 配置slave节点的服务启动规则
sudo stop ZooKeeperecho manual | sudo tee /etc/init/ZooKeeper.overrideecho manual | sudo tee /etc/init/Mesos-master.overridesudo stop Mesos-master
  • 配置slave节点的识别ip和hostname(以slave1节点为例)
echo 192.168.2.94 | sudo tee /etc/Mesos-slave/ipsudo cp /etc/Mesos-slave/ip /etc/Mesos-slave/hostname
五、在所有节点上启动服务
  • 在master节点上启动服务(以在master1节点上为例)
initctl reload-configuration service Docker start service Zookeeper start service Mesos-master start service Mesos-slave start cd marathon-0.6.0 ./bin/start --master zk://10.162.2.91:2181,10.162.2.92:2181,10.162.2.93:2181/Mesos --zk_hosts 10.162.2.91:2181
  • 在所有slave节点上启动服务
sudo start mesos-slave

好,至此,如果配置没有出现错误的话就会成功了,由于有的网络情况和设备情况不一样,所以选举的过程有的快有的慢,当发现slave节点有些正常有些不正常时,可以通过reboot来促使自己被master发现。

六、Marathon简单的使用
要使用Marathon进行应用的部署,需要先在slave节点上有Docker镜像,这个可以自己写Dockerfile来做,也可以直接从公共仓库里pull下来一个。Marathon的应用部署和k8s一样,使用通过JSON文件来进行的,以下就是一个jason文件的范例:
curl -X POST -H "Accept: application/json" -H "Content-Type: application/json" \
10.162.2.91:8080/v2/apps -d '{
"container": {"image": "docker:///ubuntu", "options": ["--privileged"]},
"cpus": 0.5,
"cmd": "ok",
"id": "cci-docker",
"instances": 2,
"mem": 30
}'


七、成功部署的结果是这样的
1393540D-028A-49CA-B4BE-D8C515C88A10.png

3677CB31-F1DF-430D-BC6A-82A576C0DFE8.png

176B3875-B5F6-42BC-9EF4-A49EBE835184.png

07CDB99D-51D6-4D5C-8B15-8151EE32F155.png

4D7C8BAF-FC99-4BCD-88E5-6CD5EEFEF85E.png

05BD7E57-90A1-4891-8AC3-4D179B6DF248.png

B8434C3A-DDCE-4562-90A2-31BF8D31CB99.png

关于对Docker应用场景的总结以及这种基于Docker的PaaS平台未来的发展前景,我会在后面的博文中进行讨论分析。

赵英俊 2015年7月21日 于 杭州 城云科技

CoreOS Fest 系列之第二篇: Systemd、Go、Calico、Sysdig

bnuhero 发表了文章 • 0 个评论 • 5486 次浏览 • 2015-06-19 17:43 • 来自相关话题

【编者的话】在 CoreOS Fest 第二天的会议中,演讲者展示了多个开源项目和工具,包括 Systemd 和 CoreOS 、 Go 语言和容器、 Calico 项目、 Sysdig 等。 在 CoreOS Fest 的第一天会议 ...查看全部
【编者的话】在 CoreOS Fest 第二天的会议中,演讲者展示了多个开源项目和工具,包括 Systemd 和 CoreOS 、 Go 语言和容器、 Calico 项目、 Sysdig 等。

CoreOS Fest第一天会议中,陆续介绍了 CoreOS 的架构、规划和规范。第二天的会议,演讲者展示了多个开源项目和工具,包括 systemd-nspawn 、 Calico 项目和 Sysdig 等。上述项目大多数已经开发了一年有余,但是大部分 CoreOS Fest 的与会者是第一次了解这些项目。

不仅是会议的内容吸引人,更引人注意的是,在过去的一年多时间内,社区开发了大量支持 Linux 容器的新工具。在 Linux 领域,如此快的发展速度,前所未见。在以容器为核心的新型基础设中, systemd 是一个基础组件,它是 Linux 的新型初始化系统,支持容器的开箱即用。

Systemd 和 CoreOS

Red Hat 的 Lennart Poettering 就 Systemd 和 CoreOS 做了演讲,他介绍了集成容器管理功能的 systemd 工具。与其它的初始化系统相比,用 systemd 构建容器更有优势。 systemd 提供了在单机上管理不同容器所需的所有工具。

他首先指出,这次并不是代表 Red Hat 官方的报告。然后,他花了很多时间介绍 Red Hat 的 systemd 团队。这个团队并不是一个产品团队,他说:「我们自认为是一个研究部门,而不是一个产品团队」。

这种表态解释了 systemd 选择某些技术的原因,例如,选择 Btrfs 作为 systemd-nspawn 模板和容器的主要文件系统。「人们都说 btrfs 是一个不稳定的文件系统。但是, btrfs 项目团队正在努力解决该文件系统存在的基础性问题」,他说。而且,在一个不稳定的文件系统上运行容器,这是可以接受的,因为数据被保存在外部的卷上,而不是在容器内(译者注:即没有保存在这个不稳定的文件系统上)。

systemd 包含多个支持容器的守护进程,即 systemd-machined, systemd-networkdsystemd-resolved 。总的来说,所有的 systemd 进程都兼容容器,因为「更多时候,我们是在容器中而不是裸机上测试 systemd 」。这么做的好处是,不用重启开发用的笔记本电脑,就可以测试 systemd 进程。Poettering 认为集成容器是 Linux 的一项重要特性,他说:「容器应该默认集成到 Linux 操作系统中,就像 Zones 已经成为 Solaris 操作系统的一部分」。

systemd 团队的目标是保持 systemd 中立,不仅支持 rkt 容器,还支持 docker, libvirt-lxc, OpenVZ 等。 systemd 提供了很多与容器相关的功能,它是一个偏底层的工具,不提供复杂的用户界面。像 CoreOS 和 kubernetes 等项目可以调用 systemd 的功能,完成对容器的基本操作。

在 systemd 中,管理容器的主要工具是 systemd-machined 及其命令行工具 machinectl 。有了它,用户能够列出所有的容器,启动和关停容器,甚至交互登录到容器中。 systemd-machined 实际上是容器的注册中心,任何容器都可以注册。 systemd-machined 配合 systemd ,执行 `systemd-run -M` ,就能在容器中执行任何命令。 systemd-machined 运行的容器,会出现在 `ps` 命令的列表或者 GNOME 的系统监控器中。

systemd-nspawn 是一个轻量的容器执行器,它是一个像 docker 那样能够启动和关停容器的工具。 为 systemd-nspawn 指定任何包含主引导记录( MBR )或者 GUID 分区表的文件系统或者块设备,它就能启动一个容器。如果用户只需要具备基本功能、免配置的容器, systemd-nspawn 是一个很有吸引力的选择。 rkt 底层也是调用 systemd-nspawn 运行容器。

systemd-networkd 是管理网络的 systemd 守护进程,而 systemd-resolved 是解析主机名字的 systemd 守护进程,它们都支持容器。 systemd-networkd 自动启动容器的网络,利用 DHCP 为容器分配内部的网络地址。 systemd-resolved 使用本地链路多播名字解析( link-local multicast name resolution, LLMNR )系统,为容器提供主机名。 LLMNR 是 Microsoft 发明的自动名字发现系统,初衷是用在客户端应用和移动设备中,但是它也可以用于网络环境中容器之间的彼此发现。

根据 Poettering 的演讲,看起来 systemd 是 Docker 公司的 libcontainer 以及其它容器初始化和管理工具的强力替代。由于大部分 Linux 发行版的最新版都会集成 systemd ,大多数用户将能够立即使用 systemd 。这也能解释为什么容器领域的大多数公司都选择编排作为主攻点,因为 systemd 本身不提供容器编排功能。

Go 语言和容器

CoreOS 公司 CEO Alex Polvi 做主题演讲时宣布 CoreOS 公司将赞助第二届 Gophercon —— Go 语言开发者大会。 有 6 家 Gophercon 赞助商都是容器公司,约占赞助商总数的 1/4 。这是有原因的,无论 CoreOS 公司还是 Docker 公司,它们使用的主要编程语言都是 go 。 Polvi 如此说道:「只有使用 go 语言才能开发出 Etcd 」。

我听的每一个演讲,我到的每一个会议室,人们都在谈论 go 语言,用它写代码。 Etcd, fleet, Swarm, Kubernetes, Kurma 等容器工具应用或者服务都是用 Go 语言编写的。 Linux 容器平台的崛起,同样是 Go 语言的崛起。

Go 语言始于 2007 年 Google 公司的一个内部项目,共有 3 个开发者。现在, go 语言项目的贡献者已经超过 500 人。 Go 是一个使用 BSD 许可的开源项目,但是仍然由 Google 公司把持,所有的贡献者都需要与 Google 公司签署贡献者许可协议( Contributor License Agreement, CLA )。 Go 逐渐成为实现可扩展基础设施的「自动化语言」。之前, 人们已经普遍使用 go 语言实现网络代理、云服务器管理工具、分布式搜索引擎和冗余数据存储。很自然地,人们继续选择 go 实现容器工具应用。

由于 CoreOS 与 go 的紧密联系, Brad Fitzpatrick 的报告主题是 go 语言的持续构建基础设施。他是 LiveJournal, memcached 和 OpenID 的开发者,现在是 Google 公司 go 语言团队的一员。他展示了在很多平台上测试 go 语言的自动构建平台。构建平台刚开始时是一个 Google 应用引擎( Google Application Engine )应用,加上桌子上一排的移动设备,然后发展成今天这个样子。他还讲述了持续构建基础设施的历史和工作机制。

Go 是一种编译型而不是解释型编程语言。编译得到的二进制程序,能够在多种平台上执行。每个版本的 go 语言在发布之前,首先要在 Google 公司机器实验室拥有的几百种平台上成功地构建。只有在各种 Linux 平台构建 go 语言时才会用到容器,其它很多平台并不支持容器,像 MAC OS X 和 Android 平台就必须使用专用的硬件,因此容器在自动构建平台中的作用比较小。你可以在这里 看到 go 语言在各个平台的构建结果,哪些成功了,哪些失败了。

Calico 项目

其实 Calico 项目 已经开源差不多一年了。当核心开发者 Spike Curtis 报告时,大多数与会者是第一次听说这个项目。 Calico 是一个多主机路由软件,还包含一个分布式防火墙。 Calico 是专门为容器和虚拟机尤其是 docker 和 OpenStack 环境设计的。 Metaswitch Networks 公司用 python 语言开发 Calico ,它也是唯一提供 Calico 商业化支持的公司。看起来, calico 有望成为这样一种解决方案:用户能够在生产环境部署容器,同时保证严格的安全。

「还记得三层架构吗?」 Curtis 抱怨说,「管理员现在还是这样管理网络。第一层是外部网络,中间是 web 资源所在的隔离区( demilitarized zone, DMZ),最后是数据层,要求保证最严格的安全」。

编排好网络中的容器,运行微服务,这种模式打破了三层模型。首先,微服务是根据服务的提供者而不是服务的安全特征划分的;其次,编排框架假设数据中心网络是无区分的,也不支持安全分层的概念;最重要的是,你得为几百个微服务定义安全策略和区域,这不像以前,网络管理员只要设定几十个策略和区域就行了。 Curtis 说,「这就像是一个动物园,所有围墙都被推到了」。

然而,就安全而言,微服务也为带来了机会。每个微服务只做一件事情,它的安全需求也相对简单。因此,可以更细致地划分服务,又不增加整体的复杂性, calico 就是这么做的。

Calico 为每个容器或者虚拟机分配一个独立的 IP 地址,然后在每台物理主机上定义包含这些 IP 地址的 iptables 规则,实现了防火墙功能。 Calico 用保存在 etcd 的标签定义每个服务,用一个 JSON 格式的配置文件定义允许访问当前服务的其它服务,以及这个服务是否可通过 Internet 访问。

只要编排框架支持为每个服务分配一个 IP 地址,就可以集成使用 calico 。Curtis 演示了 calico 与 kubernetes 的集成,包括用扩展的 pod 定义来设定每个容器的安全设置。 Apache Mesos 正在实现为每个服务分配一个 IP 地址的功能,暂时还不能集成 calico 。

Sysdig

最后一个「新」项目是 Sysdig ,就像 calico 项目一样, 它也是一年多以前就发布了,但是大部分与会者都是第一次听说这个项目。站在 sysdig 背后的公司是 Sysdig Cloud ,它为 sysdig 的用户提供商业化支持。 Sysdig Cloud 公司 CEO Loris Degioanni 展示了这款工具。

Sysdig 是一个网络流监控系统,部分功能实现为一个 Linux 内核模块。这个模块捕捉了所有的网络流,特别是容器之间的网络包。用户还可以用 Lua 语言编写网络流信息的过滤器,然后把过滤得到的信息聚合在一起,进行统计分析。你可以把它视为 wiresharktcpdump 的高级版本,尤其是支持容器。

Degioanni 说,sysdig 比 Google 的 cAdvisor 项目高明,因为 cAdvisor 只报告容器占用的全部 CPU 、内存和网络,而 sysdig 还能够提供网络流的发送方、接收方和内容。例如,用户可以找出对某个数据库的查询,搞清楚为什么两个 IP 地址之间的网络通信延误得厉害。

Degioanni 还展示了即将发布的 sysdig 的文本用户界面,这是一个基于 curses) 的开源项目。有了它,系统管理员能够用 ssh 登录到系统,执行交互的监控任务。他演示了如何深入检查和汇总容器之间的网络通信,还演示了如何调查网络延迟。在 Sysdig Cloud 公司的展台,工作人员演示了商业化产品—— sysdig 的图形界面,用户只需点击多层嵌套的服务器、 pod 和容器,就可以查看对应的网络流。

小结

我从 CoreOS Fest 大会了解了这么多的新项目、新工具、标准草案和新架构,这充分说明 Linux 容器领域发展之快。要知道,在一年前,当我报道首届 DockerCon 时,这些新技术和新工具,大部分才刚刚发布,有的甚至还不存在。等再过一年,我们看看是不是还发展得这么快。

我们还没有涉及一个主要的话题:如何在容器中持久保存数据。前文曾经提及,目前普遍的看法是容器应该是无状态的,不保存数据。不坚持这一点,容器管理和编排会遇到一系列亟需解决的问题,例如,外部卷的管理、容器迁移和有状态服务的负载均衡等。下个星期,我们将讨论与持久话数据和容器相关的多个话题,内容来自于 CoreOS Fest 和 Container Camp

原文链接: New projects from day two of CoreOS Fest (翻译:柳泉波)

容器化 Go 开发环境的尝试

齐达内 发表了文章 • 0 个评论 • 928 次浏览 • 2019-04-08 13:08 • 来自相关话题

【编者的话】本文是我在思考“如何组建团队”时候的一个小尝试,旨在通过容器技术(Docker)降低探索 Golang 技术开发的门槛。目前的效果还不是很明显,不过作为一种新思路,非常值得大家了解。 #容器化 Go 开发环境 ##容器化的价 ...查看全部
【编者的话】本文是我在思考“如何组建团队”时候的一个小尝试,旨在通过容器技术(Docker)降低探索 Golang 技术开发的门槛。目前的效果还不是很明显,不过作为一种新思路,非常值得大家了解。
#容器化 Go 开发环境
##容器化的价值
搭建开发环境往往是一个啰嗦繁杂的过程。对职业开发者如此,对知识学习者和探索者亦如此。

职业编码工作中,代码编辑测试完成后部署到生产环境,需要按照自己本地的开发环境重新配置生产环境的机器。由于本地开发环境的搭建比较随性,往往,本地能够跑起来的代码部署到生产环境后跑不起来,或并未达到预期的运行效果。

对于一个刚刚开始学习 《C 语言程序设计》课程的大学生来说,编译出自己的 “Hello World” 往往意味着很多事先的准备工作(至少先把课堂上老师三言两语带过的开发环境搭建起来)。

之前因为项目的需要我魔改过日志收集工具 fluent/fluent-bit,这是一个主要由 C 语言进行开发的项目,而我对 C 语言的认识还停留在大学课堂的水平,更何况我本地没有搭建过开发 C 的环境。

容器化技术能很好地解决上面的问题。职业开发者使用 Docker(容器化技术的一种)把环境搭建的过程封装到容器里,并以镜像的形式复制到生产环境得以“复现”相同的环境。作为知识学习者,完全可以利用相似的技术“复现”老师课堂上使用的环境。而作为知识探索者,在修改了 fluent-bit 的源码后,我利用其源码中提供的 Dockerfile 很方便地实现了定制化源码的编译,快速验证了思路可行性及定制化功能的可用性。

如果读者未使用过 Docker,可以参考《如何用一个例子上手 Docker》这篇博客的内容及其参考中列出的地址了解并尝试一下,应该会被甜到。
##容器化的 Go 开发环境
为了说明问题并方便读者能容易地在自己机器上验证,我在《Go 反序列化 JSON 字符串的两种常见用法》和 《浅谈 Go 标准库对 JSON 的处理效率》两篇博客里刻意贴了完整而冗长的源码内容。虽说 package 和 import 语句对博客的内容并没有任何作用,但是如果因为多这样几句内容就能让代码成为完整可运行的源码,从而节省读者自己构造完整源代码的时间,我认为是值得且必要的。

可以把思考更进一步,如果读者朋友没有 Go 开发环境(或者与作者本地的开发环境不一致),如何才能以一种低成本的方式开始这一切呢?不知不觉就想到了 Docker 技术。

定制化 Go 开发环境镜像

想要低成本获取 Go 开发环境,思路很简单,把 Go 开发环境打包到容器里(其实官方已经存在这种镜像),大家只需要拉取相应的镜像然后运行就可以了。如下面的源码所示,为了方便编辑并调试 Go 源码,我在 Go 官方镜像的基础上安装并简单配置了 vim 和 delve,并把镜像推送到了 Docker Hub 仓库中。更详尽的内容可以参考 GitHub - chalvern/smile
# cat https://github.com/chalvern/smile/blob/master/docker/Dockerfile
FROM golang:1.12

[size=16] vim[/size]
RUN apt-get update \
&& apt-get install -y vim \
&& rm -rf /var/lib/apt/lists/*

# vim setting
COPY vimrc /root/.vimrc

RUN go get -u github.com/derekparker/delve/cmd/dlv

WORKDIR $GOPATH

运行 Go 开发环境镜像

  1. 拉取镜像:docker pull chalvern/golang:1.12
  2. 以 privileged 方式运行镜像:docker run -it --privileged chalvern/golang:1.12 bash
  3. 此时便有了一个 Go 开发环境。

##环境(上下文)一致的必要性
我在学生时代发现一个很有趣的现象,国外的教材往往页码很足整本书很厚,而中文的教材页码比较少相对要薄一些。排除一部分语言表达力的因素,主要是因为国外的教材喜欢包含比较多知识之外的细节。

以《C 语言程序设计》类似的书籍来说,是直接从 Hello World 讲起好呢?还是从详细的环境搭建步骤讲起好呢?我记得当年在学习 C 语言编程的时候,为了搭建开发环境到图书馆找了很多资料,最终也未“复现”教科书上一模一样的开发环境,导致在学习过程中产生非常多的疑惑。有的同学在疑惑面前退缩了,渐渐失去了编码的兴趣,最终的成绩自然也不如人意。

国外教材比较厚重的另一个原因,是国外教材中喜欢包含比较详细的参考文献。那么,书籍或者博客中,是否应该把参考文献放进正文呢?我认为是必要的。把参考文献列出来,一方面可以表达对相关论点提出者的尊重,另一方面则方便让读者能够进一步了解论点的渊源或者进一步考证“真相”。书里或博客里所论述的是“集百家之长的一家之言”呢?还是纯碎个人思考得出来的“一家之言”呢?不同的分类,其说服力以及可采纳率其实是不一样的;如果混淆在一起使人不可分辨,容易让人忽视共识的力量,
#小结
本文尝试通过容器技术(Docker)降低探索 Golang 技术开发的门槛。相比于把开发环境直接安装到自己的电脑上“尝鲜”,容器化技术能够很好地避免 Go 开发环境及其依赖项(比如 $GOPATH、$GOROOT 等变量)对电脑的污染,同时容器化技术能够很好地“复现”一致可用的开发环境,避免引入其他变量,从而降低技术探索的难度。
#参考

* 以认真的态度做完美的事情(2018年总结) - 敬维 之前写的 2018 年的总结
* Docker基本原理简析 - 敬维 简单介绍了 Docker 涉及到的三种技术:Namespace、CGroup与AUFS
* 如何用一个例子上手Docker - 敬维 用一个例子来上手使用 docker。
* GitHub - fluent/fluent-bit 轻量级日志收集应用
* Go 反序列化 JSON 字符串的两种常见用法 - 敬维 两种反序列化 JSON 字符串的方法,包含了复制黏贴即可运行的源码
* 浅谈 Go 标准库对 JSON 的处理效率 - 敬维 探究 Go 标准库对 JSON 的处理效率,包含了复制黏贴即可运行的源码

原文链接:容器化 Go 开发环境的尝试

如何为你的Go应用创建轻量级Docker镜像?

ScofieldDM 发表了文章 • 0 个评论 • 4572 次浏览 • 2018-09-03 21:46 • 来自相关话题

恭喜!你已经创建了一个出色的go应用程序,现在你想创建一个Docker容器来分发你的应用。 但是,如何尽可能为你的Golang应用程序打造一个最小的镜像呢?提示:我们将会使用多阶段构建(自从Docker 17.05版本提供的方法)来完 ...查看全部
恭喜!你已经创建了一个出色的go应用程序,现在你想创建一个Docker容器来分发你的应用。

但是,如何尽可能为你的Golang应用程序打造一个最小的镜像呢?提示:我们将会使用多阶段构建(自从Docker 17.05版本提供的方法)来完成这个目标。
# 介绍
## 多什么?
简单来讲,多阶段。

多阶段允许在创建Dockerfile时使用多个from,它非常有用,因为它使我们能够使用所有必需的工具构建应用程序。举个例子,首先我们使用Golang的基础镜像,然后在第二阶段的时候使用构建好的镜像的二进制文件,最后阶段构建出来的镜像用于发布到我们自己的仓库或者是用于上线发布。

在上述的案例中,我们总共有三个阶段:

  • build编译阶段
  • certs(可选,可有可无)证书认证阶段
  • prod生产阶段
在build阶段主要是编译我们的应用程序,证书认证阶段将会安装我们所需要的CA证书,最后的生产发布阶段会将我们构建好的镜像推到镜像仓库中。而且发布阶段将会使用build阶段编译完毕的二进制文件和certs阶段安装的证书。
1.png
项目发布的多个build阶段# 示例工程对于这个方法,我们将使用一个非常简单的项目。它只是一个运行在8080端口的HTTP服务,并且返回结果为传递过去的URL的内容结果。## 举例GET http://localhost:8080?url=https://google.com 返回结果为goole页面内容展示。你也可以在这里找到代码仓库。在master分支上只包含了应用程序,final分支上还包含本篇教程中使用的Dockerfile文件如果你想跟着本教程来做,只需要拉下master上的代码并且跟着我来创建Dockerfile。# 步骤1 - 编译阶段第一阶段主要是使用Golang基础镜像来将我们的应用程序打包为二进制文件。这个基础镜像包含了将我们的应用程序编译成可执行二进制文件的所有工具。下面是我们最原始的Dockerfile:
1 #2 # BUILD 阶段3 # 4 FROM golang:1.10 AS build56 # 设置我们应用程序的工作目录7 WORKDIR /go/src/github.com/scboffspring/blog-multistage-go89 # 添加所有需要编译的应用代码10 ADD . .1112 # 编译一个静态的go应用(在二进制构建中包含C语言依赖库)13 RUN CGO_ENABLED=0 GOOS=linux go build -a -installsuffix cgo .1415 # 设置我们应用程序的启动命令16 CMD ["./blog-multistage-go"]
  • 第4行:使用的基础镜像(golang:1.10)并且我们使用as给当前阶段一个别名,也可以使用阶段索引来引用前一阶段,但这使得它更清晰。
  • 第7行:我们将工作目录设置为Golang基础镜像的默认$GOPATH中的应用程序目录。
  • 第10行:添加我们的应用程序源文件。
  • 第13行:编译二进制文件。使用不同的参数来创建一个完整的静态库,因为在生产环境拉取镜像时可能不一定需要所有的Golang VM以及C语言库。
  • 第16行:使用设定的命令来启动应用程序。
现在我们进行编译并使用Docker容器,我们的应用程序如我们预期正常运行:
docker build -t scboffspring/blog-multistage-go .docker run --rm -ti -p 8080:8080 \scboffspring/blog-multistage-go
我们可以使用curl命令来请求,并且它会返回http://google.com页面内容。在终端运行`curl localhost:8080`。
1 2  3  5  Google7 ....
让我们使用`docker images`,来看看镜像的大小:
REPOSITORY                                       ... SIZEscboffspring/blog-multistage-go                  ... 818MB
荒唐,太荒唐了,一个这么小的应用居然占了磁盘818M内存空间。推送到镜像仓库后,镜像大小被压缩到309M。
2.png
docker hub 占用309M接下来我们来改善这种情况,把镜像的大小降低到10M!# 步骤2 - 生产阶段上面提供的镜像是完全可以进行部署使用的,但是它真的是太大了。每次在Kubernetes上启动你的容器时需要拉取309M的镜像?真的是太浪费时间和带宽。让我们来为我们的镜像构建一个生产阶段,正如上面解释的,这个阶段只是从build阶段拷贝二进制文件到容器中。我们新的Dockerfile将会如下所示:
1 #2 # BUILD 阶段3 # 4 FROM golang:1.10 AS build56 # 设置我们应用程序的工作目录7 WORKDIR /go/src/github.com/scboffspring/blog-multistage-go89 # 添加所有需要编译的应用代码10 ADD . .1112 # 编译一个静态的go应用(在二进制构建中包含C语言依赖库)13 RUN CGO_ENABLED=0 GOOS=linux go build -a -installsuffix cgo .1415 # 设置我们应用程序的启动命令16 CMD ["./blog-multistage-go"]17181920 #21 # 生产阶段22 # 23 FROM scratch AS prod24 25 # 从buil阶段拷贝二进制文件26 COPY --from=build /go/src/github.com/scboffspring/blog-multistage-go/blog-multistage-go .27 CMD ["./blog-multistage-go"]
如你所见,同一个Dockerfile文件中我们添加了第二个FROM语句。这次,我们直接拉取二进制文件,不需要添加任何其他依赖。
  • 第23行:拉取基础镜像
  • 第26行:从`/go/src/github.com/scboffspring/blog-multistage-go/blog-multistage-go`拷贝build阶段编译的文件
  • 第27行:使用设定的命令来启动应用程序
简单吧。让我们像之前一样编译并使用Docker容器:
docker build -t scboffspring/blog-multistage-go . docker run --rm -ti -p 8080:8080 \scboffspring/blog-multistage-go
我们可以看到服务正常启动,也就是意味着它正确的启动了!我们完成了!让我们使用`docker images`,来看看镜像的大小:
REPOSITORY                                       ... SIZEscboffspring/blog-multistage-go                  ... 6.65MB
如我们之前所说,镜像的大小变为10MB以下。而且镜像被推送到镜像仓库后,它只有2MB。当你启动容器时,只需下载2MB即可,相比于之前节省了大量的时间和带宽呢。
3.png
使用prod阶段编译的容器仅2MB但是,它在我们的例子中不起作用。 如果运行`curl localhost:8080`,你看到的返回的结果为500。
curl localhost:8080500 - Something bad happened
如果你查看容器的日志,你可以找到如下错误:

发生了一个错误:Get http://google.com:X509:加载系统根目录失败并且没有根目录可以使用。

我们尝试使用https来连接Goole服务器,但是我们没有用于验证Google的SSL证书的CA(证书颁发机构)证书或是其他网站的CA证书。如果你的应用不需要使用SSL的话,可以选择跳到下一节,否则,让我们来改善我们的软件使得其可以进行访问。# 阶段3 - (可选)认证阶段
1 #2 # BUILD 阶段3 # 4 FROM golang:1.10 AS build56 # 设置我们应用程序的工作目录7 WORKDIR /go/src/github.com/scboffspring/blog-multistage-go89 # 添加所有需要编译的应用代码10 ADD . .1112 # 编译一个静态的go应用(在二进制构建中包含C语言依赖库)13 RUN CGO_ENABLED=0 GOOS=linux go build -a -installsuffix cgo .1415 # 设置我们应用程序的启动命令16 CMD ["./blog-multistage-go"]171819 # 20 # CERTS Stage21 #22 FROM alpine:latest as certs23 24 # Install the CA certificates25 RUN apk --update add ca-certificates26 27 #28 # PRODUCTION STAGE29 # 30 FROM scratch AS prod31 32 # 从certs阶段拷贝CA证书33 COPY --from=certs /etc/ssl/certs/ca-certificates.crt /etc/ssl/certs/ca-certificates.crt34 # 从buil阶段拷贝二进制文件35 COPY --from=build /go/src/github.com/scboffspring/blog-multistage-go/blog-multistage-go .36 CMD ["./blog-multistage-go"]
  • 第23行:我们新的certs阶段,使用alpine镜像
  • 第25行:安装最新版的CA证书
  • 第33行:从certs层拷贝证书,并保存为`/etc/ssl/certs/ca-certificates.crt`

让我们再次编译并使用Docker容器:
docker build -t scboffspring/blog-multistage-go . 
docker run --rm -ti -p 8080:8080 \
scboffspring/blog-multistage-go

现在,`curl localhost:8080`将会返回真实的页面!它真的奏效了!

使用`docker images`查看,镜像依然还是非常小的:
REPOSITORY                                       ... SIZE
scboffspring/blog-multistage-go ... 6.89MB

#额外福利:在指定的阶段为镜像添加tag
有时候我们可能会在各个阶段为镜像创建一个tag,在我们的示例中,我们可能也会将build阶段产生的结果发布到Docker,因为它对开发真的十分有用。

要想这样做的话,只需要在build镜像的时候简单的使用`--target=NAMEOFTHESTAGE`。

举个例子:
docker build -t scboffspring/blog-multistage-go:build . --target=build

# 总结
现在你已经能够为你的Golang应用程序创建一个非常轻量级的应用程序。阶段构建的概念对其他许多案例也是非常有用的。

我在NodeJS世界中的一个用法是第一阶段编译TypeScript项目。然后第一个阶段编译以便使得该镜像可以运行测试。此镜像也能够用于开发环境,因为它包含了所有开发环境所需的依赖。

当第一阶段测试通过后,第二阶段只是简单的安装项目中的`package.json`中的依赖(并不是测试环境依赖)。它只将编译和缩小的代码复制到镜像中,然后将该镜像推送并部署到生产中。

原文链接:HOW-TO: Create lightweight docker images for your Go applications using multi-stage builds(翻译:刘明)

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

alex_wang2 发表了文章 • 0 个评论 • 4686 次浏览 • 2017-12-19 22:11 • 来自相关话题

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

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

你可以从这里找到这篇章中使用的例子,跟随我们一步一步的进行。
#第1步 最简单的http服务程序
下面就是这个程序:

`main.go`
package main

import (
"fmt"
"net/http"
)

func main() {
http.HandleFunc("/home", func(w http.ResponseWriter, _ *http.Request) {
fmt.Fprint(w, "Hello! Your request was processed.")
},
)
http.ListenAndServe(":8000", nil)
}

如果是第一次运行,仅仅执行`go run main.go`就可以了。如果你想知道它是怎么工作的,你可以用下面这个命令:`curl -i http://127.0.0.1:8000/home`。但是当我们运行这个应用的时候,我们找不到任何关于状态的信息。
#第2步 增加日志
首先,增加日志功能可以帮助我们了解程序现在处于一个什么样的状态,并记录错误(译者注:如果有错误的话)等其他一些重要信息。在这个例子里我们使用Go语言标准库里最简单的日志模块,但是如果是跑在Kubernetes上的服务程序,你可能还需要一些额外的库,比如glog或者logrus

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

`main.go`
func main() {
log.Print("Starting the service...")

http.HandleFunc("/home", func(w http.ResponseWriter, _ *http.Request) {
fmt.Fprint(w, "Hello! Your request was processed.")
},
)

log.Print("The service is ready to listen and serve.")
log.Fatal(http.ListenAndServe(":8000", nil))
}

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

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

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

`handler/handers.go`
package handlers

import (
"github.com/gorilla/mux"
)

// Router register necessary routes and returns an instance of a router.
func Router() *mux.Router {
r := mux.NewRouter()
r.HandleFunc("/home", home).Methods("GET")
return r
}

`handlers/home.go`
package handlers

import (
"fmt"
"net/http"
)

// home is a simple HTTP handler function which writes a response.
func home(w http.ResponseWriter, _ *http.Request) {
fmt.Fprint(w, "Hello! Your request was processed.")
}

然后我们稍微修改一下`main.go`:
package main

import (
"log"
"net/http"

"github.com/rumyantseva/advent-2017/handlers"
)

// How to try it: go run main.go
func main() {
log.Print("Starting the service...")
router := handlers.Router()
log.Print("The service is ready to listen and serve.")
log.Fatal(http.ListenAndServe(":8000", router))
}

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

`handlers/handles_test.go`
package handlers

import (
"net/http"
"net/http/httptest"
"testing"
)

func TestRouter(t *testing.T) {
r := Router()
ts := httptest.NewServer(r)
defer ts.Close()

res, err := http.Get(ts.URL + "/home")
if err != nil {
t.Fatal(err)
}
if res.StatusCode != http.StatusOK {
t.Errorf("Status code for /home is wrong. Have: %d, want: %d.", res.StatusCode, http.StatusOK)
}

res, err = http.Post(ts.URL+"/home", "text/plain", nil)
if err != nil {
t.Fatal(err)
}
if res.StatusCode != http.StatusMethodNotAllowed {
t.Errorf("Status code for /home is wrong. Have: %d, want: %d.", res.StatusCode, http.StatusMethodNotAllowed)
}

res, err = http.Get(ts.URL + "/not-exists")
if err != nil {
t.Fatal(err)
}
if res.StatusCode != http.StatusNotFound {
t.Errorf("Status code for /home is wrong. Have: %d, want: %d.", res.StatusCode, http.StatusNotFound)
}
}

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

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

`handlers/home_test.go`
package handlers

import (
"io/ioutil"
"net/http"
"net/http/httptest"
"testing"
)

func TestHome(t *testing.T) {
w := httptest.NewRecorder()
home(w, nil)

resp := w.Result()
if have, want := resp.StatusCode, http.StatusOK; have != want {
t.Errorf("Status code is wrong. Have: %d, want: %d.", have, want)
}

greeting, err := ioutil.ReadAll(resp.Body)
resp.Body.Close()
if err != nil {
t.Fatal(err)
}
if have, want := string(greeting), "Hello! Your request was processed."; have != want {
t.Errorf("The greeting is wrong. Have: %s, want: %s.", have, want)
}
}

现在我们运行`go tests`来检查代码的正确性:
$ go test -v ./...
? github.com/rumyantseva/advent-2017 [no test files]
=== RUN TestRouter
--- PASS: TestRouter (0.00s)
=== RUN TestHome
--- PASS: TestHome (0.00s)
PASS
ok github.com/rumyantseva/advent-2017/handlers 0.018s

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

`main.go`
package main

import (
"log"
"net/http"
"os"

"github.com/rumyantseva/advent-2017/handlers"
)

// How to try it: PORT=8000 go run main.go
func main() {
log.Print("Starting the service...")

port := os.Getenv("PORT")
if port == "" {
log.Fatal("Port is not set.")
}

r := handlers.Router()
log.Print("The service is ready to listen and serve.")
log.Fatal(http.ListenAndServe(":"+port, r))
}

在这个例子里,如果没有设置端口,应用程序会退出并返回一个错误。因为如果配置错误了,就没有必要再继续执行了。
#第6步 Makefile
几天以前有一篇文章介绍`make`工具,如果你有一些重复性比较强的工作,那么使用它就大有帮助。现在我们来看一看我们的应用程序如何使用它。当前,我们有两个操作,测试和编译并运行。我们对Makefile文件进行了如下修改。但是我们用`go build`代替了`go run`,并且运行那个编译出来的二进制程序,因为这样修改更适合为我们的生产环境做准备:

`Makefile`
APP?=advent
PORT?=8000

clean:
rm -f ${APP}

build: clean
go build -o ${APP}

run: build
PORT=${PORT} ./${APP}

test:
go test -v -race ./...

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

这里,为了运行应用程序,我们需要删除掉旧的程序(如果它存在的话),编译代码并用环境变量代表的参数运行新编译出的程序,做这些操作,我们仅仅需要执行`make run`。
#第7步 版本控制
下一步,我们将为我们的程序加入版本控制。因为有的时候,它对我们知道正在生产环境中运行和编译的代码非常有帮助。(译者注:就是说,我们在生产环境中运行的代码,有的时候我们自己都不知道对这个代码进行和什么样的提交和修改,有了版本控制,就可以显示出这个版本的变化和历史记录)。

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

`version/version.go`
package version

var (
// BuildTime is a time label of the moment when the binary was built
BuildTime = "unset"
// Commit is a last commit hash at the moment when the binary was built
Commit = "unset"
// Release is a semantic version of current build
Release = "unset"
)

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

`main.go`
...
func main() {
log.Printf(
"Starting the service...\ncommit: %s, build time: %s, release: %s",
version.Commit, version.BuildTime, version.Release,
)
...
}

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

`handlers/home.go`
package handlers

import (
"encoding/json"
"log"
"net/http"

"github.com/rumyantseva/advent-2017/version"
)

// home is a simple HTTP handler function which writes a response.
func home(w http.ResponseWriter, _ *http.Request) {
info := struct {
BuildTime string `json:"buildTime"`
Commit string `json:"commit"`
Release string `json:"release"`
}{
version.BuildTime, version.Commit, version.Release,
}

body, err := json.Marshal(info)
if err != nil {
log.Printf("Could not encode info data: %v", err)
http.Error(w, http.StatusText(http.StatusServiceUnavailable), http.StatusServiceUnavailable)
return
}
w.Header().Set("Content-Type", "application/json")
w.Write(body)
}

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

为`Makefile`增加一些变量:

`Makefile`
RELEASE?=0.0.1
COMMIT?=$(shell git rev-parse --short HEAD)
BUILD_TIME?=$(shell date -u '+%Y-%m-%d_%H:%M:%S')

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

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

`Makefile`
build: clean
go build \
-ldflags "-s -w -X ${PROJECT}/version.Release=${RELEASE} \
-X ${PROJECT}/version.Commit=${COMMIT} -X ${PROJECT}/version.BuildTime=${BUILD_TIME}" \
-o ${APP}

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

`Makefile`
PROJECT?=github.com/rumyantseva/advent-2017


所有的变化都可以在这里找到,现在可以用`make run`去运行它了。
#第8步 减少一些依赖
这里有一些代码里我不喜欢的地方:`handle`pakcage依赖于`version`package。这个很容易修改:我们需要让`home` 处理变得可以配置。

`handler/home.go`
// home returns a simple HTTP handler function which writes a response.
func home(buildTime, commit, release string) http.HandlerFunc {
return func(w http.ResponseWriter, _ *http.Request) {
...
}
}

别忘了同时去修改测试和必须的环境变量。
#第9步 健康检查
在某些情况下,我们需要经常对运行在Kubernetes里的服务程序进行健康检查:liveness and readiness probes。这么做的目的是为了知道容器里的应用程序是否还在运行。如果liveness探测失败,这个服务程序将会被重启,如果readness探测失败,说明服务还没有准备好。

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

`handlers/healthz.go`
// healthz is a liveness probe.
func healthz(w http.ResponseWriter, _ *http.Request) {
w.WriteHeader(http.StatusOK)
}

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

`handlers/readyz.go`
// readyz is a readiness probe.
func readyz(isReady *atomic.Value) http.HandlerFunc {
return func(w http.ResponseWriter, _ *http.Request) {
if isReady == nil || !isReady.Load().(bool) {
http.Error(w, http.StatusText(http.StatusServiceUnavailable), http.StatusServiceUnavailable)
return
}
w.WriteHeader(http.StatusOK)
}
}

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

现在我们看看怎么使用:

`handles.go`
func Router(buildTime, commit, release string) *mux.Router {
isReady := &atomic.Value{}
isReady.Store(false)
go func() {
log.Printf("Readyz probe is negative by default...")
time.Sleep(10 * time.Second)
isReady.Store(true)
log.Printf("Readyz probe is positive.")
}()

r := mux.NewRouter()
r.HandleFunc("/home", home(buildTime, commit, release)).Methods("GET")
r.HandleFunc("/healthz", healthz)
r.HandleFunc("/readyz", readyz(isReady))
return r
}

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

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

`main.go`
func main() {
...
r := handlers.Router(version.BuildTime, version.Commit, version.Release)

interrupt := make(chan os.Signal, 1)
signal.Notify(interrupt, os.Interrupt, os.Kill, syscall.SIGTERM)

srv := &http.Server{
Addr: ":" + port,
Handler: r,
}
go func() {
log.Fatal(srv.ListenAndServe())
}()
log.Print("The service is ready to listen and serve.")

killSignal := <-interrupt
switch killSignal {
case os.Kill:
log.Print("Got SIGKILL...")
case os.Interrupt:
log.Print("Got SIGINT...")
case syscall.SIGTERM:
log.Print("Got SIGTERM...")
}

log.Print("The service is shutting down...")
srv.Shutdown(context.Background())
log.Print("Done")
}

这里,我们会捕获系统信号,如果发现有`SIGKILL`,`SIGINT`或者`SIGTERM`,我们将优雅的关闭程序。
#第11步 Dockerfile
我们的应用程序马上就以运行在Kubernetes里了,现在我们把它容器化。

下面是一个最简单的Dockerfile:

`Dockerfile`
FROM scratch

ENV PORT 8000
EXPOSE $PORT

COPY advent /
CMD ["/advent"]

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

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

`Makefile`
...

GOOS?=linux
GOARCH?=amd64

...

build: clean
CGO_ENABLED=0 GOOS=${GOOS} GOARCH=${GOARCH} go build \
-ldflags "-s -w -X ${PROJECT}/version.Release=${RELEASE} \
-X ${PROJECT}/version.Commit=${COMMIT} -X ${PROJECT}/version.BuildTime=${BUILD_TIME}" \
-o ${APP}

container: build
docker build -t $(APP):$(RELEASE) .

run: container
docker stop $(APP):$(RELEASE) || true && docker rm $(APP):$(RELEASE) || true
docker run --name ${APP} -p ${PORT}:${PORT} --rm \
-e "PORT=${PORT}" \
$(APP):$(RELEASE)

...

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

现在我们终于可以用`make run`去检验一下整个过程了。
#第12步 发布
在我们的项目里,我们还依赖一个外部的包(github.com/gorilla/mux)。而且,我们需要为生产环境里的
readness安装依赖管理。所以我们用了dep之后我们唯一要做的就是运行`dep init`:
$ dep init
Using ^1.6.0 as constraint for direct dep github.com/gorilla/mux
Locking in v1.6.0 (7f08801) for direct dep github.com/gorilla/mux
Locking in v1.1 (1ea2538) for transitive dep github.com/gorilla/context

这个工具会创建两个文件`Gopkg.toml`和`Gopkg.lock`,还有一个目录`vendor`,个人认为,我会把`vendor`放到git上去,特别是对与那些比较重要的项目来说。
#第13步 Kubernetes
这也是最后一步了。运行一个应用程序到Kubernetes上。最简单的方法就是在本地去安装和配置一个minikube(这是一个单点的kubernetes测试环境)。

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

`Makefile:`
CONTAINER_IMAGE?=docker.io/webdeva/${APP}

...

container: build
docker build -t $(CONTAINER_IMAGE):$(RELEASE) .

...

push: container
docker push $(CONTAINER_IMAGE):$(RELEASE)

这个`CONTAINER_IMAGE`变量用来定义一个镜像的名字,我们用这个镜像存放我们的服务程序。如你所见,在这个例子里包含了我的用户名(`webdeva`)。如果你在hub.docker.com上没有账户,那你就先得创建一个,然后用`docker login`命令登陆,这个时候你就可以推送你的镜像了。

现在我们试一下`make push`:
$ make push
...
docker build -t docker.io/webdeva/advent:0.0.1 .
Sending build context to Docker daemon 5.25MB
...
Successfully built d3cc8f4121fe
Successfully tagged webdeva/advent:0.0.1
docker push docker.io/webdeva/advent:0.0.1
The push refers to a repository [docker.io/webdeva/advent]
ee1f0f98199f: Pushed
0.0.1: digest: sha256:fb3a25b19946787e291f32f45931ffd95a933100c7e55ab975e523a02810b04c size: 528

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

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

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

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

`deployment.yaml`
apiVersion: extensions/v1beta1
kind: Deployment
metadata:
name: {{ .ServiceName }}
labels:
app: {{ .ServiceName }}
spec:
replicas: 3
strategy:
type: RollingUpdate
rollingUpdate:
maxUnavailable: 50%
maxSurge: 1
template:
metadata:
labels:
app: {{ .ServiceName }}
spec:
containers:
- name: {{ .ServiceName }}
image: docker.io/webdeva/{{ .ServiceName }}:{{ .Release }}
imagePullPolicy: Always
ports:
- containerPort: 8000
livenessProbe:
httpGet:
path: /healthz
port: 8000
readinessProbe:
httpGet:
path: /readyz
port: 8000
resources:
limits:
cpu: 10m
memory: 30Mi
requests:
cpu: 10m
memory: 30Mi
terminationGracePeriodSeconds: 30

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

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

`service.yaml`
apiVersion: v1
kind: Service
metadata:
name: {{ .ServiceName }}
labels:
app: {{ .ServiceName }}
spec:
ports:
- port: 80
targetPort: 8000
protocol: TCP
name: http
selector:
app: {{ .ServiceName }}

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

`ingress.yaml`
apiVersion: extensions/v1beta1
kind: Ingress
metadata:
annotations:
kubernetes.io/ingress.class: nginx
ingress.kubernetes.io/rewrite-target: /
labels:
app: {{ .ServiceName }}
name: {{ .ServiceName }}
spec:
backend:
serviceName: {{ .ServiceName }}
servicePort: 80
rules:
- host: advent.test
http:
paths:
- path: /
backend:
serviceName: {{ .ServiceName }}
servicePort: 80

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

运行`minikube`,需要开启ingress并且准备好`kubectl`,我们要用它运行一些命令:
minikube start
minikube addons enable ingress
kubectl config use-context minikube

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

`Makefile`
minikube: push
for t in $(shell find ./kubernetes/advent -type f -name "*.yaml"); do \
cat $$t | \
gsed -E "s/\{\{(\s[i])\.Release(\s[/i])\}\}/$(RELEASE)/g" | \
gsed -E "s/\{\{(\s[i])\.ServiceName(\s[/i])\}\}/$(APP)/g"; \
echo ---; \
done > tmp.yaml
kubectl apply -f tmp.yaml

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

现在我们来看一下我的工作成果:
$ kubectl get deployment
NAME DESIRED CURRENT UP-TO-DATE AVAILABLE AGE
advent 3 3 3 3 1d

$ kubectl get service
NAME CLUSTER-IP EXTERNAL-IP PORT(S) AGE
advent 10.109.133.147 80/TCP 1d

$ kubectl get ingress
NAME HOSTS ADDRESS PORTS AGE
advent advent.test 192.168.64.2 80 1d

现在我们可以发送一个http的请求到我们的服务上,但是首先还是要把域名`adventtest`增加到`/etc/host`文件里:
echo "$(minikube ip) advent.test" | sudo tee -a /etc/hosts 

现在,我们终于可以使用我们的服务了:
curl -i http://advent.test/home
HTTP/1.1 200 OK
Server: nginx/1.13.6
Date: Sun, 10 Dec 2017 20:40:37 GMT
Content-Type: application/json
Content-Length: 72
Connection: keep-alive
Vary: Accept-Encoding

{"buildTime":"2017-12-10_11:29:59","commit":"020a181","release":"0.0.5"}%

看,它工作了!

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

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

非常感谢 Natalie PistunovichPaul BrousseauSandor Szücs等人的的建议和审核。

原文链接:Write a Kubernetes-ready service from zero step-by-step(翻译:王晓轩)

Mesos+ZooKeeper+Marathon+Docker分布式部署打造PaaS云平台实践(一)

赵英俊 发表了文章 • 5 个评论 • 20419 次浏览 • 2015-07-21 16:39 • 来自相关话题

【编者的话】本文先给出一个分布式部署的过程,在完成这种分布式部署的过程花费了我一个周末的时间,因为国内几乎没有找到分布式部署的实践过程记录,希望我的实践过程能够给有兴趣的小伙伴在进行分布式部署中提供一定的帮助。 最近开始对Mesos非 ...查看全部
【编者的话】本文先给出一个分布式部署的过程,在完成这种分布式部署的过程花费了我一个周末的时间,因为国内几乎没有找到分布式部署的实践过程记录,希望我的实践过程能够给有兴趣的小伙伴在进行分布式部署中提供一定的帮助。

最近开始对Mesos非常的感兴趣,Mesos和Docker一样是一个使用Go语言编写的新兴且具有活力的项目,因为最近痴迷学习Go语言,所以对使用Go语言编写的项目都比较看好。本文先给出一个分布式部署的过程,在完成这种分布式部署的过程花费了我一个周末的时间,因为国内几乎没有找到分布式部署的实践过程记录,希望我的实践过程能够给有兴趣的小伙伴在进行分布式部署中提供一定的帮助。之后我会在本系列博文的后半部分综合阐述一下当前Docker的一些应用场景,以及未来基于Mesos+ZooKeeper+Marathon+Docker分布式部署打造PaaS云平台的可行性。
#本文中用到的项目组件介绍(全部摘自网络)
Mesos:Mesos采用与Linux Kernel相同的机制,只是运行在不同的抽象层次上。Mesos Kernel利用资源管理和调度的API在整个数据中心或云环境中运行和提供引用(例如,Hadoop、Spark、Kafaka、Elastic Search)。

ZooKeeper:ZooKeeper是一个分布式的,开放源码的分布式应用程序协调服务,是Google的Chubby一个开源的实现,是Hadoop和HBase的重要组件。它是一个为分布式应用提供一致性服务的软件,提供的功能包括:配置维护、名字服务、分布式同步、组服务等。

Marathon:Marathon是一个Mesos框架,能够支持运行长服务,比如Web应用等。它是集群的分布式Init.d,能够原样运行任何Linux二进制发布版本,如Tomcat、Play等等。它也是一种私有的PaSS,实现服务的发现,为部署提供提供REST API服务,有授权和SSL、配置约束,通过HAProxy实现服务发现和负载平衡。

Docker:Docker 是一个开源的应用容器引擎,让开发者可以打包他们的应用以及依赖包到一个可移植的容器中,然后发布到任何流行的 Linux 机器上,也可以实现虚拟化。
#Mesos+ZooKeeper+Marathon+Docker分布式部署过程记录
Mesos为了管理的简单也采用了master-slave的架构,因此我本次使用了6个Ubuntu 14.04的虚拟机作为部署节点。其中3个mastser节点,3个slave节点。为直观的理解,我简单的画了一张架构图:
3AD0EBE4-D449-4E1F-B5FA-AB8CDC8F265B.png

如图所示其中master节点都需要运行ZooKeeper、Mesos-master、Marathon,在slave节点上只需要运行master-slave就可以了,但是需要修改ZooKeeper的内容来保证slave能够被master发现和管理。为了节约时间和搞错掉,我在公司内部云平台上开一个虚拟机把所有的软件都安装上去,做成快照进行批量的创建,这样只需要在slave节点上关闭ZooKeeper、Mesos-master服务器就可以了,在文中我是通过制定系统启动规则来实现的。希望我交代清楚了,现在开始部署。
##一、准备部署环境

* 在Ubuntu 14.04的虚拟机上安装所有用到软件,虚拟机可以上互联网。

* 安装Python依赖
apt-get install curl python-setuptools python-pip python-dev python-protobuf


* 安装配置ZooKeeper
apt-get install ZooKeeperd
echo 1 | sudo dd of=/var/lib/ZooKeeper/myid


* 安装Docker
echo deb http://get.Docker.io/ubuntu Docker main | sudo tee /etc/apt/sources.list.d/Docker.list
sudo apt-key adv --keyserver keyserver.ubuntu.com --recv-keys 36A1D7869245C8950F966E92D8576A8BA88D21E9
apt-get update && apt-get install lxc-Docker


* 安装配置Mesos-master和Mesos-slave
 curl -fL http://downloads.Mesosphere.io/master/ubuntu/14.04/Mesos_0.19.0~ubuntu14.04%2B1_amd64.deb -o /tmp/Mesos.deb 
dpkg -i /tmp/Mesos.deb
mkdir -p /etc/Mesos-master
echo in_memory | sudo dd of=/etc/Mesos-master/registry


* 安装配置Mesos的Python框架
 curl -fL http://downloads.Mesosphere.io/master/ubuntu/14.04/Mesos-0.19.0_rc2-py2.7-linux-x86_64.egg -o /tmp/Mesos.egg
easy_install /tmp/Mesos.egg


* 下载Marathon
curl -O http://downloads.Mesosphere.io/marathon/marathon-0.6.0/marathon-0.6.0.tgz  
tar xvzf marathon-0.6.0.tgz


* 下载安装Mesos管理Docker的代理组件Deimos
 pip install deimos


* 配置Mesos使用Deimos
mkdir -p /etc/mesos-slave
echo /usr/local/bin/deimos | sudo dd of=/etc/Mesos-slave/containerizer_path
echo external | sudo dd of=/etc/Mesos-slave/isolation


好,至此在一个虚拟机上就完成了所有组件的安装部署,下面就是对虚拟机打快照,然后快速的复制出6个一样的虚拟机,按照上图的ip配置进行配置之后就可以进入下个阶段,当然为了保险你可以测试一下上处组件是否安装成功和配置正确。如果你没有使用云平台,或者不具备快照功能,那就只能在6个虚拟机上重复6遍上处过程了。
##二、在所有的节点上配置ZooKeeper
在配置maser节点和slave节点之前,需要先在所有的6个节点上配置一下ZooKeeper,配置步骤如下:

  • 修改zk的内容
sudo vi /etc/Mesos/zk} 
  • 将zk的内容修改为如下:
zk://10.162..2.91:2181,10.162.2.92:2181,10.162.2.93:2181/Mesos
##三、 配置所有的master节点在所有的master节点上都要进行如下操作:
  • 修改ZooKeeper的myid的内容
sudo vi /etc/ZooKeeper/conf/myid
将三个master节点的myid按照顺序修改为1,2,3。
  • 修改ZooKeeper的zoo.cfg
sudo vi/etc/ZooKeeper/conf/zoo.cfg
server.1=10.162.2.91:2888:3888server.2=10.162.2.92:2888:3888server.3=10.162.2.93:2888:3888
  • 修改Mesos的quorum
sudo vi /etc/Mesos-master/quorum
将值修改为2。
  • 配置master节点的Mesos 识别ip和和hostname(以在master1上的配置为例)
echo 10.162.2.91 | sudo tee /etc/Mesos-master/ip 
sudo cp /etc/Mesos-master/ip /etc/Mesos-master/hostname
  • 配置master节点服务启动规则
sudo stop Mesos-slaveecho manual | sudo tee /etc/init/Mesos-slave.override
四、配置所有的slave节点
  • 配置slave节点的服务启动规则
sudo stop ZooKeeperecho manual | sudo tee /etc/init/ZooKeeper.overrideecho manual | sudo tee /etc/init/Mesos-master.overridesudo stop Mesos-master
  • 配置slave节点的识别ip和hostname(以slave1节点为例)
echo 192.168.2.94 | sudo tee /etc/Mesos-slave/ipsudo cp /etc/Mesos-slave/ip /etc/Mesos-slave/hostname
五、在所有节点上启动服务
  • 在master节点上启动服务(以在master1节点上为例)
initctl reload-configuration service Docker start service Zookeeper start service Mesos-master start service Mesos-slave start cd marathon-0.6.0 ./bin/start --master zk://10.162.2.91:2181,10.162.2.92:2181,10.162.2.93:2181/Mesos --zk_hosts 10.162.2.91:2181
  • 在所有slave节点上启动服务
sudo start mesos-slave

好,至此,如果配置没有出现错误的话就会成功了,由于有的网络情况和设备情况不一样,所以选举的过程有的快有的慢,当发现slave节点有些正常有些不正常时,可以通过reboot来促使自己被master发现。

六、Marathon简单的使用
要使用Marathon进行应用的部署,需要先在slave节点上有Docker镜像,这个可以自己写Dockerfile来做,也可以直接从公共仓库里pull下来一个。Marathon的应用部署和k8s一样,使用通过JSON文件来进行的,以下就是一个jason文件的范例:
curl -X POST -H "Accept: application/json" -H "Content-Type: application/json" \
10.162.2.91:8080/v2/apps -d '{
"container": {"image": "docker:///ubuntu", "options": ["--privileged"]},
"cpus": 0.5,
"cmd": "ok",
"id": "cci-docker",
"instances": 2,
"mem": 30
}'


七、成功部署的结果是这样的
1393540D-028A-49CA-B4BE-D8C515C88A10.png

3677CB31-F1DF-430D-BC6A-82A576C0DFE8.png

176B3875-B5F6-42BC-9EF4-A49EBE835184.png

07CDB99D-51D6-4D5C-8B15-8151EE32F155.png

4D7C8BAF-FC99-4BCD-88E5-6CD5EEFEF85E.png

05BD7E57-90A1-4891-8AC3-4D179B6DF248.png

B8434C3A-DDCE-4562-90A2-31BF8D31CB99.png

关于对Docker应用场景的总结以及这种基于Docker的PaaS平台未来的发展前景,我会在后面的博文中进行讨论分析。

赵英俊 2015年7月21日 于 杭州 城云科技

CoreOS Fest 系列之第二篇: Systemd、Go、Calico、Sysdig

bnuhero 发表了文章 • 0 个评论 • 5486 次浏览 • 2015-06-19 17:43 • 来自相关话题

【编者的话】在 CoreOS Fest 第二天的会议中,演讲者展示了多个开源项目和工具,包括 Systemd 和 CoreOS 、 Go 语言和容器、 Calico 项目、 Sysdig 等。 在 CoreOS Fest 的第一天会议 ...查看全部
【编者的话】在 CoreOS Fest 第二天的会议中,演讲者展示了多个开源项目和工具,包括 Systemd 和 CoreOS 、 Go 语言和容器、 Calico 项目、 Sysdig 等。

CoreOS Fest第一天会议中,陆续介绍了 CoreOS 的架构、规划和规范。第二天的会议,演讲者展示了多个开源项目和工具,包括 systemd-nspawn 、 Calico 项目和 Sysdig 等。上述项目大多数已经开发了一年有余,但是大部分 CoreOS Fest 的与会者是第一次了解这些项目。

不仅是会议的内容吸引人,更引人注意的是,在过去的一年多时间内,社区开发了大量支持 Linux 容器的新工具。在 Linux 领域,如此快的发展速度,前所未见。在以容器为核心的新型基础设中, systemd 是一个基础组件,它是 Linux 的新型初始化系统,支持容器的开箱即用。

Systemd 和 CoreOS

Red Hat 的 Lennart Poettering 就 Systemd 和 CoreOS 做了演讲,他介绍了集成容器管理功能的 systemd 工具。与其它的初始化系统相比,用 systemd 构建容器更有优势。 systemd 提供了在单机上管理不同容器所需的所有工具。

他首先指出,这次并不是代表 Red Hat 官方的报告。然后,他花了很多时间介绍 Red Hat 的 systemd 团队。这个团队并不是一个产品团队,他说:「我们自认为是一个研究部门,而不是一个产品团队」。

这种表态解释了 systemd 选择某些技术的原因,例如,选择 Btrfs 作为 systemd-nspawn 模板和容器的主要文件系统。「人们都说 btrfs 是一个不稳定的文件系统。但是, btrfs 项目团队正在努力解决该文件系统存在的基础性问题」,他说。而且,在一个不稳定的文件系统上运行容器,这是可以接受的,因为数据被保存在外部的卷上,而不是在容器内(译者注:即没有保存在这个不稳定的文件系统上)。

systemd 包含多个支持容器的守护进程,即 systemd-machined, systemd-networkdsystemd-resolved 。总的来说,所有的 systemd 进程都兼容容器,因为「更多时候,我们是在容器中而不是裸机上测试 systemd 」。这么做的好处是,不用重启开发用的笔记本电脑,就可以测试 systemd 进程。Poettering 认为集成容器是 Linux 的一项重要特性,他说:「容器应该默认集成到 Linux 操作系统中,就像 Zones 已经成为 Solaris 操作系统的一部分」。

systemd 团队的目标是保持 systemd 中立,不仅支持 rkt 容器,还支持 docker, libvirt-lxc, OpenVZ 等。 systemd 提供了很多与容器相关的功能,它是一个偏底层的工具,不提供复杂的用户界面。像 CoreOS 和 kubernetes 等项目可以调用 systemd 的功能,完成对容器的基本操作。

在 systemd 中,管理容器的主要工具是 systemd-machined 及其命令行工具 machinectl 。有了它,用户能够列出所有的容器,启动和关停容器,甚至交互登录到容器中。 systemd-machined 实际上是容器的注册中心,任何容器都可以注册。 systemd-machined 配合 systemd ,执行 `systemd-run -M` ,就能在容器中执行任何命令。 systemd-machined 运行的容器,会出现在 `ps` 命令的列表或者 GNOME 的系统监控器中。

systemd-nspawn 是一个轻量的容器执行器,它是一个像 docker 那样能够启动和关停容器的工具。 为 systemd-nspawn 指定任何包含主引导记录( MBR )或者 GUID 分区表的文件系统或者块设备,它就能启动一个容器。如果用户只需要具备基本功能、免配置的容器, systemd-nspawn 是一个很有吸引力的选择。 rkt 底层也是调用 systemd-nspawn 运行容器。

systemd-networkd 是管理网络的 systemd 守护进程,而 systemd-resolved 是解析主机名字的 systemd 守护进程,它们都支持容器。 systemd-networkd 自动启动容器的网络,利用 DHCP 为容器分配内部的网络地址。 systemd-resolved 使用本地链路多播名字解析( link-local multicast name resolution, LLMNR )系统,为容器提供主机名。 LLMNR 是 Microsoft 发明的自动名字发现系统,初衷是用在客户端应用和移动设备中,但是它也可以用于网络环境中容器之间的彼此发现。

根据 Poettering 的演讲,看起来 systemd 是 Docker 公司的 libcontainer 以及其它容器初始化和管理工具的强力替代。由于大部分 Linux 发行版的最新版都会集成 systemd ,大多数用户将能够立即使用 systemd 。这也能解释为什么容器领域的大多数公司都选择编排作为主攻点,因为 systemd 本身不提供容器编排功能。

Go 语言和容器

CoreOS 公司 CEO Alex Polvi 做主题演讲时宣布 CoreOS 公司将赞助第二届 Gophercon —— Go 语言开发者大会。 有 6 家 Gophercon 赞助商都是容器公司,约占赞助商总数的 1/4 。这是有原因的,无论 CoreOS 公司还是 Docker 公司,它们使用的主要编程语言都是 go 。 Polvi 如此说道:「只有使用 go 语言才能开发出 Etcd 」。

我听的每一个演讲,我到的每一个会议室,人们都在谈论 go 语言,用它写代码。 Etcd, fleet, Swarm, Kubernetes, Kurma 等容器工具应用或者服务都是用 Go 语言编写的。 Linux 容器平台的崛起,同样是 Go 语言的崛起。

Go 语言始于 2007 年 Google 公司的一个内部项目,共有 3 个开发者。现在, go 语言项目的贡献者已经超过 500 人。 Go 是一个使用 BSD 许可的开源项目,但是仍然由 Google 公司把持,所有的贡献者都需要与 Google 公司签署贡献者许可协议( Contributor License Agreement, CLA )。 Go 逐渐成为实现可扩展基础设施的「自动化语言」。之前, 人们已经普遍使用 go 语言实现网络代理、云服务器管理工具、分布式搜索引擎和冗余数据存储。很自然地,人们继续选择 go 实现容器工具应用。

由于 CoreOS 与 go 的紧密联系, Brad Fitzpatrick 的报告主题是 go 语言的持续构建基础设施。他是 LiveJournal, memcached 和 OpenID 的开发者,现在是 Google 公司 go 语言团队的一员。他展示了在很多平台上测试 go 语言的自动构建平台。构建平台刚开始时是一个 Google 应用引擎( Google Application Engine )应用,加上桌子上一排的移动设备,然后发展成今天这个样子。他还讲述了持续构建基础设施的历史和工作机制。

Go 是一种编译型而不是解释型编程语言。编译得到的二进制程序,能够在多种平台上执行。每个版本的 go 语言在发布之前,首先要在 Google 公司机器实验室拥有的几百种平台上成功地构建。只有在各种 Linux 平台构建 go 语言时才会用到容器,其它很多平台并不支持容器,像 MAC OS X 和 Android 平台就必须使用专用的硬件,因此容器在自动构建平台中的作用比较小。你可以在这里 看到 go 语言在各个平台的构建结果,哪些成功了,哪些失败了。

Calico 项目

其实 Calico 项目 已经开源差不多一年了。当核心开发者 Spike Curtis 报告时,大多数与会者是第一次听说这个项目。 Calico 是一个多主机路由软件,还包含一个分布式防火墙。 Calico 是专门为容器和虚拟机尤其是 docker 和 OpenStack 环境设计的。 Metaswitch Networks 公司用 python 语言开发 Calico ,它也是唯一提供 Calico 商业化支持的公司。看起来, calico 有望成为这样一种解决方案:用户能够在生产环境部署容器,同时保证严格的安全。

「还记得三层架构吗?」 Curtis 抱怨说,「管理员现在还是这样管理网络。第一层是外部网络,中间是 web 资源所在的隔离区( demilitarized zone, DMZ),最后是数据层,要求保证最严格的安全」。

编排好网络中的容器,运行微服务,这种模式打破了三层模型。首先,微服务是根据服务的提供者而不是服务的安全特征划分的;其次,编排框架假设数据中心网络是无区分的,也不支持安全分层的概念;最重要的是,你得为几百个微服务定义安全策略和区域,这不像以前,网络管理员只要设定几十个策略和区域就行了。 Curtis 说,「这就像是一个动物园,所有围墙都被推到了」。

然而,就安全而言,微服务也为带来了机会。每个微服务只做一件事情,它的安全需求也相对简单。因此,可以更细致地划分服务,又不增加整体的复杂性, calico 就是这么做的。

Calico 为每个容器或者虚拟机分配一个独立的 IP 地址,然后在每台物理主机上定义包含这些 IP 地址的 iptables 规则,实现了防火墙功能。 Calico 用保存在 etcd 的标签定义每个服务,用一个 JSON 格式的配置文件定义允许访问当前服务的其它服务,以及这个服务是否可通过 Internet 访问。

只要编排框架支持为每个服务分配一个 IP 地址,就可以集成使用 calico 。Curtis 演示了 calico 与 kubernetes 的集成,包括用扩展的 pod 定义来设定每个容器的安全设置。 Apache Mesos 正在实现为每个服务分配一个 IP 地址的功能,暂时还不能集成 calico 。

Sysdig

最后一个「新」项目是 Sysdig ,就像 calico 项目一样, 它也是一年多以前就发布了,但是大部分与会者都是第一次听说这个项目。站在 sysdig 背后的公司是 Sysdig Cloud ,它为 sysdig 的用户提供商业化支持。 Sysdig Cloud 公司 CEO Loris Degioanni 展示了这款工具。

Sysdig 是一个网络流监控系统,部分功能实现为一个 Linux 内核模块。这个模块捕捉了所有的网络流,特别是容器之间的网络包。用户还可以用 Lua 语言编写网络流信息的过滤器,然后把过滤得到的信息聚合在一起,进行统计分析。你可以把它视为 wiresharktcpdump 的高级版本,尤其是支持容器。

Degioanni 说,sysdig 比 Google 的 cAdvisor 项目高明,因为 cAdvisor 只报告容器占用的全部 CPU 、内存和网络,而 sysdig 还能够提供网络流的发送方、接收方和内容。例如,用户可以找出对某个数据库的查询,搞清楚为什么两个 IP 地址之间的网络通信延误得厉害。

Degioanni 还展示了即将发布的 sysdig 的文本用户界面,这是一个基于 curses) 的开源项目。有了它,系统管理员能够用 ssh 登录到系统,执行交互的监控任务。他演示了如何深入检查和汇总容器之间的网络通信,还演示了如何调查网络延迟。在 Sysdig Cloud 公司的展台,工作人员演示了商业化产品—— sysdig 的图形界面,用户只需点击多层嵌套的服务器、 pod 和容器,就可以查看对应的网络流。

小结

我从 CoreOS Fest 大会了解了这么多的新项目、新工具、标准草案和新架构,这充分说明 Linux 容器领域发展之快。要知道,在一年前,当我报道首届 DockerCon 时,这些新技术和新工具,大部分才刚刚发布,有的甚至还不存在。等再过一年,我们看看是不是还发展得这么快。

我们还没有涉及一个主要的话题:如何在容器中持久保存数据。前文曾经提及,目前普遍的看法是容器应该是无状态的,不保存数据。不坚持这一点,容器管理和编排会遇到一系列亟需解决的问题,例如,外部卷的管理、容器迁移和有状态服务的负载均衡等。下个星期,我们将讨论与持久话数据和容器相关的多个话题,内容来自于 CoreOS Fest 和 Container Camp

原文链接: New projects from day two of CoreOS Fest (翻译:柳泉波)

Go版微服务开发框架Micro及标准2019年大整合

cleverlzc 发表了文章 • 0 个评论 • 299 次浏览 • 2019-06-16 08:38 • 来自相关话题

【编者的话】Micro是构建微服务的最简单方式,并逐渐成为云计算中基于Go的微服务开发的实际标准。通过将我们的努力整合到一个开发框架和运行时中,我们使这个过程更加简单。 Micro作为[go-micro](https://gith ...查看全部

【编者的话】Micro是构建微服务的最简单方式,并逐渐成为云计算中基于Go的微服务开发的实际标准。通过将我们的努力整合到一个开发框架和运行时中,我们使这个过程更加简单。 

Micro作为[go-micro](https://github.com/micro/go-micro)——一个微服务框架开始了它的旅程,专注于提供微服务开发的核心需求。它通过抽象出分布式系统的复杂性,为构建微服务创造了更简单的体验。

随着时间的推移,我们已经从go-micro扩展到其他工具、库和插件。这导致了我们解决问题的方式和开发人员使用微服务工具的方式的分散化。我们现在正在整合所有这些工具,以简化开发人员的体验。

从本质上来说,Micro已经成为一个独立的开发框架和微服务开发的运行时。

在讨论整合之前,让我们回顾一下迄今为止的历程。

### 主要关注点

Go-micro最初主要专注于微服务的通信方面,我们一直努力做到这一点。到目前为止,这种固执己见的方法和关注点是驱动框架成功的真正驱动力。多年来,我们已经收到了无数的请求,要求解决第二天在go-micro中构建生产就绪软件的问题。其中大部分都与可伸缩性、安全性、同步和配置有关。


虽然增加所要求的额外特性是有好处的,但我们确实希望一开始就非常专注于很好地解决一个问题。所以我们采取了一种不同的方式来促进社区这样做。

### 生态系统和插件

投入生产所涉及的不仅仅是普通的服务发现、消息编码和请求-响应。我们真正明白这一点,希望使用户能够通过可插拔和可扩展的接口选择更广泛的平台需求。通过[资源管理器](https://micro.mu/explore/)促进生态系统,资源管理器聚合了GitHub上的基于微服务的开源项目,并通过[go-plugins](https://github.com/micro/go-plugins)扩展插件。


一般来说,Go插件已经取得了巨大的成功,因为它允许开发人员将大量的复杂性转移到为这些需求构建的系统上。例如用于度量的Prometheus、用于分布式跟踪的Zipkin和用于持久消息传递的Kafka。

### 交互点

Go Micro确实是微服务开发的核心,但是随着服务的编写,接下来的问题就转移到了:我如何查询它们,如何与它们交互,如何通过传统方式为它们服务。

鉴于go-micro使用了一个基于RPC/Protobuf的协议,该协议既可插拔又不依赖于运行时,我们需要某种方法来解决这个问题。这导致了微服务工具包[Micro](https://github.com/micro/micro)的产生。Micro提供了API网关、网络仪表板、cli命令行工具、slack bot机器人程序、服务代理等等。


Micro工具包通过http api、浏览器、slack命令和命令行接口充当交互点。这些是我们查询和构建应用程序的常见方式,对于我们来说,提供一个真正支持这一点的运行时非常重要。然而,但它仍然把重点放在通信上。

###其他工具

虽然插件和工具包极大地帮助了使用了Micro的用户,但在关键领域仍然缺乏。很明显,我们的社区希望我们能够围绕产品开发的平台工具来解决更多的问题,而不是必须在他们各自的公司中单独完成。我们需要为动态配置、分布式同步和为Kubernetes这样的系统提供更广泛的解决方案等方面提供相同类型的抽象。

于是我们创建了以下项目:


- [micro/go-config](https://github.com/micro/go-config): 一个动态配置库

- [micro/go-sync](https://github.com/asim/go-sync):一个分布式同步库

- [micro/kubernetes](https://github.com/micro/kubernetes):在Kubernetes平台上的初始化

- [examples](https://github.com/micro/examples):使用举例

- [microhq](https://github.com/microhq):微服务预构建


这些是一部分repos、库和工具,用于尝试解决我们社区更广泛的需求。在过去的四年里,repos的数量不断增长,新用户的入门体验也变得更加困难。进入壁垒急剧增加,我们意识到需要做出一些改变。

在过去的几周里,我们意识到[go-micro](https://github.com/micro/go-micro)确实是大多数用户开发微服务的焦点。很明显,他们想要额外的功能,作为这个库的一部分以及一个自我描述的框架,我们真的需要通过解决那些第二天的问题来实现这一点,而不要求开发人员寻求其他方法。

本质上,go-micro将成为微服务开发的全面和独立框架。

我们通过将所有库迁移到go-micro开始了整合过程,在接下来的几周里,我们将继续进行重构,以提供更简单的默认入门体验,同时还为日志记录、跟踪、度量、身份验证等添加更多功能。


不过,我们也没有忘记Micro。在我们看来,当构建了微服务之后,仍然需要一种查询、运行和管理它们的方法。所有人都认为Micro将是微服务开发的运行时。我们正在致力于提供一种更简单的方法来管理微服务开发的端到端流程,并且应该很快会有更多消息发布。

### 总结

Micro是构建微服务的最简单方式,并逐渐成为云计算中基于Go的微服务开发的实际标准。通过将我们的努力整合到一个开发框架和运行时中,我们使这个过程更加简单。

原文链接:Micro - The great consolidation of 2019


**译者**:Mr.lzc,软件工程师、DevOpsDays深圳核心组织者,目前供职于华为,从事云存储工作,以Cloud Native方式构建云文件系统服务,专注于Kubernetes、微服务领域。


容器化 Go 开发环境的尝试

齐达内 发表了文章 • 0 个评论 • 928 次浏览 • 2019-04-08 13:08 • 来自相关话题

【编者的话】本文是我在思考“如何组建团队”时候的一个小尝试,旨在通过容器技术(Docker)降低探索 Golang 技术开发的门槛。目前的效果还不是很明显,不过作为一种新思路,非常值得大家了解。 #容器化 Go 开发环境 ##容器化的价 ...查看全部
【编者的话】本文是我在思考“如何组建团队”时候的一个小尝试,旨在通过容器技术(Docker)降低探索 Golang 技术开发的门槛。目前的效果还不是很明显,不过作为一种新思路,非常值得大家了解。
#容器化 Go 开发环境
##容器化的价值
搭建开发环境往往是一个啰嗦繁杂的过程。对职业开发者如此,对知识学习者和探索者亦如此。

职业编码工作中,代码编辑测试完成后部署到生产环境,需要按照自己本地的开发环境重新配置生产环境的机器。由于本地开发环境的搭建比较随性,往往,本地能够跑起来的代码部署到生产环境后跑不起来,或并未达到预期的运行效果。

对于一个刚刚开始学习 《C 语言程序设计》课程的大学生来说,编译出自己的 “Hello World” 往往意味着很多事先的准备工作(至少先把课堂上老师三言两语带过的开发环境搭建起来)。

之前因为项目的需要我魔改过日志收集工具 fluent/fluent-bit,这是一个主要由 C 语言进行开发的项目,而我对 C 语言的认识还停留在大学课堂的水平,更何况我本地没有搭建过开发 C 的环境。

容器化技术能很好地解决上面的问题。职业开发者使用 Docker(容器化技术的一种)把环境搭建的过程封装到容器里,并以镜像的形式复制到生产环境得以“复现”相同的环境。作为知识学习者,完全可以利用相似的技术“复现”老师课堂上使用的环境。而作为知识探索者,在修改了 fluent-bit 的源码后,我利用其源码中提供的 Dockerfile 很方便地实现了定制化源码的编译,快速验证了思路可行性及定制化功能的可用性。

如果读者未使用过 Docker,可以参考《如何用一个例子上手 Docker》这篇博客的内容及其参考中列出的地址了解并尝试一下,应该会被甜到。
##容器化的 Go 开发环境
为了说明问题并方便读者能容易地在自己机器上验证,我在《Go 反序列化 JSON 字符串的两种常见用法》和 《浅谈 Go 标准库对 JSON 的处理效率》两篇博客里刻意贴了完整而冗长的源码内容。虽说 package 和 import 语句对博客的内容并没有任何作用,但是如果因为多这样几句内容就能让代码成为完整可运行的源码,从而节省读者自己构造完整源代码的时间,我认为是值得且必要的。

可以把思考更进一步,如果读者朋友没有 Go 开发环境(或者与作者本地的开发环境不一致),如何才能以一种低成本的方式开始这一切呢?不知不觉就想到了 Docker 技术。

定制化 Go 开发环境镜像

想要低成本获取 Go 开发环境,思路很简单,把 Go 开发环境打包到容器里(其实官方已经存在这种镜像),大家只需要拉取相应的镜像然后运行就可以了。如下面的源码所示,为了方便编辑并调试 Go 源码,我在 Go 官方镜像的基础上安装并简单配置了 vim 和 delve,并把镜像推送到了 Docker Hub 仓库中。更详尽的内容可以参考 GitHub - chalvern/smile
# cat https://github.com/chalvern/smile/blob/master/docker/Dockerfile
FROM golang:1.12

[size=16] vim[/size]
RUN apt-get update \
&& apt-get install -y vim \
&& rm -rf /var/lib/apt/lists/*

# vim setting
COPY vimrc /root/.vimrc

RUN go get -u github.com/derekparker/delve/cmd/dlv

WORKDIR $GOPATH

运行 Go 开发环境镜像

  1. 拉取镜像:docker pull chalvern/golang:1.12
  2. 以 privileged 方式运行镜像:docker run -it --privileged chalvern/golang:1.12 bash
  3. 此时便有了一个 Go 开发环境。

##环境(上下文)一致的必要性
我在学生时代发现一个很有趣的现象,国外的教材往往页码很足整本书很厚,而中文的教材页码比较少相对要薄一些。排除一部分语言表达力的因素,主要是因为国外的教材喜欢包含比较多知识之外的细节。

以《C 语言程序设计》类似的书籍来说,是直接从 Hello World 讲起好呢?还是从详细的环境搭建步骤讲起好呢?我记得当年在学习 C 语言编程的时候,为了搭建开发环境到图书馆找了很多资料,最终也未“复现”教科书上一模一样的开发环境,导致在学习过程中产生非常多的疑惑。有的同学在疑惑面前退缩了,渐渐失去了编码的兴趣,最终的成绩自然也不如人意。

国外教材比较厚重的另一个原因,是国外教材中喜欢包含比较详细的参考文献。那么,书籍或者博客中,是否应该把参考文献放进正文呢?我认为是必要的。把参考文献列出来,一方面可以表达对相关论点提出者的尊重,另一方面则方便让读者能够进一步了解论点的渊源或者进一步考证“真相”。书里或博客里所论述的是“集百家之长的一家之言”呢?还是纯碎个人思考得出来的“一家之言”呢?不同的分类,其说服力以及可采纳率其实是不一样的;如果混淆在一起使人不可分辨,容易让人忽视共识的力量,
#小结
本文尝试通过容器技术(Docker)降低探索 Golang 技术开发的门槛。相比于把开发环境直接安装到自己的电脑上“尝鲜”,容器化技术能够很好地避免 Go 开发环境及其依赖项(比如 $GOPATH、$GOROOT 等变量)对电脑的污染,同时容器化技术能够很好地“复现”一致可用的开发环境,避免引入其他变量,从而降低技术探索的难度。
#参考

* 以认真的态度做完美的事情(2018年总结) - 敬维 之前写的 2018 年的总结
* Docker基本原理简析 - 敬维 简单介绍了 Docker 涉及到的三种技术:Namespace、CGroup与AUFS
* 如何用一个例子上手Docker - 敬维 用一个例子来上手使用 docker。
* GitHub - fluent/fluent-bit 轻量级日志收集应用
* Go 反序列化 JSON 字符串的两种常见用法 - 敬维 两种反序列化 JSON 字符串的方法,包含了复制黏贴即可运行的源码
* 浅谈 Go 标准库对 JSON 的处理效率 - 敬维 探究 Go 标准库对 JSON 的处理效率,包含了复制黏贴即可运行的源码

原文链接:容器化 Go 开发环境的尝试

如何为你的Go应用创建轻量级Docker镜像?

ScofieldDM 发表了文章 • 0 个评论 • 4572 次浏览 • 2018-09-03 21:46 • 来自相关话题

恭喜!你已经创建了一个出色的go应用程序,现在你想创建一个Docker容器来分发你的应用。 但是,如何尽可能为你的Golang应用程序打造一个最小的镜像呢?提示:我们将会使用多阶段构建(自从Docker 17.05版本提供的方法)来完 ...查看全部
恭喜!你已经创建了一个出色的go应用程序,现在你想创建一个Docker容器来分发你的应用。

但是,如何尽可能为你的Golang应用程序打造一个最小的镜像呢?提示:我们将会使用多阶段构建(自从Docker 17.05版本提供的方法)来完成这个目标。
# 介绍
## 多什么?
简单来讲,多阶段。

多阶段允许在创建Dockerfile时使用多个from,它非常有用,因为它使我们能够使用所有必需的工具构建应用程序。举个例子,首先我们使用Golang的基础镜像,然后在第二阶段的时候使用构建好的镜像的二进制文件,最后阶段构建出来的镜像用于发布到我们自己的仓库或者是用于上线发布。

在上述的案例中,我们总共有三个阶段:

  • build编译阶段
  • certs(可选,可有可无)证书认证阶段
  • prod生产阶段
在build阶段主要是编译我们的应用程序,证书认证阶段将会安装我们所需要的CA证书,最后的生产发布阶段会将我们构建好的镜像推到镜像仓库中。而且发布阶段将会使用build阶段编译完毕的二进制文件和certs阶段安装的证书。
1.png
项目发布的多个build阶段# 示例工程对于这个方法,我们将使用一个非常简单的项目。它只是一个运行在8080端口的HTTP服务,并且返回结果为传递过去的URL的内容结果。## 举例GET http://localhost:8080?url=https://google.com 返回结果为goole页面内容展示。你也可以在这里找到代码仓库。在master分支上只包含了应用程序,final分支上还包含本篇教程中使用的Dockerfile文件如果你想跟着本教程来做,只需要拉下master上的代码并且跟着我来创建Dockerfile。# 步骤1 - 编译阶段第一阶段主要是使用Golang基础镜像来将我们的应用程序打包为二进制文件。这个基础镜像包含了将我们的应用程序编译成可执行二进制文件的所有工具。下面是我们最原始的Dockerfile:
1 #2 # BUILD 阶段3 # 4 FROM golang:1.10 AS build56 # 设置我们应用程序的工作目录7 WORKDIR /go/src/github.com/scboffspring/blog-multistage-go89 # 添加所有需要编译的应用代码10 ADD . .1112 # 编译一个静态的go应用(在二进制构建中包含C语言依赖库)13 RUN CGO_ENABLED=0 GOOS=linux go build -a -installsuffix cgo .1415 # 设置我们应用程序的启动命令16 CMD ["./blog-multistage-go"]
  • 第4行:使用的基础镜像(golang:1.10)并且我们使用as给当前阶段一个别名,也可以使用阶段索引来引用前一阶段,但这使得它更清晰。
  • 第7行:我们将工作目录设置为Golang基础镜像的默认$GOPATH中的应用程序目录。
  • 第10行:添加我们的应用程序源文件。
  • 第13行:编译二进制文件。使用不同的参数来创建一个完整的静态库,因为在生产环境拉取镜像时可能不一定需要所有的Golang VM以及C语言库。
  • 第16行:使用设定的命令来启动应用程序。
现在我们进行编译并使用Docker容器,我们的应用程序如我们预期正常运行:
docker build -t scboffspring/blog-multistage-go .docker run --rm -ti -p 8080:8080 \scboffspring/blog-multistage-go
我们可以使用curl命令来请求,并且它会返回http://google.com页面内容。在终端运行`curl localhost:8080`。
1 2  3  5  Google7 ....
让我们使用`docker images`,来看看镜像的大小:
REPOSITORY                                       ... SIZEscboffspring/blog-multistage-go                  ... 818MB
荒唐,太荒唐了,一个这么小的应用居然占了磁盘818M内存空间。推送到镜像仓库后,镜像大小被压缩到309M。
2.png
docker hub 占用309M接下来我们来改善这种情况,把镜像的大小降低到10M!# 步骤2 - 生产阶段上面提供的镜像是完全可以进行部署使用的,但是它真的是太大了。每次在Kubernetes上启动你的容器时需要拉取309M的镜像?真的是太浪费时间和带宽。让我们来为我们的镜像构建一个生产阶段,正如上面解释的,这个阶段只是从build阶段拷贝二进制文件到容器中。我们新的Dockerfile将会如下所示:
1 #2 # BUILD 阶段3 # 4 FROM golang:1.10 AS build56 # 设置我们应用程序的工作目录7 WORKDIR /go/src/github.com/scboffspring/blog-multistage-go89 # 添加所有需要编译的应用代码10 ADD . .1112 # 编译一个静态的go应用(在二进制构建中包含C语言依赖库)13 RUN CGO_ENABLED=0 GOOS=linux go build -a -installsuffix cgo .1415 # 设置我们应用程序的启动命令16 CMD ["./blog-multistage-go"]17181920 #21 # 生产阶段22 # 23 FROM scratch AS prod24 25 # 从buil阶段拷贝二进制文件26 COPY --from=build /go/src/github.com/scboffspring/blog-multistage-go/blog-multistage-go .27 CMD ["./blog-multistage-go"]
如你所见,同一个Dockerfile文件中我们添加了第二个FROM语句。这次,我们直接拉取二进制文件,不需要添加任何其他依赖。
  • 第23行:拉取基础镜像
  • 第26行:从`/go/src/github.com/scboffspring/blog-multistage-go/blog-multistage-go`拷贝build阶段编译的文件
  • 第27行:使用设定的命令来启动应用程序
简单吧。让我们像之前一样编译并使用Docker容器:
docker build -t scboffspring/blog-multistage-go . docker run --rm -ti -p 8080:8080 \scboffspring/blog-multistage-go
我们可以看到服务正常启动,也就是意味着它正确的启动了!我们完成了!让我们使用`docker images`,来看看镜像的大小:
REPOSITORY                                       ... SIZEscboffspring/blog-multistage-go                  ... 6.65MB
如我们之前所说,镜像的大小变为10MB以下。而且镜像被推送到镜像仓库后,它只有2MB。当你启动容器时,只需下载2MB即可,相比于之前节省了大量的时间和带宽呢。
3.png
使用prod阶段编译的容器仅2MB但是,它在我们的例子中不起作用。 如果运行`curl localhost:8080`,你看到的返回的结果为500。
curl localhost:8080500 - Something bad happened
如果你查看容器的日志,你可以找到如下错误:

发生了一个错误:Get http://google.com:X509:加载系统根目录失败并且没有根目录可以使用。

我们尝试使用https来连接Goole服务器,但是我们没有用于验证Google的SSL证书的CA(证书颁发机构)证书或是其他网站的CA证书。如果你的应用不需要使用SSL的话,可以选择跳到下一节,否则,让我们来改善我们的软件使得其可以进行访问。# 阶段3 - (可选)认证阶段
1 #2 # BUILD 阶段3 # 4 FROM golang:1.10 AS build56 # 设置我们应用程序的工作目录7 WORKDIR /go/src/github.com/scboffspring/blog-multistage-go89 # 添加所有需要编译的应用代码10 ADD . .1112 # 编译一个静态的go应用(在二进制构建中包含C语言依赖库)13 RUN CGO_ENABLED=0 GOOS=linux go build -a -installsuffix cgo .1415 # 设置我们应用程序的启动命令16 CMD ["./blog-multistage-go"]171819 # 20 # CERTS Stage21 #22 FROM alpine:latest as certs23 24 # Install the CA certificates25 RUN apk --update add ca-certificates26 27 #28 # PRODUCTION STAGE29 # 30 FROM scratch AS prod31 32 # 从certs阶段拷贝CA证书33 COPY --from=certs /etc/ssl/certs/ca-certificates.crt /etc/ssl/certs/ca-certificates.crt34 # 从buil阶段拷贝二进制文件35 COPY --from=build /go/src/github.com/scboffspring/blog-multistage-go/blog-multistage-go .36 CMD ["./blog-multistage-go"]
  • 第23行:我们新的certs阶段,使用alpine镜像
  • 第25行:安装最新版的CA证书
  • 第33行:从certs层拷贝证书,并保存为`/etc/ssl/certs/ca-certificates.crt`

让我们再次编译并使用Docker容器:
docker build -t scboffspring/blog-multistage-go . 
docker run --rm -ti -p 8080:8080 \
scboffspring/blog-multistage-go

现在,`curl localhost:8080`将会返回真实的页面!它真的奏效了!

使用`docker images`查看,镜像依然还是非常小的:
REPOSITORY                                       ... SIZE
scboffspring/blog-multistage-go ... 6.89MB

#额外福利:在指定的阶段为镜像添加tag
有时候我们可能会在各个阶段为镜像创建一个tag,在我们的示例中,我们可能也会将build阶段产生的结果发布到Docker,因为它对开发真的十分有用。

要想这样做的话,只需要在build镜像的时候简单的使用`--target=NAMEOFTHESTAGE`。

举个例子:
docker build -t scboffspring/blog-multistage-go:build . --target=build

# 总结
现在你已经能够为你的Golang应用程序创建一个非常轻量级的应用程序。阶段构建的概念对其他许多案例也是非常有用的。

我在NodeJS世界中的一个用法是第一阶段编译TypeScript项目。然后第一个阶段编译以便使得该镜像可以运行测试。此镜像也能够用于开发环境,因为它包含了所有开发环境所需的依赖。

当第一阶段测试通过后,第二阶段只是简单的安装项目中的`package.json`中的依赖(并不是测试环境依赖)。它只将编译和缩小的代码复制到镜像中,然后将该镜像推送并部署到生产中。

原文链接:HOW-TO: Create lightweight docker images for your Go applications using multi-stage builds(翻译:刘明)

如何在GO语言中使用Kubernetes API?

Rancher 发表了文章 • 1 个评论 • 2067 次浏览 • 2018-03-02 14:01 • 来自相关话题

Rancher Labs首席软件工程师Alena Prokharchyk受邀在2017年12月6-8日的CNCF主办的Kubernetes领域顶级盛会KubeCon + CloudNativeCon 2017北美峰会上进行演讲,本文由演讲内容整理而成。 ...查看全部
Rancher Labs首席软件工程师Alena Prokharchyk受邀在2017年12月6-8日的CNCF主办的Kubernetes领域顶级盛会KubeCon + CloudNativeCon 2017北美峰会上进行演讲,本文由演讲内容整理而成。

----------

随着Kubernetes越来越受欢迎,围绕它的集成和监控服务的数量也在不断增长。Golang编写的所有此类服务的关键组件是kubernetes / client-go——一个用于与Kubernetes集群API通信的软件包。在本文中,我们将讨论client-go使用的基本知识,以及如何为开发人员节约编写实际应用程序逻辑所需的时间。我们还将展示使用该软件包的最佳实践,并从每天与Kubernetes进行集成工作的开发人员的角度,分享我们已有的经验。内容将包括:

- 集群中的客户端认证 vs. 集群外的客户端认证
- 基本列表,使用client-go去创建和删除Kubernetes对象的操作
- 如何使用ListWatch和Informers监视K8s事件并做出反应
- 如何管理软件包依赖

Kubernetes是一个平台
Kubernetes有很多受欢迎的地方。用户喜欢它的丰富功能、稳定性和性能。对贡献者来说,Kubernetes开源社区不仅规模庞大,还易于上手、反馈迅速。而真正让Kubernetes吸引了第三方开发者的是它的可扩展性。该项目提供了很多方式来添加新功能、扩展现有功能而且不会中断主代码库。正是这些,使得Kubernetes发展成为了一个平台。
这里有一些方式来扩展Kubernetes:



上图所示,你可以发现每个Kubernetes集群组件无论是Kubelet还是API服务器,都可以以某种方式进行扩展。今天我们将重点介绍一种“自定义控制器”的方式,从现在起我将它称为Kubernetes控制器(Kubernetes Controller),或者简单地称为控制器(Controller)。
Kubernetes控制器究竟是什么?
控制器最常见的定义是:使得系统的当前状态达到所期望的状态的代码。但这究竟是什么意思呢?我们以Ingress控制器为例。Ingress是一个Kubernetes资源,它能够对集群中服务的外部访问进行定义。通常采用HTTP并且有负载均衡支持。然而Kubernetes的核心代码中并没有ingress的实现。第三方控制器的实现将包含:
1.监控ingress/services/endpoint 资源的事件(创建、更新、删除)
2.程序内部或外部的负载均衡器
3.使用负载均衡器的地址来更新Ingress
“所期望的状态”在Ingress这里指的是IP地址指向运行着的负载均衡器,该均衡器由用户根据ingress规范定义的规则实现。并且由外部Ingress控制器负责将ingress资源转移到这一状态。
对相同的资源,控制器的实现以及部署他们的方式也可能会有所不同。你可以选择nginx控制器并将其部署到集群中的每个节点上作为守护进程集(Daemon Set),也可以选择在Kubernetes集群外部运行ingress控制器并且对F5编程作为负载均衡器。这里没有严格的规定,Kubernetes就是如此灵活。
Client-go
这里有几种获得Kubernetes集群及其资源相关信息的方法,你可以使用Dashboard、kubectl或者使用对Kubernetes API的编程式访问来实现。Client-go所有用Go语言编写的工具中使用最为广泛的库,还有许多其他语言的版本(java、python等)。如果你还没自己写过控制器,我推荐你首先去尝试go/client-go。Kubernetes是用Go编写的,而且我发现使用和主项目相同的语言来开发插件会更加方便。
我们来搭建吧…
要熟悉相关的平台和工具,最好的办法就是去实践,去实现一些东西。我们从简单入手,先实现一个如下的控制器:
1.监控Kubernetes节点
2.当节点上的镜像占用存储空间时进行警报,并且可以更改
这部分的实现,源码可以在这里找到:https://github.com/alena1108/kubecon2017
基本流程
#配置项目
作为一名开发者,我和Rancher Labs的同事们更愿意使用轻便简易的工具,在这里我将分享3个我最喜欢的工具,它们将帮助我们完成第一个项目。
1.go-skel – Go语言的微服务skeleton,只需执行run ./skel.sh test123即可,它会为新的go项目test123创建skeleton。
2.trash – Go语言的供应商管理工具。实际上这儿有很多依赖项管理工具,但是在临时依赖项管理方面,trash使用起来非常出色,而且简单。
3.dapper – 在一致性环境中对任何现有构建工具进行封装的一种工具
#添加client-go作为一个依赖项
为了方便使用client-go的代码,我们必须要将其设置为项目的依赖项。将它添加到vendor.conf文件中:



接着运行trash。它会将vendor.conf中定义的所有依赖项都拉到项目的vendor文件夹中。在这里需要确保client-go与你集群对应的Kubernetes版本是兼容的。
#创建一个客户端
在创建与Kubernetes API通信的客户端之前,我们必须要先决定如何运行我们的工具:是在Kubetnetes集群内部还是外部。当应用程序在集群内部运行时,对它进行容器化,部署成为Kubernetes pod。它还提供了一些额外的功能:你可以选择部署它的方式(Deamon set运行在每个节点上,或者作为n个副本的部署),配置针对它的健康检查等等。当应用程序在集群外部运行时,就需要自己来管理它。下面的配置可以让我们的工具变得更灵活,并且支持基于config flag定义客户端的两种方式:



我们将在调试应用程序时使用集群外部运行的方式,这样你不需要每次都构建镜像并且将其重新部署成Kubernetes pod。在测试好应用程序后,我们就可以构建镜像并将其部署到集群中。
正如在截图中看到的那样,正在构建配置,并将其传递到kubernetes.NewForConfig来生成客户端。
#使用基本的CRUDs
我们的工具需要监控节点。在实现逻辑流程之前,我们先来熟悉使用client-go执行CRUD操作:



上面的截图展示了:
1.List节点minikube,是经过FieldSelector过滤器实现的
2.用新的标注来更新节点
3.使用gracePerios=10秒指令删除节点—意思是从该命令执行后10秒才会执行删除操作
上面所有的步骤都是使用我们之前创建的用户集(clientset)进行的。
我们还需要节点上镜像的相关信息;它可以通过访问相应的字段来检索:



#使用Informer来进行监控/通知
现在我们知道了如何从Kubernetes APIs中获取节点并从中得到镜像信息。那么我们该如何监控镜像大小的变化呢?最简单的方法是周期性轮询节点,计算当前的镜像存储容量,并将其和先前轮询的结果比较。这里的不足之处在于:无论节点是否发生变化,我们执行的列表调用都会获取所有的节点,这可能会很费资源——特别是当轮询间隔很短的时候。而我们真正想要实现的是—在节点发生变化时得到通知,只有在这之后才执行我们的逻辑流程。这些就是client-go的Informer来做的。



在这个例子中,我们经过watchList指令为节点对象创建Informer来监控节点,设置对象类型为api.Node和30秒的同步周期来周期性地轮询节点,无论节点是否发生改变——这种方式在更新事件出于某种原因发生终止时可以很好的进行撤回。在最后一个参数,我们传递了2个回调函数——handleNodeAdd和handleNodeUpdate。这些回调函数具有实际的逻辑,并且在节点上的镜像占用存储发生改变时触发。NewInformer返回2个对象——controller和store。一旦controller启动,将会开始对node.update和node.add的监控,并且调用回调函数。这部分代码的存储区位于内存缓存中,由informer负责更新,另外你可以在缓存区中获取节点对象而不用直接调用Kubernetes APIs:



我们的项目中只有一个控制器,使用常规的Informer就足够了。不过,如果未来你的项目最终同一个对象拥有了多个控制器,我建议你使用SharedInformer。这样一来你不用再一个一个为每个控制器配上Informer,只需要注册一个Shared informer即可,并且让每个控制器注册自己的一组回调函数,返回共享缓存,这可以减少内存占用:



部署时间
是时候来部署和测试代码了!对于第一次运行,我们只需要创建一个go的二进制文件并且在集群外模式下运行它即可:



如要更改消息输出,那么使用镜像部署一个pod,该镜像是没有在当前节点显示的镜像。
在基本的功能通过测试之后,接下来就是按照集群模式尝试运行它了。为此我们必须先创建镜像,定义它的Dockerfile:



并使用docker build创建一个镜像,该命令将生成一个可用在Kubernetes中部署pod的镜像。现在你的应用程序可以作为一个pod运行在Kubernetes集群上了。这里是一个部署定义的例子,在之前的截图中,我使用了该例部署我们的应用程序:



在本文中我们做了如下工作:
1.创建go项目
2.为项目添加client-go包的依赖项
3.创建用于和Kubernetes api通信的客户端
4.定义一个用于监控节点对象改变,并且一旦发生就执行回调函数的Informer
5.在回调函数中实现一个实际的逻辑
6.在集群外运行二进制文件来测试代码,并把它部署到集群中

推荐阅读
http://mp.weixin.qq.com/s/4-cFeRsa0J4tr6GE91Grag
http://mp.weixin.qq.com/s/2qZHVM-JrJ4kaKt4qBhq5w
http://mp.weixin.qq.com/s/prP9pFTXG6EHoDGoh_nyEA
http://mp.weixin.qq.com/s/FJ2D34ewH_bL8kSM-eUE1g

如若转载,请注明出处谢谢!
微信号:RancherLabs

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

alex_wang2 发表了文章 • 0 个评论 • 4686 次浏览 • 2017-12-19 22:11 • 来自相关话题

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

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

你可以从这里找到这篇章中使用的例子,跟随我们一步一步的进行。
#第1步 最简单的http服务程序
下面就是这个程序:

`main.go`
package main

import (
"fmt"
"net/http"
)

func main() {
http.HandleFunc("/home", func(w http.ResponseWriter, _ *http.Request) {
fmt.Fprint(w, "Hello! Your request was processed.")
},
)
http.ListenAndServe(":8000", nil)
}

如果是第一次运行,仅仅执行`go run main.go`就可以了。如果你想知道它是怎么工作的,你可以用下面这个命令:`curl -i http://127.0.0.1:8000/home`。但是当我们运行这个应用的时候,我们找不到任何关于状态的信息。
#第2步 增加日志
首先,增加日志功能可以帮助我们了解程序现在处于一个什么样的状态,并记录错误(译者注:如果有错误的话)等其他一些重要信息。在这个例子里我们使用Go语言标准库里最简单的日志模块,但是如果是跑在Kubernetes上的服务程序,你可能还需要一些额外的库,比如glog或者logrus

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

`main.go`
func main() {
log.Print("Starting the service...")

http.HandleFunc("/home", func(w http.ResponseWriter, _ *http.Request) {
fmt.Fprint(w, "Hello! Your request was processed.")
},
)

log.Print("The service is ready to listen and serve.")
log.Fatal(http.ListenAndServe(":8000", nil))
}

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

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

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

`handler/handers.go`
package handlers

import (
"github.com/gorilla/mux"
)

// Router register necessary routes and returns an instance of a router.
func Router() *mux.Router {
r := mux.NewRouter()
r.HandleFunc("/home", home).Methods("GET")
return r
}

`handlers/home.go`
package handlers

import (
"fmt"
"net/http"
)

// home is a simple HTTP handler function which writes a response.
func home(w http.ResponseWriter, _ *http.Request) {
fmt.Fprint(w, "Hello! Your request was processed.")
}

然后我们稍微修改一下`main.go`:
package main

import (
"log"
"net/http"

"github.com/rumyantseva/advent-2017/handlers"
)

// How to try it: go run main.go
func main() {
log.Print("Starting the service...")
router := handlers.Router()
log.Print("The service is ready to listen and serve.")
log.Fatal(http.ListenAndServe(":8000", router))
}

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

`handlers/handles_test.go`
package handlers

import (
"net/http"
"net/http/httptest"
"testing"
)

func TestRouter(t *testing.T) {
r := Router()
ts := httptest.NewServer(r)
defer ts.Close()

res, err := http.Get(ts.URL + "/home")
if err != nil {
t.Fatal(err)
}
if res.StatusCode != http.StatusOK {
t.Errorf("Status code for /home is wrong. Have: %d, want: %d.", res.StatusCode, http.StatusOK)
}

res, err = http.Post(ts.URL+"/home", "text/plain", nil)
if err != nil {
t.Fatal(err)
}
if res.StatusCode != http.StatusMethodNotAllowed {
t.Errorf("Status code for /home is wrong. Have: %d, want: %d.", res.StatusCode, http.StatusMethodNotAllowed)
}

res, err = http.Get(ts.URL + "/not-exists")
if err != nil {
t.Fatal(err)
}
if res.StatusCode != http.StatusNotFound {
t.Errorf("Status code for /home is wrong. Have: %d, want: %d.", res.StatusCode, http.StatusNotFound)
}
}

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

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

`handlers/home_test.go`
package handlers

import (
"io/ioutil"
"net/http"
"net/http/httptest"
"testing"
)

func TestHome(t *testing.T) {
w := httptest.NewRecorder()
home(w, nil)

resp := w.Result()
if have, want := resp.StatusCode, http.StatusOK; have != want {
t.Errorf("Status code is wrong. Have: %d, want: %d.", have, want)
}

greeting, err := ioutil.ReadAll(resp.Body)
resp.Body.Close()
if err != nil {
t.Fatal(err)
}
if have, want := string(greeting), "Hello! Your request was processed."; have != want {
t.Errorf("The greeting is wrong. Have: %s, want: %s.", have, want)
}
}

现在我们运行`go tests`来检查代码的正确性:
$ go test -v ./...
? github.com/rumyantseva/advent-2017 [no test files]
=== RUN TestRouter
--- PASS: TestRouter (0.00s)
=== RUN TestHome
--- PASS: TestHome (0.00s)
PASS
ok github.com/rumyantseva/advent-2017/handlers 0.018s

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

`main.go`
package main

import (
"log"
"net/http"
"os"

"github.com/rumyantseva/advent-2017/handlers"
)

// How to try it: PORT=8000 go run main.go
func main() {
log.Print("Starting the service...")

port := os.Getenv("PORT")
if port == "" {
log.Fatal("Port is not set.")
}

r := handlers.Router()
log.Print("The service is ready to listen and serve.")
log.Fatal(http.ListenAndServe(":"+port, r))
}

在这个例子里,如果没有设置端口,应用程序会退出并返回一个错误。因为如果配置错误了,就没有必要再继续执行了。
#第6步 Makefile
几天以前有一篇文章介绍`make`工具,如果你有一些重复性比较强的工作,那么使用它就大有帮助。现在我们来看一看我们的应用程序如何使用它。当前,我们有两个操作,测试和编译并运行。我们对Makefile文件进行了如下修改。但是我们用`go build`代替了`go run`,并且运行那个编译出来的二进制程序,因为这样修改更适合为我们的生产环境做准备:

`Makefile`
APP?=advent
PORT?=8000

clean:
rm -f ${APP}

build: clean
go build -o ${APP}

run: build
PORT=${PORT} ./${APP}

test:
go test -v -race ./...

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

这里,为了运行应用程序,我们需要删除掉旧的程序(如果它存在的话),编译代码并用环境变量代表的参数运行新编译出的程序,做这些操作,我们仅仅需要执行`make run`。
#第7步 版本控制
下一步,我们将为我们的程序加入版本控制。因为有的时候,它对我们知道正在生产环境中运行和编译的代码非常有帮助。(译者注:就是说,我们在生产环境中运行的代码,有的时候我们自己都不知道对这个代码进行和什么样的提交和修改,有了版本控制,就可以显示出这个版本的变化和历史记录)。

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

`version/version.go`
package version

var (
// BuildTime is a time label of the moment when the binary was built
BuildTime = "unset"
// Commit is a last commit hash at the moment when the binary was built
Commit = "unset"
// Release is a semantic version of current build
Release = "unset"
)

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

`main.go`
...
func main() {
log.Printf(
"Starting the service...\ncommit: %s, build time: %s, release: %s",
version.Commit, version.BuildTime, version.Release,
)
...
}

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

`handlers/home.go`
package handlers

import (
"encoding/json"
"log"
"net/http"

"github.com/rumyantseva/advent-2017/version"
)

// home is a simple HTTP handler function which writes a response.
func home(w http.ResponseWriter, _ *http.Request) {
info := struct {
BuildTime string `json:"buildTime"`
Commit string `json:"commit"`
Release string `json:"release"`
}{
version.BuildTime, version.Commit, version.Release,
}

body, err := json.Marshal(info)
if err != nil {
log.Printf("Could not encode info data: %v", err)
http.Error(w, http.StatusText(http.StatusServiceUnavailable), http.StatusServiceUnavailable)
return
}
w.Header().Set("Content-Type", "application/json")
w.Write(body)
}

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

为`Makefile`增加一些变量:

`Makefile`
RELEASE?=0.0.1
COMMIT?=$(shell git rev-parse --short HEAD)
BUILD_TIME?=$(shell date -u '+%Y-%m-%d_%H:%M:%S')

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

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

`Makefile`
build: clean
go build \
-ldflags "-s -w -X ${PROJECT}/version.Release=${RELEASE} \
-X ${PROJECT}/version.Commit=${COMMIT} -X ${PROJECT}/version.BuildTime=${BUILD_TIME}" \
-o ${APP}

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

`Makefile`
PROJECT?=github.com/rumyantseva/advent-2017


所有的变化都可以在这里找到,现在可以用`make run`去运行它了。
#第8步 减少一些依赖
这里有一些代码里我不喜欢的地方:`handle`pakcage依赖于`version`package。这个很容易修改:我们需要让`home` 处理变得可以配置。

`handler/home.go`
// home returns a simple HTTP handler function which writes a response.
func home(buildTime, commit, release string) http.HandlerFunc {
return func(w http.ResponseWriter, _ *http.Request) {
...
}
}

别忘了同时去修改测试和必须的环境变量。
#第9步 健康检查
在某些情况下,我们需要经常对运行在Kubernetes里的服务程序进行健康检查:liveness and readiness probes。这么做的目的是为了知道容器里的应用程序是否还在运行。如果liveness探测失败,这个服务程序将会被重启,如果readness探测失败,说明服务还没有准备好。

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

`handlers/healthz.go`
// healthz is a liveness probe.
func healthz(w http.ResponseWriter, _ *http.Request) {
w.WriteHeader(http.StatusOK)
}

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

`handlers/readyz.go`
// readyz is a readiness probe.
func readyz(isReady *atomic.Value) http.HandlerFunc {
return func(w http.ResponseWriter, _ *http.Request) {
if isReady == nil || !isReady.Load().(bool) {
http.Error(w, http.StatusText(http.StatusServiceUnavailable), http.StatusServiceUnavailable)
return
}
w.WriteHeader(http.StatusOK)
}
}

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

现在我们看看怎么使用:

`handles.go`
func Router(buildTime, commit, release string) *mux.Router {
isReady := &atomic.Value{}
isReady.Store(false)
go func() {
log.Printf("Readyz probe is negative by default...")
time.Sleep(10 * time.Second)
isReady.Store(true)
log.Printf("Readyz probe is positive.")
}()

r := mux.NewRouter()
r.HandleFunc("/home", home(buildTime, commit, release)).Methods("GET")
r.HandleFunc("/healthz", healthz)
r.HandleFunc("/readyz", readyz(isReady))
return r
}

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

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

`main.go`
func main() {
...
r := handlers.Router(version.BuildTime, version.Commit, version.Release)

interrupt := make(chan os.Signal, 1)
signal.Notify(interrupt, os.Interrupt, os.Kill, syscall.SIGTERM)

srv := &http.Server{
Addr: ":" + port,
Handler: r,
}
go func() {
log.Fatal(srv.ListenAndServe())
}()
log.Print("The service is ready to listen and serve.")

killSignal := <-interrupt
switch killSignal {
case os.Kill:
log.Print("Got SIGKILL...")
case os.Interrupt:
log.Print("Got SIGINT...")
case syscall.SIGTERM:
log.Print("Got SIGTERM...")
}

log.Print("The service is shutting down...")
srv.Shutdown(context.Background())
log.Print("Done")
}

这里,我们会捕获系统信号,如果发现有`SIGKILL`,`SIGINT`或者`SIGTERM`,我们将优雅的关闭程序。
#第11步 Dockerfile
我们的应用程序马上就以运行在Kubernetes里了,现在我们把它容器化。

下面是一个最简单的Dockerfile:

`Dockerfile`
FROM scratch

ENV PORT 8000
EXPOSE $PORT

COPY advent /
CMD ["/advent"]

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

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

`Makefile`
...

GOOS?=linux
GOARCH?=amd64

...

build: clean
CGO_ENABLED=0 GOOS=${GOOS} GOARCH=${GOARCH} go build \
-ldflags "-s -w -X ${PROJECT}/version.Release=${RELEASE} \
-X ${PROJECT}/version.Commit=${COMMIT} -X ${PROJECT}/version.BuildTime=${BUILD_TIME}" \
-o ${APP}

container: build
docker build -t $(APP):$(RELEASE) .

run: container
docker stop $(APP):$(RELEASE) || true && docker rm $(APP):$(RELEASE) || true
docker run --name ${APP} -p ${PORT}:${PORT} --rm \
-e "PORT=${PORT}" \
$(APP):$(RELEASE)

...

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

现在我们终于可以用`make run`去检验一下整个过程了。
#第12步 发布
在我们的项目里,我们还依赖一个外部的包(github.com/gorilla/mux)。而且,我们需要为生产环境里的
readness安装依赖管理。所以我们用了dep之后我们唯一要做的就是运行`dep init`:
$ dep init
Using ^1.6.0 as constraint for direct dep github.com/gorilla/mux
Locking in v1.6.0 (7f08801) for direct dep github.com/gorilla/mux
Locking in v1.1 (1ea2538) for transitive dep github.com/gorilla/context

这个工具会创建两个文件`Gopkg.toml`和`Gopkg.lock`,还有一个目录`vendor`,个人认为,我会把`vendor`放到git上去,特别是对与那些比较重要的项目来说。
#第13步 Kubernetes
这也是最后一步了。运行一个应用程序到Kubernetes上。最简单的方法就是在本地去安装和配置一个minikube(这是一个单点的kubernetes测试环境)。

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

`Makefile:`
CONTAINER_IMAGE?=docker.io/webdeva/${APP}

...

container: build
docker build -t $(CONTAINER_IMAGE):$(RELEASE) .

...

push: container
docker push $(CONTAINER_IMAGE):$(RELEASE)

这个`CONTAINER_IMAGE`变量用来定义一个镜像的名字,我们用这个镜像存放我们的服务程序。如你所见,在这个例子里包含了我的用户名(`webdeva`)。如果你在hub.docker.com上没有账户,那你就先得创建一个,然后用`docker login`命令登陆,这个时候你就可以推送你的镜像了。

现在我们试一下`make push`:
$ make push
...
docker build -t docker.io/webdeva/advent:0.0.1 .
Sending build context to Docker daemon 5.25MB
...
Successfully built d3cc8f4121fe
Successfully tagged webdeva/advent:0.0.1
docker push docker.io/webdeva/advent:0.0.1
The push refers to a repository [docker.io/webdeva/advent]
ee1f0f98199f: Pushed
0.0.1: digest: sha256:fb3a25b19946787e291f32f45931ffd95a933100c7e55ab975e523a02810b04c size: 528

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

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

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

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

`deployment.yaml`
apiVersion: extensions/v1beta1
kind: Deployment
metadata:
name: {{ .ServiceName }}
labels:
app: {{ .ServiceName }}
spec:
replicas: 3
strategy:
type: RollingUpdate
rollingUpdate:
maxUnavailable: 50%
maxSurge: 1
template:
metadata:
labels:
app: {{ .ServiceName }}
spec:
containers:
- name: {{ .ServiceName }}
image: docker.io/webdeva/{{ .ServiceName }}:{{ .Release }}
imagePullPolicy: Always
ports:
- containerPort: 8000
livenessProbe:
httpGet:
path: /healthz
port: 8000
readinessProbe:
httpGet:
path: /readyz
port: 8000
resources:
limits:
cpu: 10m
memory: 30Mi
requests:
cpu: 10m
memory: 30Mi
terminationGracePeriodSeconds: 30

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

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

`service.yaml`
apiVersion: v1
kind: Service
metadata:
name: {{ .ServiceName }}
labels:
app: {{ .ServiceName }}
spec:
ports:
- port: 80
targetPort: 8000
protocol: TCP
name: http
selector:
app: {{ .ServiceName }}

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

`ingress.yaml`
apiVersion: extensions/v1beta1
kind: Ingress
metadata:
annotations:
kubernetes.io/ingress.class: nginx
ingress.kubernetes.io/rewrite-target: /
labels:
app: {{ .ServiceName }}
name: {{ .ServiceName }}
spec:
backend:
serviceName: {{ .ServiceName }}
servicePort: 80
rules:
- host: advent.test
http:
paths:
- path: /
backend:
serviceName: {{ .ServiceName }}
servicePort: 80

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

运行`minikube`,需要开启ingress并且准备好`kubectl`,我们要用它运行一些命令:
minikube start
minikube addons enable ingress
kubectl config use-context minikube

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

`Makefile`
minikube: push
for t in $(shell find ./kubernetes/advent -type f -name "*.yaml"); do \
cat $$t | \
gsed -E "s/\{\{(\s[i])\.Release(\s[/i])\}\}/$(RELEASE)/g" | \
gsed -E "s/\{\{(\s[i])\.ServiceName(\s[/i])\}\}/$(APP)/g"; \
echo ---; \
done > tmp.yaml
kubectl apply -f tmp.yaml

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

现在我们来看一下我的工作成果:
$ kubectl get deployment
NAME DESIRED CURRENT UP-TO-DATE AVAILABLE AGE
advent 3 3 3 3 1d

$ kubectl get service
NAME CLUSTER-IP EXTERNAL-IP PORT(S) AGE
advent 10.109.133.147 80/TCP 1d

$ kubectl get ingress
NAME HOSTS ADDRESS PORTS AGE
advent advent.test 192.168.64.2 80 1d

现在我们可以发送一个http的请求到我们的服务上,但是首先还是要把域名`adventtest`增加到`/etc/host`文件里:
echo "$(minikube ip) advent.test" | sudo tee -a /etc/hosts 

现在,我们终于可以使用我们的服务了:
curl -i http://advent.test/home
HTTP/1.1 200 OK
Server: nginx/1.13.6
Date: Sun, 10 Dec 2017 20:40:37 GMT
Content-Type: application/json
Content-Length: 72
Connection: keep-alive
Vary: Accept-Encoding

{"buildTime":"2017-12-10_11:29:59","commit":"020a181","release":"0.0.5"}%

看,它工作了!

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

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

非常感谢 Natalie PistunovichPaul BrousseauSandor Szücs等人的的建议和审核。

原文链接:Write a Kubernetes-ready service from zero step-by-step(翻译:王晓轩)

基于Go技术栈的微服务构建

ucloud 发表了文章 • 0 个评论 • 2204 次浏览 • 2017-11-29 11:21 • 来自相关话题

在大型系统的微服务化构建中,一个系统会被拆分成许多模块。这些模块负责不同的功能,组合成系统,最终可以提供丰富的功能。在这种构建形式中,开发者一般会聚焦于最大程度解耦模块的功能以减少模块间耦合带来的额外开发成本。同时,微服务面临着如何部署这些大量的服务系统、如何 ...查看全部
在大型系统的微服务化构建中,一个系统会被拆分成许多模块。这些模块负责不同的功能,组合成系统,最终可以提供丰富的功能。在这种构建形式中,开发者一般会聚焦于最大程度解耦模块的功能以减少模块间耦合带来的额外开发成本。同时,微服务面临着如何部署这些大量的服务系统、如何运维这些系统等新问题。

本文的素材来源于我们在开发中的一些最佳实践案例,从开发、监控、日志等角度介绍了一些我们基于Go技术栈的微服务构建经验。

开 发

微服务的开发过程中,不同模块由不同的开发者负责,明确定义的接口有助于确定开发者的工作任务。最终的系统中,一个业务请求可能会涉及到多次接口调用,如何准确清晰的调用远端接口,这也是一大挑战。对于这些问题,我们使用了gRPC来负责协议的制订和调用。

传统的微服务通常基于http协议来进行模块间的调用,而在我们的微服务构建中,选用了Google推出的gRPC框架来进行调用。下面这张简表比较了http rpc框架与gRPC的特性:


gRPC的接口需要使用Protobuf3定义,通过静态编译后才能成功调用。这一特性减少了由于接口改变带来的沟通成本。如果使用http rpc,接口改变就需要先改接口文档,然后周知到调用者,如果调用者没有及时修改,很可能会到服务运行时才能发现错误。而gRPC的这种模式,接口变动引起的错误保证在编译时期就能消除。

在性能方面,gRPC相比传统的http rpc协议有非常大的改善(根据这个评测,gRPC要快10倍)。gRPC使用http 2协议进行传输,相比较http 1.1, http 2复用tcp连接,减少了每次请求建立tcp连接的开销。需要指出的是,如果单纯追求性能,之前业界一般会选用构建在tcp协议上的rpc协议(thrift等),但四层协议无法方便的做一些传输控制。相比而言,gRPC可以在http header中放入控制字段,配合nginx等代理服务器,可以很方便的实现转发/灰度等功能。

接下来着重谈谈我们在实践中如何使用gRPC的一些特性来简化相关开发流程。

1. 使用context来控制请求的生命周期

在gRPC的go语言实现中,每个rpc请求的第一个参数都是context。http2协议会将context放在HEADER中,随着链路传递下去,因此可以为每个请求设置过期时间,一旦遇到超时的情况,发起方就会结束等待,返回错误。

ctx := context.Background() // blank context

ctx, cancel = context.WithTimeout(ctx, 5*time.Second)

defer cancel( )

grpc.CallServiveX(ctx, arg1)

上述这段代码,发起方设置了大约5s的等待时间,只要远端的调用在5s内没有返回,发起方就会报错。

除了能加入超时时间,context还能加入其他内容,下文我们还会见到context的另一个妙用。

2.使用TLS实现访问权限控制

gRPC集成了TLS证书功能,为我们提供了很完善的权限控制方案。在实践中,假设我们的系统中存在服务A,由于它负责操作用户的敏感内容,因此需要保证A不被系统内的其他服务滥用。为了避免滥用,我们设计了一套自签名的二级证书系统,服务A掌握了自签名的根证书,同时为每个调用A的服务颁发一个二级证书。这样,所有调用A的服务必须经过A的授权,A也可以鉴别每个请求的调用方,这样可以很方便的做一些记录日志、流量控制等操作。

3. 使用trace在线追踪请求

gRPC内置了一套追踪请求的trace系统,既可以追踪最近10个请求的详细日志信息,也可以记录所有请求的统计信息。

当我们为请求加入了trace日志后,trace系统会为我们记录下最近10个请求的日志,下图中所示的例子就是在trace日志中加入了对业务数据的追踪。


在宏观上,trace系统为我们记录下请求的统计信息,比如请求数目、按照不同请求时间统计的分布等。



需要说明的是,这套系统暴露了一个http服务,我们可以通过debug开关在运行时按需打开或者关闭,以减少资源消耗。

监控

1.确定监控指标

在接到为整个系统搭建监控系统这个任务时,我们面对的第一个问题是要监控什么内容。针对这个问题,GoogleSRE这本书提供了很详细的回答,我们可以监控四大黄金指标,分别是延时、流量、错误和饱和度。

延时衡量了请求花费的时间。需要注意的,考虑到长尾效应,使用平均延时作为延时方面的单一指标是远远不够的。相应的,我们需要延时的中位数90%、95%、99%值来帮助我们了解延时的分布,有一种更好的办法是使用直方图来统计延时分布。

流量衡量了服务面临的请求压力。针对每个API的流量统计能让我们知道系统的热点路径,帮助优化。
错误监控是指对错误的请求结果的统计。同样的,每个请求有不同的错误码,我们需要针对不同的错误码进行统计。配合上告警系统,这类监控能让我们尽早感知错误,进行干预。

饱和度主要指对系统CPU和内存的负载监控。这类监控能为我们的扩容决策提供依据。

2.监控选型

选择监控方案时,我们面临的选择主要有两个,一是公司自建的监控系统,二是使用开源Prometheus系统搭建。这两个系统的区别列在下表中。



考虑到我们的整个系统大约有100个容器分布在30台虚拟机上,Prometheus的单机存储对我们并不是瓶颈。我们不需要完整保留历史数据,自建系统的最大优势也不足以吸引我们使用。相反,由于希望能够统计四大黄金指标延生出的诸多指标,Prometheus方便的DSL能够很大程度上简化我们的指标设计。

最终,我们选择了Prometheus搭建监控系统。整个监控系统的框架如下图所示。


各服务将自己的地址注册到consul中,Prometheus会自动从consul中拉取需要监控的目标地址,然后从这些服务中拉取监控数据,存放到本地存储中。在Prometheus自带的Web UI中可以快捷的使用PromQL查询语句获取统计信息,同时,还可以将查询语句输入grafana,固定监控指标用于监控。


此外,配合插件AlertManager,我们能够编写告警规则,当系统出现异常时,将告警发送到手机/邮件/信箱。

日志

1.日志格式

一个经常被忽略的问题是如何选择日志记录的格式。良好的日志格式有利于后续工具对日志内容的切割,便于日志存储的索引。我们使用logrus来打印日志到文件,logrus工具支持的日志格式包裹以空格分隔的单行文本格式、json格式等等。

文本格式

time=”2015-03-26T01:27:38-04:00″ level=debug g=”Started observing beach” animal=walrus number=8

time=”2015-03-26T01:27:38-04:00″ level=info msg=”A group of walrus emerges from the ocean” animal=walrus size=10Json格式

{“animal”:”walrus”,”level”:”info”,”msg”:”A group of walrus emerges from theocean”,”size”:10,”time”:”2014-03-10 19:57:38.562264131 -0400 EDT”}

{“level”:”warning”,”msg”:”The group’s number increased tremendously!”,”number”:122,”omg”:true,”time”:”2014-03-10 19:57:38.562471297 -0400 EDT”}

2.端到端链路上的调用日志收集

在微服务架构中,一个业务请求会经历多个服务,收集端到端链路上的日志能够帮助我们判断错误发生的具体位置。在这个系统中,我们在请求入口处,生成了全局ID,通过gRPC中的context将ID在链路中传递。将不同服务的日志收集到graylog中,查询时就能通过一个ID,将整个链路上的日志查询出来。


上图中,使用session-id来作为整个调用链的ID可以进行全链路检索。

小结

微服务构建的系统中,在部署、调度、服务发现、一致性等其他方面都有挑战,Go技术栈在这些方面都有最佳实践(docker,k8s,consul,etcd等等)。具体内容在网上已经有很完善的教程,在此不用班门弄斧,有需要的可以自行查阅。

Docker 学习笔记 ( 一 ): 简介以及构架剖析

腾讯云技术社区 发表了文章 • 0 个评论 • 18292 次浏览 • 2017-10-20 16:53 • 来自相关话题

欢迎大家前往腾讯云技术社区,获取更多腾讯海量技术实践干货哦~ 作者:陈云 导语 之前一直忙于项目工作,一直没有好好去系统学习,完善自己的知识体系,以致于在腾讯两年知识体系都没 ...查看全部
欢迎大家前往腾讯云技术社区,获取更多腾讯海量技术实践干货哦~

作者:陈云



导语

之前一直忙于项目工作,一直没有好好去系统学习,完善自己的知识体系,以致于在腾讯两年知识体系都没有深度发展,还是停留在原来的领域,所以从8月份开始学习docker。中间穿插的学习了linux内核和python爬虫。

Docker简介

Docker是2013发起的一个项目,早在2013年,Docker自诞生起,就是整个技术界的明星项目,当时我还在上海实习,就在各种技术媒体上看到了Docker的介绍文章,很多技术媒体宣称docker是一项技术突破,并且是一次技术革命,可惜当时由于本身是一个Android Framework开发者,眼界很低,对于这种OS虚拟化技术有点不屑一顾,而今转后台后才发现这项技术的重要性

Docker的特征

Docker是一个云开源项目,托管在github,任何人都可以通过 git clone 或者fork参与进来,本身是基于linux的容器技术,采用当时如日中天google新推出的Go语言实现。采用apache 2.0协议开源。

docker镜像地址

Go语言与Docker

相比Go语言与其它语言的对比,国内外很多技术媒体都有列举,在Docker领域,Go语言相比其它语言的优势在于

  • 相对于C/C 开发难度低,支持向前兼容,运维维护成本小
  • 相对于python,生成的是静态文件,有效的避免的低级错误,并且性能高一个等级
  • 并发性好,内存占用低
  • 部署简单,毕竟生成的静态文件,有glibc的地方就能运行
一门语言当然也有自己的缺点,比如,内存回收延迟久,图片处理库有bug,对包版本要求严格等一些问题,但是瑕不掩瑜,一个开发成本极其简单,性能优良,部署简单的语言与Docker简直就是 天作之合至于Go语言的优势,在Go的社区中都有非常详尽的讨论,这里不多讲 Docker的目标 Docker的是一个轻量级的操作系统虚拟化解决方案。 主要目标,用官网的概括来说就是“Build,Ship and Run Any App,Anywhere”:编译,装载任何App,在任何地方都可以运行,我们大概理解就是一个容器,实现了对应用的封装,部署,运行等生命周期管理,只要在glibc的环境下,到处都可以运行。这点在企业的云服务部署是有非常广泛的应用前景。后面我们将详细讨论。 Docker的引擎 Docker的是基于Linux自带的(Linux。 Containers,LXC)技术,在LXC上,Docker进行了近一步封装。正因为如此,Docker只能在Linux环境下运行,当然,前段时间docker终于支持OSX和Windows了,虽然还是体验尝鲜版,但更加方便开发者去开发了! Docker的原理 其实前面讲了这么多,Docker的原理已经不言而喻,这里用IBM的解释就是容器有效的将单个操作系统管理的资源划分到孤立的组中,以便更好的在孤立的组之间平衡有冲突的资源使用需求。与虚拟化相比,这样既不需要指令级模拟,也不需要即时编译。容器可以在核心CPU本地运行指令,而不需要任何专门的解释机制。此外,也避免了准虚拟化(paravirtualization)和系统调用替换中的复杂性。简而言之就是,Docker是一个盒子,一个盒子装一个玩具,无论你丢在哪里,你给他通电(glibc),他就能运行。你的玩具大就用大盒子,小玩具就用小盒子。两个应用之间的环境是环境是完全隔离的,建立通信机制来互相调用。容器的创建和停止都十分快速(秒级),容器自身对资源的需求十分有限,远比虚拟机本身占用的资源少。 Docker VS VM Docker与虚拟机(虚拟机)的区别可以看:
1.jpg
左图是虚拟机的工作原理图,对资源进行抽象,着重体现在硬件层面的虚拟化上,这种方式增加了两场调用链,对性能的损耗比较大,而且还会占用大量的内存资源有图是Docker的工作原理图,属于OS级别的虚拟化,kernel通过创建多个镜像来隔离不同的app进程,由于kernel是是共享,而且本身linux image也不大,性能损耗几乎可以不计,而且内存占用也不大,大大节约了设备成本。 Docker架构总览
2.png
最核心的是 Docker Daemon我们称之为Docker守护进程,也就是Server端,Server端可以部署在远程,也可以部署在本地,因为Server端与客户端(Docker Client)是通过Rest API进行通信。docker CLI 实现容器和镜像的管理,为用户提供统一的操作界面,这个 客户端提供一个只读的镜像,然后通过镜像可以创建一个或者多个容器(container),这些容器可以只是一个RFS(Root File System),也可以是一个包含了用户应用的RFS。容器在docker Client中只是一个进程,两个进程是互不可见的。用户不能与server直接交互,但可以通过与容器这个桥梁来交互,由于是操作系统级别的虚拟技术,中间的损耗几乎可以不计注:CLI:command line interface。命令行接口.RFS:Root File System 根文件系统. Image & Container 在docker中,我们重点关注的就是镜像和容器了。因为在实际应用中,我们封装好镜像,然后通过镜像来创建容器,在容器运行我们的应用就好了。而server端掌控网络和磁盘,我们不用去关心,启动docker sever 和 docker client都是一条命令的事情。后面会详细讲docker的启动过程。Image: 一个只读的镜像模板。可以自己创建一个镜像也可以从网站上下载镜像供自己使用。镜像包含了一个RFS.一个镜像可以创建很多容器。Container:由docker client通过镜像创建的实例,用户在容器中运行应用,一旦创建后就可以看做是一个简单的RFS,每个应用运行在隔离的容器中,享用独自的权限,用户,网络。确保安全与互相干扰两者在创建后,都是一堆layer的统一视角,唯一的却别是镜像最上面那一层是只读的,不可以修改,但是容器最上面一层是rw的,提供给用户操作repository:仓库,这个东西没有单独介绍不是因为它不重要,而是因为之前做个比较多的Android源码编译,所以这里就没有仔细往下看,大概就是一个镜像库,最大的是docker hub,类似于google 的aosp,当然也可以本地搭,比如mig事业群就有自己的repo。 Docker的应用 最后,这里讲一下docker的应用作为本文的终结。A:为什么会想起来学习docker技术2016年年中的时候,我转做后台,经历了一段时间的时候痛苦转型后,中学摸到了门槛,年底赶上事业群的服务器docker化,那段时间非常痛苦,因为相对实体机或者虚拟机,各种问题频出,因为虚拟机或者实体机是不会迁移的,我们部署一套服务后会有一些依赖库需要安装,但是那段时间docker经常迁移,之前也没有接触过docker,导致问题频出。到2017年4月的时候,docker基本稳定下来,我们也开始享受docker带来的种种便利,比如:
  • 发布服务再也不用care服务器的运行环境了,所有的服务器都是自动分配docker,自动部署,自动安装,自动运行
  • 再也不用担心其他服务引起的磁盘问题,cpu问题,系统问题了。之前我们固定在一台idc上发布我们所有的服务,导致后面这台idc上挂了200多个服务,日志文件经常导致磁盘爆满,一旦磁盘爆满,200多个服务就要挂掉一半服务。
  • 更好的资源利用,因为今年还没有数据出来,但个人预计是会给公司节省一半的服务器资源,既避免了资源浪费的同时,又保证了服务的稳定运行
  • 自动迁移,学历了docker后,我们可以自己制作镜像,后面服务迁移的时候,只要使用我们自己的镜像,无论怎么迁移都不会出现任何问题
  • 对于运维来说,管理更加方便了。
目前MIG事业群已经全面接入了docker,也证明了docker容器技术的成功性。PS:
  • edhat 已经集中支持 Docker
  • Google 在PaaS 产品中广泛应用。

后记

看完docker整体架构后,内心真心感谢那些为了推动事业群docker化力排众议者,后面有时间自己也会去封装一些docker image,毕竟很多项目需要使用特有的环境,以往每次迁移都要去安装一次软件。

这篇文章是8月中开始写,写写断断,持续了一个半月,这一个半月项目组一直很忙,连续加了好几个周末。知道国庆才有时间好好整理一下所学。有时间再去写docker的启动流程。

纯手打的文章,写辣么多,路过就点个赞吧


相关阅读

码农小马与 Docker 不得不说的故事
Docker 入门实践
5 种 Docker 日志最佳实践

此文已由作者授权腾讯云技术社区发布,转载请注明文章出处
原文链接:https://cloud.tencent.com/community/article/739560

Mesos+ZooKeeper+Marathon+Docker分布式部署打造PaaS云平台实践(一)

赵英俊 发表了文章 • 5 个评论 • 20419 次浏览 • 2015-07-21 16:39 • 来自相关话题

【编者的话】本文先给出一个分布式部署的过程,在完成这种分布式部署的过程花费了我一个周末的时间,因为国内几乎没有找到分布式部署的实践过程记录,希望我的实践过程能够给有兴趣的小伙伴在进行分布式部署中提供一定的帮助。 最近开始对Mesos非 ...查看全部
【编者的话】本文先给出一个分布式部署的过程,在完成这种分布式部署的过程花费了我一个周末的时间,因为国内几乎没有找到分布式部署的实践过程记录,希望我的实践过程能够给有兴趣的小伙伴在进行分布式部署中提供一定的帮助。

最近开始对Mesos非常的感兴趣,Mesos和Docker一样是一个使用Go语言编写的新兴且具有活力的项目,因为最近痴迷学习Go语言,所以对使用Go语言编写的项目都比较看好。本文先给出一个分布式部署的过程,在完成这种分布式部署的过程花费了我一个周末的时间,因为国内几乎没有找到分布式部署的实践过程记录,希望我的实践过程能够给有兴趣的小伙伴在进行分布式部署中提供一定的帮助。之后我会在本系列博文的后半部分综合阐述一下当前Docker的一些应用场景,以及未来基于Mesos+ZooKeeper+Marathon+Docker分布式部署打造PaaS云平台的可行性。
#本文中用到的项目组件介绍(全部摘自网络)
Mesos:Mesos采用与Linux Kernel相同的机制,只是运行在不同的抽象层次上。Mesos Kernel利用资源管理和调度的API在整个数据中心或云环境中运行和提供引用(例如,Hadoop、Spark、Kafaka、Elastic Search)。

ZooKeeper:ZooKeeper是一个分布式的,开放源码的分布式应用程序协调服务,是Google的Chubby一个开源的实现,是Hadoop和HBase的重要组件。它是一个为分布式应用提供一致性服务的软件,提供的功能包括:配置维护、名字服务、分布式同步、组服务等。

Marathon:Marathon是一个Mesos框架,能够支持运行长服务,比如Web应用等。它是集群的分布式Init.d,能够原样运行任何Linux二进制发布版本,如Tomcat、Play等等。它也是一种私有的PaSS,实现服务的发现,为部署提供提供REST API服务,有授权和SSL、配置约束,通过HAProxy实现服务发现和负载平衡。

Docker:Docker 是一个开源的应用容器引擎,让开发者可以打包他们的应用以及依赖包到一个可移植的容器中,然后发布到任何流行的 Linux 机器上,也可以实现虚拟化。
#Mesos+ZooKeeper+Marathon+Docker分布式部署过程记录
Mesos为了管理的简单也采用了master-slave的架构,因此我本次使用了6个Ubuntu 14.04的虚拟机作为部署节点。其中3个mastser节点,3个slave节点。为直观的理解,我简单的画了一张架构图:
3AD0EBE4-D449-4E1F-B5FA-AB8CDC8F265B.png

如图所示其中master节点都需要运行ZooKeeper、Mesos-master、Marathon,在slave节点上只需要运行master-slave就可以了,但是需要修改ZooKeeper的内容来保证slave能够被master发现和管理。为了节约时间和搞错掉,我在公司内部云平台上开一个虚拟机把所有的软件都安装上去,做成快照进行批量的创建,这样只需要在slave节点上关闭ZooKeeper、Mesos-master服务器就可以了,在文中我是通过制定系统启动规则来实现的。希望我交代清楚了,现在开始部署。
##一、准备部署环境

* 在Ubuntu 14.04的虚拟机上安装所有用到软件,虚拟机可以上互联网。

* 安装Python依赖
apt-get install curl python-setuptools python-pip python-dev python-protobuf


* 安装配置ZooKeeper
apt-get install ZooKeeperd
echo 1 | sudo dd of=/var/lib/ZooKeeper/myid


* 安装Docker
echo deb http://get.Docker.io/ubuntu Docker main | sudo tee /etc/apt/sources.list.d/Docker.list
sudo apt-key adv --keyserver keyserver.ubuntu.com --recv-keys 36A1D7869245C8950F966E92D8576A8BA88D21E9
apt-get update && apt-get install lxc-Docker


* 安装配置Mesos-master和Mesos-slave
 curl -fL http://downloads.Mesosphere.io/master/ubuntu/14.04/Mesos_0.19.0~ubuntu14.04%2B1_amd64.deb -o /tmp/Mesos.deb 
dpkg -i /tmp/Mesos.deb
mkdir -p /etc/Mesos-master
echo in_memory | sudo dd of=/etc/Mesos-master/registry


* 安装配置Mesos的Python框架
 curl -fL http://downloads.Mesosphere.io/master/ubuntu/14.04/Mesos-0.19.0_rc2-py2.7-linux-x86_64.egg -o /tmp/Mesos.egg
easy_install /tmp/Mesos.egg


* 下载Marathon
curl -O http://downloads.Mesosphere.io/marathon/marathon-0.6.0/marathon-0.6.0.tgz  
tar xvzf marathon-0.6.0.tgz


* 下载安装Mesos管理Docker的代理组件Deimos
 pip install deimos


* 配置Mesos使用Deimos
mkdir -p /etc/mesos-slave
echo /usr/local/bin/deimos | sudo dd of=/etc/Mesos-slave/containerizer_path
echo external | sudo dd of=/etc/Mesos-slave/isolation


好,至此在一个虚拟机上就完成了所有组件的安装部署,下面就是对虚拟机打快照,然后快速的复制出6个一样的虚拟机,按照上图的ip配置进行配置之后就可以进入下个阶段,当然为了保险你可以测试一下上处组件是否安装成功和配置正确。如果你没有使用云平台,或者不具备快照功能,那就只能在6个虚拟机上重复6遍上处过程了。
##二、在所有的节点上配置ZooKeeper
在配置maser节点和slave节点之前,需要先在所有的6个节点上配置一下ZooKeeper,配置步骤如下:

  • 修改zk的内容
sudo vi /etc/Mesos/zk} 
  • 将zk的内容修改为如下:
zk://10.162..2.91:2181,10.162.2.92:2181,10.162.2.93:2181/Mesos
##三、 配置所有的master节点在所有的master节点上都要进行如下操作:
  • 修改ZooKeeper的myid的内容
sudo vi /etc/ZooKeeper/conf/myid
将三个master节点的myid按照顺序修改为1,2,3。
  • 修改ZooKeeper的zoo.cfg
sudo vi/etc/ZooKeeper/conf/zoo.cfg
server.1=10.162.2.91:2888:3888server.2=10.162.2.92:2888:3888server.3=10.162.2.93:2888:3888
  • 修改Mesos的quorum
sudo vi /etc/Mesos-master/quorum
将值修改为2。
  • 配置master节点的Mesos 识别ip和和hostname(以在master1上的配置为例)
echo 10.162.2.91 | sudo tee /etc/Mesos-master/ip 
sudo cp /etc/Mesos-master/ip /etc/Mesos-master/hostname
  • 配置master节点服务启动规则
sudo stop Mesos-slaveecho manual | sudo tee /etc/init/Mesos-slave.override
四、配置所有的slave节点
  • 配置slave节点的服务启动规则
sudo stop ZooKeeperecho manual | sudo tee /etc/init/ZooKeeper.overrideecho manual | sudo tee /etc/init/Mesos-master.overridesudo stop Mesos-master
  • 配置slave节点的识别ip和hostname(以slave1节点为例)
echo 192.168.2.94 | sudo tee /etc/Mesos-slave/ipsudo cp /etc/Mesos-slave/ip /etc/Mesos-slave/hostname
五、在所有节点上启动服务
  • 在master节点上启动服务(以在master1节点上为例)
initctl reload-configuration service Docker start service Zookeeper start service Mesos-master start service Mesos-slave start cd marathon-0.6.0 ./bin/start --master zk://10.162.2.91:2181,10.162.2.92:2181,10.162.2.93:2181/Mesos --zk_hosts 10.162.2.91:2181
  • 在所有slave节点上启动服务
sudo start mesos-slave

好,至此,如果配置没有出现错误的话就会成功了,由于有的网络情况和设备情况不一样,所以选举的过程有的快有的慢,当发现slave节点有些正常有些不正常时,可以通过reboot来促使自己被master发现。

六、Marathon简单的使用
要使用Marathon进行应用的部署,需要先在slave节点上有Docker镜像,这个可以自己写Dockerfile来做,也可以直接从公共仓库里pull下来一个。Marathon的应用部署和k8s一样,使用通过JSON文件来进行的,以下就是一个jason文件的范例:
curl -X POST -H "Accept: application/json" -H "Content-Type: application/json" \
10.162.2.91:8080/v2/apps -d '{
"container": {"image": "docker:///ubuntu", "options": ["--privileged"]},
"cpus": 0.5,
"cmd": "ok",
"id": "cci-docker",
"instances": 2,
"mem": 30
}'


七、成功部署的结果是这样的
1393540D-028A-49CA-B4BE-D8C515C88A10.png

3677CB31-F1DF-430D-BC6A-82A576C0DFE8.png

176B3875-B5F6-42BC-9EF4-A49EBE835184.png

07CDB99D-51D6-4D5C-8B15-8151EE32F155.png

4D7C8BAF-FC99-4BCD-88E5-6CD5EEFEF85E.png

05BD7E57-90A1-4891-8AC3-4D179B6DF248.png

B8434C3A-DDCE-4562-90A2-31BF8D31CB99.png

关于对Docker应用场景的总结以及这种基于Docker的PaaS平台未来的发展前景,我会在后面的博文中进行讨论分析。

赵英俊 2015年7月21日 于 杭州 城云科技

CoreOS Fest 系列之第二篇: Systemd、Go、Calico、Sysdig

bnuhero 发表了文章 • 0 个评论 • 5486 次浏览 • 2015-06-19 17:43 • 来自相关话题

【编者的话】在 CoreOS Fest 第二天的会议中,演讲者展示了多个开源项目和工具,包括 Systemd 和 CoreOS 、 Go 语言和容器、 Calico 项目、 Sysdig 等。 在 CoreOS Fest 的第一天会议 ...查看全部
【编者的话】在 CoreOS Fest 第二天的会议中,演讲者展示了多个开源项目和工具,包括 Systemd 和 CoreOS 、 Go 语言和容器、 Calico 项目、 Sysdig 等。

CoreOS Fest第一天会议中,陆续介绍了 CoreOS 的架构、规划和规范。第二天的会议,演讲者展示了多个开源项目和工具,包括 systemd-nspawn 、 Calico 项目和 Sysdig 等。上述项目大多数已经开发了一年有余,但是大部分 CoreOS Fest 的与会者是第一次了解这些项目。

不仅是会议的内容吸引人,更引人注意的是,在过去的一年多时间内,社区开发了大量支持 Linux 容器的新工具。在 Linux 领域,如此快的发展速度,前所未见。在以容器为核心的新型基础设中, systemd 是一个基础组件,它是 Linux 的新型初始化系统,支持容器的开箱即用。

Systemd 和 CoreOS

Red Hat 的 Lennart Poettering 就 Systemd 和 CoreOS 做了演讲,他介绍了集成容器管理功能的 systemd 工具。与其它的初始化系统相比,用 systemd 构建容器更有优势。 systemd 提供了在单机上管理不同容器所需的所有工具。

他首先指出,这次并不是代表 Red Hat 官方的报告。然后,他花了很多时间介绍 Red Hat 的 systemd 团队。这个团队并不是一个产品团队,他说:「我们自认为是一个研究部门,而不是一个产品团队」。

这种表态解释了 systemd 选择某些技术的原因,例如,选择 Btrfs 作为 systemd-nspawn 模板和容器的主要文件系统。「人们都说 btrfs 是一个不稳定的文件系统。但是, btrfs 项目团队正在努力解决该文件系统存在的基础性问题」,他说。而且,在一个不稳定的文件系统上运行容器,这是可以接受的,因为数据被保存在外部的卷上,而不是在容器内(译者注:即没有保存在这个不稳定的文件系统上)。

systemd 包含多个支持容器的守护进程,即 systemd-machined, systemd-networkdsystemd-resolved 。总的来说,所有的 systemd 进程都兼容容器,因为「更多时候,我们是在容器中而不是裸机上测试 systemd 」。这么做的好处是,不用重启开发用的笔记本电脑,就可以测试 systemd 进程。Poettering 认为集成容器是 Linux 的一项重要特性,他说:「容器应该默认集成到 Linux 操作系统中,就像 Zones 已经成为 Solaris 操作系统的一部分」。

systemd 团队的目标是保持 systemd 中立,不仅支持 rkt 容器,还支持 docker, libvirt-lxc, OpenVZ 等。 systemd 提供了很多与容器相关的功能,它是一个偏底层的工具,不提供复杂的用户界面。像 CoreOS 和 kubernetes 等项目可以调用 systemd 的功能,完成对容器的基本操作。

在 systemd 中,管理容器的主要工具是 systemd-machined 及其命令行工具 machinectl 。有了它,用户能够列出所有的容器,启动和关停容器,甚至交互登录到容器中。 systemd-machined 实际上是容器的注册中心,任何容器都可以注册。 systemd-machined 配合 systemd ,执行 `systemd-run -M` ,就能在容器中执行任何命令。 systemd-machined 运行的容器,会出现在 `ps` 命令的列表或者 GNOME 的系统监控器中。

systemd-nspawn 是一个轻量的容器执行器,它是一个像 docker 那样能够启动和关停容器的工具。 为 systemd-nspawn 指定任何包含主引导记录( MBR )或者 GUID 分区表的文件系统或者块设备,它就能启动一个容器。如果用户只需要具备基本功能、免配置的容器, systemd-nspawn 是一个很有吸引力的选择。 rkt 底层也是调用 systemd-nspawn 运行容器。

systemd-networkd 是管理网络的 systemd 守护进程,而 systemd-resolved 是解析主机名字的 systemd 守护进程,它们都支持容器。 systemd-networkd 自动启动容器的网络,利用 DHCP 为容器分配内部的网络地址。 systemd-resolved 使用本地链路多播名字解析( link-local multicast name resolution, LLMNR )系统,为容器提供主机名。 LLMNR 是 Microsoft 发明的自动名字发现系统,初衷是用在客户端应用和移动设备中,但是它也可以用于网络环境中容器之间的彼此发现。

根据 Poettering 的演讲,看起来 systemd 是 Docker 公司的 libcontainer 以及其它容器初始化和管理工具的强力替代。由于大部分 Linux 发行版的最新版都会集成 systemd ,大多数用户将能够立即使用 systemd 。这也能解释为什么容器领域的大多数公司都选择编排作为主攻点,因为 systemd 本身不提供容器编排功能。

Go 语言和容器

CoreOS 公司 CEO Alex Polvi 做主题演讲时宣布 CoreOS 公司将赞助第二届 Gophercon —— Go 语言开发者大会。 有 6 家 Gophercon 赞助商都是容器公司,约占赞助商总数的 1/4 。这是有原因的,无论 CoreOS 公司还是 Docker 公司,它们使用的主要编程语言都是 go 。 Polvi 如此说道:「只有使用 go 语言才能开发出 Etcd 」。

我听的每一个演讲,我到的每一个会议室,人们都在谈论 go 语言,用它写代码。 Etcd, fleet, Swarm, Kubernetes, Kurma 等容器工具应用或者服务都是用 Go 语言编写的。 Linux 容器平台的崛起,同样是 Go 语言的崛起。

Go 语言始于 2007 年 Google 公司的一个内部项目,共有 3 个开发者。现在, go 语言项目的贡献者已经超过 500 人。 Go 是一个使用 BSD 许可的开源项目,但是仍然由 Google 公司把持,所有的贡献者都需要与 Google 公司签署贡献者许可协议( Contributor License Agreement, CLA )。 Go 逐渐成为实现可扩展基础设施的「自动化语言」。之前, 人们已经普遍使用 go 语言实现网络代理、云服务器管理工具、分布式搜索引擎和冗余数据存储。很自然地,人们继续选择 go 实现容器工具应用。

由于 CoreOS 与 go 的紧密联系, Brad Fitzpatrick 的报告主题是 go 语言的持续构建基础设施。他是 LiveJournal, memcached 和 OpenID 的开发者,现在是 Google 公司 go 语言团队的一员。他展示了在很多平台上测试 go 语言的自动构建平台。构建平台刚开始时是一个 Google 应用引擎( Google Application Engine )应用,加上桌子上一排的移动设备,然后发展成今天这个样子。他还讲述了持续构建基础设施的历史和工作机制。

Go 是一种编译型而不是解释型编程语言。编译得到的二进制程序,能够在多种平台上执行。每个版本的 go 语言在发布之前,首先要在 Google 公司机器实验室拥有的几百种平台上成功地构建。只有在各种 Linux 平台构建 go 语言时才会用到容器,其它很多平台并不支持容器,像 MAC OS X 和 Android 平台就必须使用专用的硬件,因此容器在自动构建平台中的作用比较小。你可以在这里 看到 go 语言在各个平台的构建结果,哪些成功了,哪些失败了。

Calico 项目

其实 Calico 项目 已经开源差不多一年了。当核心开发者 Spike Curtis 报告时,大多数与会者是第一次听说这个项目。 Calico 是一个多主机路由软件,还包含一个分布式防火墙。 Calico 是专门为容器和虚拟机尤其是 docker 和 OpenStack 环境设计的。 Metaswitch Networks 公司用 python 语言开发 Calico ,它也是唯一提供 Calico 商业化支持的公司。看起来, calico 有望成为这样一种解决方案:用户能够在生产环境部署容器,同时保证严格的安全。

「还记得三层架构吗?」 Curtis 抱怨说,「管理员现在还是这样管理网络。第一层是外部网络,中间是 web 资源所在的隔离区( demilitarized zone, DMZ),最后是数据层,要求保证最严格的安全」。

编排好网络中的容器,运行微服务,这种模式打破了三层模型。首先,微服务是根据服务的提供者而不是服务的安全特征划分的;其次,编排框架假设数据中心网络是无区分的,也不支持安全分层的概念;最重要的是,你得为几百个微服务定义安全策略和区域,这不像以前,网络管理员只要设定几十个策略和区域就行了。 Curtis 说,「这就像是一个动物园,所有围墙都被推到了」。

然而,就安全而言,微服务也为带来了机会。每个微服务只做一件事情,它的安全需求也相对简单。因此,可以更细致地划分服务,又不增加整体的复杂性, calico 就是这么做的。

Calico 为每个容器或者虚拟机分配一个独立的 IP 地址,然后在每台物理主机上定义包含这些 IP 地址的 iptables 规则,实现了防火墙功能。 Calico 用保存在 etcd 的标签定义每个服务,用一个 JSON 格式的配置文件定义允许访问当前服务的其它服务,以及这个服务是否可通过 Internet 访问。

只要编排框架支持为每个服务分配一个 IP 地址,就可以集成使用 calico 。Curtis 演示了 calico 与 kubernetes 的集成,包括用扩展的 pod 定义来设定每个容器的安全设置。 Apache Mesos 正在实现为每个服务分配一个 IP 地址的功能,暂时还不能集成 calico 。

Sysdig

最后一个「新」项目是 Sysdig ,就像 calico 项目一样, 它也是一年多以前就发布了,但是大部分与会者都是第一次听说这个项目。站在 sysdig 背后的公司是 Sysdig Cloud ,它为 sysdig 的用户提供商业化支持。 Sysdig Cloud 公司 CEO Loris Degioanni 展示了这款工具。

Sysdig 是一个网络流监控系统,部分功能实现为一个 Linux 内核模块。这个模块捕捉了所有的网络流,特别是容器之间的网络包。用户还可以用 Lua 语言编写网络流信息的过滤器,然后把过滤得到的信息聚合在一起,进行统计分析。你可以把它视为 wiresharktcpdump 的高级版本,尤其是支持容器。

Degioanni 说,sysdig 比 Google 的 cAdvisor 项目高明,因为 cAdvisor 只报告容器占用的全部 CPU 、内存和网络,而 sysdig 还能够提供网络流的发送方、接收方和内容。例如,用户可以找出对某个数据库的查询,搞清楚为什么两个 IP 地址之间的网络通信延误得厉害。

Degioanni 还展示了即将发布的 sysdig 的文本用户界面,这是一个基于 curses) 的开源项目。有了它,系统管理员能够用 ssh 登录到系统,执行交互的监控任务。他演示了如何深入检查和汇总容器之间的网络通信,还演示了如何调查网络延迟。在 Sysdig Cloud 公司的展台,工作人员演示了商业化产品—— sysdig 的图形界面,用户只需点击多层嵌套的服务器、 pod 和容器,就可以查看对应的网络流。

小结

我从 CoreOS Fest 大会了解了这么多的新项目、新工具、标准草案和新架构,这充分说明 Linux 容器领域发展之快。要知道,在一年前,当我报道首届 DockerCon 时,这些新技术和新工具,大部分才刚刚发布,有的甚至还不存在。等再过一年,我们看看是不是还发展得这么快。

我们还没有涉及一个主要的话题:如何在容器中持久保存数据。前文曾经提及,目前普遍的看法是容器应该是无状态的,不保存数据。不坚持这一点,容器管理和编排会遇到一系列亟需解决的问题,例如,外部卷的管理、容器迁移和有状态服务的负载均衡等。下个星期,我们将讨论与持久话数据和容器相关的多个话题,内容来自于 CoreOS Fest 和 Container Camp

原文链接: New projects from day two of CoreOS Fest (翻译:柳泉波)