k8s☞03Pod

阅读量: zyh 2020-08-26 21:19:04
Categories: > Tags:

前言

pod 可以说是 k8s 的基础单元. 我觉得可以类比云环境的ecs/ec2这一类的基本计算单元.而 pod 上运行的容器, 可以类比为ecs/ec2上的app程序.

你总能在k8s的各类资源中找到云环境对应的资源影子. 如果你用过GCP,你会更有这种感觉.

https://kubernetes.io/docs/concepts/workloads/pods/

Pod与容器

一个pod可以拥有多个容器。

pod 内包含多个容器,所以多个容器共享以下资源。

另外,Pod可以包含一个init的特殊容器,它始终首先运行。

若pod只有一个容器,那么pod就是一个包装器

若pod有多个容器,则一般主容器提供服务;边车/挂斗/附属容器提供额外功能,例如刷新主容器的文件,收集日志。

ℹ️上述的功能的实现基于pod内的容器共享网络命名空间和存储空间.

example pod diagram

如果你玩过星际争霸,那么应该知道一个人族建筑物,总是会有一个附属建筑物,它很小,但提供了主建筑物所需的科技。

因此,除非你两个容器必须放在一起,否则你应该用多个单容器pod.

Pod与负载控制器

生产环境中,pod 一般不单独使用,因为单独使用意味着没有高可用,且难以管理。k8s建议 pod 要始终和负载控制器一起使用。负载控制器可以批量创建pod。

k8s将负载控制器主要分为三种:

还有CronJob、Jobs任务类型的.

负载控制器需要依托于镜像模板创建 pod 和依托于缩放规则控制 pod 数量。

镜像模板即 pod 模板(pod template).

负载控制器 - Pod模板

一个构建nginx的例子

apiVersion: apps/v1
kind: Deployment
metadata:
  name: nginx-deployment
  labels:
    app: nginx
spec:
  replicas: 3
  selector:
    matchLabels:
      app: nginx
  template:
    metadata:
      labels:
        app: nginx
    spec:
      containers:
      - name: nginx
        image: nginx:1.14.2
        ports:
        - containerPort: 80

如何寻找最合适的kind所属的apiVersion

在这里可能有人不知道如何选择apiVersion。你可以通过kubectl api-versions来找到kind所属的apiGroup,然后再通过

kubectl get --raw “/apis” 的输出找 preferredVersion。

➜   kubectl api-resources | grep deployment
deployments                       deploy       apps/v1                                true         Deployment
➜   kubectl get --raw "/apis"|jq '.groups[]|select(.name=="apps")'
{
  "name": "apps",
  "versions": [
    {
      "groupVersion": "apps/v1",
      "version": "v1"
    }
  ],
  "preferredVersion": {
    "groupVersion": "apps/v1",
    "version": "v1"
  }
}

如上命令所示,在我的k8s版本中kind: Deployment的最合适apiVersion是apps/v1

pod 存储

这是一个大问题. 如果你想真正的使用k8s的pod资源, 那么需要先看这一部分的内容.

简单来说, 存储资源主要分网络和本地两大类.

细致的说明, 参考官方文档https://kubernetes.io/docs/concepts/storage/

pod 网络

鉴于前面提到的pod类同于ecs/ec2. 因此. pod中的容器就如同ecs/ec2里的app一样,都有相同的ip, 端口范围, 主机名.

k8s的网络基于各种插件.每一种插件的实现详情见官网. https://kubernetes.io/docs/concepts/cluster-administration/networking/#how-to-implement-the-kubernetes-networking-model

如果你是本地搭建, 那么常用的插件是Flannel. 如果你是在云服务上搭建,那么建议使用云服务已有的k8s服务.

如果你必须在云服务上自己搭建,那么aws/azure/gcp都有对应的网络插件.它可以让你在k8s中结合使用云服务的网络组件.
当然你依然可以用 flannel 网络插件。

静态pod

特点:

  1. 永远运行在固定节点
  2. 由所在节点的kubelet管理,但只负责保活,即pod崩溃重生
  3. kubelet会让apiserver创建一个镜像pod,便于可以通过kubectl查询到静态pod

