在本地搭建Kubernetes环境

什么是容器编排

容器技术的核心概念是容器、镜像、仓库,使用这三大基本要素我们就可以轻松地完成应用的打包、分发工作,实现了"一次开发,到处运行"。

不过,当我们熟练地掌握了容器技术,却会发现容器技术的创新只是解决了运维部署工作中一个很小的问题。现实生产环境的复杂程度实在是太高了,除了最基本的安装,还会有各式各样的需求,比如服务发现、负载均衡、状态监控、健康检查、扩容缩容、应用迁移、高可用等等。

虽然容器技术开启了云原生时代,但它也只走出了一小步,再继续前进就无能为力了,因为这已经不再是隔离进程的问题,还有它们之间互相通信、互相协作的问题。

在容器之上的管理、调度工作,就是:"容器编排"(Container Orchestration)。

像我们使用Docker部署一个网站的时候,把Nginx容器、业务代码容器、数据库容器这三个容器理清次序、配好IP地址去运行,就是最初级的一种"容器编排",只不过这是纯手工操作,比较原始、粗糙。

面对单机上的几个容器,"人肉"编排调度还可以应付,但如果规模上到几百台服务器、成千上万的容器,处理它们之间的复杂联系就必须要依靠计算机了,而目前计算机用来调度管理的"事实标准",就是:Kubernetes。

什么是Kubernetes

说起 Kubernetes,这一切还得从云计算这个词说起,云计算从起初的概念演变为现在的 AWS、阿里云等实实在在的云产品(主要是虚拟机和相关的网络、存储服务),可见已经变得非常成熟和稳定。

正当大家以为云计算领域已经变成了以虚拟机为代表的云平台时,Docker 在 2013 年横空出世,Docker 提出了镜像、仓库等核心概念,规范了服务的交付标准,使得复杂服务的落地变得更加简单,之后 Docker 又定义了 OCI 标准,可以说在容器领域 Docker 已经成了事实的标准。

然而 Docker 诞生只是帮助我们定义了开发和交付标准,如果想要在生产环境中大批量的使用容器,还离不开的容器的编排技术。于是,在 2014 年 6 月 7 日,Kubernetes(Kubernetes 简称为 K8S,8 代表 ubernete 8个字母) 的第一个 commit(提交)拉开了容器编排标准定义的序幕。

Kubernetes 是舵手的意思,我们把 Docker 比喻成一个个集装箱,而 Kubernetes 正是运输这些集装箱的舵手。

Kubernetes 架构

Kubernetes 采用声明式 API 来工作,所有组件的运行过程都是异步的,整个工作过程大致为用户声明想要的状态,然后 Kubernetes 各个组件相互配合并且努力达到用户想要的状态。

Kubernetes 采用典型的主从架构,分为 Master 和 Node 两个角色。

  • Master 是 Kubernetes 集群的控制节点,负责整个集群的管理和控制功能。
  • Node 为工作节点,负责业务容器的生命周期管理。

Master 节点

Master 节点负责对集群中所有容器的调度,各种资源对象的控制,以及响应集群的所有请求。Master 节点包含三个重要的组件: kube-apiserver、kube-scheduler、kube-controller-manager。下面对这三个组件逐一介绍。

  • kube-apiserver

kube-apiserver 主要负责提供 Kubernetes 的 API 服务,所有的组件都需要与 kube-apiserver 交互获取或者更新资源信息,它是 Kubernetes Master 中最前端组件。

kube-apiserver 的所有数据都存储在etcd中,etcd 是一种采用 Go 语言编写的高可用 Key-Value 数据库,由 CoreOS 开发。etcd 虽然不是 Kubernetes 的组件,但是它在 Kubernetes 中却扮演着至关重要的角色,它是 Kubernetes 的数据大脑。可以说 etcd 的稳定性直接关系着 Kubernetes 集群的稳定性,因此生产环境中 etcd 一定要部署多个实例以确保集群的高可用。

  • kube-scheduler

kube-scheduler 用于监听未被调度的 Pod,然后根据一定调度策略将 Pod 调度到合适的 Node 节点上运行。

  • kube-controller-manager

