从0到1 Kubernetes 落地我的博客线上环境

Kubernetes 在我的博客中的应用

Kubernetes 环境使用minikube

一、Kubernetes 介绍

1.1 什么是Kubernetes

让我们一起来看看官方文档对其的声明介绍

Kubernetes 是一个可移植的、可扩展的开源平台,用于管理容器化的工作负载和服务,可促进声明式配置和自动化。Kubernetes 拥有一个庞大且快速增长的生态,其服务、支持和工具使用范围相当广泛。

我们为什么需要 Kubernetes 呢?

  • 非云原生环境下,我们上线某一个服务时,需要人工去手动上线灰度,同时对应的流量管理也需要人工去监控配置以及无法保证在容器启动前不分配流量等(或者需要人工干预),而 Kubernetes 原生支持这几个特性:滚动更新(Deployment配置发生变化时,Kubernetes会执行滚动更新,其会逐渐替换旧的Pod实例,而非同时终止所有实例)、健康检查、流量管理(在更新过程中,流量逐渐从旧的Pod中转移到新的Pod中)、自动扩展(基于资源使用情况自动扩展Pod的数量)等。对于回滚操作,Kubernetes 原生支持版本控制、自动回滚、手动回滚、回滚策略配置等用于进行应用程序回滚操作。

  • 非云原生环境下,当服务内因为某个极少出现的路径而出现导致 panic 时,需要人工去重启某个服务(也有可能是通过脚本,但是类似人工),而 Kubernetes 原生支持自我修复能力。

  • 非云原生环境下,配置文件复杂,需要指定将配置文件放在二进制文件相关的目录下,或者存放在公有配置平台上(类似Nacos)等,而Kubernetes原生支持Pod 环境变量支持、Service 默认环境变量支持、存储卷的支持以及 ConfigMap 对象等,其天然支持性,足可以让传统应用摒弃掉其老旧的配置方式,让业务聚焦在业务上,而非业务之外的配置上。

  • 非云原生环境下,想要实现负载均衡效果,必须借助于第三方服务均衡平台来实现(例如Nacos),而Kubernetes原生支持负载均衡效果,其通过Service将一组Pod上的应用程序公开为网络服务,而请求通过宿主机到Service后,由Service默认通过轮训机制来将请求分发到属于该Service的不同Pod上,从而实现负载均衡。

  • 非云原生环境下,当网络流量突增的情况下,无法及时对所需要的服务进行扩容,以及当流量下降后,无法自动实现缩容来降低资源的使用情况,而 Kubernetes 原生支持服务级别按照资源的使用情况进行扩缩容,而无需人工干预。

1.2 Kubernetes 特性

  • 自动化上线和回滚

    Kubernetes 会分步骤地将针对应用或其配置的更改上线,同时监视应用程序运行状况以确保你不会同时终止所有实例。如果配置有问题,Kubernetes 会为你回滚所做更改。

  • 服务发现与负载均衡

    无需更改应用来使用陌生的服务发现机制,Kubernetes 为每个 Pod 提供了自己的 IP 地址并为一组 Pod 提供了一个 DNS 名称,并且可以在他们之间实现负载均衡,默认的负载均衡机制为轮训机制。

  • 自我修复

    重新启动失败的容器,在节点死亡时替换并重新调度容器,杀死不响应用户自定义的健康检查的容器,并且在他们准备好服务之前不会将他们公布给客户端。

  • 存储编排

    自动挂在所选择的存储系统,包括本地存储、公有云提供商所提供的存储或者注入 iSCSI 或 NFS 这类网络存储系统。

  • Secret 和配置管理

    部署和更新 Secret 和应用程序的配置而不必重新构建容器镜像,且不必将软件堆栈配置中的秘密信息暴露出来。

  • 自动装箱

    根据资源需求和其他限制自动放置容器,同时避免影响可用性,将关键性的和尽力而为性质的工作负载进行混合配置,以提高资源利用率并节省更多资源。

  • 批量执行

    除了服务之外,Kubernetes 还可以管理你的批处理和 CI 工作负载,在期望时替换掉失效的容器。

  • IPv4/IPv6 双协议栈

    为 Pod 和 Service 分配 IPv4 和 IPv6 地址。

  • 水平扩缩容

    使用一个简单的命令、Yaml配置等, 可以让 Deployment 根据其 CPU 或者 Memory 使用情况来进行自动扩缩容。

  • 可扩展

    无需更改上游源码即可扩展你的 Kubernetes 集群。

1.3 Kubernetes 基础架构

我们先来看看 Kubernetes 基本的基础架构图,然后我们再来依次理解每一个核心组件的含义以及其所承担的责任在整个 Kubernetes 集群中。

