【云原生|实战研发】2:Pod的深入实践与理解

本文涉及的产品
容器服务 Serverless 版 ACK Serverless,952元额度 多规格
日志服务 SLS,月写入数据量 50GB 1个月
容器服务 Serverless 版 ACK Serverless,317元额度 多规格
简介: 本期文章是介绍Pod的简单实践和容器设计模式深入学习,上次的文章中我们已经学习过了Docker、K8s的核心概念理解与简单的代码实战相关知识,也学习了DevOps与微服务的概念,感兴趣的同学可以去我的云原生专栏中学习,任意门:云原生学习专栏

本期文章是介绍Pod的简单实践和容器设计模式深入学习,上次的文章中我们已经学习过了Docker、K8s的核心概念理解与简单的代码实战相关知识,也学习了DevOps与微服务的概念,感兴趣的同学可以去我的云原生专栏中学习,任意门:云原生学习专栏

Pod深入实践学习


我们已经了解到,K8s的所有功能都是通过Pod进行展开实现的,大致如下图所示:

Pod是一组container的集合,container之间可以通过localhost:port的方式直接访问。

1、创建一个简单的Pod

apiVersion: v1
kind: Pod
metadata:
  name: nginx
  labels:
    app: hello-world
spec:
  containers:
  - name: nginx
    image: nginx
    ports:
      - containerPort: 80

创建一个Pod

kubectl create -f pod1.yaml

查看Pod信息:

$ kubectl get pod -o wide
NAME    READY   STATUS    RESTARTS   AGE   IP           NODE     NOMINATED NODE   READINESS GATES
nginx   1/1     Running   0          69s   10.244.1.3   node01   <none>           <none>

对该node01节点进行docker ps命令操作得到:

通过上图我们可以看到,该pod的组成是:一个pause的容器和n个自定义的容器。

大致如下图所示:

2、通过label组成一个Pod

Pod使用label把上述的container组成一个的,接下来查看看container的label信息:

pause的label信息

nginx的label信息

通过上面的对比分析,会发现: pod.name:“nginx”, namespace: “default”,"pod.uid"都是一样的。

而K8s就是通过这些label来组织Pod的。

3、到底为什么需要Pod?

首先需要回顾一下容器的概念,容器的本质是一个视图被隔离、资源受限的进程。


而容器里PID=1的进程就是应用本身(如下图与下文所示),所以,管理虚拟机 = 管理基础设施;管理容器=直接管理应用本身。


而这一点也正是不可变基础设施的最佳表现,这个时候应用就是基础设施,一定是不可变的。


Docker中,进程管理的基础就是Linux的PID名空间技术。在不同PID名空间中,进程ID是独立的:即在两个不同名空间下的进程可以有相同的PID。


当创建一个Docker容器的时候,就会新建一个PID名空间。容器启动进程在该名空间内PID为1。当PID1进程结束之后,Docker会销毁对应的PID名空间,并向容器内所有其它的子进程发送SIGKILL。


需要注意的是:PID=1的进程对于操作系统而言具有特殊意义。操作系统的PID1进程是init进程,以守护进程方式运行,是所有其他进程的祖先,具有完整的进程生命周期管理能力。在Docker容器中,PID1进程是启动进程,它也会负责容器内部进程管理的工作。而这也将导致进程管理在Docker容器内部和完整操作系统上的不同。

而介于上面的场景之下,K8s的作用又是什么呢?这里需要引用一个特殊的经典话语,那就是容器本身没有价值,有价值的是“容器编排”。而K8s就是用来容器编排的,也有很多人说K8s是云原生时代的操作系统。那么如果按照这个比较关系下去,那么容器镜像就是操作系统里的软件安装包,容器、容器镜像、K8s就是这样一个相互之间的关系。


可以大致的认为:K8s=操作系统linux、容器=进程(linux线程),Pod=进程组(线程组)

真实的操作系统案例:进程组