kube-controller-manager 负责维护整个集群的状态和资源的管理。例如多个副本数量的保证,Pod 的滚动更新等。每种资源的控制器都是一个独立协程。kube-controller-manager 实际上是一系列资源控制器的总称。

为了保证 Kubernetes 集群的高可用,Master 组件需要部署在多个节点上,由于 Kubernetes 所有数据都存在于 etcd 中,Etcd 是基于 Raft 协议实现,因此生产环境中 Master 通常建议至少三个节点。

Node 节点

Node 节点是 Kubernetes 的工作节点,负责运行业务容器。Node 节点主要包含两个组件 :kubelet 和 kube-proxy。

  • kubelet

Kubelet 是在每个工作节点运行的代理,它负责管理容器的生命周期。Kubelet 通过监听分配到自己运行的主机上的 Pod 对象,确保这些 Pod 处于运行状态,并且负责定期检查 Pod 的运行状态,将 Pod 的运行状态更新到 Pod 对象中。

  • kube-proxy

Kube-proxy 是在每个工作节点的网络插件,它实现了 Kubernetes 的 Service 的概念。Kube-proxy 通过维护集群上的网络规则,实现集群内部可以通过负载均衡的方式访问到后端的容器。

Kubernetes 的成功不仅得益于其优秀的架构设计,更加重要的是 Kubernetes 提出了很多核心的概念,这些核心概念构成了容器编排的主要模型。

Kubernetes 核心概念

Kubernetes 这些概念是 Google 多年的技术沉淀和积累,理解 Kubernetes 的核心概念有助于我们更好的理解 Kubernetes 的设计理念。

(1)集群

集群是一组被 Kubernetes 统一管理和调度的节点,被 Kubernetes 纳管的节点可以是物理机或者虚拟机。集群其中一部分节点作为 Master 节点,负责集群状态的管理和协调,另一部分作为 Node 节点,负责执行具体的任务,实现用户服务的启停等功能。

(2)标签(Label)

Label 是一组键值对,每一个资源对象都会拥有此字段。Kubernetes 中使用 Label 对资源进行标记,然后根据 Label 对资源进行分类和筛选。

(3)命名空间(Namespace)

Kubernetes 中通过命名空间来实现资源的虚拟化隔离,将一组相关联的资源放到同一个命名空间内,避免不同租户的资源发生命名冲突,从逻辑上实现了多租户的资源隔离。

(4)容器组(Pod)

Pod 是 Kubernetes 中的最小调度单位,它由一个或多个容器组成,一个 Pod 内的容器共享相同的网络命名空间和存储卷。Pod 是真正的业务进程的载体,在 Pod 运行前,Kubernetes 会先启动一个 Pause 容器开辟一个网络命名空间,完成网络和存储相关资源的初始化,然后再运行业务容器。

(5)部署(Deployment)

Deployment 是一组 Pod 的抽象,通过 Deployment 控制器保障用户指定数量的容器副本正常运行,并且实现了滚动更新等高级功能,当我们需要更新业务版本时,Deployment 会按照我们指定策略自动的杀死旧版本的 Pod 并且启动新版本的 Pod。

(6)状态副本集(StatefulSet)

StatefulSet 和 Deployment 类似,也是一组 Pod 的抽象,但是 StatefulSet 主要用于有状态应用的管理,StatefulSet 生成的 Pod 名称是固定且有序的,确保每个 Pod 独一无二的身份标识。

(7)守护进程集(DaemonSet)

DaemonSet 确保每个 Node 节点上运行一个 Pod,当我们集群有新加入的 Node 节点时,Kubernetes 会自动帮助我们在新的节点上运行一个 Pod。一般用于日志采集,节点监控等场景。

(8)任务(Job)

Job 可以帮助我们创建一个 Pod 并且保证 Pod 的正常退出,如果 Pod 运行过程中出现了错误,Job 控制器可以帮助我们创建新的 Pod,直到 Pod 执行成功或者达到指定重试次数。

(9)服务(Service)