核心组件:控制平面
  • kube-api-service

    Kubernetes API 服务器验证并配置 API 对象数据,这些对象包括 Pod、Service等。 API 服务器为 REST 操作提供了服务,并为集群的共享状态提供前端,所有组件都通过该前端与其进行交互。

    同时 kube-api-service 还会将操作对象的状态持久化到 etcd 中。

  • schduler

    Kubernetes 调度器是一个控制面进程,负责将 Pod 指派到节点上。调度器基于约束条件和可用资源为调度队列中的每个 Pod 确定其合法放置的节点。调度器之后对所有合法的节点进行排序,将 Pod 绑定到一个合适的节点上。

  • Controller Manager

    Kubernetes 控制器管理器是一个守护进程,内嵌随 Kubernetes 一起发布的核心控制回路。在机器人和自动化应用中,控制回路是一个永不休止的循环,用于调节系统的状态。在 Kubernetes 中,每个控制器都是一个控制回路,通过 kube-api-service 监视集群的共享状态,并尝试进行更改以将当前状态转换为期望的状态。

  • etcd

    提供所有配置数据和状态信息的可靠存储,Kubernetes 所有需要持久化的内容都将会持久化到 etcd 中

  • Cloud Controller Manager

    仅当Kubernetes 运行在云提供商的环境中使用,管理与云控制相关的控制器

核心组件:Node 节点
  • kubelet

    Kubernetes 中的 Kubelet 是每个 Node 上的主要代理,负责维护容器的生命周期以及与 Kubernetes 的控制平面的通信。

    Kubelet 基于 PodSpec 来进行工作,Kubelet 接受通过各种机制提供的一组 PodSpec,并确保这些 PodSpec 中描述的容器处于运行状态并且运行状态良好,其不负责不是 Kubernetes 创建的容器。简单来说,Kubelet 处于 CRI 和 kube-api-service 中间状态,且 Kubelet 处于每一个 Node上,其向 Control Plane 控制平面提供相关的容器运行时接口,同时接受来自 kube-api-service 的指令调用 CRI 接口来实现具体的操作。

    • 与 Kubernetes 通信:

      启动注册:Kubelet 首次启动时会向 Kubernetes 集群中注册自己作为一个 Node ,这个过程包括向 API 服务器发送节点信息,比如 IP地址、资源信息(CPU、Memory、IO)等。

      **接受 PodSpec 规约:**Kubelet 周期性的从 kube-api-service 服务器中查询分配给该节点的 PodSpec。其中 PodSpec 描述了 Pod 需要的运行时资源、环境变量等该 Pod 所预期的状态。

    • 管理容器的生命周期:

      **创建和启动容器:**Kubelet 根据从 kube-api-service 组件中接收到的 PodSpec,kubelet 指导容器运行时(CRI Container Runtime Interface)在当前 Kubelet 所属的节点上创建和启动容器。

      健康检查: Kubelet 定期对节点上的容器执行健康检查,这类操作包括 PodSpec 中定义的 livenessProbereadiness 探针。如果探针检测到容器不健康(探针定义的检测方式未按照预期响应)时,Kubelet 则会重启不健康的容器。

      资源管理: Kubelet 管理节点上的资源分配,确保每个 Pod 都能获取其需要的资源,也包括执行资源限制以避免单个应用消耗过多资源。

    • 更新节点 Pod 的状态:

      **节点状态报告:**Kubelet 定期向控制平面 API 服务器报告节点的运行状态,包括资源使用情况、运行的 Pod 信息和节点的健康状态。

      **Pod 状态报告:**Kubelet 定期向控制平面 API 服务器报告每个 Pod 状态,包括运行状态、任何错误信息和其他重要的状态信息。其中控制平面的 Controller Manager 则会根据 Pod 上报来的状态和其预先定义的期望值来进行不断更正,使其永远向其期望的状态靠齐。

    • 容器运行时交互:

      Kubelet 通过容器运行时接口与容器运行时进行交互,这使得 Kubelet 可以与不同的容器技术进行兼容。

    • 卷管理和网络配置:

      **管理存储卷:**Kubelet 负责挂载 Pod 所需要的存储卷,并确保这些卷可用于 Pod 中的容器。

      **管理网络:**Kubelet 配合网络插件设置 Pod 网络,包括为 Pod 分配 IP 地址和设置网络规则。

  • kube-proxy

    Kubernetes 网络代理在每个节点上运行。网络代理反映了每个节点上 Kubernetes API 中定义的服务,并且可以执行简单的 TCP、UDP、SCTP 流转发,或者在一组后端进行循环 TCP、UDP 和 SCTP 转发。其最主要的目的就是实现网络代理,允许通过集群内虚拟IP实现对不同的 Pod 访问。

    • kube-proxy 的几种模式:

      userspace: kube-proxy 在用户空间内为每个服务设置一个监听端口,所有发送到服务 IP 和端口的流量都被转发到这个监听的端口,然后 kube-proxy 将流量转发到服务的后端 Pods 中。这种模式比较简单,但是效率并不是很高,因为流量需要在用户态和内核态之间多次拷贝。

      **iptables:**kube-proxy 使用 netfilteriptables 规则来直接在 Linux 内核中进行流量转发,这种模式的效率要比 userspace 高,因为它避免了流量在用户空间和内核空间的拷贝。kube-proxy 如果使用该方式,则 kube-proxy 会设置相对应的 iptables 规则,这些规则可以捕获到服务 IP 和端口的流量,并将其重定向到正确的 Pods 中。

      **ipvs:基于内核的负载均衡解决方案,它可以处理大量流量并提供更加复杂的负载均衡算法,比如最小链接、最短响应等。**kube-proxy 在 IPVS 模式下为每个服务创建 IPVS 规则,并定期与 Kubernetes API 服务器同步服务和 Endpoints 信息来更新这些规则。

  • CRI Container Runtime Interface

    CRI 是 Kubernetes 中的一个关键组件,提供了一种标准化的方法来与不同的容器运行时进行交互,通过 CRI,Kubernetes 能够更灵活的适应不用的技术和需求,同时也为容器生态系统的发展和创新提供了空间。

