Kubernetes基石-pod容器

引用三个问题来叙述Kubernetes的pod容器

  1.为什么不直接在一个Docker容器中运行所有的应用进程。

  2.为什么pod这种容器中要同时运行多个Docker容器(可以只有一个)

  3.为什么k8s使用pod这种容器而不直接使用Docker容器

  一个由多个进程进行组成的应用程序,无论是通过ipc(进程间通信)还是本地存储文件进行通信,都要求它们运行于同一台机器上。Docker容器非常像一台独立的机器,此时你可能认为在单个容器中运行多个进程是合乎逻辑的,然而在实践中这种做法并不合理。
  容器被设计为每个容器只运行一个进程(除非进程本身产生子进程)。如果在单个容器中运行多个不相关的进程,那么保持所有进程运行、管理它们的日志等将会是我们的责任。例如,我们需要包含一种在进程奔溃时能够自动重启的机制。同时这些进程都将记录到相同的标准输出中,而此时我们将很难确定每个进程分别记录了什么
综上所述,我们需要让每个进程运行于自己的容器中,而这就是Docker和Kubernetes期望使用的方式。
  由于不能将多个进程聚集在一个单独的容器中,我们需要另一种更高级的结构来将容器绑定在一起,并将它们作为一个单元进行管理,这就是pod背后的根本原理。
  在包含容器的pod下,我们可以同时运行一些密切相关的进程,并为它们提供(几乎)相同的环境,此时这些进程就好像全部运行于单个容器中一样,同时又保持着一定的隔离。这样一来,我们便能全面地利用容器所提供的特性,同时对这些进程来说它们就像运行在一起一样,实现两全其美。
  Docker容器之间彼此是完全隔离的,但此时我们期望的是隔离容器组,而不是单个容器,并让每个容器组内的容器共享一些资源,而不是全部(换句话说,没有完全隔离)。Kubernetes通过配置Docker来让一个pod内的所有容器共享相同的Linux命名空间,而不是每个容器都有自己的一组命名空间。
  由于一个pod中的所有容器都在相同的network和UTS命名空间下运行(在这里我们讨论的是Linux命名空间),所以它们都共享相同的主机名和网络接口。同样地,这些容器也都在相同的IPC命名空间下运行,因此能够通过IPC进行通信。在最新的Kubernetes和Docker版本中,它们也能够共享相同的PID命名空间,但是该特征默认是未激活的。
  注意:当同一个pod中的容器使用单独的PID命名空间时,在容器中执行psaux就只会看到容器自己的进程。
  但当涉及文件系统时,情况就有所不同。由于大多数容器的文件系统来自容器镜像,因此默认情况下,每个容器的文件系统与其他容器完全隔离。但我们可以使用名为Volume的Kubermetes资源来共享文件目录。
  需要强调的一点是,由于一个pod中的容器运行于相同的Network命名空间中,因此它们共享相同的IP地址和端口空间。这意味着在同一pod中的容器运行的多个进程需要注意不能绑定到相同的端口号,否则会导致端口冲突,但这只涉及同一pod中的容器。由于每个pod都有独立的端口空间,对于不同pod中的容器来说则永远不会遇到端口冲突。此外,一个pod中的所有容器也都具有相同的loopback网络接口,因此容器可以通过localhost与同一pod中的其他容器进行通信。
  Kubernetes 集群中的所有pod 都在同一个共享网络地址空间中,这意味着每个pod都可以通过其他pod的IP地址来实现相互访问。换句话说,这也表示它们之间没有NAT(网络地址转换)网关。当两个pod彼此之间发送网络数据包时,它们都会将对方的实际IP地址看作数据包中的源IP。
  因此,pod之间的通信其实是非常简单的。不论是将两个pod安排在单一的还是不同的工作节点上,同时不管实际节点间的网络拓扑结构如何,这些pod内的容器都能够像在无NAT的平坦网络中一样相互通信,就像局域网(LAN)上的计算机一样。此时,每个pod都有自己的IP地址,并且可以通过这个专门的网络实现pod之间互相访问。这个专门的网络通常是由额外的软件基于真实链路实现的。
  总结本文主要内容:pod是逻辑主机,其行为与非容器世界中的物理主机或虚拟机非常相似。此外,运行在同一个pod中的进程与运行在同一物理机或虚拟机上的进程相似,只是每个进程都封装在一个容器之中。
  如何决定何时在pod中使用多个容器?当决定是将两个容器放入一个pod还是两个单独的pod时,我们需要问自己以下问题:·
    1.它们需要一起运行还是可以在不同的主机上运行?
    2.它们代表的是一个整体还是相互独立的组件?
    3.它们必须一起进行扩缩容还是可以分别进行?
  基本上,我们应该倾向于在单独的pod中运行容器,除非有特定的原因要求它们是同一pod的一部分。容器不应该包含多个进程,pod也不应该包含多个并不需要运行在同一主机上的容器。
 
