Kubernetes 快速入门

从Docker到Kubernetes

前面已经提到,微服务架构离不开容器技术。

为什么需要容器呢?我们先来看一个集装箱的例子:在一艘货轮上,货物按照整齐的规格码放整齐,从而可以封装进集装箱。集装箱之间不会相互影响,这大大地提升了运输效率。

容器就好比这个集装箱,运行的各式应用程序是货物。为了让应用程序在生产机上跑起来,往往需要做各种配置,非常繁琐,还经常会由于系统版本等原因,和开发环境不一致,从而导致“这个程序本地好好的,放到服务器上就出Bug”这类情况,这就是环境不标准,容器可以对运行环境标准化,很好的解决这类问题。此外,不同的应用程序需要不同的应用环境,如果都部署在一台物理机上,很可能会发生包、依赖冲突,导致无法运维,有了容器后,不同的应用程序放置在不同的容器中,相互隔离开、不会相互影响。其实,虚拟机加一些脚本,也能解决上述的标准化和隔离问题,但是容器更加轻量级,性能损耗比虚拟机低很多,并且多数集成了环境的自描述语言(例如DockerFile),更方便进行维护。

Docker是目前最流行的容器技术之一,按照官方的解释说明:Docker是开源的引擎,用于在开发或运维中构建、部署和运行应用程序。

需要指出的是,很多人把容器技术和Docker划等号,这是不对的。目前的主流容器技术,除了Docker外,还有Rkt、LXD等。

已经有了Docker,为什么还需要Kuberntes呢?因为从单击的Docker到分布式的容器集群,还有很多路要走。Kubernetes是分布式容器集群操作平台,可以轻松地完成部署、调度、扩容等操作。如果说Docker是手动挡汽车的话,那么Kubernetes相当于自动驾驶汽车,只需要很少的步骤,就可以完成复杂的容器集群管理工作。Kubernetes构建于容器引擎之上,除了支持Docker外,也支持Rtk,本书以Kubernetes和Docker为例进行讨论。

Kubernetes中的操作单元

为了适应复杂的业务需求,Kubernetes中内置了不同层级的操作单元:

  • Pod: Pod是Kubernetes的基本操作单元,也是应用运行的载体。如果你了解Docker的话,可以理解为Pod = 若干紧密相连的Docker + 数据卷。Pod中可能包含若干容器,它们是无法进行更细粒度的分割的,例如:微服务和它的日志收集进程。Pod内部的这些容器共享相同的资源(网络、进程通信、数据卷)
  • Replica Set:高可用、高性能是分布式系统中常见的问题。一般都可以采用增加冗余节点的方式解决,Replica Set通过标签关联Pod,并可以设置一个副本数,以实现微服务的冗余。
  • Deployment: Deployment描述了一个部署。在Kubernetes中,并不推荐直接启动Pod,也不推荐使用Replica Set,而建议直接使用Deployment。通过在Deployment中描述所期望的Pod、版本和副本数量,就可以实现管理、滚动升级、回滚、扩容、缩容等复杂的操作。Deployment与Pod并非是包含关系,而是相互独立的。Deployment通过“标签匹配”,可以关联若干Pod。
  • Service: 从字面意义理解,Service就是服务组。类似的,Service也是独立于Pod、Deployment概念。它也是通过“标签匹配”的方式关联若干Pod,并对外提供了统一的服务代理。通过访问统一服务代理,流量被自动分发到所有关联的Pod上,服务代理可以根据不同策略,进行负载均衡。如果你仔细阅读了微服务架构概览,就会明白,Kubernetes的Service就是服务发现的一种实现方式。

minikube

Kubernetes提供了强大的集群管理功能,当然,它的集群环境的配置较为复杂,并非简短篇幅可以说清楚。

本书的核心是微服务架构,而非Kubernetes的使用,因此,我们不会详细讲解k8s集群的配置。

幸运的是,k8s为我们提供了minikube,它一个用于快速开发的单机k8s环境,拥有与k8s集群完全相同的功能。本章的剩余章节,我们将使用minikube来进行讲解。

关于minikube的安装,可以参考官方的这篇minikube安装教程,这里不做详细展开。

需要特别指出:minikube只限于开发和学习使用。对于生产环境,请务必配置Kubernetes的分布式集群,大家可以参考官方文档

Hello Deployment

minikube安装妥当后,让我们来部署第一个Deployment。

首先,启动minikube。第一次启动需要下载ISO镜像,时间较长,请耐心等待一下。

minikube start --disk-size 50g --memory 4096 --insecure-registry "192.168.99.0/24"

上述第一次start实际是配置了minikube虚拟机的参数,我们简单解释一下:

  • disk-size 磁盘空间我们给了50g。我们之后会配置私有Maven仓库,需要建立主仓库索引,默认的20g不太够用。
  • memory 内存,给了4G,可以根据你的需求自己设定。
  • insecure-registry 我们之后会搭建不带证书的私有仓库,所以这里预先设置好仓库的IP范围。

提醒一下,上述minikube参数只对第一次启动(实际是创建)生效,一旦虚拟机生成完毕,这些参数的修改都不会生效了。