Kubectl 执行命令的流程:我们通过创建一个 Pod 的命令来简单理解一下 Kubernetes 执行命令的流程

我们执行以下命令:

shell 复制代码
kubectl create -f pod.yaml
  • kubectl 发送请求:

    kubectl 将客户端定义的 Pod 的配置文件 pod.yaml 发送到 kube-api-service 服务器

  • kube-api-service 处理请求

    1. 验证本次操作的权限
    2. 持久化本次要操作的对象的配置文件 Yaml 文件到 etcd 中,只有成功持久化了,Kubernetes 才认为本次操作对象时成功的,可能这个时候还没有具体去执行本次配所对应的对象的操作,但是未来的一段时间内是一定会去执行的
    3. 响应 kubectl 客户端,告知本次操作是成功的或者失败的
  • schduler 进行资源调度

    1. 调度器监视新的未分配节点的 Pod
    2. 当发现有新的未调度节点的 Pod 时,调度器会选择一个节点来运行该 Pod,其具体分配到那个节点上将会基于资源需求、节点亲和性、污点和容忍等
    3. 调度器将其决定更新到 Pod 的状态中,并将该信息返回给 kube-api-service。
  • Controller Manager 负责运行控制循环,保证集群的状态永远向用户所期望的状态靠齐。

    Pod 创建,控制前不直接参与创建流程,而是永远保证 Pod 的运行状态永远和我们所定义的状态靠齐

  • kubelet 管理当前 Node 上的 Pod

    1. Pod 被调度到 特定的节点,该节点的 Kubelet 则会接受到这个信息
    2. Kubelet 与容器运行时通信,根据 Pod 的定义或者 kube-api-service 的操作来操作 Pod
    3. Kubelet 负责监控容器的状态,确保他们按照 Pod 的定义正常运行
    4. Kubelet 定期向 API 服务器报告 Pod 状态
  • kube-proxy:创建 Pod 的网络代理

    Kube-proxy 负责创建 Pod 的网络代理,每个 Pod 都有一个 Kubernetes 集群内的虚拟IP。

1.4 Pod 的生命周期、状态以及回调

生命周期(Pod 状态)
  • Pending:Pod 已经被 Kubernetes 系统接受,但是容器还为按照预期状态创建完毕。
  • Running:Pod 已经绑定到了某个节点,Pod 中的所有容器都已经被创建。
  • Succeeded:Pod 中的所有容器都已经成功终止,并且不会在重启。
  • Failed:Pod 中的所有容器都已经终止,至少有一个容器是因为失败而终止,会进行重启。
  • Unknown:因为某些原因无法取的 Pod 状态,通常为与 Pod 所在的主机通信失败。
生命周期回调
  • PostStart :在容器创建后立即执行,但是并不能保证优先入口点执行。
  • PreStop:在容器终止之前进行调用。Kubernetes 的容器管理逻辑会一直阻塞等待 PreStop 处理函数执行完毕直到 Pod 的宽限期超时。
容器状态
  • Waiting:容器尚未运行,可能正在等待启动或者下载镜像中。
  • Running:容器正在运行。
  • Terminated:容器已经运行完毕并且停止。