⒉以YAML或JSON描述文件创建pod
  pod和其他Kubernetes资源通常是通过向Kubernetes RESTAPI提供JSON或YAML描述文件来创建的。此外还有其他更简单的创建资源的方法,比如使用kubectl run命令,但这些方法通常只允许你配置一组有限的属性。另外,通过YAML文件定义所有的Kubernetes对象之后,还可以将它们存储在版本控制系统中,充分利用版本控制所带来的便利性。
  pod定义由这么几个部分组成:
    首先是YAML中使用的KubernetesAPI版本和YAML描述的资源类型;
    其次是几乎在所有Kubernetes资源中都可以找到的三大重要部分:·
      1.metadata包括名称、命名空间、标签和关于该容器的其他信息。
      2.spec包含pod内容的实际说明,例如pod的容器、卷和其他数据。
      3·status包含运行中的pod的当前信息,例如pod所处的条件、每个容器的描述和状态,以及内部IP和其他基本信息。
  status部分包含只读的运行时数据,该数据展示了给定时刻的资源状态。而在创建新的pod时,永远不需要提供status部分。
  上述三部分展示了KubernetesAPI对象的典型结构。其他对象也都具有相同的结构,这使得理解新对象相对来说更加容易。
apiVersion: v1  #指定当前描述文件遵循v1版本的KubernetesAPI
kind: Pod #我们在描述一个pod
metadata:
  name: kubia-manual  #指定pod的名称
  namespace: custom-namespace #指定当前描述的pod所在的命名空间
  labels: #指定pod标签
    creation_method: manual
    env: prod
spec:
  nodeSelector: #告诉k8s我们希望把当前pod部署到拥有指定标签的节点上,k8s节点拥有一个默认的节点标签,其中键为kubernetes.io/hostname,值为该节点的实际主机名,因此我们可以将pod调度到某个确定的节点。但如果指定节点处于离线状态,通过hostname 标签将nodeSelector设置为特定节点可能会导致pod不可调度。
    labelKey: "labelValue"
  containers:
  - image: fanqisoft/coreqi #创建容器所使用的镜像
  name: coreqi  #容器的名称
  ports: 
    -containerPort: 8080  #应用监听的端口
    protocol: TCP
  livenessProbe:  
    httpGet:  #一个Http Get 存活探针
      path: / #Http请求的路径
      port: 8080  #探针连接的网络端口
    initialDelaySeconds: 15 #kubernetes会在第一次探测前等待15秒,没有没有设置该选项,探针将在启动时立即开始探测容器,如果使用探针,务必设置该选项。

⒊向pod发送请求

  向pod发送请求可以使用kubectl expose命令创建服务然后暴露出来,也可以通过端口转发将本地网络端口转发到pod中的端口访问pod,Kubernetes允许我们配置端口转发到pod,可以通过kubectl port-forward命令完成上述操作。例如以下命令会将机器的本地端口8888转发到我们的kubia-manual pod的8080端口