引入一个Helloworld程序,而该程序由4个进程组构成,这些进程之间共享某些系统的资源文件。


如下图所示,程序中有4个进程。(这里的进程就是linux系统中的线程),分别是api、main、log、compute,这四个线程共同协作helloworld程序的系统资源,进行运行程序的工作。这就是进程组的概念。

假设现在需要用容器跑起来上面的程序,那么该怎么进行处理?


最直接的方法,就是通过启动一个docker容器,里面运行四个进程,那么就会存在一个问题,即该容器中PID=1的进程是应用本身的话,如mian进程,那么谁来负责管理剩余的3个进程成了需要解决的难题。


那么对于这个方法,最需要注意的问题是,容器本身是“单进程”模型。(这并不是意味着容器只能跑一个进程。)

而是说因为容器=应用=进程,所以只能管理PID=1的进程,而其他再跑起来的进程,只能认为是托管状态。所以说除非应用进程具备“进程管理”能力,即helloworld程序需要具备systemd的能力。或者将容器的Pid=1的进程改成systemd,而这样会导致管理容器=管理systemd!=直接管理应用本身。


而如果不具备systemd的能力,那么一旦PID=1的进程kill或者死掉了,那么剩下3个进程的资源没有进行回收,那么这是一个很严重的问题。


总结的来说,一旦容器启动了多个进程,那么只能有一个PID=1的进程,而如果PID=1的进程挂了,那么就会没人回收剩下3个应用的回收,而如果run一个systemd进程来管理其他进程,那么这个时候没有办法直接管理其他的应用,这个时候应用状态的生命周期就不等于容器的生命周期,这样的情况就十分复杂了。


引申:Linux 容器的“单进程”模型,指的是容器的生命周期等同于 PID=1 的进程(容器应用进程)的生命周期,而不是说容器里不能创建多进程。当然,一般情况下,容器应用进程并不具备进程管理能力,所以你通过 exec 或者 ssh 在容器里创建的其他进程,一旦异常退出(比如 ssh 终止)是很容易变成孤儿进程的。

Pod的真正作用

而Pod就是用来K8s抽象出来的类比为进程组的概念,而上面提到的情况,就可以认为是一个拥有4个容器的pod,即现在不会塞进一个容器中,而是用4个分别独立的容器启动起来,包含在一个pod里面,而4个容器共享了一部分资源,所以说,Pod在K8s里面是一个逻辑单位(没有真实的去对应某些东西)。


总的来说,Pod 是 Kubernetes 分配资源的一个单位,因为里面的容器要共享某些资源,所以 Pod 也是 Kubernetes 的原子调度单位。

而Pod的设计问题早在 Google 研发 Borg 的时候,就已经发现了这样一个情况:这些应用之前往往有着密切的协作关系,使得它们必须部署在同一台机器上并共享某些资源。

4、Pod的具体业务案例

现在通过一个例子来帮助我们更好的理解为什么Pod是原子调度单位。


假设现在两个容器,是需要协作完成业务的,所以应该在一个Pod里面。第一个容器为业务容器A,完成写日志文件的功能,第二个容器为转发容器B,顾名思义,即将日志文件转发到后端进行对应处理。


A和B的资源需求如下:A 需要 1G 内存,B需要 0.5G 内存。

而当前集群环境的可用内存情况如下:Node01:1.25G 内存,Node02:2G 内存。


如果没有Pod,那么A和B需要运行在一台机子上,调度器先把A调到了Node01上,那么这就会使得B不能调度到01上了,因为资源不够,也就是内存不够B用了,这个时候调度失败,需要重新调度。


这类问题在以前没有K8s的Pod的帮助下,很多企业解决这类问题都比较复杂,并且需要额外的开销。


而现在只需要一个Pod就解决了。这样的一个 A 容器和 B 容器一定是属于一个 Pod 的,在调度时是以一个 Pod 为单位进行调度,问题是不存在的。


5、Pod的深入理解:超亲密关系