1.5 Kubernetes 对象类型

Kuberentes 对象类型以及各个对象的含义

TODO: 待补充

二、Kubernetes 具体操作步骤

在写具体操作步骤之前,我已经将 blogsvr 服务部署到 Kubernetes 中,而在写该篇文章时是边写边将 authsvr 服务部署到 Kubernetes 环境中,这样读者可能会更加清晰明了。

2.1 创建 Kubernetes 环境

参考文档:

通过 minikube 创建

安装文档:minikube.sigs.k8s.io/docs/start/

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

执行上面命令即可安装完成,安装完成之后直接启动:添加参数 --drive=docker --force 的原因是 minikube 不建议使用 Root 用户来进行操作,所以这里直接通过强制性使用 Root 用户来操作。

shell 复制代码
minikube  start --driver=docker --force

出现下面则可认为 minikube 安装成功。

由于我们已经理解了 Kubernetes 的基本概念,那么我们现在只需要定义 Pod 即可让我的博客后台应用服务直接切换到 Kubernetes 环境中。

**注意:**我们要注意的是 minikube 只是一个虚拟机,其内部包含了我们所需要的各个组件(将 Kubernetes 的各个组件运行在容器内,统一通过 minikube 虚拟机对外提供 Kubernetes 环境),既然其是虚拟机,我们可以通过直接进入其内部来实现我们所需要的各个操作

shell 复制代码
# 直接进入内部
minikube ssh
配置 kubectl

安装文档:kubernetes.io/zh-cn/docs/...

2.2 创建 Pod 用以部署应用程序

我们在上面已经熟悉了 Kubernetes 的基本概念,那么我们现在只要用 Kubernetes 支持的声明式 API 来创建 Pod 即可,具体 Pod 定义(其中每个配置的含义都会通过注释来进行解释)如下:

yaml 复制代码
apiVersion: apps/v1                             # Kubernetes api-service 版本号
kind: Deployment                                # Kubernetes 对象类型: 定义了一组 Pods 的声明,它定义了 Pods 的期望状态以及如何管理这些 Pods.
metadata:                                       # Deployment 元信息.
  name: authsvr                                 # Deployment 名称信息,用于通过 kubectl get deployment name(blogsvr) 来获取相关信息.
  labels:                                       # Deployment 标签,类型为 KV 类型,这些键值对为资源(Pod、Deployment)提供了附加的标识信息,用于资源的选择、组织和管理。
    app: authsvr                                # 给当前 Deployment 对象添加标签 Key 为 app,value 为 blogsvr 的附属信息,其常常配合 .spec.selector.matchLabels 来一起使用,后者会匹配前者,当匹配上,后者的对象则会管理前者所定义的对象.
spec:                                           # Deployment Spec 规约:Deployment 对象所期望的状态的配置信息。其中 Kubernetes Controller Manager 将会通过控制回路不断地监听 Deployment 对象状态,使其永远趋近于 Spec 所描述的状态.
  replicas: 1                                   # Deployment 控制器希望拥有的 Pod 副本的数量,如果不填默认为 1.
  selector:                                     # 定义如何选择一组资源,被当前 Yaml 文件所定义的对象管理,在 Deployment、ReplicaSet 中,该字段选择那些 Pod 应该会被当前对象管理,在 Service 中,用于决定那些 Pod 将接受通过该 Service 发送的网络流量.
    matchLabels:                                # 通过匹配 Labels 来选择那些对象被当前对象管理.
      app: authsvr                              # 管理存在标签 Key 为 app, value 为 blogsvr 所关联的对象.
  template:                                     # .spec.template 在 Deployment 对象中,该字段表明 Deployment 控制器管理的 Pod 的模版.
    metadata:                                   # 这一组 Pods 的元信息.
      labels:                                   # 这一组 Pods 所包含的标签信息,后续 Deployment、Service 都将会通过该标签来选择相关的 Pod 进行管理.
        app: authsvr
    spec:                                       # Pod 规约:定义 Pod 所期望的预期状态,Deployment 将配合 Controller Manager 和 kube-api-service 一起配合来使当前 Pod 一直朝着预期的状态运行.
      containers:                               # 容器配置
        - name: authsvr                         # 名称,但是实际上 Kubelet 在创建容器时会给容器加上随机的 ID,但是前缀为 name 所定义的值.
          image: openxm/authsvr:latest          # 构建容器所依赖的镜像,如果本地镜像没有,则会从 Docker Hub 中获取,如果不想从 Docker Hub 中获取,那么就需要配置仓库对应的镜像.
          imagePullPolicy: IfNotPresent         # 镜像拉取原则:如果镜像不存在则才会进行拉取,如果镜像存在则不进行拉取.
          ports:                                # 定义容器运行端口信息,容器可以开放多个端口提供服务.
            - containerPort: 8849               # 对外暴露 8848 端口
              name: authsvrhttpport             # 该端口对应的名称为 blogsvrport,后续在 Service 中可以通过 blogsvrport 来替代 8848.
            - containerPort: 18849
              name: authsvrrpcport
          livenessProbe:                        # 存活探针,如果存活探针失败,则 kubelet 会杀死容器,并根据重启策略来重启.
            httpGet:                            # 存活探针类型,这里我选择的是 httpGet,同时还支持 exec、grpc、tcpSocket(如果目标端口打开, net.Dial(xxx) 成功,则认为存活)
              path: /openxm/api/v1/probe        # httpGet 存活探针路径
              port: 8849                        # httpGet 存活探针端口
            initialDelaySeconds: 10             # 延迟调用时机,为了避免容器启动时间过长,导致无限重启
            periodSeconds: 60                   # 探针检测时间间隔
          resources:                            # Pod 内应用服务容器资源限制
            limits:                             # 资源最大限制
              cpu: "0.1"                        # 指在说明容器可使用的CPU最大限度为0.2
              memory: "50Mi"                    # 容器最大可使用内存为 70Mi
            requests:                           # 指在说明容器在调度到某个Node上时需要的最小资源限制
              cpu: "0.1"                        # 允许调度的最小CPU为0.1核
              memory: "20Mi"                    # 允许调度的最小内存为40Mi

