什么是Pod?

Pod是Kubernetes中最小的调度单元,一个Pod可以包含一个或多个容器,这些容器运行在同一个节点上,从而可以共享网络,存储以及其他资源,简单来说,Pod类似于共享名字空间并共享文件系统卷的一组容器.


Pod资源常见字段及值类型

apiVersion: v1               # API版本
kind: Pod                    # 资源类型
metadata: <Object>           # 资源元数据
  labels:
    key: value
  name: <string>             # Pod名称
  namespace: <string>        # 指定命名空间
spec:
  containers: <[]Object>     # Pod中的容器列表
  - image: <string>          # 镜像地址
    imagePullPolicy: <string> # 镜像下载策略
    name: <string>            # 容器名称
    args: <[]string>         # entrypoint参数
    command: <[]string>      # 执行命令
    ports: <[]Object>        # 容器公开的端口
    env:  <[]Object>         # 环境变量
    resources: <Object>      # 容器所需的计算资源
    livenessProbe: <Object>  # 存活探针
    readlinessProbe: <Object> # 就绪探针
    startupProbe: <Object>    # 启动探针
    volumeMounts: <[]Object>  # 卷挂载
    securityContext: <Object> # 安全上下文
    lifecycle: <Object>       # 容器声明周期回调
  volumes: <[]Object>         # 卷来源

注:<string>表示一个字符串, <[]string>表示一个字符串的数组,<Object>表示一个对象,<[]Object>表示一个对象的数组,即每个元素都是对象


Pod管理常用命令

# 创建一个Pod
kubectl run <Pod名称> --image=<容器镜像地址>
# 查看当前命名空间中的Pod对象
kubectl get pods
# 查看指定命名空间中的Pod对象
kubectl get pods -n <命名空间>
# 查看所有命名空间中的Pod对象
kubectl get all -A
# 查看Pod日志,默认来自第一个容器
kubectl logs <Pod名称>
# 查看Pod中指定容器的日志
kubectl logs <Pod名称> -c <容器名称>
# 在Pod容器中执行命令,默认为第一个容器
kubectl exec -it <Pod名称> -- date
# 在Pod指定的容器中执行命令
kubectl exec -it <Pod名称> -c <容器名称> -- date
# 在Pod中启动一个交互式Bash终端,默认为第一个容器
kubectl exec -it <Pod名称> -- bash
# 在Pod指定的容器中启动一个交互式Bash终端
kubectl exec -it <Pod名称> -c <容器名称> -- bash
# 删除Pod对象
kubectl delete pod <名称>
# 删除当前命令空间中的所有Pod对象
kubectl delete podd --all


容器运行命令与参数

在Pod配置中,"command"和"args"字段用于定义容器的命令和参数. 

command

"command"字段用于定义容器启动时要执行的命令,并覆盖镜像中默认的启动命令.它的值是一个字符串列表类型,其中第一个元素视为命令名称,后续元素视为命令的参数.

示例:

apiVersion: v1
kind: Pod
metadata:
  labels:
    app: pod-exampel1
  name: pod-exampel1
spec:
  containers:
  - image: centos:7
    name: test
    command: ["echo", "Hello World!"]


args

"args"字段用于指定容器启动时的命令参数,它的值是一个字符串列表类型,每个元素被视为"command"的一个参数.

示例:

apiVersion: v1
kind: Pod
metadata:
  labels:
    app: pod-exampel1
  name: pod-exampel1
spec:
  containers:
  - image: centos:7
    name: test
    command: ["echo"]
    args: ["Hello", "World!"]


镜像拉取策略

在Pod配置中,"imagePullPolicy"字段用于设置镜像拉取策略,有以下可选想

  • Always: 默认值,始终从镜像仓库拉取最新的镜像
  • IfNotPresent: 优先使用节点上的镜像.如果节点上的镜像不存在,则从镜像仓库拉取
  • Never: 仅使用节点上的镜像.如果节点上的镜像不存在,则Pod将处于错误状态.


声明端口

在Pod配置中,"ports"字段用于定义容器公开的端口列表.该字段的值是一个对象列表类型,其中每个元素对应一个端口规则,每个端口规则由以下字段组成:

  • name: 端口名称.仅定义一个端口时,该字段可选
  • containerPort: 容器端口,即容器内应用程序监听的端口.
  • protocol: 端口使用的协议,支持TCP,UDP,SCTP,默认为TCP


容器健康检查

当Pod状态显示未"Running"时,这表明Pod中所有容器都已经运行,但这并不意味着Pod中的应用程序已经准备好提供服务,实际

