上一篇博文 我们看了看用 Terraform 创建容器引擎集群。在本篇博文里,我们看看使用容器引擎和 Kubernetes 部署容器到集群里。
目录
Kubernetes
首先,什么是 Kubernetes ? Kubernetes 是一个开源的、管理容器的框架。与平台无关,就是说着你可以在你本机上,在 AWS 或者 Google Cloud,任何其他的平台运行它。(Kubernetes)能让你通过使用声明的配置内容,控制一组容器,和容器的网络规则。
你只需要写个 yaml/json 文件,描述下需要在哪运行哪个容器。定义你的网络规则,比如端口转发。它就会帮你管理服务发现。
Kubernetes 是云场景的重要补充,而且现在正迅速成为云容器管理实际选择。因此了解下是比较好的。
那么我们开始吧!
首先,确保你已经在本地安装了 kubectl cli:
$ gcloud components install kubectl现在确保你连接到集群,并且认证正确。第一步,我们登录进去,确保已被认证。第二步我们设置下项目配置,确保我们使用正确的项目 ID 和可访问区域。
$ echo "This command will open a web browser, and will ask you to login$ gcloud auth application-default login$ gcloud config set project shippy-freight $ gcloud config set compute/zone eu-west2-a$ echo "Now generate a security token and access to your KB cluster"$ gcloud container clusters get-credentials shippy-freight-cluster在上面的命令中,你可以将 compute/zone 替换成你选的任何区域,你的项目 id 和集群名称也可以和我的不一样。
下面是个概括描述…
$ echo "This command will open a web browser, and will ask you to login$ gcloud auth application-default login$ gcloud config set project <project-id>$ gcloud config set compute/zone <availability-zone>$ echo "Now generate a security token and access to your KB cluster"$ gcloud container clusters get-credentials <cluster-name>点这你可以看到项目 ID…
现在找下我们的项目ID…
集群区域 region/zone 和集群名称可以点菜单左上角的 ComputeEngine,然后选 VM Instances 就找到了。 你能看到你的 Kubernetes VM,点进去看更多细节,这能看见和你集群相关的每个内容。
如果你运行下…
$ kubectl get pods你会看到 ... No resources found.。没关系,我们还没有部署任何内容。我们可以想想我们需要实际部署些什么。我们需要一个 Mongodb 实例。一般来说,我们会部署一个 mongodb 实例,或者为了完全分离,将数据库实例和每个服务放一起。但是这个例子里,我们耍点小聪明,就用一个中心化的实例。这是个单一故障点,但是在实际应用场景中,你要考虑下将数据库实例分开部署,和服务保持一致。不过我们这种方法也可以。
然后我需要部署服务了,vessel 服务,user 服务,consignment 服务和 email 服务。好了,很简单!
从 Mongodb 实例开始吧。因为它不属于一个单独的服务,而且这是的平台整体的一部分,我们把这些部署放在 shippy-infrastructure 仓库下。这个仓库我提交到了 Github ,因为包含了很多敏感数据,但是我可以给你们所有的部署文件。
首先,我们需要一个配置创建一个 ssd,用于长期存储。这样当我们重启容器的时候就不会丢失数据。
// shippy-infrastructure/deployments/mongodb-ssd.ymlkind: StorageClassapiVersion: storage.k8s.io/v1beta1metadata:name: fastprovisioner: kubernetes.io/gce-pdparameters:type: pd-ssd然后是我们的部署文件(我们通过本文来深入更多细节)…
// shippy-infrastructure/deployments/mongodb-deployment.ymlapiVersion: apps/v1beta1kind: StatefulSetmetadata:name: mongospec:serviceName: "mongo"replicas: 3selector:matchLabels:app: mongotemplate:metadata:labels:app: mongorole: mongospec:terminationGracePeriodSeconds: 10containers:- name: mongoimage: mongocommand:- mongod- "--replSet"- rs0- "--smallfiles"- "--noprealloc"- "--bind_ip"- "0.0.0.0"ports:- containerPort: 27017volumeMounts:- name: mongo-persistent-storagemountPath: /data/db- name: mongo-sidecarimage: cvallance/mongo-k8s-sidecarenv:- name: MONGO_SIDECAR_POD_LABELSvalue: "role=mongo,environment=test"volumeClaimTemplates:- metadata:name: mongo-persistent-storageannotations:volume.beta.kubernetes.io/storage-class: "fast"spec:accessModes: [ "ReadWriteOnce" ]resources:requests:storage: 10Gi然后是 service 文件…
apiVersion: v1kind: Servicemetadata:name: mongolabels:name: mongospec:ports:- port: 27017targetPort: 27017clusterIP: Noneselector:role: mongo还有很多,现在对你来说可能没什么意义。那么我们试试理清一些 Kubernetes 的关键概念。
Nodes
阅读此文
Nodes 是你的物理机或者 VM,你的容器通过 node 做集群,服务通过运行在不同 node/pod 上的一组组容器互相访问。
Pods
阅读此文
Pod 是一组相关的容器。比如,一个 pod 可以包含你的认证服务容器,用户数据库容器,登陆注册用户接口等等。这些容器都是明显有相关性的。Pod 允许你将他们组合在一起,这样他们能互相访问,并且运行在相同的即时网络环境下,你可以把他们当做一个整体。这很酷啊!Pod 是 Kubernetes 里非常不容易理解的特性。
Deployment
阅读此文
Deployment 是用来控制状态的,一个 deployment 就是最终要输出和要保持的状态的描述文件。一个 deployment 是 Kubernetes 的介绍,比如说,我想要三个容器,运行在三个端口,用某些环境变量。Kubernetes 会确保维持这个状态。如果一个容器崩溃了,剩下两个容器,它会再启动一个满足三个容器的需求。
StatefulSet
阅读此文
stateful set 和 deployment 有相似的地方,除了它会用一些存储方式,保持容器相关的状态。比如分布存储的概念。
实际情况里,Mongodb 将数据写入二进制数据存储格式,很多数据库都是这样做的。创建一个可回收的数据库实例,比如 docker 容器。如果容器重启数据会丢失。一般来说你需要在容器启动的时候使用分卷装载数据/文件。
你可以在 Kubernetes 上做这些部署。但是 StatefulSets,在相关的集群点有一些额外的自动化操作。因此这个对 mongodb 容器天然的合适。
Service
阅读此文
服务是一组网络相关的规则,比如端口转发和 DNS 规则,在网络层面上连接你的 pod,控制谁和谁可以通信,谁可以被外部访问。
有两种服务你可能会遇到,一是 load balancer,一是 node port。
load balancer,是一个轮询的负载均衡器,可以给你选的 node 节点创建一个 IP 地址给代理。通过代理把服务暴露给外部。
node port 将 pod 暴露给上层的网络环境,这样他们可以被其他服务, 内部的pod/实例访问。这样对暴露 node 给其他的 pod 来说是有用的。这就是你能用来允许服务和其他服务通信的方式。这就是服务发现的本质。至少是一部分。
现在我们刚看了一点点 Kubernetes 的内容,我们来再多谈些,再挖掘挖掘。值得注意的是,如果你是个在本机上使用 docker ,比如如果你用的是 mac/windows 上的 docker 的 edge 版本,你可以把 Kubernetes 集群钉在本机上。测试时候很有用。
那么我们已经创建了三个文件,一个用于存储,一个用于 stateful set,一个用于我们的服务。最后结果是有 mongodb 容器的副本,stateful 存储和通过 pod 保留给数据存储的服务。我们继续看看,创建,按正确的顺序,因为有些操作是需要依赖前面创建的内容。
echo "shippy-infrastructure"$ kubectl create -f ./deployments/mongodb-ssd.yml$ kubectl create -f ./deployments/mongodb-deployment.yml$ kubectl create -f ./deployments/mongodb-service.yml等几分钟,你可以查下 mongodb 容器的状态,运行:
$ kubectl get pods你可能注意到你的 pod 状态是pending。如果运行$ kubectl describe node 你会看到关于 CPU 不足的错误。很尴尬,有些集群管理和 Kubernetes 工具对 CPU 很敏感。所以一台 node 可能不够,mongo 的实例也一样。
那么我们给集群打开自动扩容,默认是一个池。为了达到目的,需要到 Google Cloud Console,选择 Kubernetes 引擎,编辑你的实例,打开自动扩容,设置最小值和最大值为 2,然后点保存。
过几分钟,你的 node 节点会扩成两个,运行 $ kubectl get pods 你会看到 ContainerCreating。直到所有的容器以期待的方式运行。
现在我们有了数据库集群,一个自动扩容的 Kubernetes 引擎,我们来部署一些服务吧!
Vessel 服务
vessel 服务很轻量级,没做太多事情,也没有依赖,因此适合上手。
首先,我们稍微改动 vessel 服务上的一些代码片段。
// shippy-vessel-service/main.goimport ( ... k8s "github.com/micro/kubernetes/go/micro" )func main() { ... // Replace existing service var with... srv := k8s.NewService( micro.Name("shippy.vessel"), micro.Version("latest"), )}我们在这做的所有事情,就是使用了 import 的新库k8s.NewService覆盖了已有的 micro.NewService()。那么新库是什么呢?
Kubernetes 中的微服务
我喜欢 micro 中的一点,因为它对 cloud 有很深理解而构建,能一直适应新技术。Micro 很重视 Kubernetes,因此创建了一个 micro 的 Kubernetes 库。
实际情况是,所有的库实际上都是 micro,配置了 Kuberntes 的一些合理的默认值,和一个直接集成在 Kubernetes 服务之上的 service selector。也就是说它把服务发现交给了 Kubernetes。默认用 gRPC 作为默认 transport 。 当然你也可以使用环境变量和 plugin 来覆盖这些状态。
在 Micro 的世界里还有很多让人着迷的功能,这也是让我很兴奋的地方。一定要加入 slack channel。
现在我们在服务上创建一个部署服务,在这我们要稍微了解下关于每个部分作用的细节。
// shippy-vessel-service/deployments/deployment.ymlapiVersion: apps/v1beta1kind: Deploymentmetadata: name: vesselspec: replicas: 1 selector: matchLabels: app: vessel template: metadata: labels: app: vessel spec: containers: - name: vessel-service image: eu.gcr.io/<project-name>/vessel-service:latest imagePullPolicy: Always command: [ "./shippy-vessel-service", "--selector=static", "--server_address=:8080", ] env: - name: DB_HOST value: "mongo:27017" - name: UPDATED_AT value: "Mon 19 Mar 2018 12:05:58 GMT" ports: - containerPort: 8080 name: vessel-port这有很多内容,不过我尝试分解下一部分。首先你能看到 kind:Deployment,在 Kubernetes 里有很多不同的 things,大部分可以被看作 cloud primatives。在编程语言里,string,interger,struct,method 等等这些类型都是元数据。在 Kubernetes 里就把 cloud 看作是同样意思。因此把这些看作元数据。这样,一个 deployment,就是控制元数据的一种形式。元控制保证你期望的状态维持正常。deployment 是一个无状态控制的形式,是不可持续的,在重启或退出之后数据就被销毁了。Stateful sets 类似 deployment,除了他们要维持一些静态数据,还有之前声明过的数据。但是我们的服务不应该包含任何状态,微服务是无状态的。因此在这我们需要一个 deployment。
下一步你需要一个标准分区,启动时带着 deployment 的 metadata,名称,多少个这种 pod (副本),需要保持(如果其中一个死掉了,假设我们用的比一个多,控制元的工作就是检查有运行的 pod 数量是我们希望的,如果不在期待状态就再启动一个)。Selector 和 template 暴露了 pod 的某些 metadata,可以允许其他服务发现和连接 pod。
然后你需要另一个标准分区(有点困惑,不过继续往下看呀!)。这次是给我们自己的容器,或者分卷,共享 meta 数据等等。在这个服务里,我们需要启动一个独立容器。容器区域是一个数组,因为我们要启动几个容器作为 pod 的一部分。是为了组合相关容器。
容器的 metadata 一目了然,我们从镜像启动一个 docker 容器,设置一些环境变量,在运行时传入一些命令,暴露一个端口(用于服务查找)。
你看到我传入了一个新的命令:--selector=static,这是告诉 Kubernetes 微服务设置使用 Kubernetes 用于服务发现和负载均衡。真的很酷,因为现在你的微服务代码是直接和 Kubernetes 强大的 DNS ,网络,负载均衡和服务发现进行交互。
你可以提交这个选项,继续像之前一样使用微服务。但是我们也可以用到 Kubernetes 的好处。
你也注意到了,我们从一个私有仓库拉取镜像。当使用 Google 的容器工具时,你可以获取一个容器注册,用来创建你的容器镜像,推送下,像下面这样…
$ docker build -t eu.gcr.io/<your-project-name>/vessel-service:latest .$ gcloud docker -- push eu.gcr.io/<your-project-name>/vessel-service:latest现在看我们的服务…
// shippy-vessel-service/deployments/service.ymlapiVersion: v1kind: Servicemetadata: name: vessel labels: app: vesselspec: ports: - port: 8080 protocol: TCP selector: app: vessel在这里,如之前所说的我们有一个 kind,在这个 case 下是一个服务元(本质上是一组网络级别的 DNS 和防火墙规则)。然后我们给服务名字和标签。spec 允许我们给服务定义端口,你也可以在这定义一个 targetPort 来查找特定的容器。不过幸好有 Kubernetes/micro 实现,我们可以自动操作。最后,selector 最重要的部分,必须和你目标的 pod 相匹配,否则服务通过代理找不到任何东西,不会工作的。
现在我们部署下集群里的修改吧。
$ kubectl create -f ./deployments/deployment.yml$ kubectl create -f ./deployments/service.yml等几分钟,然后运行…
$ kubectl get pods$ kubectl get services你应该能看到你的新 pod,新服务了。确保他们的运行状态符合期待效果。
如果你遇到错误,你可以运行 $kubectl proxy,然后在浏览器打开:8001/ui,看下 Kubernetes 的 ui,你可以深度探索下容器的状态等待。
在这有一点值得提一下,deployment 是原子操作并且是不可更改的,意思是他们必须通过某种方式更新才能被修改。他们有一个唯一的哈希值,如果哈希值没有变化,deployment 是不会更新的。
如果你运行 $ kubectl replace -f ./deployments/deployment.yml,什么也不会发生。因为 Kubernetes 没有检测到变化。
有很多方法可以规避这种情况,不过需要注意的是,大部分情况下,是你的容器会发生变化,所以不要用 latest 标签,你应该给每一个容器一个唯一的标签,比如编译编号,比如:vessel-service:。 这样就会标记为修改,deployment 就可以替换了。
但是在这个教程里,我们做点好玩的事,不过要留神,这是一种懒人写法,而且不是最好的实践方法。我创建了一个新文件 deployments/deployment.tmpl ,作为 deployment 的模板。然后我设置了一个环境变量UPDATED_AT,值为 {{ UPDATED_AT }}。我更新了 Makefile 来打开模板文件,通过环境变量设置当前的 date/time ,然后输出到最终的 deployment.yml 文件。这有点不规范的感觉,不过只是暂时这么做。我看过很多方式,你感觉怎么合适怎么做吧。
// shippy-vessel-service/Makefiledeploy: sed "s/{{ UPDATED_AT }}/$(shell date)/g" ./deployments/deployment.tmpl > ./deployments/deployment.yml kubectl replace -f ./deployments/deployment.yml好了,我们成功了,部署了一个服务,运行的和我们想的一样。
我现在给其他服务也做同样的操作。我在仓库里给每个服务做了简短的更新,如下…
[Consignment service](https://github.com/EwanValentine/shippy-consignment-service) [Email service](https://github.com/EwanValentine/shippy-email-service) [User service](https://github.com/EwanValentine/shippy-user-service) [Vessel service](https://github.com/EwanValentine/shippy-vessel-service) [UI](https://github.com/EwanValentine/shippy-ui)给我们的用户服务部署 Postgres …
apiVersion: apps/v1beta2kind: StatefulSetmetadata: name: postgresspec: serviceName: postgres selector: matchLabels: app: postgres replicas: 3 template: metadata: labels: app: postgres role: postgres spec: terminationGracePeriodSeconds: 10 containers: - name: postgres image: postgres ports: - name: postgres containerPort: 5432 volumeMounts: - name: postgres-persistent-storage mountPath: /var/lib/postgresql/data volumeClaimTemplates: - metadata: name: postgres-persistent-storage annotations: volume.beta.kubernetes.io/storage-class: "fast" spec: accessModes: [ "ReadWriteOnce" ] resources: requests: storage: 10GiPostgres 服务…
>apiVersion: v1kind: Servicemetadata: name: postgres labels: app: postgresspec: ports: - name: postgres port: 5432 targetPort: 5432 clusterIP: None selector: role: postgresPostgres 存储…
kind: StorageClassapiVersion: storage.k8s.io/v1beta1metadata: name: fastprovisioner: kubernetes.io/gce-pdparameters: type: pd-ssd部署 micro
// shippy-infrastructure/deployments/micro-deployment.ymlapiVersion: apps/v1kind: Deploymentmetadata: name: microspec: replicas: 3 selector: matchLabels: app: micro template: metadata: labels: app: micro spec: containers: - name: micro image: microhq/micro:kubernetes args: - "api" - "--handler=rpc" - "--namespace=shippy" env: - name: MICRO_API_ADDRESS value: ":80" ports: - containerPort: 80 name: port现在是服务…
// shippy-infrastructure/deployments/micro-service.ymlapiVersion: v1kind: Servicemetadata: name: microspec: type: LoadBalancer ports: - name: api-http port: 80 targetPort: "port" protocol: TCP selector: app: micro在这些服务里,我们用了一个LoadBalancer 类型,暴露了一个外部的 load balancer,提供给外部一个 IP 地址。如果你运行$ kubectl get services,等一两分钟(你会看到pending 一会),你就有了这个 ip 地址。这是公共部分的,你可以分配一个域名。
一旦部署完毕了,让服务调用 micro:
$ curl localhost/rpc -XPOST -d { "request": { "name": "test", "capacity": 200, "max_weight": , "available": true }, "method": "VesselService.Create", "service": "vessel"} -H Content-Type: application/json你会看到一个返回 created: true。超简洁!这就是你的 gRPC 服务,被代理并且转成了 web 友好的格式,使用了分片的 mongodb 实例。没费多大劲!
部署 UI
服务部署的不错,我们来部署下用户接口
// shippy-ui/deployments/deployment.ymlapiVersion: apps/v1beta1kind: Deploymentmetadata: name: uispec: replicas: 1 selector: matchLabels: app: ui template: metadata: labels: app: ui spec: containers: - name: ui-service image: ewanvalentine/ui:latest imagePullPolicy: Always env: - name: UPDATED_AT value: "Tue 20 Mar 2018 08:26:39 GMT" ports: - containerPort: 80 name: ui现在是服务…
// shippy-ui/deployments/service.ymlapiVersion: v1kind: Servicemetadata: name: ui labels: app: uispec: type: LoadBalancer ports: - port: 80 protocol: TCP targetPort: ui selector: app: ui注意到服务是 80 端口上的负载均衡,因为这是一个公共的用户接口,这就是用户如何与我们服务交互的。一看就明白!
最后总结
看我们成功了,用 docker 容器和 Kubernetes 管理我们的容器,成功的将整个工程部署到云端。希望你能从这篇文章发现一些有用的内容,没有觉得太不好消化。
本系列的下一部分,我们将看看把所有这些内容和 CI 进程联系起来,来管理我们的 deployment。
via:
作者:Ewan Valentine 译者:ArisAries 校对:polaris1119
本文由 GCTT 原创编译
-事必有法,然后有成- 最后祝大家学习顺利 ~
版权申明:内容来源网络,仅供分享学习,版权归原创者所有。除非无法确认,我们都会标明作者及出处,如有侵权烦请告知,我们会立即删除并表示歉意。谢谢!
来源:聚焦未来
扫码关注我们了解更多资讯PS:因平台更改了推送规则,如果不想错过内容,记得读完点一下“在看”,加个“星标”,这样每次新文章推送才会第一时间出现在你的订阅列表里。点“在看”支持一下呀!😀