Service 是一组 Pod 访问配置的抽象。由于 Pod 的地址是动态变化的,我们不能直接通过 Pod 的 IP 去访问某个服务,Service 通过在主机上配置一定的网络规则,帮助我们实现通过一个固定的地址访问一组 Pod。

(10)配置集(ConfigMap)

ConfigMap 用于存放我们业务的配置信息,使用 Key-Value 的方式存放于 Kubernetes 中,使用 ConfigMap 可以帮助我们将配置数据和应用程序代码分开。

(11)加密字典(Secret)

Secret 用于存放我们业务的敏感配置信息,类似于 ConfigMap,使用 Key-Value 的方式存在于 Kubernetes 中,主要用于存放密码和证书等敏感信息。

了解完 Kubernetes 的架构和核心概念,你是不是已经迫不及待地想要体验下了。下面就让我们动手安装一个 Kubernetes 集群,来体验下 Kubernetes 的强大之处吧。

了解完 Kubernetes 的架构和核心概念,你是不是已经迫不及待地想要体验下了。下面就让我们动手安装一个 Kubernetes 集群,来体验下 Kubernetes 的强大之处吧。

安装 Kubernetes

什么是minikube

Kubernetes一般都运行在大规模的计算集群上,管理很严格,这就对我们个人来说造成了一定的障碍,没有实际操作环境怎么能够学好用好呢?

好在Kubernetes充分考虑到了这方面的需求,提供了一些快速搭建Kubernetes环境的工具,在官网(kubernetes.io/zh/docs/tas...)上推荐的有两个:kind和minikube,它们都可以在本机上运行完整的Kubernetes环境。

  • kind基于Docker,意思是"Kubernetes in Docker"。它功能少,用法简单,也因此运行速度快,容易上手。不过它缺少很多Kubernetes的标准功能,例如仪表盘、网络插件,也很难定制化,所以我认为它比较适合有经验的Kubernetes用户做快速开发测试,不太适合学习研究。

  • minikube,从名字就能够看出来,它是一个"迷你"版本的Kubernetes,自从2016年发布以来一直在积极地开发维护,紧跟Kubernetes的版本更新,同时也兼容较旧的版本(最多只到之前的6个小版本)。

    minikube最大特点就是"小而美",可执行文件仅有不到100MB,运行镜像也不过1GB,但就在这么小的空间里却集成了Kubernetes的绝大多数功能特性,不仅有核心的容器编排功能,还有丰富的插件,例如Dashboard、GPU、Ingress、Istio、Kong、Registry等等,综合来看非常完善。

如何搭建minikube环境

minikube支持Mac、Windows、Linux这三种主流平台,你可以在它的官网找到详细的安装说明,当然在我们这里就只用虚拟机里的Linux了。

minikube的官网提供了各种系统的安装命令,通常就是下载、拷贝这两步,不过你需要注意一下本机电脑的硬件架构,Intel芯片要选择带"amd64"后缀,Apple M1芯片要选择"arm64"后缀,选错了就会因为CPU指令集不同而无法运行:

如果你想要在其他平台使用 minikube 安装 Kubernetes,请参考官网安装教程。 在使用 minikube 安装 Kubernetes 之前,请确保我们的机器已经正确安装并且启动 Docker。

第一步,安装 minikube 和 kubectl。首先执行以下命令安装 minikube。

ruby 复制代码
curl -LO https://storage.googleapis.com/minikube/releases/latest/minikube-darwin-amd64
sudo install minikube-darwin-amd64 /usr/local/bin/minikube

不过minikube只能够搭建Kubernetes环境,要操作Kubernetes,还需要另一个专门的客户端工具"kubectl"。

kubectl的作用有点类似之前我们学习容器技术时候的工具"docker",它也是一个命令行工具,作用也比较类似,同样是与Kubernetes后台服务通信,把我们的命令转发给Kubernetes,实现容器和集群的管理功能。

kubectl是一个与Kubernetes、minikube彼此独立的项目,所以不包含在minikube里,但minikube提供了安装它的简化方式,你只需执行下面的这条命令:

minikube kubectl

它就会把与当前Kubernetes版本匹配的kubectl下载下来,存放在内部目录(例如 .minikube/cache/linux/arm64/v1.23.3),然后我们就可以使用它来对Kubernetes"发号施令"了。

