Kubernetes简介及入门

Published: 07 Aug 2019 Category: Kubernetes

如今单体应用(monolithic application)日渐被认为是一种反模式(antipattern),而云平台则成为了应用部署的香饽饽。这个转变可不仅仅像是在别人的机器上启动一个虚机那么简单。如何能有效地利用云的资源和伸缩性,意味着和要过去的单体应用划清界限,转而拥抱新的架构和开发实践。

微服务正逐渐成为云端应用及服务分发的事实标准。应用被拆分成松耦合的小模块,每个模块都有自己的职责。这种新的架构使得团队能够更加独立地完成自己所负责的功能——而不必依赖于整个公司或组织的大的路线图来进行。除此之外,离散的软件组件使得测试更加简单,还能实现流水化部署。

微服务也带来了新的挑战。在虚机上创建和部署服务只是万里长征第一步,如何维护整个软件的生命周期?容器化便是在此驱动下应运而生的。容器化可以解决微服务架构下的许多问题,比如说:

  • 应有软件和宿主环境解耦,提供比较强的可移植性。
  • 容器是轻量且相对透明的,因此更容易扩展。
  • 软件及其依赖共同打包发布。

除此之外,容器还是微服务打包部署的非常棒的解决方案。但容器也不是万能的,说白了也还是软件,是软件就涉及到大规模的部署、维护以及管理。开发人员以前只需要监控和维护单个应用,但现在他们有成百上千的服务要管理。这正是Kubernetes所要解决的问题。

Kubernetes是一个开源平台,它可以用来自动化部署、扩展及管理容器化的应用及服务。Google在部署和维护大批量容器时遇到了挑战,于是便有了Kubernetes,之后又把它开源并捐赠给了云原生计算基金会(Cloud Native Computing Foundation,CNCF)。该基金会致力于促进云原生计算生态的发展。Kubernetes是CNCF孵化成功的第一个项目,也成为开源史上发展速度最快的项目。Kubernetes拥有2300个贡献者,被大小公司广泛采用,财富100强企业中有一半都在使用它。

Kubernetes入门

如何开始学习Kubernetes?Kubernetes生态周边出现了大批的支撑项目。整个生态的大图非常复杂,一个很简单的问题可能又把你带到了一片新的未知领域,这很容易让人产生挫败感。所幸的是,入门还是比较简单的,后面再根据你的实际需去了解更多高级概念就好了。本文将会告诉大家如何:

  • 使用Docker和Kubernetes来设置本地开发环境
  • 使用Helidon创建一个简单的Java微服务
  • 用Docker为微服务构建一个Docker镜像
  • 将微服务部署到Kubernetes集群
  • 在集群上对微服务进行扩容和缩容

在开始之前,本地需要先安装好这些工具:

  • Docker 18.02或更高版本
  • Kubernetes 1.7.4或更新版本
  • JDK 8或更高版本
  • Maven 3.5或更高版本

可以在MacOS、Linux或Windows平台上安装这些软件的最新版本。

如果还没有安装Docker,可以参考这篇教程,找到对应的平台,按照指令一步步完成。需要熟悉Docker的一些基本知识。

还需要有一个可用的Kubernetes集群,在本文中我们使用的是Minikube。Minikube会在你本地系统的虚拟机内运行一个单节点的Kubernetes集群,这对我们来说就够用了。如果还没有安装过Minikube的话可以看下这篇教程

装完了Docker、Minikube也能启动本地Kubernetes集群后,我们还缺少一个微服务。之前的文章中有一篇关于Helidon微服务框架的文章,这里我们将参考里面的内容来创建一个微服务。

创建一个基础的微服务

Helidon的Maven原型模板可以帮助我们可以快速地创建一个新工程。下面我们将告诉你如何快速创建并运行一个基础的微服务:

$ mvn archetype:generate -DinteractiveMode=false \
    -DarchetypeGroupId=io.helidon.archetypes \
    -DarchetypeArtifactId=helidon-quickstart-se \
    -DarchetypeVersion=1.0.1 \
    -DgroupId=io.helidon.examples \
    -DartifactId=helidon-quickstart-se \
    -Dpackage=io.helidon.examples.quickstart.se

进到helidon-quickstart-se目录下去构建服务:

$ cd helidon-quickstart-se
$ mvn package

现在我们有了一个可以使用的微服务。这个工程还生成了一个应用jar包。我们来运行下看看是否能正常工作:

$ java -jar ./target/helidon-quickstart-se.jar
[DEBUG] (main) Using Console logging
2019.03.20 12:52:46 INFO io.helidon.webserver.NettyWebServer
Thread[nioEventLoopGroup-2-1,10,main]: Channel '@default'
started: [id: 0xbdfca94d, L:/0:0:0:0:0:0:0:0:8080]
WEB server is up! http://localhost:8080/greet

在另一个终端中执行curl命令:

$ curl -X GET http://localhost:8080/greet
{"message":"Hello World!"}
$ curl -X GET http://localhost:8080/greet/Mary
{"message":"Hello Mary!"}
$ curl -X PUT -H "Content-Type: application/json" -d '{"greeting" : "Hola"}'  /
http://localhost:8080/greet/greeting
$ curl -X GET http://localhost:8080/greet/Maria
{"message":"Hola Maria!"}

这个服务的功能很简单,我们只是用它来作为构建容器镜像的一个demo。在开始Kubernetes之前,我们需要先为这个微服务构建一个Docker镜像。Helidon提供了一个Dockerfile,我们可以直接用它来创建镜像:

docker build -t helidon-quickstart-se target

现在我们来看看Kubernetes能为我们提供些什么。

迁移到Kubernetes

Docker可以用来创建镜像,容器,同时对它们进行本地管理。如果要在生产环境大规模地部署容器,这时候Kubernetes就能派上用场了。

Kubernetes会把相关联的容器按组来部署,这个部署的组就叫做Pod。Pod是一个可部署单元,它可以包含一个或多个容器。比如说,一个Pod可以由两个容器组成:一个容器运行web服务器,一个是服务器的日志服务。接下来我们会为你的微服务创建一个Pod,它只有一个容器:就是helidon-quickstart-se镜像的实例。

Kubernetes的一个职责就是要确保应用可以正常运行。一个可运行并监控的单元就叫做Deployment(部署)。Kubernetes会监控Deployment内的Pods的健康状态,如果Pod中的容器挂了,它会负责去重启。Deployment是部署和扩展Pod的最佳方式。

我们通过一个简单的例子来测试下本地的Kubernetes集群。首先执行下minikube start命令把本地集群启动起来。这个命令会告诉你它的执行状态。看到结束信息后再继续操作。

$ minikube start
  minikube v0.35.0 on darwin (amd64)
  ...
  Done! Thank you for using minikube!

minikube安装完后会带有kubectl,它是操作Kubernetes的主要接口。和Docker的客户端一样,这是一个功能强大的多用途工具,可以用它来管理集群和上面所部署的容器。

我们通过kubectl create来为一个创建好的镜像生成一个简单的Deployment。然后再用kubectl get来查看 Deployment以及内部的Pod的运行状态。过程如下:

$ kubectl create deployment hello-node \
  --image=gcr.io/hello-minikube-zero-install/hello
deployment.apps/hello-node created
$ kubectl get deployments
NAME         READY   UP-TO-DATE   AVAILABLE   AGE
hello-node   0/1     1             0            27s
$ kubectl get pods
NAME                            READY   STATUS  RESTARTS   AGE
hello-node-64c578bdf8-5b7jm     1/1     Running 0          10m

Kubernetes默认会为Pod分配一个只能在集群内部访问的IP地址。如果想从外部访问Pod内的容器,需要把这个Pod声明为一个服务(Service)。Kubernetes中的服务是一层抽象,它定义了如何去访问一个或一组Pod。

通过kubectl expose来创建一个LoadBalancer的服务。这样便可以通过负载均衡器从外部访问我们的服务了。

$ kubectl expose deployment hello-node --type=LoadBalancer --port=8080
service/hello-node exposed
$ kubectl get services
NAME         TYPE          CLUSTER-IP      EXTERNAL-IP PORT(S)
hello-node   LoadBalancer  10.104.108.47   <pending>   8080:30631/TCP
kubernetes   ClusterIP     10.96.0.1       <none>      443/TCP

在云提供商或者其它托管的kubernetes平台上,这个操作会去分配负载均衡器的资源,它的IP地址也能在EXTERNAL-IP中看到。而在Minikube中,可以通过执行service命令来打开浏览器并显示服务的工作状态:

$ minikube service hello-node

完成之后,就可以把它们都删除掉了。这时可以使用kubectl delete命令。

$ kubectl delete service hello-node
service "hello-node" deleted

$ kubectl delete deployment hello-node
deployment.extensions "hello-node" deleted

目前为止我们已经有了一个本地的kubernetes集群,也知道它是如何工作的了。现在我们来看下怎么让我们的镜像工作起来。

部署到kubernetes