Kubernetes支持两种操作方式:命令行参数、yaml文件定义。鉴于维护性等角度,我们更推荐推荐后者,即用yaml文件的方式。

Deployment描述文件,lmsia-abc-server-deployment.yaml


apiVersion: apps/v1
// Deployment
kind: Deployment
metadata:
  name: lmsia-abc-server-deployment
spec:
  selector:
    matchLabels:
      app: lmsia-abc-server 
  replicas: 2
// Pod define
  template:
    metadata:
      labels:
        app: lmsia-abc-server 
    spec:
      containers:
      - name: lmsia-abc-server-ct
        image: coder4/lmsia-abc-server:1.0
        ports:
        - containerPort: 8080
        - containerPort: 3000

我们来解读一下这个yaml文件,采用自底向上的步骤:

  • Pod定义:如注释标记,文件的下半部分定义了Pod信息。  * metadata.labels.app是Pod的标签名,用于与Deployment、Replica Set和Service做关联。
    • spec.containers定义了Pod的名字(name)、镜像(image)和开放端口(ports)。这里使用了我预先编译好的一个微服务镜像,它集成了REST服务和RPC服务,分别监听8080端口和3000端口。
  • Deployment定义:文件的上半部分,是部署的定义。
  • kind类型是Deployment
  • metadata.name是Deployment的名字,用于后续的进一步操作
  • replica是副本数定义,这里我们的副本数(replicas)设定为2。
  • selector.matchLabels定义了与Pod的关联,请注意selector.matchLabels与Pod中的metadata.labels需要保持一致,才能成功关联。

理解了文件内容后,让我们来新建这个部署:

kubectl apply -f ./lmsia-abc-server-deployment.yaml

我们来看看启动了哪些Pod。这里我们以标签为查询参数。

kubectl get pods -l app=lmsia-abc-server

NAME                                          READY     STATUS    RESTARTS   AGE
lmsia-abc-server-deployment-bd4949ff9-jcczg   1/1       Running   0          10m
lmsia-abc-server-deployment-bd4949ff9-zqsvc   1/1       Running   0          10m

不难发现,启动了两个Pod,和我们在yaml文件中设定的副本数一致。

注意:上图展示的是最终结果,Pod的启动前需要先拉取镜像,因此会存在“非Running”的中间状态。

截至目前,我们已经通过Deployment的方式,成功的启动了两个Pod。前面已经介绍,镜像中的微服务对外暴露了两个端口:REST(HTTP)服务的8080端口,和RPC服务的3000端口。接下来,我们尝试访问Pod内的HTTP服务。

先来获取一下IP地址,以第一个Pod为例:

kubectl describe pod lmsia-abc-server-deployment-bd4949ff9-jcczg

Name:           lmsia-abc-server-deployment-bd4949ff9-jcczg
....
Status:         Running
IP:             172.17.0.5
Controlled By:  ReplicaSet/lmsia-abc-server-deployment-bd4949ff9
....

由于结果较多,这里只截取了关键的几行,可以从结果中看到,名字为“lmsia-abc-server-deployment-bd4949ff9-jcczg”的Pod,它的IP是“"172.17.0.5"。

尝试访问一下,会报"No route to host"错误:

curl http://172.17.0.5:8080/lmsia-abc/api/

curl: (7) Failed to connect to 172.17.0.5 port 8080: No route to host

为什么会这样呢?因为我们启动的minikube集群实际是一个虚拟机,172.17.0.5是虚拟机的内网地址。我们执行命令的命令行,是在虚拟机外部。相当与我们从外网要访问内网地址,这自然是无法成功访问的。 要说明的是,我们这里并没有访问跟路径,而是访问了“lmsia-abc/api/”,大家可以暂且认为这是一个合法的url pattern,我们将在微服务开发中对此进行讲解。

如何解决呢,有两种方案:

  • 登录到虚拟机上,再访问
  • 打通内网和外网 其中,第二种方案是日常工作中常见的需求,我们在OpenVPN + NAT 打通办公网与IDC](devops/openvpn-nat.md)一节中,详细介绍了一种方案。在此处,我们先采用第一种方案。

登录到minikube虚拟机很简单:

minikube ssh

$

注意:若需要登录到minikube虚拟机后再执行的操作,我们会增加一个$符号,以便区分。

登录到minikube虚拟机后,再次尝试Pod上的HTTP服务,可以成功访问了:

$curl http://172.17.0.5:8080/lmsia-abc/api/

Hello, REST

至此,我们成功的创建了Deployment、查看了Pod的信息、访问了Pod上的Rest服务。

最后,我们学习下如何删除Deployment。在虚拟机环境下,是没有kubectl可以使用的,所以首先要退出虚拟机。

$exit

然后再来删除Deployment,命令可以成功执行:

kubectl delete deployment lmsia-abc-server-deployment

deployment.extensions "lmsia-abc-server-deployment" deleted

再来看一下相关的Pod信息,发现已经找不到对应Pod了:

kubectl get pods -l app=lmsia-abc-server

No resources found.

书籍推荐