所以,在minikube环境里,我们会用到两个客户端:minikube管理Kubernetes集群环境,kubectl操作实际的Kubernetes功能,和Docker比起来有点复杂。

我画了一个简单的minikube环境示意图,方便你理解它们的关系。

第二步,安装 Kubernetes 集群。 执行以下命令使用 minikube 安装 Kubernetes 集群:

ruby 复制代码
$ minikube start

执行完上述命令后,minikube 会自动帮助我们创建并启动一个 Kubernetes 集群。命令输出如下,当命令行输出 Done 时,代表集群已经部署完成。

第三步,检查集群状态。集群安装成功后,我们可以使用以下命令检查 Kubernetes 集群是否成功启动。

ruby 复制代码
 xuyatao@evan171206  ~  kubectl cluster-info
Kubernetes control plane is running at https://127.0.0.1:64684
CoreDNS is running at https://127.0.0.1:64684/api/v1/namespaces/kube-system/services/kube-dns:dns/proxy

To further debug and diagnose cluster problems, use 'kubectl cluster-info dump'.
scss 复制代码
 xuyatao@evan171206  ~  minikube status

minikube
type: Control Plane
host: Running
kubelet: Running
apiserver: Running
kubeconfig: Configured

 xuyatao@evan171206  ~  minikube node list
minikube	192.168.49.2

从截图里可以看到,Kubernetes集群里现在只有一个节点,名字就叫"minikube",类型是"Control Plane",里面有host、kubelet、apiserver三个服务,IP地址是192.168.49.2。

你还可以用命令 minikube ssh 登录到这个节点上,虽然它是虚拟的,但用起来和实机也没什么区别:

yaml 复制代码
 xuyatao@evan171206  ~  minikube ssh
Last login: Wed Feb 22 07:27:45 2023 from 192.168.49.1
docker@minikube:~$ uname -a
Linux minikube 5.15.49-linuxkit #1 SMP Tue Sep 13 07:51:46 UTC 2022 x86_64 x86_64 x86_64 GNU/Linux
docker@minikube:~$ ll
total 36
drwxr-xr-x 1 docker docker 4096 Feb 22  2023 ./
drwxr-xr-x 1 root   root   4096 Jan 27  2023 ../
-rw------- 1 docker docker   78 Feb 22  2023 .bash_history
-rw-r--r-- 1 docker docker  220 Jan 27  2023 .bash_logout
-rw-r--r-- 1 docker docker 3771 Jan 27  2023 .bashrc
-rw-r--r-- 1 docker docker  807 Jan 27  2023 .profile
drwxr-xr-x 1 docker docker 4096 Feb 22  2023 .ssh/
-rw-r--r-- 1 docker docker    0 Feb 22  2023 .sudo_as_admin_successful
docker@minikube:~$ exit;
logout

有了集群,接下来我们就可以使用kubectl来操作一下,初步体会Kubernetes这个容器编排系统,最简单的命令当然就是查看版本:

css 复制代码
 xuyatao@evan171206  ~   minikube version
minikube version: v1.29.0
commit: ddac20b4b34a9c8c857fc602203b6ba2679794d3

下面我们在Kubernetes里运行一个Nginx应用,命令与Docker一样,也是 run,不过形式上有点区别,需要用 --image 指定镜像,然后Kubernetes会自动拉取并运行:

sql 复制代码
 ✘ xuyatao@evan171206  ~  kubectl run ngx --image=nginx:alpine
Error from server (AlreadyExists): pods "ngx" already exists
 ✘ xuyatao@evan171206  ~  kubectl run nginx --image=nginx:alpine
pod/nginx created

这里涉及Kubernetes里的一个非常重要的概念:Pod ,你可以暂时把它理解成是"穿了马甲"的容器,查看Pod列表需要使用命令 kubectl get pod,它的效果类似 docker ps

sql 复制代码
 xuyatao@evan171206  ~  kubectl get pod
NAME    READY   STATUS    RESTARTS        AGE
nginx   1/1     Running   0               52s
ngx     1/1     Running   1 (3h15m ago)   259d