通过上述讲解,已经有了一定的了解。那么继续深入研究一下Pod的“超亲密关系”。


对于普通的亲密关系,就是通过调度来解决的。例如两个应用需要在同一台主机上运行。


而对于Pod的超亲密关系则会存在这样几类关系:


1、两个进程之间会发生文件交换,一个写日志,一个读日志

2、两个进程之间需要通过 localhost 或者说是本地的 Socket 去进行通信,这种本地通信也属于超亲密关系

3、两个容器是微服务,需要发生非常频繁的 RPC 调用,考虑性能方面将之认为是超亲密关系

4、两个容器是应用,需要共享某些 Linux Namespace。举个简单例子:有一个容器需要加入另一个容器的 Network Namespace。这样就能看到另一个容器的网络设备,和它的网络信息。


总结


所以通过上述的讲解,理解了Pod的概念设计来由,以及为什么需要Pod。


通过Pod,可以知道怎么去描述超亲密关系,怎么去进行统一的业务调度。


这就是Pod最主要的来由与作用。


Pod的实现机制

刨根究底:Pod到底怎么实现的?

在机器上,Pod是怎么实现的,对于这个问题,核心就在于如何让一个 Pod 里的多个容器之间最高效的共享某些资源和数据。


容器之间原本是被 Linux Namespace 和 cgroups 隔开的,现在要解决的是怎么去打破这个隔离,然后共享某些资源等。这就是 Pod 的设计要解决的核心问题所在。


解法1:通过共享网络

假设现在有一个 Pod,其中包含了两个容器A 和 B,它们要共享 Network Namespace。

在 Kubernetes 里的解法是这样的:它会在每个 Pod 里,额外起一个 Infra container 小容器来共享整个 Pod 的 Network Namespace。


Infra container 是一个十分小的镜像,大概 100-200KB ,是一个汇编语言写的、永远处于“暂停”状态的容器。


这就可以使得其他所有容器都会通过 Join Namespace 的方式加入到 Infra container 的 Network Namespace 中。


所以 Pod 里面的所有容器,它们看到的网络视图是一致的,如它们看到的网络设备、IP地址、Mac地址等等,跟网络相关的信息等。上述这些信息都来自于Pod 第一次创建的这个 Infra container。这就是 Pod 解决网络共享的一个方法。


在 Pod 里面,有一个 IP 地址,是这个 Pod 的 Network Namespace 对应的地址,也是这个 Infra container 的 IP 地址。所以Pod里面的容器看到的都是这个。而对于宿主机上的其他网络资源,都是一个 Pod为 一组来分的,并且被该 Pod 中的所有容器共享网络视图。


该Infra容器相当于总dialing,所以整个 Pod 中是 Infra container 第一个启动。并且整个 Pod 的生命周期是等同于 Infra container 的生命周期的,与容器 A 和 B 是无关的。这也是为什么 K8s中,允许去单独更新 Pod 里的某一个镜像的,即做这个操作,整个 Pod 并不会进行重建或者重启。


解法2:通过共享存储

有两个容器,一个是 A,一个是B,在A放一些文件,使之能通过 Nginx 访问到。所以A需要去 share 这个目录。 share 文件或者是 share 目录在 Pod 里面是容易实现的,就是把 volume 变成了 Pod level。对于同一个Pod下的容器来说共享所有的 volume即可。


在下图的案例中,这个 volume 叫做 shared-data,是属于 Pod level 的,在每一个容器里可以直接声明:挂载 shared-data 这个 volume。只要声明挂载这个 volume,在容器里去看这个目录,实际上每个容器看到的就是同一份。


在之前的例子中,A写了日志,只要这个日志是写在一个 volume 中,只要声明挂载了同样的 volume,这个 volume 就可以立刻被另外一个 B容器给看到。以上就是 Pod 实现存储的方式。


附录:Pod的详细定义