前面我们通过kubectl create命令创建了一个简单的部署。通常来说,你可以给kubectl传入一个yaml文件,里面详细描述清楚部署的细节信息。这种方式支持定义Kubernetes部署的所有参数。通过YAML文件来定义部署的好处是它可以和项目源码一起进行维护和管理。

Helidon的启动工程中包含了一个模板配置,它在target/app.yaml里面定义了一个部署和服务。用编辑器来打开它,看看里面是什么内容。

$ cat target/app.yaml
kind: Service
apiVersion: v1
metadata:
  name: helidon-quickstart-se
  labels:
    app: helidon-quickstart-se

spec:
  type: NodePort
  selector:
    app: helidon-quickstart-se
  ports:
  - port: 8080
    targetPort: 8080
    name: http
---
kind: Deployment
apiVersion: extensions/v1beta1
metadata:
  name: helidon-quickstart-se
spec:
  replicas: 1
  template:
    metadata:
      labels:
        app: helidon-quickstart-se
        version: v1
    spec:
      containers:
      - name: helidon-quickstart-se
        image: helidon-quickstart-se
        imagePullPolicy: IfNotPresent
        ports:
        - containerPort: 8080
---

这看上去不过是一堆元数据信息,仔细看会发现它定义了一个Deployment,由一个包含了helidon-quickstart-se容器在内的Pod组成,同时它还定义了一个服务,以便从外部进行访问。注意,服务中的app和部署中的app的名字是一样的。

Deployment中定义的镜像名字也就是本地镜像的名字:helidon-quickstart-se。如果将它部署在Kubernetes中,Kubernetes会优先从本地查找这个镜像。我们使用的是本地镜像,并且Docker也运行在本地机器上,看起来没什么问题。不过Minikube有一个问题是,它自己在虚拟机中运行了一个Docker实例,是无法找到我们之前构建的那个镜像的。

当然Minikube也提供了一个方便的解决办法:docker-env。这个命令会打印出一组shell环境变量,它能告诉Docker客户端去使用Minikube中运行的Docker服务。把这个命令嵌在eval中运行,它便会自动在当前的shell环境中设置好环境变量,这在Linux, Unix, Mac上都是支持的。

$ eval $(minikube docker-env)

现在再唤起docker客户端,它连接的就是Minikube虚拟机中的Docker服务了。注意的是这只在当前shell中起作用,而不是永久改变了环境变量。

我们再和之前一样重新构建一下镜像,这时镜像便会存储到Minikube内部的虚拟机中了,也就是成了本地镜像了:

$ docker build -t helidon-quickstart-se target
Sending build context to Docker daemon  5.877MB
Step 1/5 : FROM openjdk:8-jre-slim
8-jre-slim: Pulling from library/openjdk
f7e2b70d04ae: Pull complete
05d40fc3cf34: Pull complete
b235bdb95dc9: Pull complete
9a9ecf5ba38f: Pull complete
91327716c461: Pull complete

Digest: sha256:...
Status: Downloaded newer image for openjdk:8-jre-slim
 ---> bafe4a0f3a02
Step 2/5 : RUN mkdir /app
 ---> Running in ec2d3dad6e73
Removing intermediate container ec2d3dad6e73
 ---> a091fb56d8c5
Step 3/5 : COPY libs /app/libs
 ---> a8a9ec8475ac
Step 4/5 : COPY helidon-quickstart-se.jar /app
 ---> b49c72bbfa4c
Step 5/5 : CMD ["java", "-jar", "/app/helidon-quickstart-se.jar"]
 ---> Running in 4a332d65a10d
Removing intermediate container 4a332d65a10d
 ---> 248aaf1a5246
Successfully built 248aaf1a5246
Successfully tagged helidon-quickstart-se:latest

Kubernetes集群中已经创建好本地镜像后,我们便可以创建Deployment和Service了:

$ kubectl create -f target/app.yaml
service/helidon-quickstart-se created
deployment.extensions/helidon-quickstart-se created

$ kubectl get pods
NAME                                     READY   STATUS    RESTARTS
helidon-quickstart-se-786bd599ff-n874p   1/1     Running   0
$ kubectl get service
NAME             TYPE       CLUSTER-IP    EXTERNAL-IP PORT(S)
helidon-quick... NodePort   10.100.20.26  <none>      8080:31803/TCP
kubernetes       ClusterIP  10.96.0.1     <none>      443/TCP

在开始测试这个刚部署完的Kubernetes服务前,还有最后一件事。之前我们创建了一个LoadBalancer类型的服务。这样便可以通过负载均衡器所配置的外部IP地址来访问服务里的Pod。这只是服务发布的一种方式,还有一种类型是我们现在要用的NodePort。NodePort通过端口映射的方式将集群的所有节点对外进行发布。