配置:

  1. 存放在 /etc/kubernetes/manifests 当采用kubeadm安装的时候,一般位于此目录。具体需要去看kubelet配置。
  2. 配置本身可以按照标准pod方式来创建

检测:

  1. kubelet会定期检测配置目录加载配置创建/重建pod

当你通过kubeadm创建的时候,那么k8s的几个重要组件均会以静态pod的方式在master节点上创建,你可以在/etc/kubernetes/manifests/这里找到他们的配置

➜   ll /etc/kubernetes/manifests/
total 16
-rw------- 1 root root 1848 Aug 25 16:28 etcd.yaml
-rw------- 1 root root 2709 Aug 25 16:28 kube-apiserver.yaml
-rw------- 1 root root 2564 Aug 25 16:33 kube-controller-manager.yaml
-rw------- 1 root root 1120 Aug 25 16:33 kube-scheduler.yaml

容器生命周期

Pod-生命周期

Pod启动、终止中涉及到的各个组件。

image-20220506125719802

pod生命周期状态包含5个状态:

pod是通过uid来鉴别,而不是pod名,pod被替换时名称可以不变。

重启策略

https://kubernetes.io/docs/reference/kubernetes-api/workload-resources/pod-v1/#lifecycle

pod重启的启示间隔时间是10s,成指数上涨,但不超过5分钟。一旦重启成功且运行10分钟,则重置为10s。

pod.spec中定义:restartPolicy:Always、Nerver、OnFailure

从实际使用来说:

运行状况

通过kubectl describe pod/<pod_name>查看Conditions字段条件

Conditions:
  Type              Status
  Initialized       True
  Ready             False
  ContainersReady   False
  PodScheduled      True

Ready为True,即表示【应该】被加入到svc的端点列表中。

但极端情况下有可能因为其它服务没准备好,导致及时Pod的Ready为True,svc也无法正常的转发流量到Pod。

这种行为,可能在滚动更新的时候,会导致丢数据。

针对上述问题,kubernetes允许在上面4个默认状态的基础上自定义就绪状态类型。

https://kubernetes.io/zh/docs/concepts/workloads/pods/pod-lifecycle/#pod-readiness-gate

终止流程

https://kubernetes.io/zh/docs/concepts/workloads/pods/pod-lifecycle/#pod-termination

  1. Apiserver拿到删除请求、Apiserver更新pod状态,转为Terminating。默认Pod有30 秒优雅终止时间。

  2. kubelet推送preStop事件到容器中执行,如果设置了preStop的话

  3. kubelet通过container runtime发送TERM信号(kill-14)给每个容器中pid为1的进程号,并同时将pod从svc端点中剥离。

    1. 如果容器在30秒内没有停止成功,则kubelet会发送SIGKILL信号(kill -9)给容器,强行杀掉。
  4. 容器关闭状态转为Terminated,Apiserver将Pod删除。

  • 2和3是并行的,并且执行时间取决于Pod终止宽限期terminationGracePeriodSeconds
  • kubectl 添加--grace-period=0 --force可以立即删除Pod

ℹ️失败的pod状态会根据kube-controller-manager 参数 terminated-pod-gc-threshold阈值设置保存一定数量。默认这个值是12500,不清楚为何设置这么高。https://github.com/kubernetes/kubernetes/pull/79047这是一个被驳回的修正,它提议设置为500

容器Terminated

通过下方命令可以查找Terminated的原因。

