Kubernetes in Action—运行于 k8s 中的 容器

pod 是 Kubernetes 最为重要的核心概念,其是 Kubernetes 基本调度单位。

介绍 pod

pod 是一组并置的容器,通常由一个或多个容器组成,当一个 pod 包含多个容器时,这些容器总是运行于同一工作节点上,如图所示,pod 绝不会跨越多个工作节点。

image-20191218124003565

为何需要 pod

为何多个容器比单个容器中包含多个进程要好

容器被设计为每个容器只运行一个进程(除非进程本身产生子进程)。当在单个容器运行多个不相关的进程时,那么保持进程运行、管理日志等将变成我们的责任。例如,当进程崩溃时自动重启的机制等等。

了解 pod

pod 将多个容器绑定在一起,并把它们作为一个单元进行管理。在 pod 下,多个进程之间密切相关,pod 为它们提供统一的环境,此时多个进程就像运行在一起一样,而我们又可以利用容器的特性进行管理。

同一 pod 中容器之间的部分隔离

我们期望隔离容器组,而不是单个容器,即让容器组内的容器共享部分资源。Kubernetes 通过配置 Docker 使 pod 内的容器共享相同 Linux 命名空间(如 Network、 UTS 命名空间、IPC 命名空间,因此共享相同的主机名和网络接口,甚至通过 IPC 进行通信)。但默认情况下,每个容器的文件系统都是隔离的,需要通过 Kubernetes 的 Volume资源共享文件目录。

容器如何共享相同的 IP 和端口空间

由于同一 pod 中的容器运行于相同的 Network 命名空间,因此共享相同的 IP 地址和端口,即意味着同一 pod 中的多个进程要注意不能绑定相同的端口号,否则会产生端口冲突,并且容器可以通过 localhost 与同一 pod 中的其它容器进行通信。

介绍平坦 pod 间网络

Kubernetes 集群中所有 pod 都运行于同一个共享网络地址空间中,因此意味着每个 pod 都可以通过其它 pod 的 IP 地址来实现相互访问,即 pod 之间没有 NAT(网络地址转换) 网关,彼此之间发送网络数据包时,都会将对方的实际 IP 地址看做数据包中的源 IP。

image-20191218130230652

通过 pod 合理管理容器

将多层应用分散到多个 pod 中

对于一个由前端应用服务器和后端数据库组成的多层应用程序,虽然可以将运行前端服务器和数据库两个容器运行在同一 pod 中,但这并不值得推荐。更合理的做法是将 pod 拆分到两个 pod 中,当在有两个或多个工作节点的 Kubernetes 集群中时能够更充分地利用计算资源,从而提高基础架构的利用率。

基于扩缩容考虑而分割到多个 pod 中

pod 是扩缩容的基本单位,对于 Kubernetes 来说,其不能横向扩缩单个容器,只能扩缩整个 pod,基于此,如果当 pod 中包含一个前端和后端容器时,扩容意味着成倍地扩大了前端和后端容器,而这两者本身具有完全不同的扩缩容需求,因此我们需要拆分它们。

当你需要单独扩缩容器时,那么这个容器可以很明确地被部署到单独的 pod 中。

何时在 pod 中使用多个容器

将多个容器添加到单个 pod 的主要原因是应用可能由一个主进程和一个或多个辅助进程组成,如下:

image-20191218131542806

举个例子,pod中的主容器是一个仅仅服务于某个目录中的文件的 Web 服务器,而另一个容器(所谓的 sidecar 容器)则定期从外部资源下载内容并将其存储在 Web 服务器目录中,sidecar 容器也可以是如日志轮换器和收集器、数据处理器、通信适配器等等。

决定何时在 pod 中使用多个容器

  • 它们需要一起运行还是在不同主机上运行?
  • 它们代表的是一个整体还是相互独立的组件?
  • 它们必须一起进行扩缩容还是可以分别进行?

我们应该倾向于在单独的 pod 中运行容器,除非有特定原因要求它们必须是同一 pod 的一部分。

image-20191218132028264

以 YAML 或 JSON 描述文件创建 pod