Minikube还有一个很方便的功能。可以使用service命令加上--url参数来获取服务的URL。执行下这个命令,然后用它返回的URL来测试下我们的服务。

$ minikube service helidon-quickstart-se –url
http://192.168.99.101:31803
$ curl -X GET http://192.168.99.101:31803/greet
{"message":"Hello World!"}

这样一个微服务就算是在Kubernetes中部署好了,很赞吧〜在继续Kubernetes的容器之旅前,我们先来看下它的一些基础功能。

监控

当我们在命令行下运行服务时,可以直接看到web服务器的启动信息。容器也会去捕获这些输出信息,可以通过kubectl logs来查看到。如果服务没有正常运行,可以先通过它来检查下。

$ kubectl logs helidon-quickstart-se-786bd599ff-n874p
[DEBUG] (main) Using Console logging
2019.03.23 01:00:53 INFO io.helidon.webserver.NettyWebServer
  Thread[nioEventLoopGroup-2-1,10,main]: Channel '@default'
  started: [id: 0x9f01de18, L:/0.0.0.0:8080]
WEB server is up! http://localhost:8080/greet

当我们使用本地集群来进行开发部署时,这个命令尤其有用。大规模部署的话,也有很多方法能收集、存储和使用这些日志数据。有不少开源项目和全自动化的商业解决方案,可以到网上去搜索一下。

扩展

如果你的服务只是应用程序中的一个小模块,它只负责向用户返回“hi“。但你们团队明天要参加一个电视节目,因此你希望将服务进行扩容以便支撑更多的用户。部署的可伸缩性是Kubernetes的一大特色。只需要修改下部署文件中定义的副本数量,再通过kubectl apply来应用下变更就可以了。

编辑target/app.yaml并应用更改,将副本数从1扩展到5。

$ grep replicas target/app.yaml
replicas: 5

$ kubectl apply -f target/app.yaml
service/helidon-quickstart-se unchanged
deployment.extensions/helidon-quickstart-se configured

之前只有1个Pod,现在我们能看到部署了5个。由于Kubernetes采用的是声明式的配置,只有必需的变更才会提交到集群上。在这个case中,会往部署中新增4个Pod。

$ kubectl get pods
NAME                                     READY      STATUS    RESTARTS
helidon-quickstart-se-786bd599ff-5gm29   1/1        Running   0
helidon-quickstart-se-786bd599ff-fkg8g   1/1        Running   0
helidon-quickstart-se-786bd599ff-g7945   1/1        Running   0
helidon-quickstart-se-786bd599ff-h6c5n   1/1        Running   0
helidon-quickstart-se-786bd599ff-n874p   1/1        Running   0

缩容也很简单,再修改下部署的定义然后应用下变更:

$ grep replicas target/app.yaml
replicas: 2
$ kubectl apply -f target/app.yaml
service/helidon-quickstart-se unchanged
deployment.extensions/helidon-quickstart-se configured

$ kubectl get pods
NAME        READY       STATUS    RESTARTS
helidon-quickstart-se-786bd599ff-h6c5n      1/1     Running   0
helidon-quickstart-se-786bd599ff-n874p  1/1     Running   0

Kubernetes还有不少更高级的扩展功能,比如Pod水平自动伸缩(Horizontal Pod Autoscaling),以及在托管云平台上按需自动伸缩节点资源。

最后

你还可以继续尝试一下Kubernetes的其它更高级的功能,不过这已经超出本文的范围了。可以看一下它的官方文档来了解一下。

服务使用完之后,可以通过kubectl delete将它从集群中删除:

$ kubectl delete service helidon-quickstart-se
service "helidon-quickstart-se" deleted
$ kubectl delete deployment helidon-quickstart-se
deployment.extensions "helidon-quickstart-se" deleted

除了丰富的文档,Kubernetes还有很多免费的在线学习课程。它的社区也非常活跃,帮助很大。另外,Kubernetes Slack频道上也有很多资源可供学习和探讨。也可以到它的Github项目上去发起issue和pull request。

总结

容器化是微服务部署的一层很好的抽象。它能将服务及宿主环境进行解耦,使之具备可移植性和可伸缩性。从本文中也能看出,Kubernetes使得大规模的容器管理成为了可能。

当然了,这只是开始。你可以从它的文档中了解到更高级的一些功能。欢迎来到Kubernetes的世界!

英文原文链接