上"Running"状态仅仅表示容器的启动状态,与应用程序是否准备好提供服务无直接关系.可能由于以下原因,应用程序无法提供服务:

  • 应用程序启动慢:容器已运行,但容器里的服务还在启动中,这时容器任然无法提供服务
  • 应用程序假死:应用程序由于某种原因(如死锁,代码bug)无法继续执行后面的工作

为了解决 这类问题,Kubernetes提供了探针机制.探针被配置为周期性检查容器中应用程序的健康状态.容器探针支持以下3种类型:

  • livenessProbe(存活探针):检查容器中应用程序是否运行,如果存活探针失败,Kubernetes将重新启动容器,以尝试恢复应用程序的运行状态
  • readinessProbe(就绪探针): 检查容器中应用程序是否准备好接收流量.如果就绪探针失败,Kubernetes将Pod标记为"未准备就绪",从而防止将新的流量转发到该Pod
  • startupProbe(启动探针): 检查容器中应用是否启动.它仅用于在容器启动阶段确定应用程序是否运行,一旦启动探针成功,它就不会再继续执行.

探针支持三种检查方法:

  • httpGet:向容器中执行路径发送HTTP请求来判断健康状态,如果HTTP响应的状态码大于或等于200且小于400,则表示成功,其他状态码则表示失败
  • tcpSocket:向容器中指定端口建立TCP连接来判断健康状态.如果TCP建立成功,则表示成功,否则表示失败.
  • exec:在容器中执行命令,根据命令的退出状态来判断健康状态.如果命令的退出状态为0则表示成功否则表示失败.


存活探针

示例:

vi pod-liveness.yaml

apiVersion: v1
kind: Pod
metadata:
  labels:
    app: pod-liveness
  name: pod-liveness
spec:
  containers:
  - name: web
    image:  nginx:1.23
    livenessProbe:              # 存活探针
      httpGet:                  # 发送Http请求来判断健康状态
        path: /index.html       # 访问路径,默认是"/"
        port: 80                # 请求端口
      initialDelaySeconds: 10   # 容器启动后等待多少秒开始执行探针,这通常根据应用程序的启动时间来设置
      periodSeconds: 20         # 执行探针的时间间隔(单位秒),默认是10秒

在上述配置中livenessProbe部分定义了存活探针,其中httpGet表示发送Http请求来判断健康状态,除了上述配置的以外还可以指定

host: 请求的IP地址,默认是Pod IP.

scheme: 请求协议, 支持HTTP和HTTPS, 默认是HTTP.

httpHeaders: 自定义HTTP头


根据上述yaml配置创建Pod资源:

kubectl apply -f pod-liveness.yaml

通过kubectl logs pod-liveness命令查看日志,将看到探针发送的http请求日志如下:

192.168.44.129 - - [24/Oct/2024:02:38:51 +0000] "GET /index.html HTTP/1.1" 200 615 "-" "kube-probe/1.28" "-"


就绪探针

就绪探针用于周期性监控应用程序是否准备好接收流量,尤其适用于Pod通过Service对外暴露的场景,当Pod为准备就绪状态时,即当"READY"值两侧相等时Service才会将流量转发给Pod

示例:

vi pod-readiness.yaml

apiVersion: v1
kind: Pod
metadata:
  labels:
    app: pod-readiness
  name: pod-readiness
spec:
  containers:
  - name: web
    image: nginx:1.23
    readinessProbe:         # 就绪探针
      httpGet:
        path: /index.html
        port: 80
      initialDelaySeconds: 10    # 设置探针在容器启动后首次探测的延迟时间为10秒
      periodSeconds: 20      # 指定探针的检查周期为20秒,检测Pod是否准备就绪

创建Pod资源

kubectl apply -f pod-readiness.yaml

为该Pod创建一个Service

kubectl expose pod pod-readiness --port=80 --target-port=80

查看Service IP地址:

kubectl get service
NAME            TYPE           CLUSTER-IP      EXTERNAL-IP   PORT(S)        AGE
kubernetes      ClusterIP      10.96.0.1       <none>        443/TCP        35d
pod-readiness   ClusterIP      10.106.150.24   <none>        80/TCP         11s

查看Endpoints对象,确认Service后端关联的Pod:

kubectl get endpoints
NAME            ENDPOINTS                                           AGE
kubernetes      192.168.44.128:6443                                 36d
pod-readiness   10.244.36.99:80                                     5m15s

Endpoints对象由Kubernetes自动创建,负责动态收集和管理与Service相关联的Pod IP和端口,以确保后端Pod的信息始终保持最新