pod 和 其他 Kubernetes 资源通常是通过向 Kubernetes REST API 提供 JSON 或 YAML 描述文件创建的,为此,需要了解并理解 Kubernetes API 对象定义,参考:https://kubernetes.io/docs/concepts/overview/kubernetes-api/。

检查现有 pod 的 YAML 描述文件

获取已部署 pod 的完整 YAML:

kubectl get pod kubia-xv6k4 -o yaml

介绍 pod 定义的主要部分

pod 定义由几个部分组成,YAML 文件中首先是 Kubernetes API 版本和 YAML 描述的资源类型,其次是几乎所有 Kubernetes 资源都包含的三大主要部分:

  • metadata:包括名称、命名空间、标签和关于该容器的其他信息
  • spec:包含 pod 内容的实际说明,如 pod 的容器、卷和其他数据
  • status:包含运行中的 pod 当前信息,如 pod 所处的条件、每个容器的描述和状态,以及内部 IP 和其他基本信息

为 pod 创建一个简单的 YAML 描述文件

创建 kubia-manual.yaml 文件:

# 遵循 v1 版本 Kubernetes API
apiVersion: v1
# 描述一个 pod
kind: Pod
metadata:
  # pod 的名称
  name: kubia-manual
spec:
  containers:
    # 创建容器所用的镜像
  - image: april5/kubia
    # 容器名称
    name: kubia
    ports:
      # 应用监听的端口
    - containerPort: 8080
      protocol: TCP

指定容器的端口

在 pod 定义中的指定端口是展示性的(informational),忽略它们对于是否可以通过端口连接到 pod 没有影响,定义端口的意义在于每个使用集群的人可以快速查看每个 pod 对外暴露的端口,且能为端口定义一个名词,方便后续使用。

准备 manifest 时,通过 https://kubernetes.io/docs/reference/generated/kubernetes-api/ 可以查看 Kubernetes 的每个 API 对象支持的属性,也可以用 kubectl explain 命令发现可能的 API 对象字段:

# 查看 pod 支持的属性
➜ kubectl explain pods
KIND:     Pod
VERSION:  v1

DESCRIPTION:
     Pod is a collection of containers that can run on a host. This resource is
     created by clients and scheduled onto hosts.

FIELDS:
   apiVersion	<string>
     APIVersion defines the versioned schema of this representation of an
     object. Servers should convert recognized schemas to the latest internal
     value, and may reject unrecognized values. More info:
     https://git.k8s.io/community/contributors/devel/sig-architecture/api-conventions.md#resources

   kind	<string>
     Kind is a string value representing the REST resource this object
     represents. Servers may infer this from the endpoint the client submits
     requests to. Cannot be updated. In CamelCase. More info:
     https://git.k8s.io/community/contributors/devel/sig-architecture/api-conventions.md#types-kinds

   metadata	<Object>
     Standard object's metadata. More info:
     https://git.k8s.io/community/contributors/devel/sig-architecture/api-conventions.md#metadata

   spec	<Object>
     Specification of the desired behavior of the pod. More info:
     https://git.k8s.io/community/contributors/devel/sig-architecture/api-conventions.md#spec-and-status

   status	<Object>
     Most recently observed status of the pod. This data may not be up to date...

Kubectl 打印出对象的解释并列出对象可以包含的属性,接下来就可以深入了解各个属性的更多信息:

➜ kubectl explain pod.spec
KIND:     Pod
VERSION:  v1

RESOURCE:spec <Object>

DESCRIPTION:
     Specification of the desired behavior of the pod. More info:
     https://git.k8s.io/community/contributors/devel/sig-architecture/api-conventions.md#spec-and-status

     PodSpec is a description of a pod.

FIELDS:
   activeDeadlineSeconds	<integer>
     Optional duration in seconds the pod may be active on the node relative to
     StartTime before the system will actively try to mark it failed and kill
     associated containers. Value must be a positive integer.

   affinity	<Object>
     If specified, the pod's scheduling constraints

   automountServiceAccountToken	<boolean>
     AutomountServiceAccountToken indicates whether a service account token
     should be automatically mounted.

   containers	<[]Object> -required-
     List of containers belonging to the pod. Containers cannot currently be
     added or removed. There must be at least one container in a Pod. Cannot be
     updated...