通过上面声明式的 Yaml 配置文件我们在结合 kubelet 命令就可以创建 Deployment 控制器和其对应的 Pod 两个对象。由于 Pod Template 模板内指定了镜像,所以首先需要将 openxm/authsvr:latest 镜像添加到 minikube 中,否则其在拉取镜像哪一步将会失败,当我们成功将镜像 openxm/authsvr:latest 添加到 minikube 中后,我们可以执行创建 Kubernetes 对象的命令来创建 Deployment 对象(Deployment 对象会自动创建其所定义的 Pod),这里我将以上几个步骤的命令通过脚本形式展现出来:

build.sh 脚本内容

sh 复制代码
# 在当前目录构建容器内应用服务的二进制文件
go mod tidy
go build -o ./svrmain ./*.go

# 删除掉上次构建的镜像,并重新构建
docker rmi openxm/authsvr:latest
docker build -t openxm/authsvr:latest .

# 删除掉 minikube 内对应的镜像
minikube cache delete openxm/authsvr:latest
# 重新将新构建的镜像添加到 minikube 内
minikube cache add openxm/authsvr:latest

# 删除掉之前创建的 Deployment 对象,其会自动删除掉其定义和关联的 Pods
kubectl delete deployment authsvr
# 创建 authsvr_pod.yaml 所定义的 kubernetes对象
kubectl apply -f ./authsvr_pod.yaml

同时,Dockerfile 文件如下:

dockerfile 复制代码
FROM centos
LABEL authors="openxm"

ENV TZ=Asia/Shanghai

# 拷贝配置文件、以及二进制脚本
COPY ./svrmain /data/code/go/authsvr/
COPY ./config/ /data/config/

# HTTP: 8849
# RPC : 18849
EXPOSE 8849 18849
ENTRYPOINT ["/data/code/go/authsvr/svrmain"]

当我们执行完上面的脚本 build.sh 后,则会重新构建一个新的镜像:openxm/authsvr:latest,并将该镜像添加到 minikube 中,同时 minikube Kubernetes 环境也会创建 2 个对象:

  1. Deployment
  2. Pod

OK,我们通过 kubectl 来查看执行成功后的结果:我们可以看到,如果一切顺利的话,则可以正常出现如下的结果,如下结果表示运行正常。

sql 复制代码
[root@VM-12-2-centos conf]# kubectl get deployments
NAME      READY   UP-TO-DATE   AVAILABLE   AGE
authsvr   1/1     1            1           32m
blogsvr   1/1     1            1           21h


[root@VM-12-2-centos authsvr]# kubectl get pods
NAME                       READY   STATUS    RESTARTS   AGE
authsvr-6cb5f487cd-v6k4l   1/1     Running   0          3m41s
blogsvr-68f9f8b754-rcwn6   1/1     Running   0          21h

为了更加确认一下,我们查看 Pod 内应用服务容器日志在更加确认我们是成功运行起 Pod 的,我们可以看到,从控制台我们可以确认我们的应用服务已经正常启动,并且可以对外提供能力了,那么我们接下来要实现的就是通过配置 Service 来实现通过 Kubernetes 宿主机访问 Service,最后 Service 访问 Pod。让我们拭目以待。

ini 复制代码
root@VM-12-2-centos authsvr]# kubectl logs authsvr-6cb5f487cd-v6k4l
[GIN-debug] [WARNING] Running in "debug" mode. Switch to "release" mode in production.
 - using env:	export GIN_MODE=release
 - using code:	gin.SetMode(gin.ReleaseMode)

2024-01-16 21:36:43.193 [authsvr.main] [INFO] http server running addr:[:8849] [/data/code/go/authsvr/main.go:85]
[GIN-debug] GET    /openxm/api/v1/probe      --> github.com/xmopen/authsvr/internal/endpoint/probe.HealthProbe (2 handlers)
[GIN-debug] POST   /openxm/api/v1/auth/login --> github.com/xmopen/authsvr/internal/endpoint/userauth.(*API).UserLogin-fm (2 handlers)
[GIN-debug] POST   /openxm/api/v1/auth/register --> github.com/xmopen/authsvr/internal/endpoint/userauth.(*API).UserRegisterAndLogin-fm (2 handlers)
[GIN-debug] GET    /openxm/api/v1/auth/check/session --> github.com/xmopen/authsvr/internal/endpoint/userauth.(*RefreshAuthAPI).CheckXMUserWithToken-fm (2 handlers)
2024-01-16 21:36:43.194 [rpcsvr] [INFO] init success [/data/code/go/authsvr/internal/server/server.go:15]
2024-01-16 21:36:43.194 [authsvr.main] [INFO] rpc server running addr:[:18849] network:[tcp] [/data/code/go/authsvr/main.go:52]
2024-01-16 21:37:42.612 [6c974380b47411eeada2ee928dfcf620] [INFO] authsvr health probe [/data/code/go/authsvr/internal/endpoint/probe/probe.go:13]

2.3 故障排查

有的时候,当我们认为我们配置正常时,但是提示我们 xxx not found 等或者其他原因,我们来分析一下如何排查,由于我在搭建过程中经常出现容器入口点文件找不到,所以我就通过这个例子来简单说明下如何进行故障排查。

既然启动容器时容器找不到入口点对应的文件,那么我们可以认为有以下二种行为:

  1. 构建镜像时的确未将入口点对应的文件复制到镜像内

  2. 镜像内部确认有入口点对应的文件,但是路径不对或者格式不对

解决办法:

进入 minikube 内,通过 docker 命令不设置入口点直接进入镜像所对应的容器内查看对应文件是否存在:

arduino 复制代码
# 1、进入 minikube 虚拟机内
minikube ssh
# 2、不设置入口点直接进入容器内部
docker run -it --entrypoint sh image

我们手动 cd 到对应的目录,查看文件是否存在,如果文件存在,我们尝试在容器内手动执行命令,确认我们的应用程序最终能够运行起来,在这个过程中如果缺少环境变量或者配置文件有可能造成 panic 等,这些过程需要我们自己去解决。

**注意:**有的时候我们为了将自己构建的镜像最小化,采用最基础的镜像,但是我们要确认一下,当我们的应用程序二进制可能依赖某些底层库的时候,这些最基础的镜像如果没有拥有的话,可能会造成二进制文件不存在等错误,我们在自己搭建的时候应该要格外注意一下哈。

2.4 创建 Service 对外暴露Pod内应用程序

这里我们直接来看一下创建 Service 的声明式配置 Yaml 文件,其中各个配置已经在其中通过注释声明了

yaml 复制代码
apiVersion: v1                    # 定义版本号
kind: Service                     # 声明 Kubernetes 对象类型为 Service,其定义了一种访问一组 Pods 的方法,Service 为一组执行相同的功能的 Pods 提供了一个统一的访问接口.
metadata:
  name: authsvr                   # 声明 当前 Service 的名称为 authsvr
  labels:
    app: authsvrService           # 给当前 Service 对象添加额外属性
spec:                             # 当前 Service 对象的规约,也就是当前对象在 Kubernetes 中所期望的状态
  type: NodePort                  # 定义当前 Service 类型为 NodePort
  selector:
    app: authsvr                  # 选择 label 中 key 为 app 且值为 authsvr 的 Pods 作为当前 Service 的访问下游.
  ports:                          # 定义访问方式
    - port: 30002                 # 当前Service开放的端口,后续可以通过 serviceip:port 访问
      targetPort: 8849            # 目标容器内的端口,也就是应用服务开放的端口,这里 Service 将端口 30002 上的流量直接全部转发到对应的 targetPort 上.
      protocol: TCP               # 当前转发协议为 TCP 协议
      name: authsvrhttpport       # name
    - port: 30003                 # 当前 Service 开放的另一个端口
      targetPort: 18849           # 目标容器的端口
      protocol: TCP               # 转发协议
      name: authsvrrpcport        # name

接下来我们直接通过 kubectl 命令创建对应的 Service 对象来对外暴露服务

shell 复制代码
kubectl apply -f ./authsvr_service.yaml

成功创建后,我们通过命令来进行验收是否成功创建,执行下面的命令,如果出现我们上面定义的 name:authsvr 所对应的 Service 则说明创建成功。

sql 复制代码
[root@VM-12-2-centos sbin]# kubectl get services
NAME          TYPE        CLUSTER-IP       EXTERNAL-IP   PORT(S)                           AGE
authsvr       NodePort    10.102.101.194   <none>        30002:30950/TCP,30003:31282/TCP   31m
blogsvrhttp   NodePort    10.98.63.249     <none>        30001:30387/TCP                   4d23h
kubernetes    ClusterIP   10.96.0.1        <none>        443/TCP                           221d

接下来,我们看下其具体的协议,其中我们看下面输出的 NodePort 两行,那两行包漏出来的随机端口就是在宿主机上暴露出来的端口,我们可以通过 Kubernetes 宿主机 IP + NodePort 实现访问该端口所对应的应用服务。

yaml 复制代码
[root@VM-12-2-centos sbin]# kubectl describe service authsvr
Name:                     authsvr
Namespace:                default
Labels:                   app=authsvrService
Annotations:              <none>
Selector:                 app=authsvr
Type:                     NodePort
IP Family Policy:         SingleStack
IP Families:              IPv4
IP:                       10.102.101.194
IPs:                      10.102.101.194
Port:                     authsvrhttpport  30002/TCP
TargetPort:               8849/TCP
NodePort:                 authsvrhttpport  30950/TCP
Endpoints:                10.244.0.49:8849
Port:                     authsvrrpcport  30003/TCP
TargetPort:               18849/TCP
NodePort:                 authsvrrpcport  31282/TCP
Endpoints:                10.244.0.49:18849
Session Affinity:         None
External Traffic Policy:  Cluster
Events:                   <none>

我们查看 minikube 宿主机的IP:这里重新说明一下,minikube 其实是一个虚拟机,所以 Kubernets 环境对应的宿主机其实就是 minikube 虚拟机的 IP

csharp 复制代码
[root@VM-12-2-centos sbin]# minikube ip
192.168.49.2

OK,这里我们直接通过该虚拟机的 IP:NodePort 实现访问 Pod 内的应用服务,这里有一个前提是,这里的 Service 所关联的 Pod 内我提供了一个用于健康检测的 HTTP Get 接口,该接口只返回了一个 succes 字符串,没有实现其他逻辑,所以我们只需要验证下该逻辑是否能走通即可:

shell 复制代码
[root@VM-12-2-centos sbin]# curl --location 'http://192.168.49.2:30950/openxm/api/v1/auth/probe'
"success"
[root@VM-12-2-centos sbin]#

如果出现上面返回结果,就说明我们的 Service + Deployment + Pod 已经部署成功,接下来我们要做的就是在 minikube 所在的服务器上做下反向代理,通过区分路径,让应用内的服务路由到 minikube 虚拟机内不同服务 Service 对外暴露的不同端口上,从而实现公网域名访问。

如果这一步没有成功,可以多参考下 Kubernetes 官方文档以及 ChartGPT,我相信这两个工具足够成功部署。

2.5 宿主机上配置反向代理,实现公网访问

这里我们直接通过 Nginx 实现反向代理,具体配置如下

bash 复制代码
server {
 	listen 80;
	
	server_name openxm.cn;
	location / {
	    root /home/nginx_local/html;
	}
	location /openxm/api/v1 {
        proxy_pass http://192.168.49.2:30387; # 后端服务器地址
        proxy_set_header Host $host;
        proxy_set_header X-Real-IP $remote_addr;
        proxy_set_header X-Forwarded-For $proxy_add_x_forwarded_for;
        proxy_set_header X-Forwarded-Proto $scheme;
    }
    location /openxm/api/v1/auth {
        proxy_pass http://192.168.49.2:30950; # 后端服务器地址
        proxy_set_header Host $host;
        proxy_set_header X-Real-IP $remote_addr;
        proxy_set_header X-Forwarded-For $proxy_add_x_forwarded_for;
        proxy_set_header X-Forwarded-Proto $scheme;
    }
}

我们可以看到,在上面的配置中,我们配了三个 location,其中具体的 location 的含义如下:

  • location / 匹配所有的请求,Nginx 的命中策略在这里通过 / 来作为保底策略,具体 Nginx 的命中策略一会再详细介绍,当命中后,Nginx 则会将其转发到 /home/nginx_local/html 目录中。
  • location /openxm/api/v1 匹配前缀为 openxm/api/v1 的所有请求,命中后则会代理转发到目标地址:http://192.168.49.2:30387
  • location /openxm/api/v1/auth 匹配前缀为 location /openxm/api/v1/auth 的所有请求,命中后则会代理转发到目标地址: http://192.168.49.2:30950

Nginx Location 匹配规则:

  • 精确匹配:location = /openxm/...
  • 普通字符串匹配:location /openxm/...,如果命中多个,则匹配字符串比较长的那个,字符串比较长的理论上是比较期望命中的。
  • 正则匹配:
    • ~开头区分大小写
    • ~*不区分大小写

Nginx Location 匹配优先级:

  • 优先匹配精确匹配
  • 然后匹配所有正则表达式
  • 如果没有正则表达式,则匹配最长的非正则表达式

这一步配置完成之后,我们就可以直接通过服务器的 IP:Port 实现对 Kubernetes 的访问了

三、总结

通过上面学习 Kubernetes 的基础概念,并手动创建 Deployment、Pod、Service 相关对象,以及 Nginx 反向代理,我们已经实现了基于 Kubernetes 、Docker 云原生的服务集群,公司生产环境中可能这个集群用的是公有云厂商提供的 Kubernetes 商用集群,但是其核心思想不会改变。下面我们一起来看一下整体架构图:

Kubernetes 在我的博客中实践的架构图:

基于以上架构图,我的博客请求路径如下:

  1. 用户请求到达 Nginx 后,通过反向代理到 Kubernetes 集群
  2. 用户请求被代理到 Kubernetes 集群后,会由 Kubernetes 组件 kube-proxy 来实现网络代理和路由
  3. 网路请求到达 kube-proxy后,具体的请求会根据具体的 Service 配置来将请求通过默认的负载均衡机制路由到不同的 Pod 上
  4. Pod 内部应用程序通过暴露出来的端口接受网络请求进行处理用户本次的操作

最后,总结一下 Kubernetes 在博客中落地后的优点来结束这篇水文:

  1. 利用 Kubernetes 的滚动更新、健康检查、流量管理、自动扩展能力实现自动化服务发布能力,滚动更新可以避免所有 Pod 全部终止,而是利用时间差来进行逐个灰度 Pod 来实现功能上线,灰度能力也是生产环境中必须要有的一个能力,健康检查能力可以保证 Pod 内应用容器还未完全启动前不给予分配流量,等真正启动后进行流量分配。
  2. 利用 Kubernetes 的自我修复能力重启 Pod,当 Pod 所在的 Node节点因为网络原因造成短暂的失联或者 Node 下线等,Kubernetes 集群内并不会不去处理该 Node 上的 Pod,而是在不同的 Node 上去重新启动 PodeSpec 中定义的数量。
  3. 利用 Kubernetes 的 kube-proxy 实现云原生的负载均衡能力。
  4. 利用 Kubernetes 的自动扩缩容能力,当流量突增、或者递降时,能够通过监听资源使用情况来自动扩缩容 Pod 的期望数量。

个人博客:openxm.cn 个人公众号:社恐的小马同学

相关推荐
2401_882727576 小时前
低代码配置式组态软件-BY组态
前端·后端·物联网·低代码·前端框架
追逐时光者6 小时前
.NET 在 Visual Studio 中的高效编程技巧集
后端·.net·visual studio
大梦百万秋7 小时前
Spring Boot实战:构建一个简单的RESTful API
spring boot·后端·restful
斌斌_____7 小时前
Spring Boot 配置文件的加载顺序
java·spring boot·后端
路在脚下@8 小时前
Spring如何处理循环依赖
java·后端·spring
海绵波波1078 小时前
flask后端开发(1):第一个Flask项目
后端·python·flask
小奏技术9 小时前
RocketMQ结合源码告诉你消息量大为啥不需要手动压缩消息
后端·消息队列
AI人H哥会Java11 小时前
【Spring】控制反转(IoC)与依赖注入(DI)—IoC容器在系统中的位置
java·开发语言·spring boot·后端·spring
凡人的AI工具箱11 小时前
每天40分玩转Django:Django表单集
开发语言·数据库·后端·python·缓存·django
奔跑草-11 小时前
【数据库】SQL应该如何针对数据倾斜问题进行优化
数据库·后端·sql·ubuntu