尝试模式应用程序故障

kubectl exec -it pod-readiness -- rm  -f /usr/share/nginx/html/index.html

查看pod状态

kubectl get pods
NAME                       READY   STATUS    RESTARTS     AGE
pod-readiness              0/1     Running   0            95m

可以看到该Pod被标记为"未准备就绪状态" 也就是READY这列值不对等,同时Endpoints对象将移除该Pod,再次查看Endpoints可以看到ENDPOINTS列的值为空

kubectl get endpoints
NAME            ENDPOINTS                                           AGE
kubernetes      192.168.44.128:6443                                 36d
pod-readiness                                                       117m

此时,Service将停止为该Pod转发流量.一旦探针成功,Endpoints对象就会自动添加该Pod,Service将重新开始为该Pod转发流量.


启动探针

启动探针是在Kubernetes1.18版本中引入的,用于在容器启动时检查应用程序是否启动,与就绪探针和存活探针不同,启动探针是在"容器启动时"进行的,而不是在"容器运行时"进行的.

使用场景:

避免不必要的重启,针对应用程序启动较慢的情况

依赖其他服务的应用


tcpSocket和exec检查方法

使用"httpGet"检查方法能准确地获取应用程序的健康状态,但该方法并不适用于所有应用程序,如MYSQL,DNS,SSH等.因此这类应用程序则需要使用tcpSocket和exec检查方法.

tcpSocket检查方法

示例:

apiVersion: v1
kind: Pod
metadata:
  labels:
    app: pod-liveness-tcp
  name:  pod-liveness-tcp
spec:
  containers:
  - name: db
    image:  mysql:5.7
    env:
      - name: MYSQL_ROOT_PASSWORD     # 设置mysql root用户密码
        value: "123456"
    livenessProbe:                    # 配置了存活探针,该探针通过尝试连接mysql的3306端口来判断健康状态
      tcpSocket:
        port: 3306
      initialDelaySeconds: 20
      periodSeconds: 20

exec检查方法

示例:

apiVersion: v1
kind: Pod
metadata:
  labels:
    app: pod-liveness-exec
  name: pod-liveness-exec
spec:
  containers:
    - name: db
      image:  mysql:5.7
      env:
      - name: MYSQL_ROOT_PASSWORD
        value: "123456"
      livenessProbe:   # 配置存活探针
        exec:          # 通过exec通过mysqladmin来判断mysql服务是否健康
          command:
          - "/bin/bash"
          - "-c"
          - "mysqladmin ping -uroot -p${MYSQL_ROOT_PASSWORD}"
        initialDelaySeconds: 20
        periodSeconds: 20


容器资源配额

默认情况下,容器可以不受限制地使用节点上的所有可用资源(如 CPU 和内存).然而,就像 JVM 启动项中的 -Xmx 参数一样, 我们需要为容器设定一个最大资源使用限制.假设一台服务器上运行了多个 Pod,如果其中某个 Pod 由于访问量突然增加而不断请求服务器资源,可能会导致服务器资源接近临界值,从而影响其他 Pod 的正常运行.为了避免这种情况,我们需要对 Pod 的容器进行资源限制,以保障服务器的稳定性和各 Pod 的资源公平分配.

  • resources.limits.cpu: 限制容器的CPU使用量.
  • resources.limits.memory: 限制容器的内存使用量.
  • resources.limits.hugepages: 限制容器的HugePages使用量.

此外,还可以为容器配置资源请求,用于指定容器所需的最小资源量,以确保这个Pod被调度到能够满足其最小资源需求的节点上.

  • resources.requests.cpu: 指定容器所需的最小CPU资源
  • resources.requests.memory: 指定容器所需的最小内存资源
  • resources.requests.hugepages: 指定容器所需的最小HugePages资源

示例:

vi pod-nginx.yaml

apiVersion: v1
kind: Pod
metadata:
  name: pod-nginx
  labels:
    app: nginx
spec:
  containers:
    - name: web
      image: nginx:1.23
      resources:
        # 资源请求
        requests:       # requests定义容器的资源请求,告诉调度器,这个Pod需要被调度到至少满足64Mi内存和0.25核CPU的节点上
          memory: "64Mi"
          cpu: "0.25"
        # 资源限制,这里表示容器不允许超过128Mi内存和0.5CPU
        limits:
          memory: "128Mi"
          cpu: "0.5"

创建Pod

kubectl apply -f pod-nginx.yaml

接下来使用stress工具对该容器进行压力测试,以验证容器的资源限制.