使用 kubectl create 来创建 pod

使用 kubectl create 命令从 YAML 文件创建 pod:

kubectl create -f kubia-manual.yaml

kubectl create -f 命令从 YAML 或 JSON 文件创建任何资源(不止是 pod)。

得到运行中 pod 的完整定义

pod 创建完成后,可以请求 Kubernetes 来获得完整的 YAML:

kubectl get po kubia-manual -o yaml/json

在 pod 列表中查看新创建的 pod

➜ kubectl get pods
NAME           READY   STATUS    RESTARTS   AGE
kubia-manual   1/1     Running   0          7h10m

kubia-manual 状态显示 Running,说明已经成功通过 yaml 文件创建了 pod 并让其运行。

查看应用程序日志

Nodejs 应用将日志记录到进程的标准输出,容器化的应用程序通常会将日志记录到标准输出和标准错误流,因此允许用户可以通过简单、标准的方式查看不同应用程序的日志。

容器运行时(Docker)将上述的输出流重定向到文件,运行下面命令获取容器日志:

docker logs <container id>

使用 kubectl logs 命令获取 pod 日志

➜ kubectl logs kubia-manual
Kubia server starting...

注意:每天或者每次日志文件达到 10MB 大小时,容器日志都会自动轮替, kubectl logs 命令仅显示最后一次轮替后的日志条目。

获取多容器 pod 的日志时指定容器名称

当 pod 中包含多个容器时,需要显示指定容器名称查看日志:

kubectl logs kubia-manual -c kubia

当 pod 被删除时,日志也将被删除,因此如果希望日志能够在 pod 删除后得以保留,需要设置中心化的、集群范围的日志系统,将日志存储到中心存储中。

向 pod 发送请求

将本地端口转发到 pod 中的端口

出于调试或者其他原因,有时候并不一定需要通过 service 与特定 pod 进行通信,Kubernetes 允许我们配置端口转发到该 pod,如下命令将本地的 8888 端口转发到 kubia-manual pod 的 8080 端口:

➜ kubectl port-forward kubia-manual 8888:8080
Forwarding from 127.0.0.1:8888 -> 8080
Forwarding from [::1]:8888 -> 8080

通过端口转发连接到 pod

此时即可通过 http://localhost:8888 与该 pod 进行通信:

➜ curl localhost:8888
You've hit kubia-manual

下图展示了发送请求的简化视图,实际上,在 kubectl 进程和 pod 之间还有额外的组件:

image-20191218234103906

使用标签组织 pod

微服务架构带来了更多的组件,组件可能部署了多个副本以及多个不同的发布版本,都会导致 pod 数量的增加,随着 pod 数量的增加,将它们分类到子集的需求也就变得更加迫切,如果没有有效组织这些 pod 的机制,将产生巨大的混乱。

通过标签组织 pod 和所有其他 Kubernetes 对象,借此方便开发人员和系统管理员轻松看到每个 pod 是什么,做什么,且能够以组的方式管理 pod,对 pod 进行操作。

介绍标签

标签可以组织所有 Kubernetes 资源,包括 pod。

标签是可以附加到资源的任意键值对,用以选择具有该确切标签的资源(通过标签选择器完成)。只要标签的 key 在资源内唯一,则一个资源便可以拥有多个标签。通过添加两个标签,左边混乱的微服务实例变成更组织化的系统,便于理解:

image-20191218235250608 image-20191218235348498
  • app:指定 pod 属于哪个应用、组件或微服务
  • rel:显示在 pod 中运行的应用程序版本是 stable、beat 还是 canary

创建 pod 时指定标签

编写 kubia-manual-with-labels.yaml 创建带有两个标签的新 pod:

apiVersion: v1
kind: Pod
metadata:
  name: kubia-manual-v2
  # 标签被附加到 pod 上
  labels:
    creation_method: manual
    env: prod
spec:
  containers:
  - image: april5/kubia
    name: kubia
    ports:
    - containerPort: 8080
      protocol: TCP
➜ kubectl create -f kubia-manual-with-labels.yaml
pod/kubia-manual-v2 created

通过 shell show-labels 选项查看标签:

➜ kubectl get po --show-labels
NAME              READY   STATUS    RESTARTS   AGE   LABELS
kubia-manual      1/1     Running   0          9h    <none>
kubia-manual-v2   1/1     Running   0          64s   creation_method=manual,env=prod

如果只对某些标签感兴趣,可以显示指定标签并将它们列在所属的列上:

➜ kubectl get po -L creation_method,env
NAME              READY   STATUS    RESTARTS   AGE    CREATION_METHOD   ENV
kubia-manual      1/1     Running   0          9h
kubia-manual-v2   1/1     Running   0          3m1s   manual            prod

修改现有 pod 的标签

在现有 pod 上添加修改标签:

➜ kubectl label po kubia-manual creation_method=manual
pod/kubia-manual labeled

# 更改现有标签需要使用 —overwrite 选项
➜ kubectl label po kubia-manual-v2 env=debug --overwrite
pod/kubia-manual-v2 labeled

➜ kubectl get po --show-labels

➜  ch03 kubectl get po --show-labels
NAME              READY   STATUS    RESTARTS   AGE     LABELS
kubia-manual      1/1     Running   0          9h      creation_method=manual
kubia-manual-v2   1/1     Running   0          7m57s   creation_method=manual,env=debug

通过标签选择器列出 pod 子集

标签选择器将选择标记有特定标签的 pod 子集选取出来,从而方便对这些 pod 执行操作。标签选择器根据资源的以下条件选择资源:

  • 包含(或不包含)使用特定键的标签
  • 包含具有特定键和值得标签
  • 包含具有特定键的标签,但其值与我们指定的不同

使用标签选择器列出 pod

➜ kubectl get po -l creation_method=manual
NAME              READY   STATUS    RESTARTS   AGE
kubia-manual      1/1     Running   0          10h
kubia-manual-v2   1/1     Running   0          44m

列出包含 env 标签的所有 pod,无论其值如何:

➜ kubectl get po -l env
NAME              READY   STATUS    RESTARTS   AGE
kubia-manual-v2   1/1     Running   0          45m

列出没有 env 标签的 pod:

➜ kubectl get po -l '!env'
NAME           READY   STATUS    RESTARTS   AGE
kubia-manual   1/1     Running   0          10h
kubia-snxzv    1/1     Running   0          16h
kubia-srh6f    1/1     Running   0          16h
kubia-xv6k4    1/1     Running   0          17h

同理,下面标签选择器也可以用于选择 pod:

  • creation_method != manual 选择带有 creation_method,且值不为 manual 的 pod
  • env in (prod, devel) 选择带有 env 标签且值为 prod 或 devel 的 pod
  • env notin (prod, devel) 选择带有 env 标签,且值不是 prod 或 devel 的 pod

在标签选择器中使用多个条件

可以用多个”,“分割标签选择器的多个条件,此时资源需要全部匹配上才能成功被选中。例如,在上述微服务示例中,当只想选择 product catalog 微服务的 beta 版本的 pod 时,可以使用 shell app=pc, rel=beta

截屏2019-12-19上午8.55.08

标签选择器在选择 pod ,并对选择的子集的所有 pod 执行操作具有重要意义。

使用标签和选择器来约束 pod 调度

大部分情况下,没有任何理由需要指定 Kubernetes 把 pod 调度到哪个工作节点上,但某些情况下我们仍然需要对 pod 调度到何处持有发言权,如当硬件基础设施不是同质的情况下(如某些工作节点是机械硬盘,某些是固态硬盘,而 pod 对使用何种硬盘敏感,或者说某些 pod 需要执行 GPU 密集型运算等情况)。但是,直接将 pod 调度到具体节点,就和应用程序和基础架构分离的理念相悖,因此需要用标签指定确切的节点来对节点进行描述,使 Kubernetes 能够选择符合要求的节点

使用标签分类工作节点

向工作节点添加 gpu=true 标签:

➜ kubectl get node
NAME       STATUS   ROLES    AGE   VERSION
minikube   Ready    master   27h   v1.17.0
➜ kubectl label node minikube gpu=true
node/minikube labeled
➜ kubectl get nodes -l gpu=true
NAME       STATUS   ROLES    AGE   VERSION
minikube   Ready    master   27h   v1.17.0