kubectl port-forward kubia-manual 8888:8080

 ⒋pod健康

  1.存活探针(liveness probe)

  Kubernetes可以通过存活探针(liveness probe)检查容器是否还在运行。可以为pod中的每个容器单独指定存活探针。如果探测失败,Kubernetes将定期执行探针并重新启动容器。
  除了存活探针(liveness probe),Kubernetes还支持就绪探针(readiness probe),一定不要混淆两者。它们适用于两种不同的场景。
  Kubernetes有以下三种探测容器的机制:·
    HTTP GET探针对容器的IP地址(你指定的端口和路径)执行HTTPGET请求。如果探测器收到响应,并且响应状态码不代表错误(换句话说,如果HTTP响应状态码是2xx或3xx),则认为探测成功。如果服务器返回错误响应状态码或者根本没有响应,那么探测就被认为是失败的,容器将被重新启动。
    TCP套接字探针尝试与容器指定端口建立TCP连接。如果连接成功建立,则探测成功。否则,容器重新启动。
    Exec探针在容器内执行任意命令,并检查命令的退出状态码。如果状态码是0,则探测成功。所有其他状态码都被认为失败。
  k8s的退出代码有特殊含义,例如退出代码为137,这有特殊的含义——表示该进程由外部信号终止。数字137是两个数字的总和:128+x,其中x是终止进程的信号编号。在这个例子中,x等于9,这是SIGKILL的信号编号,意味着这个进程被强行终止。
  注意,当容器被强行终止时,会创建一个全新的容器—一而不是重启原来的容器。
  对于在生产中运行的pod,一定要定义一个存活探针。没有探针的话,Kubernetes无法知道你的应用是否还活着。只要进程还在运行,Kubernetes会认为容器是健康的。
  简易的存活探针仅仅检查了服务器是否响应。虽然这看起来可能过于简单,但即使是这样的存活探针也可以创造奇迹,因为如果容器内运行的web服务器停止响应HTTP请求,它将重启容器。与没有存活探针相比,这是一项重大改进,而且在大多数情况下可能已足够。
  但为了更好地进行存活检查,需要将探针配置为请求特定的URL路径(例如/health),并让应用从内部对内部运行的所有重要组件执行状态检查,以确保它们都没有终止或停止响应。
  提示:请确保/health HTTP端点不需要认证,否则探测会一直失败,导致你的容器无限重启。
  一定要检查应用程序的内部,而没有任何外部因素的影响。例如,当服务器无法连接到后端数据库时,前端Web服务器的存活探针不应该返回失败。如果问题的底层原因在数据库中,重启Web服务器容器不会解决问题。由于存活探测将再次失败,你将反复重启容器直到数据库恢复。
  存活探针不应消耗太多的计算资源,并且运行不应该花太长时间。默认情况下,探测器执行的频率相对较高,必须在一秒之内执行完毕。一个过重的探针会大大减慢你的容器运行。探针的CPU时间计入容器的CPU时间配额,因此使用重量级的存活探针将减少主应用程序进程可用的CPU时间。
  提示:如果你在容器中运行Java应用程序,请确保使用HTTP GET存活探针,而不是启动全新JVM以获取存活信息的Exec探针。任何基于JVM或类似的应用程序也是如此,它们的启动过程需要大量的计算资源。
  探针的失败阀值是可配置的,并且通常在容器被终止之前探针必须失败多次。但即使你将失败阀值设置为1,Kubernetes为了确认一次探测的失败,会尝试若干次。因此在探针中自己实现重试循环是浪费精力。
  Kubernetes会在你的容器崩溃或其存活探针失败时,通过重启容器来保持运行。这项任务由承载pod的节点上的Kubelet执行——在主服务器上运行的Kubernetes Control Plane组件不会参与此过程。
  但如果节点本身崩溃,那么Control Plane必须为所有随节点停止运行的pod创建替代品。它不会为你直接创建的pod执行此操作。这些pod只被Kubelet管理,但由于Kubelet本身运行在节点上,所以如果节点异常终止,它将无法执行任何操作。
  为了确保你的应用程序在另一个节点上重新启动,需要使用Replicationcontroller或类似机制管理pod。
  
  2.ReplicationController

  ReplicationController是一种Kubernetes资源,可确保它的pod始终保持运行状态。
  如果pod因任何原因消失(例如节点从集群中消失或由于该pod己从节点中逐出),则ReplicationController会注意到缺少了pod并创建替代pod。
  非托管的pod节点异常退出后将没有东西负责重建它,而由ReplicationController管理的pod异常退出后,ReplicationController将会创建一个新的pod来替换异常退出的pod。
  一般而言,ReplicationController旨在创建和管理一个pod的多个副本(replicas)。这就是ReplicationController名字的由来。

  当节点故障时,只有ReplicationController 管理的pod 会被重新创建。

  ReplicationController会持续监控正在运行的pod列表,并保证相应“类型”的pod的数目与期望相符。如正在运行的pod太少,它会根据pod模板创建新的副本。如正在运行的pod太多,它将删除多余的副本。

  你可能会对有多余的副本感到奇怪。这可能有几个原因:

  ·有人会手动创建相同类型的pod。
  ·有人更改现有的pod的“类型”。
  ·有人减少了所需的pod的数量,等等。

  一个ReplicationController有三个主要部分:
    label selector(标签选择器),用于确定ReplicationController作用域中有哪些
    pod·replica count(副本个数),指定应运行的pod数量
    pod template(pod模板),用于创建新的pod副本 

  ReplicationController的副本个数、标签选择器,甚至是pod模板都可以随时修改,但只有副本数目的变更会影响现有的pod。
  更改ReplicationController的标签选择器和pod模板对现有pod没有影响。更改标签选择器会使现有的pod 脱离ReplicationController的监视范围,因此ReplicationController将会停止关注它们。
  在创建pod后,ReplicationController并不关心其pod的实际“内容”(容器镜像、环境变量及其他)。因此,修改ReplicationController的pod模板仅影响由此ReplicationController创建的新pod。
  使用ReplicationController的好处像Kubernetes中的许多事物一样,ReplicationController尽管是一个令人难以置信的简单概念,却提供或启用了以下强大功能:
    ·确保一个pod(或多个pod副本)持续运行,解决方法是在监控的pod丢失时启动一个新pod。
    ·集群节点发生故障时,它将为故障节点上运行的所有pod(即受ReplicationController 控制的节点上的那些pod)创建替代副本。
    ·它能轻松实现pod的水平伸缩,手动和自动都可以
  注意:pod实例永远不会重新安置到另一个节点。ReplicationController的解决方案是根据pod模板创建一个全新的pod实例而不是去移动当前实例,它与正在替换的实例无关。

   ReplicationController的创建

    就像pod和其他Kubernetes资源,可以通过上传JSON或YAML描述文件到Kubernetes API 服务器来创建ReplicationController。