K8s从0开始-Pod笔记_Pod

stress -c参数用于指定压测时生成的进程数量,此时可以使用kubectl top pod命令查看Pod的资源利用率, 使用这个命令需要提前安装metrics-server服务

K8s从0开始-Pod笔记_Pod_02

可以看到该Pod的CPU使用受到限制,正常不会超过500m, 如果容器内存使用量超过限制值,kubelet组件会重新启动容器,并将容器的状态标记为"OOMKilled",当CPU使用量超过限制值时,kubelet组件不会触发容器的重启,这是因为超出内存限制通常会导致应用程序无法正常工作.而重启容器可以简单地解决这种问题,相比之下,超过CPU限制并不会直接导致应用程序无法正常工作.只是降低了处理性能,这是一种可接受的情况.


资源请求对Pod调度的影响

当容器配置资源请求后, 调度器会根据该请求值选择能够满足的节点.如果没有节点能满足,则Pod将无法被调度,并且处于"Pending"状态, 因此,Kubernetes中存在一中资源管理机制,这个机制有以下作用.

  • 调度决策: 帮助调度器在集群中选择合适的节点,以满足Pod的资源需求.
  • 均衡负载: 根据资源请求来平衡节点上的资源使用,减少某些节点资源过载而其他节点资源空闲的情况.
  • 资源规划: 有助于规划和优化集群的资源分配,提高整体资源利用率.

需要了解的是,调度器还会考虑其他因素,如节点资源利用率,污点,亲和性规则等以做出最终的调度决策,而资源请求只是影响决策的因素之一


容器环境变量

在Pod配置中,"env"字段用于设置容器的环境变量,通过环境变量,你可以向容器中传递数据,如配置信息,授权凭据等,容器中的应用程序可以读取这些环境变量以获取数据并使用.

示例:

apiVersion: v1
kind: Pod
metadata:
  name: pod-env
  labels:
    app: pod-env
spec:
  containers:
    - name: web
      image: nginx:1.23
      env:
        - name: API_URL  # 变量名称
          value: "https://192.168.1.10/api"  # 变量值
        - name: API_KEY
          value: "LGvoUerBG7"

创建Pod后进入容器,使用env命令查看环境变量

K8s从0开始-Pod笔记_Pod_03

此外Kubernets还提供了"DownloadAPI"功能,该功能允许容器获取与Pod相关的元数据.

示例:

apiVersion: v1
kind: Pod
metadata:
  name:  pod-info-env
  labels:
    app: pod-info-env
spec:
  containers:
  - name: web
    image: nginx:1.23
    env:
      - name: MY_NODE_NAME  # 这个环境变量的值来自Pod所在的节点名称
        valueFrom:
          fieldRef:
            fieldPath: spec.nodeName
      - name: MY_POD_NAME     # 这个环境变量的值来自Pod的名称
        valueFrom:
          fieldRef:
            fieldPath: metadata.name
      - name: MY_POD_NAMESPACE  # 这个环境变量的值来自Pod所在的命名空间
        valueFrom:
          fieldRef:
            fieldPath: metadata.namespace
      - name: MY_POD_IP      # 这个环境变量的值来自Pod的IP地址
        valueFrom:
          fieldRef:
            fieldPath: status.podIP


初始化容器

初始化容器(initContainers)是Pod中一种特殊类型的容器,专用于在主容器启动之前执行一些初始化任务和操作,以满足主容器所需的环境.初始化容器在整个Pod周期内仅运行一次,并且在主容器启动之前完成它们的任务,即初始化容器一旦任务完成就必须退出.

初始化容器具有以下应用场景.

  • 数据库初始化: 在主容器启动之前执行数据库的初始化操作,如创建数据库,执行数据迁移,初始化表结构
  • 文件准备: 在主容器启动之前准备好应用程序使用的文件,如二进制文件,配置文件,证书等
  • 依赖其他服务: 如果主容器依赖其他服务,你可以在启动主容器之前检查依赖服务是否准备就绪,如果未准备就绪,则初始化容器不退出,以确保依赖的服务启动后再启动主容器.

示例:

initContainers:
  - args:             # args部分相当于执行/bin/sh -c 'cp -R ...'
      - '-c'
      - cp -R /skywalking/agent /agent/
    command:
      - /bin/sh
    image: 'registry.xxx.com/xxx/skywalking:8.7.0-alpine'
    imagePullPolicy: IfNotPresent
    name: dataease-1
    resources: {}
    terminationMessagePath: /dev/termination-log
    terminationMessagePolicy: File
    volumeMounts:
      - mountPath: /agent  # 数据卷skywalking-agent挂载到容器的/agent目录
        name: skywalking-agent