命令执行之后可以看到,在Kubernetes集群里就有了一个名字叫nginx的Pod正在运行,表示我们的这个单节点minikube环境已经搭建成功。

所谓的"云",现在就指的是Kubernetes,那么"云原生"的意思就是应用的开发、部署、运维等一系列工作都要向Kubernetes看齐,使用容器、微服务、声明式API等技术,保证应用的整个生命周期都能够在Kubernetes环境里顺利实施,不需要附加额外的条件。

创建第一个应用

集群搭建好后,下面我们来试着使用 Kubernetes 来创建我们的第一个应用。

这里我们使用 Deployment 来定义应用的部署信息,使用 Service 暴露我们的应用到集群外部,从而使得我们的应用可以从外部访问到。

第一步,创建 deployment.yaml 文件,并且定义启动的副本数(replicas)为 3。

scss 复制代码
 xuyatao@evan171206  ~/learn/k8s  touch deployment.yaml
 xuyatao@evan171206  ~/learn/k8s  vi deployment.yaml
 xuyatao@evan171206  ~/learn/k8s  cat deployment.yaml
apiVersion: apps/v1
kind: Deployment
metadata:
  name: hello-world
spec:
  replicas: 3
  selector:
    matchLabels:
      app: hello-world
  template:
    metadata:
      labels:
        app: hello-world
    spec:
      containers:
        - name: hello-world
          image: wilhelmguo/nginx-hello:v1
          ports:
            - containerPort: 80
yaml 复制代码
apiVersion: apps/v1
kind: Deployment
metadata:
  name: hello-world
spec:
  replicas: 3
  selector:
    matchLabels:
      app: hello-world
  template:
    metadata:
      labels:
        app: hello-world
    spec:
      containers:
        - name: hello-world
          image: wilhelmguo/nginx-hello:v1
          ports:
            - containerPort: 80

第二步,发布部署文件到 Kubernetes 集群中。

typescript 复制代码
 xuyatao@evan171206  ~/learn/k8s  kubectl create -f deployment.yaml
deployment.apps/hello-world created

部署发布完成后,我们可以使用 kubectl 来查看一下 Pod 是否被成功启动。

sql 复制代码
 ✘ xuyatao@evan171206  ~/learn/k8s  kubectl get pod -o wide
NAME                          READY   STATUS    RESTARTS        AGE     IP            NODE       NOMINATED NODE   READINESS GATES
hello-world-cb5c88574-8xxfg   1/1     Running   0               4m12s   10.244.0.13   minikube   <none>           <none>
hello-world-cb5c88574-cdnlv   1/1     Running   0               4m12s   10.244.0.11   minikube   <none>           <none>
hello-world-cb5c88574-g56r8   1/1     Running   0               4m12s   10.244.0.12   minikube   <none>           <none>
nginx                         1/1     Running   0               12m     10.244.0.10   minikube   <none>           <none>

这里可以看到 Kubernetes 帮助我们创建了 3 个 Pod 实例。

第三步,创建 service.yml 文件,帮助我们将服务暴露出去,内容如下:

scss 复制代码
xuyatao@evan171206  ~/learn/k8s  touch service.yml
xuyatao@evan171206  ~/learn/k8s  vi service.yml
xuyatao@evan171206  ~/learn/k8s  cat service.yml
apiVersion: v1
kind: Service
metadata:
  name: hello-world
spec:
  type: NodePort
  ports:
    - port: 80
      targetPort: 80
  selector:
    app: hello-world
yaml 复制代码
apiVersion: v1
kind: Service
metadata:
  name: hello-world
spec:
  type: NodePort
  ports:
    - port: 80
      targetPort: 80
  selector:
    app: hello-world

小插曲

Service和Pod

Pod是有生命周期的,当一个工作节点(node)销毁时,节点上运行的pods也会被销毁,ReplicationController会动态地在其他节点上创建Pod来保持应用程序的运行,每一个Pod都有一个独立的IP地址,甚至是同一个节点上的Pod,可以看出Pod的IP是动态的,它随Pod的创建而创建,随Pod的销毁而消失,这就引出一个问题:如果由一组Pods组合而成的集群来提供服务,那如何访问这些Pods呢?