apiVersion: v1  #指定当前描述文件遵循v1版本的KubernetesAPI
kind: ReplicationController #我们在描述一个ReplicationController
metadata:
  name: coreqi-manual  #指定ReplicationController的名称
spec:
  replicas: 3 #pod实例的目标数目
  selector: #pod选择器决定了ReplicationController的操作对象
    app: coreqi #当前ReplicationController将确保符合标签选择器app=coreqi的pod实例始终是三个,当没有足够的pod时,将使用下面的pod模板创建新的pod
  template: #创建新pod所使用的pod模板
    metadata:
      labels:
        app: coreqi #模板中的pod标签显然必须和ReplicationController的标签选择器相匹配,否则控制器将无休止的创建新的pod实例。因为创建新的pod不会使实际的副本数量接近期望的副本数量。为了防止出现这种情况,Kubernetes API服务会校验ReplicationController的定义不会接收错误的配置。
                    #不指定ReplicationController的标签选择器也是一种选择,因为ReplicationController会自动从模板中提取标签,而且描述文件也将更简短
    spec:
      containers:
      - name: coreqi
        image: fanqisoft/coreqi
        ports:
        - containerPort: 8080

  创建了ReplicationController的描述文件后我们就可以通过相应的指令去创建它了。

kubectl create -f coreqi-rc.yaml

  由ReplicationController创建的pod并不是绑定到ReplicationController。ReplicationController只会管理与当前ReplicationController标签选择器相匹配的pod。通过更改pod的标签,可以将它从ReplicationController的作用域中添加或删除。它甚至可以从一个ReplicationController移动到另一个。
  提示:尽管一个pod没有绑定到一个ReplicationController,但该pod在metadata.ownerReferences字段中引用它,可以轻松使用它来找到一个pod属于哪个ReplicationController。
  如果你更改了一个pod的标签,使它不再与ReplicationController的标签选择器相匹配,那么该pod就变得和其他手动创建的pod一样了。它不再被任何东西管理。如果运行该节点的pod异常终止,它显然不会被重新调度。但请记住,当你更改pod的标签时,ReplicationController发现一个pod丢失了,将会启动一个新的pod替换它。
  例如,一个ReplicationController管理具有app=coreqi标签的pod,当我们对一个pod删除这个标签或修改其值时,ReplicationController会将该pod移出管理范围。添加新的标签并没有用,因为ReplicationController 并不关心该pod是否有任何附加标签,它只关心该pod是否具有当前ReplicationController标签选择器中引用的所有标签。

  更改ReplicationController的pod模板只影响之后创建的pod,并且不会影响现有的pod。要修改旧的pod,需要删除它们,并让ReplicationController根据新模板将其替换为新的pod。

 

posted @ 2019-08-22 15:47  SpringCore  阅读(1095)  评论(0编辑  收藏  举报