volumes:   
    - emptyDir: {}
      name: skywalking-agent


容器声明周期回调

容器声明周期回调是指在容器的声明周期中执行用户定义的操作,Kubernetes支持以下容器声明周期回调:

  • PostStart(容器启动后): 在容器启动后立即执行的回调,它可以用于执行一些初始化任务
  • PreStop(容器停止前): 在容器即将停止之前执行的回调,它可以用于执行清理或保存状态的操作

PostStart

PostStart回调在容器启动后立即执行,它有以下应用场景:

  • 加载应用程序所需的数据或缓存数据
  • 准备文件:修改容器中已有文件或从外部地址下载文件
  • 通知其他组件: 向其他组件发送数据以通知它们容器已启动

示例:

apiVersion: v1
kind: Pod
metadata:
  labels:
    app: django
  name: django-app
spec:
  containers:
    - name: web
      image: python:3.6 
      lifecycle:
        postStart:
          exec:
            command: ["/bin/sh", "-c", "echo 'python manage.py makemigrations && python manage.py migrate' > /tmp/poststart.log"]


PreStop

preStop回调在容器终止之前执行,它有以下应用场景:

  • 优雅关闭连接: 关闭数据库连接,网络连接等,以确保数据的完整性和资源的正确释放
  • 保存状态和数据: 将应用程序生成的状态和数据写入数据库中或远程存储,以便在下次启动时恢复到之前的状态.
  • 通知其他组件


Pod生命周期

K8s从0开始-Pod笔记_Pod_04

假设通过"kubectl run nginx --image=nginx" 命令创建一个Pod,其工作流程如下:

  1. kubectl向API Server发送创建Pod的请求,请求包含Pod的配置信息
  2. API Server接收到请求后,检验字段合法性,如格式,镜像地址不能为空等,校验通过后,将Pod的配置数据写入到Etcd中
  3. Scheduler通过与API Server进行交互感知到新的Pod创建,它获取Pod的配置信息,根据调度算法选择一个合适的节点,将选择的节点添加到配置中,并响应给API Server,然后API Server将配置数据写入到Etcd中
  4. Kubelet通过与API Server进行交互感知到已分配到自身节点的Pod,因此,将Pod配置传递给底层的容器运行时(如Docker)创建相应的容器,并将容器的状态上报给API Server,然后API Server将状态数据写入到Etcd中


启动Pod

K8s从0开始-Pod笔记_Pod_05

Kubelet创建Pod的过程如下:

  1. Kubelet调用容器运行时创建Pause容器,以建立一个容器环境
  2. 创建初始化容器.如果多个初始化容器,则按照顺序创建它们,并确保每个初始化容器都是在上一个初始化容器成功运行后创建的
  3. 最后一个初始化容器执行完成后,并行创建主容器,在主容器启动前执行PostStart回调,在该回调执行完成前,Pod处于Pending状态.
  4. PostStart回调执行完成后,主容器启动,开始执行应用程序的健康检查.最终一个Pod启动


销毁Pod

销毁Pod流程如下:

  1. Kubelet向Pod中的容器发送一个优雅终止信号,通知容器它即将被终止,并给予容器一些时间来完成清理工作,保存状态,释放资源等操作
  2. 执行容器PreStop回调
  3. 从EndPoints对象中移除该Pod,终止Service为其转发流量
  4. 如果容器在一定时间内无法正常终止(最多可以容忍的时间由terminationGracePeriodSeconds控制,默认为30s),Kubelet向Pod中容器发送一个强制终止信号,用于强制关闭容器进程.
  5. Pod被终止,Pod处于Terminating状态
  6. Kubenetes删除Pod相关资源,如网络配置,数据卷等.


上述实验中,使用YAML创建Pod可能会出现pull image error创建失败的情况,这时候需要买一台阿里云服务器并且配置上阿里镜像加速地址用于拉取镜像,通过还需要搭建一个私有仓库(Harbor),这个网上都是有教程,关键命令主要有

docker pull xxx        # 阿里云服务器
docker save xxx        # 阿里云服务器导出镜像文件
docker load -i xxx     # Harbor服务器导入镜像文件
docker tag 原镜像名  (harbor地址前缀)镜像名    # Harbor机器
docker push (harbor地址前缀)镜像名 # 镜像推送到Harbor上

然后在用YAML创建Pod的时候,image配置项需要修改成自己harbor私有仓库地址


参考文档: Kubernetes 文档 | Kubernetes