apiVersion: v1 # 必选,API的版本号
kind: Pod       # 必选,类型Pod
metadata:       # 必选,元数据
  name: nginx   # 必选,符合RFC 1035规范的Pod名称
  # namespace: default # 可选,Pod所在的命名空间,不指定默认为default,可以使用-n 指定namespace 
  labels:       # 可选,标签选择器,一般用于过滤和区分Pod
    app: nginx
    role: frontend # 可以写多个
  annotations:  # 可选,注释列表,可以写多个
    app: nginx
spec:   # 必选,用于定义容器的详细信息
#  initContainers: # 初始化容器,在容器启动之前执行的一些初始化操作
#  - command:
#    - sh
#    - -c
#    - echo "I am InitContainer for init some configuration"
#    image: busybox
#    imagePullPolicy: IfNotPresent
#    name: init-container
  containers:   # 必选,容器列表
  - name: nginx # 必选,符合RFC 1035规范的容器名称
    image: nginx:1.15.2    # 必选,容器所用的镜像的地址
    imagePullPolicy: IfNotPresent     # 可选,镜像拉取策略, IfNotPresent: 如果宿主机有这个镜像,那就不需要拉取了. Always: 总是拉取, Never: 不管是否存储都不拉去
    command: # 可选,容器启动执行的命令 ENTRYPOINT, arg --> cmd
    - nginx 
    - -g
    - "daemon off;"
    workingDir: /usr/share/nginx/html       # 可选,容器的工作目录
#    volumeMounts:   # 可选,存储卷配置,可以配置多个
#    - name: webroot # 存储卷名称
#      mountPath: /usr/share/nginx/html # 挂载目录
#      readOnly: true        # 只读
    ports:  # 可选,容器需要暴露的端口号列表
    - name: http    # 端口名称
      containerPort: 80     # 端口号
      protocol: TCP # 端口协议,默认TCP
    env:    # 可选,环境变量配置列表
    - name: TZ      # 变量名
      value: Asia/Shanghai # 变量的值
    - name: LANG
      value: en_US.utf8
#    resources:      # 可选,资源限制和资源请求限制
#      limits:       # 最大限制设置
#        cpu: 1000m
#        memory: 1024Mi
#      requests:     # 启动所需的资源
#        cpu: 100m
#        memory: 512Mi
#    startupProbe: # 可选,检测容器内进程是否完成启动。注意三种检查方式同时只能使用一种。
#      httpGet:      # httpGet检测方式,生产环境建议使用httpGet实现接口级健康检查,健康检查由应用程序提供。
#            path: /api/successStart # 检查路径
#            port: 80
#    readinessProbe: # 可选,健康检查。注意三种检查方式同时只能使用一种。
#      httpGet:      # httpGet检测方式,生产环境建议使用httpGet实现接口级健康检查,健康检查由应用程序提供。
#            path: / # 检查路径
#            port: 80        # 监控端口
#    livenessProbe:  # 可选,健康检查
      #exec:        # 执行容器命令检测方式
            #command: 
            #- cat
            #- /health
    #httpGet:       # httpGet检测方式
    #   path: /_health # 检查路径
    #   port: 8080
    #   httpHeaders: # 检查的请求头
    #   - name: end-user
    #     value: Jason 
#      tcpSocket:    # 端口检测方式
#            port: 80
#      initialDelaySeconds: 60       # 初始化时间
#      timeoutSeconds: 2     # 超时时间
#      periodSeconds: 5      # 检测间隔
#      successThreshold: 1 # 检查成功为2次表示就绪
#      failureThreshold: 2 # 检测失败1次表示未就绪
#    lifecycle:
#      postStart: # 容器创建完成后执行的指令, 可以是exec httpGet TCPSocket
#        exec:
#          command:
#          - sh
#          - -c
#          - 'mkdir /data/ '
#      preStop:
#        httpGet:      
#              path: /
#              port: 80
      #  exec:
      #    command:
      #    - sh
      #    - -c
      #    - sleep 9
  restartPolicy: Always   # 可选,默认为Always,容器故障或者没有启动成功,那就自动该容器,Onfailure: 容器以不为0的状态终止,自动重启该容器, Never:无论何种状态,都不会重启
  #nodeSelector: # 可选,指定Node节点
  #      region: subnet7