将 pod 调度到特定节点

为了让调度器只在提供适当 GPU 的节点选择,需要在 pod 的 YAML 文件添加节点选择器,创建 kubia-gpu.yaml 文件:

apiVersion: v1
kind: Pod
metadata:
  name: kubia-gpu
spec:
  nodeSelector:
    gpu: "true"
  containers:
  - image: april5/kubia
    name: kubia

在 spec 添加了 nodeSelector 字段,则当创建 pod 时,调度器将只在 gpu=true 的节点中选择。

➜ kubectl create -f kubia-gpu.yaml

调度到一个特定节点

实际上每个节点都有一个唯一标签,其键为 kubernetes.io/hostname,值为该节点实际主机名,因此我们也可以将 pod 调度到某个确定节点上,但当节点处于离线状态时,则通过 hostname 将 nodeSelector 设置为特定节点的 pod 可能会不可调度,因此通常情况下不应该考虑单节点,而应该通过标签选择器选择标准的逻辑节点组。

注解 pod

除标签外, pod 和其他对象还可以包含注解,注解也是键值对形式的,其与标签不同的是,注解并不是为了保存标识信息存在的,因此也不能对对象进行分组(当然也不存在注解选择器类似的东西),再者,注解可以容纳更多信息,用于工具使用。Kubernetes 会将一些注解自动添加到对象,而其他的注解需要由用户手动添加。

此外,大量使用注解可以为 pod 或其他 API 对象添加说明,以便使用集群的人可以快速查找单独对象的信息(如指定创建对象的人员姓名等)。

查找对象的注解

为了获取注解,需要获取 pod 的完整 YAML 文件或者使用 kubectl describe 命令:

kubectl get po kubia-snxzv -o yaml

annotations 下的 JSON 数据即为注解。

添加和修改注解

和标签一样,注解可以在创建 pod 时就添加,也可以在运行时进行添加或修改,最简单的就是通过 kubect annotate对注解进行添加或修改:

➜ kubectl annotate pod kubia-manual mycompany.com/someannotation="foo bar"
pod/kubia-manual annotated

使用 mycompany.com/someannotation=“foo bar” 这种格式添加注解是避免键值冲突的好方法,使用 kubectl describe 命令查看添加的注解:

➜ kubectl describe pod kubia-manual
...
Annotations:  mycompany.com/someannotation: foo bar
...

使用命名空间对资源进行分组

了解对命名空间的需求

使用多个 namespace 的前提下,可以将包含大量组件的复杂系统拆分为更小的不同组,这些不同组也可以用于在多租户环境中分配资源,将资源分配为生产、开发和 QA 环境,或者其他任何你需要的方式分配资源。资源名称只需在命名空间保持唯一即可,而两个不同的命名空间可以保护同名的资源。

发现其他命名空间及其 pod

➜ kubectl get ns
NAME                   STATUS   AGE
default                Active   27h
kube-node-lease        Active   27h
kube-public            Active   27h
kube-system            Active   27h
kubernetes-dashboard   Active   27h

目前为止,我们只在 default 命名空间进行操作,当使用 kubectl get 命令列出资源时,由于没有指定命名空间,因此都是默认 default 命名空间,只显示该命名空间下的对象。

获取属于 kube-system 命名空间的 pod:

➜ kubectl get po --namespace/-n kube-system
NAME                               READY   STATUS    RESTARTS   AGE
coredns-6955765f44-vkpn4           1/1     Running   0          28h
coredns-6955765f44-xd65q           1/1     Running   0          27h
etcd-minikube                      1/1     Running   0          28h
kube-addon-manager-minikube        1/1     Running   0          28h
kube-apiserver-minikube            1/1     Running   0          28h
kube-controller-manager-minikube   1/1     Running   0          28h
kube-proxy-vqs89                   1/1     Running   0          28h
kube-scheduler-minikube            1/1     Running   0          28h
storage-provisioner                1/1     Running   0          28h