kubectl get pod -o go-template='{{range.status.containerStatuses}}{{"Container Name: "}}{{.name}}{{"\r\nLastState: "}}{{.lastState}}{{end}}'  <pod_name:simmemleak>
simmemleak
Container Name: simmemleak
LastState: map[terminated:map[exitCode:137 reason:OOM Killed startedAt:2015-07-07T20:58:43Z finishedAt:2015-07-07T20:58:43Z containerID:docker://0e4095bba1feccdfe7ef9fb6ebffe972b4b14285d5acdec6f0d3ae8a22fad8b2]]

Downward-API

👙需要注意的是,Downward API 能够获取到的信息,一定是 Pod 里的容器进程启动之前就能够确定下来的信息。而如果你想要获取 Pod 容器运行后才会出现的信息,比如,容器进程的 PID,那就肯定不能使用 Downward API 了,而应该考虑在 Pod 里定义一个 sidecar 容器来获取了。

其支持的字段有:

1. 使用 fieldRef 可以声明使用:

spec.nodeName - 宿主机名字
status.hostIP - 宿主机IP
metadata.name - Pod的名字
metadata.namespace - Pod的Namespace
status.podIP - Pod的IP
spec.serviceAccountName - Pod的Service Account的名字
metadata.uid - Pod的UID
metadata.labels['<KEY>'] - 指定<KEY>的Label值
metadata.annotations['<KEY>'] - 指定<KEY>的Annotation值
metadata.labels - Pod的所有Label
metadata.annotations - Pod的所有Annotation

2. 使用 resourceFieldRef 可以声明使用:

容器的 CPU limit
容器的 CPU request
容器的 memory limit
容器的 memory request

环境变量方式

通过Downward API 获取Pod信息并存入到环境变量中,然后在容器里打印出来。

# env-pod.yaml
apiVersion: v1
kind: Pod
metadata:
  name: env-pod
  namespace: kube-system
spec:
  containers:
  - name: env-pod
    image: busybox
    command: ["/bin/sh", "-c", "env"]
    env:
    - name: POD_NAME
      valueFrom:
        fieldRef:
          fieldPath: metadata.name
    - name: POD_NAMESPACE
      valueFrom:
        fieldRef:
          fieldPath: metadata.namespace
    - name: POD_IP
      valueFrom:
        fieldRef:
          fieldPath: status.podIP

volume卷方式

将Pod的metadata.labels和metadata.annotations以 labels 和 annotations 文件方式挂载到容器里的 /etc/podinfo 目录下。

# volume-pod.yaml
apiVersion: v1
kind: Pod
metadata:
  name: volume-pod
  namespace: kube-system
  labels:
    k8s-app: test-volume
    node-env: test
  annotations:
    own: youdianzhishi
    build: test
spec:
  volumes:
  - name: podinfo
    downwardAPI:
      items:
      - path: labels
        fieldRef:
          fieldPath: metadata.labels
      - path: annotations
        fieldRef:
          fieldPath: metadata.annotations
  containers:
  - name: volume-pod
    image: busybox
    args:
    - sleep
    - "3600"
    volumeMounts:
    - name: podinfo
      mountPath: /etc/podinfo
➜  ~ kubectl exec -it volume-pod /bin/sh -n kube-system
/ # ls /etc/podinfo/
..2019_11_13_09_57_15.990445016/  annotations
..data/                           labels
/ # cat /etc/podinfo/labels
k8s-app="test-volume"
/ # cat /etc/podinfo/annotations
build="test"
kubectl.kubernetes.io/last-applied-configuration="{\"apiVersion\":\"v1\",\"kind\":\"Pod\",\"metadata\":{\"annotations\":{\"build\":\"test\",\"own\":\"youdianzhishi\"},\"labels\":{\"k8s-app\":\"test-volume\",\"node-env\":\"test\"},\"name\":\"volume-pod\",\"namespace\":\"kube-system\"},\"spec\":{\"containers\":[{\"args\":[\"sleep\",\"3600\"],\"image\":\"busybox\",\"name\":\"volume-pod\",\"volumeMounts\":[{\"mountPath\":\"/etc/podinfo\",\"name\":\"podinfo\"}]}],\"volumes\":[{\"downwardAPI\":{\"items\":[{\"fieldRef\":{\"fieldPath\":\"metadata.labels\"},\"path\":\"labels\"},{\"fieldRef\":{\"fieldPath\":\"metadata.annotations\"},\"path\":\"annotations\"}]},\"name\":\"podinfo\"}]}}\n"
kubernetes.io/config.seen="2019-11-13T17:57:15.320894744+08:00"
kubernetes.io/config.source="api"

注意点

  1. pod.spec.containers.ports 这里只是一个容器端口的信息公告,哪怕不设定,容器内程序的端口也会暴露。也就是说,如果你容器是nginx,其端口是80。而哪怕你这里设置的 pod.spec.conntainers.port.containerPort 是8080,则实际暴露的依然是80。