#  imagePullSecrets:     # 可选,拉取镜像使用的secret,可以配置多个
#  - name: default-dockercfg-86258
#  hostNetwork: false    # 可选,是否为主机模式,如是,会占用主机端口
#  volumes:      # 共享存储卷列表
#  - name: webroot # 名称,与上述对应
#    emptyDir: {}    # 挂载目录
#        #hostPath:              # 挂载本机目录
#        #  path: /etc/hosts
#


附录:Pod的常用命令

#创建一个pod
kubectl create -f po.yml
#查看创建的pod
kubectl get po
#创建一个指定命名空间的pod
kubectl create -f po.yml -n kube-public
#创建一个命名空间
kubectl create ns ns_name
#删除一个pod
kubectl delete po nginx
#删除指定命名空间的pod
kubectl delete po nginx -n kube-public
#查看删除键pod 所用的时间
time kubectl delete po nginx
相关实践学习
通过Ingress进行灰度发布
本场景您将运行一个简单的应用,部署一个新的应用用于新的发布,并通过Ingress能力实现灰度发布。
容器应用与集群管理
欢迎来到《容器应用与集群管理》课程,本课程是“云原生容器Clouder认证“系列中的第二阶段。课程将向您介绍与容器集群相关的概念和技术,这些概念和技术可以帮助您了解阿里云容器服务ACK/ACK Serverless的使用。同时,本课程也会向您介绍可以采取的工具、方法和可操作步骤,以帮助您了解如何基于容器服务ACK Serverless构建和管理企业级应用。 学习完本课程后,您将能够: 掌握容器集群、容器编排的基本概念 掌握Kubernetes的基础概念及核心思想 掌握阿里云容器服务ACK/ACK Serverless概念及使用方法 基于容器服务ACK Serverless搭建和管理企业级网站应用
相关文章
|
2天前
|
运维 Kubernetes Cloud Native
云原生技术入门及实践
【10月更文挑战第39天】在数字化浪潮的推动下,云原生技术应运而生,它不仅仅是一种技术趋势,更是企业数字化转型的关键。本文将带你走进云原生的世界,从基础概念到实际操作,一步步揭示云原生的魅力和价值。通过实例分析,我们将深入探讨如何利用云原生技术提升业务灵活性、降低成本并加速创新。无论你是云原生技术的初学者还是希望深化理解的开发者,这篇文章都将为你提供宝贵的知识和启示。
|
1天前
|
Cloud Native 安全 API
云原生架构下的微服务治理策略与实践####
—透过云原生的棱镜,探索微服务架构下的挑战与应对之道 本文旨在探讨云原生环境下,微服务架构所面临的关键挑战及有效的治理策略。随着云计算技术的深入发展,越来越多的企业选择采用云原生架构来构建和部署其应用程序,以期获得更高的灵活性、可扩展性和效率。然而,微服务架构的复杂性也带来了服务发现、负载均衡、故障恢复等一系列治理难题。本文将深入分析这些问题,并提出一套基于云原生技术栈的微服务治理框架,包括服务网格的应用、API网关的集成、以及动态配置管理等关键方面,旨在为企业实现高效、稳定的微服务架构提供参考路径。 ####
14 5
|
2天前
|
负载均衡 监控 Cloud Native
云原生架构下的微服务治理策略与实践####
在数字化转型浪潮中,企业纷纷拥抱云计算,而云原生架构作为其核心技术支撑,正引领着一场深刻的技术变革。本文聚焦于云原生环境下微服务架构的治理策略与实践,探讨如何通过精细化的服务管理、动态的流量调度、高效的故障恢复机制以及持续的监控优化,构建弹性、可靠且易于维护的分布式系统。我们将深入剖析微服务治理的核心要素,结合具体案例,揭示其在提升系统稳定性、扩展性和敏捷性方面的关键作用,为读者提供一套切实可行的云原生微服务治理指南。 ####
|
2天前
|
消息中间件 缓存 Cloud Native
云原生架构下的性能优化实践与挑战####
随着企业数字化转型的加速,云原生架构以其高度解耦、弹性伸缩和快速迭代的特性,成为现代软件开发的首选模式。本文深入探讨了云原生环境下性能优化的关键策略与面临的主要挑战,通过案例分析,揭示了如何有效利用容器化、微服务、动态调度等技术手段提升应用性能,同时指出了在复杂云环境中确保系统稳定性和高效性的难题,为开发者和架构师提供了实战指南。 ####
10 3
|
1天前
|
弹性计算 Kubernetes Cloud Native
云原生技术的实践与思考
云原生技术的实践与思考
12 2
|
2天前
|
Kubernetes Cloud Native 持续交付
云原生技术在现代应用架构中的实践与思考
【10月更文挑战第38天】随着云计算的不断成熟和演进,云原生(Cloud-Native)已成为推动企业数字化转型的重要力量。本文从云原生的基本概念出发,深入探讨了其在现代应用架构中的实际应用,并结合代码示例,展示了云原生技术如何优化资源管理、提升系统弹性和加速开发流程。通过分析云原生的优势与面临的挑战,本文旨在为读者提供一份云原生转型的指南和启示。
10 3
|
2天前
|
运维 Kubernetes Cloud Native
云原生技术在现代应用架构中的实践与挑战####
本文深入探讨了云原生技术的核心概念、关键技术组件及其在实际项目中的应用案例,分析了企业在向云原生转型过程中面临的主要挑战及应对策略。不同于传统摘要的概述性质,本摘要强调通过具体实例揭示云原生技术如何促进应用的灵活性、可扩展性和高效运维,同时指出实践中需注意的技术债务、安全合规等问题,为读者提供一幅云原生技术实践的全景视图。 ####
|
3天前
|
运维 Kubernetes Cloud Native
云原生技术:容器化与微服务架构的完美结合
【10月更文挑战第37天】在数字化转型的浪潮中,云原生技术以其灵活性和高效性成为企业的新宠。本文将深入探讨云原生的核心概念,包括容器化技术和微服务架构,以及它们如何共同推动现代应用的发展。我们将通过实际代码示例,展示如何在Kubernetes集群上部署一个简单的微服务,揭示云原生技术的强大能力和未来潜力。
|
12天前
|
弹性计算 Kubernetes Cloud Native
云原生架构下的微服务设计原则与实践####
本文深入探讨了在云原生环境中,微服务架构的设计原则、关键技术及实践案例。通过剖析传统单体架构面临的挑战,引出微服务作为解决方案的优势,并详细阐述了微服务设计的几大核心原则:单一职责、独立部署、弹性伸缩和服务自治。文章还介绍了容器化技术、Kubernetes等云原生工具如何助力微服务的高效实施,并通过一个实际项目案例,展示了从服务拆分到持续集成/持续部署(CI/CD)流程的完整实现路径,为读者提供了宝贵的实践经验和启发。 ####
|
4天前
|
消息中间件 存储 Cloud Native
云原生架构下的数据一致性挑战与应对策略####
本文探讨了在云原生环境中,面对微服务架构的广泛应用,数据一致性问题成为系统设计的核心挑战之一。通过分析云原生环境的特点,阐述了数据不一致性的常见场景及其对业务的影响,并深入讨论了解决这些问题的策略,包括采用分布式事务、事件驱动架构、补偿机制以及利用云平台提供的托管服务等。文章旨在为开发者提供一套系统性的解决方案框架,以应对在动态、分布式的云原生应用中保持数据一致性的复杂性。 ####