可以发现以上资源和 Kubernetes 系统本身密切相关,将它们放置于单独命名空间将保持组织良好。如果将 default 的资源和系统资源混合在一起,会造成难以区分资源的所属和不小心删除系统资源等问题。

当有多个用户或者用户组使用同一个 Kubernetes 集群时,它们各自管理自己的独特资源集合,那么就应该使用各自的命名空间,即命名空间提供了作用域,保护好各自的资源,也方便做权限管理限定特定用户访问特定的资源或资源数量

创建一个命名空间

从 YAML 文件创建命名空间

创建 custom-namespace.yaml 文件:

apiVersion: v1
kind: Namespace
metadata:
  name: custom-namespace
➜ kubectl create -f custom-namespace.yaml
namespace/custom-namespace created

使用 kubectl create namespace 命令创建命名空间

kubectl create namespace custom-namespace

管理其他命名空间中的对象

如果想在 custom-namespace 命名空间创建资源,可以选择在 metadata 字段添加 namespace: custom-namespace 属性,也可以使用 kubectl create 命令时指定命名空间

➜ kubectl create -f kubia-manual.yaml -n custom-namespace
pod/kubia-manual created

此时,两个同名的 pod(kubia-manual) 在两个不同的命名空间中。

不指定命名空间时,kubectl 将在当前上下文配置中的默认命名空间执行操作,通过 shell kubectl config 更改当前上下文。

# 通过设置别名快速切换不同明明空间
alias kcd='kubectl config set-context $(kubectl config current-context) --namespace'
kcd some-namespace

命名空间提供的隔离

实际上,命名空间不提供对正在运行的对象的任何隔离,不同用户在不同命名空间部署 pod 时,pod 是否能够进行通信取决于 Kubernetes 所使用的网络解决方案,当该解决方案不提供命名空间的网络隔离时,则命名空间 foo 中的某个 pod 指定命名空间 bar 中的 pod 的 IP 地址,即可将流量发送到另一个命名空间的 pod。

停止和移除 pod

按名称删除 pod

➜ kubectl delete po kubia-gpu
# 通过空格分隔多个 pod ,以批量删除 pod
pod "kubia-gpu" deleted

删除 pod 的过程,实际上是我们指示 Kubernetes 终止该 pod 中的所有容器。Kubernetes 向进程发送 SIGTERM 信号并等待一定时间使其真正关闭,如果没能及时关闭,则通过 SIGKILL 终止进程,因此为了进程总是能正常关闭,进程通过需要正确处理 SIGTERM 信号。

使用标签选择器删除 pod

➜ kubectl delete po -l creation_method=manual
pod "kubia-manual" deleted
pod "kubia-manual-v2" deleted

当 pod 非常多时,通过标签删除 pod 是一个明智的选择。

通过删除整个命名空间来删除 pod

➜ kubectl delete ns custom-namespace
namespace "custom-namespace" deleted

删除命令空间中的所有 pod,但保留命名空间

➜ kubectl delete po --all
kubectl delete po --all
pod "kubia-snxzv" deleted
pod "kubia-srh6f" deleted
pod "kubia-xv6k4" deleted

➜ kubectl get pod
NAME          READY   STATUS        RESTARTS   AGE
kubia-f6sn4   1/1     Running       0          21s
kubia-rhlxd   1/1     Running       0          21s
kubia-snxzv   1/1     Terminating   0          21h
kubia-srh6f   1/1     Terminating   0          21h
kubia-twkt5   1/1     Running       0          20s
kubia-xv6k4   1/1     Terminating   0          22h

ReplicationCcontroller 在我们删除了 pod 时又重新创建了新的 pod,因此想要删除所有 pod ,还要删除 ReplicationCcontroller。

删除命名空间中的(几乎)所有资源

➜ kubectl delete all --all
pod "kubia-f6sn4" deleted
pod "kubia-rhlxd" deleted
pod "kubia-twkt5" deleted
replicationcontroller "kubia" deleted
service "kubernetes" deleted
service "kubia-http" deleted

all 指定删除所有资源类型,—all 指定将删除所有资源实例,不过 “kubernetes” 服务将在几分钟后被重新创建。

注意:使用 all 并不是真的删除所有内容,有些被保留下来的资源需要明确指定删除。