Kubenetes的Service就是用来解决这个问题的。一个Service可以看作一组提供相同服务的Pods的对外访问接口,Service作用于哪些Pods是通过label selector来定义的,这些Pods能被Service访问,Pod之间的发现和路由(如应用中的前端和后端组件)由Kubernetes Service处理。

Service有四种type: ClusterIP(默认)、NodePort、LoadBalancer、ExternalName. 其中NodePort和LoadBalancer两类型的Services可以对外提供服务。

yaml 复制代码
apiVersion: v1
kind: Service
metadata:
  name: kube-node-service
  labels:
    name: kube-node-service
spec:
  type: NodePort      #这里代表是NodePort类型的
  ports:
  - port: 80          #这里的端口和clusterIP(10.97.114.36)对应,即10.97.114.36:80,供内部访问。
    targetPort: 8081  #端口一定要和container暴露出来的端口对应,nodejs暴露出来的端口是8081,所以这里也应是8081
    protocol: TCP
    nodePort: 32143   # 所有的节点都会开放此端口,此端口供外部调用。
  selector:
    app: web          #这里选择器一定要选择容器的标签

sql 复制代码
 xuyatao@evan171206  ~/learn/k8s  kubectl create -f service.yml
service/hello-world created

 xuyatao@evan171206  ~/learn/k8s  kubectl  get service -o wide
NAME          TYPE        CLUSTER-IP       EXTERNAL-IP   PORT(S)        AGE    SELECTOR
hello-world   NodePort    10.108.150.245   <none>        80:32061/TCP   8s     app=hello-world
kubernetes    ClusterIP   10.96.0.1        <none>        443/TCP        259d   <none>


 xuyatao@evan171206  ~/learn/k8s  minikube service hello-world
|-----------|-------------|-------------|---------------------------|
| NAMESPACE |    NAME     | TARGET PORT |            URL            |
|-----------|-------------|-------------|---------------------------|
| default   | hello-world |          80 | http://192.168.49.2:32061 |
|-----------|-------------|-------------|---------------------------|
🏃  Starting tunnel for service hello-world.
|-----------|-------------|-------------|------------------------|
| NAMESPACE |    NAME     | TARGET PORT |          URL           |
|-----------|-------------|-------------|------------------------|
| default   | hello-world |             | http://127.0.0.1:60249 |
|-----------|-------------|-------------|------------------------|
🎉  正通过默认浏览器打开服务 default/hello-world...
❗  Because you are using a Docker driver on darwin, the terminal needs to be open to run it.

可以看到 minikube 将我们的服务暴露在了 60249 端口上,我们通过 http://127.0.0.1:60249/ 可以访问到我们启动的服务,如下图所示。

总结下,我们首先使用 Deployment 创建了三个 nginx-hello 的实例,然后使用 Service 的方式随机负载到后端的三个实例,并将服务通过 NodePort 的方式暴露在主机上,使得我们可以直接使用主机的端口访问到容器中的服务。

相关推荐
monkey_meng36 分钟前
【Rust中的迭代器】
开发语言·后端·rust
余衫马39 分钟前
Rust-Trait 特征编程
开发语言·后端·rust
monkey_meng43 分钟前
【Rust中多线程同步机制】
开发语言·redis·后端·rust
paopaokaka_luck5 小时前
【360】基于springboot的志愿服务管理系统
java·spring boot·后端·spring·毕业设计
码农小旋风7 小时前
详解K8S--声明式API
后端
Peter_chq7 小时前
【操作系统】基于环形队列的生产消费模型
linux·c语言·开发语言·c++·后端
Yaml47 小时前
Spring Boot 与 Vue 共筑二手书籍交易卓越平台
java·spring boot·后端·mysql·spring·vue·二手书籍
小小小妮子~7 小时前
Spring Boot详解:从入门到精通
java·spring boot·后端
hong1616887 小时前
Spring Boot中实现多数据源连接和切换的方案
java·spring boot·后端
睡觉谁叫~~~8 小时前
一文解秘Rust如何与Java互操作
java·开发语言·后端·rust