Pod
Pod 是 Kubernetes 中最小的计算单元,拥有自己独自的 IP,主机名,进程等等。由一个或多个容器组成;这些容器共享存储、网络等资源,可以理解成同一主机上运行多个应用。每一个 Pod 都会有一个特殊的根容器,叫做 pause 容器,pause 容器对应的镜像也是属于 K8s 的一部分。
我们通过 pod 中的 pause 容器 来管理其他容器的, 因为 pause 容器会存储所有的容器状态
Pod、Service、Volume 和 Namespace 是 Kubernetes 集群中四大基本对象,它们能够表示系统中部署的应用、工作负载、网络和磁盘资源,共同定义了集群的状态。Kubernetes 中很多其他的资源其实只对这些基本的对象进行了组合。
Pod:集群中的基本单元。Service:解决如何访问Pod里面服务的问题。Volume:集群中的存储卷。Namespace:命名空间为集群提供虚拟的隔离作用。
apiVersion: v1
kind: Pod
metadata:
name: busybox
labels:
app: busybox
spec:
containers:
restartPolicy: Always
- name: busybox
image: busybox
command:
- sleep
- "3600"
imagePullPolicy: IfNotPresent
如何定义一个Pod
在 K8s 中定义一个 pod 就是写一个 yaml 文件,如下:
apiVersion: v1 // 版本号
kind: Pod // 类型
metadata:
name: pod name
namespace: pod namespace
labels:
- name: pod label
annotations:
- name: pod annotation // 自定义注释列表
spec: // pod 中容器的详细定义
container:
- name: container name
image: container image
imagesPullPolicy: [Always|Never|IfNotPresent] // 镜像拉取策略
command: command list
agrs: app start params
workingDir: work dir
volumeMounts:
- name: volume name
mountPath: volume absolutely path
readonly: boolean
ports:
- name: ports list name
containerPort: 8888
hostPort: 9999
protocol: TCP // 可以是 TCP 和 UDP
env:
- name: env name
value: string
resources:
limits:
cpu: string
memory: string
requeste:
cpu: string
memory: string
livenessProbe: // 健康检查设置
exec:
command: [string]
httpGet: // 通过 httpGet 方式检查
path: string
port: string
host: string
scheme: string
httpHeaders:
- name: httpHeaders name
value: string
tcpSocket: // 通过 tcpSocket 方式检查
port: 80
initialDelaySeconds: 0 // 首次检查时间
timeoutSeconds: 0 // 超时检查时间
periodSeconds: 0 // 检查间隔时间
successTreshold: 0
failuerTreshold: 0
securityContext: // 安全配置
privileged: false
restartPolicy: [Always|Never|OnFailure] //重启策略
nodeSelector: object
imagePullSecrets:
- name: string
hostNetwork: false // 是否使用主机网络模式?
volumes: // 存储卷
- name: volumes list name
emptyDir: {}
hostPath: // pod 所在主机的目录,用于挂载
path: string
secret: // secret 类型 存储卷
secretName: secret name
item:
- key: specific key
path: key path
configMap: // configmap 类型存储卷
name: string
items:
- key: specific key
path: string
Pod 的基本分类
静态
Pod
- 由
Kubelet进行管理。- 存在于特定
Node上的Pod。- 不能通过
Api Server管理。- 无法
ReplicationController,Deployment,Daemonset进行关联。Kubelet无法对该Pod进行健康检查。普通
Pod
- 一旦创建,就会被放到
etcd存储中。- 会被
K8s中的Master调度到某个Node上面并绑定,该Node上的Kubelet会实例化成Docker容器 运行起来。K8s会对Pod做健康检查,若Pod中的容器暂停或者异常,K8s会将他们重启。- 若
Pod所在的Node宕机了,那么K8s会将 该Node的所有Pod重新调度到别的节点上面。
Pod 的多种类型
ReplicationController
ReplicationController 用来确保容器应用的副本数量始终保持在用户定义的副本数,即如果有容器异常退出,会自动创建新的 Pod 来代替,而如果异常多出现的容器会自动回收。
ReplicaSet
在新版本(相对而言的较优方式)的 Kubernetes 中建议使用 ReplicaSet 来取代 ReplicationController 来管理 Pod。虽然 ReplicaSet 和 ReplicationController 并没有本质上的不同,只是名字不一样而已,唯一的区别就是 ReplicaSet 支持集合式的 selector,可供标签筛选。
虽然 ReplicaSet 可以独立使用,但一般还是建议使用 Deployment 来自动管理 ReplicaSet 创建的 Pod,这样就无需担心跟其他机制的不兼容问题。比如 ReplicaSet 自身并不支持滚动更新(rolling-update),但是使用 Deployment 来部署就原生支持。
Deployment
Deployment 为 Pod 和 ReplicaSet 提供了一个声明式定义方法,用来替代以前使用 ReplicationController 来方便且便捷的管理应用。主要的应用场景,包括:滚动升级和回滚应用、扩容和缩容、暂停和继续。
HPA
HPA 仅仅适用于 Deployment 和 ReplicaSet,在 V1 版本中仅支持根据 Pod 的 CPU 利用率扩缩容,在新版本中,支持根据内存和用户自定义的 metric 动态扩缩容。
StatefulSet
StatefulSet 是为了解决有状态服务的问题,相对于 Deployment 和 ReplicaSet 而已。其主要的使用场景,包括:稳定的持久化存储、稳定的网络标识、有序部署、有序收缩。
DaemonSet
DaemonSet 确保全部或者一些 Node 上面运行一个 Pod 副本。当有 Node 加入集群的时候,也会为它们新加一个 Pod。当有 Node 从集群中移除的时候,这些 Pod 也会被回收。删除 DaemonSet 将会删除它所创建的所有 Pod。
使用 DaemonSet 的典型场景就是,在每个节点运行日志收集、运行监控系统、运行集群存储等服务,只要新加进来的节点都需要运行该服务。
Job
Job 负责批处理任务,仅执行一次的任务,它保证批处理任务的一个或者多个 Pod 成功结束,才会返回成功。
Cront Job
Cront Job 管理是基于时间的 Job,即在给定时间点只运行一次,且周期性的在给定时间点运行特定任务。
Pod的状态
Pending
Pending状态表示Api Server已经创建好了该Pod,但是Pod中的一个或者多个容器还没有被创建或者现在正在下载镜像。 RunningPod内所有的容器已经创建好了,至少有一个容器是运行状态、正在启动状态或者是正在重启状态。 CompletedPod内的容器均已执行正常退出,且不会再次重启了 。 FailedPod内的容器均已执行正常退出,至少有一个容器是退出失败的。 Unknown 由于某种原因无法获取到Pod的状态,例如网络问题等等。
Pod 的内部结构
Pod 代表着集群中运行的进程:共享网络、共享存储。
在同一个 Pod 中,有几个概念特别值得关注,首先就是容器,在 Pod 中其实可以同时运行一个或者多个容器,这些容器能够共享网络、存储以及 CPU/内存等资源。
首先,我们需要知道的是,每个 Pod 都有一个特殊的被称为根容器的Pause容器。Pause容器对应的镜像属于 Kubernetes平台的一部分,通过 Pause 容器使工作在对应 Pod 的容器之间可以共享网络、共享存储。
Pod 共享资源
使用 Pause 容器作为 Pod 根容器,以它的状态代表整个容器组的状态;其次,Pod 里的多个业务容器共享 Pause 容器的 IP 地址,共享 Pause 容器挂接的 Volume 资源。
共享存储资源
可以为一个
Pod指定多个共享的 Volume 资源。Pod 中的所有容器都可以访问共享的 volume 资源。Volume 也可以用来持久化 Pod 中的存储资源,以防容器重启后文件丢失。共享网络资源
每个 Pod 都会被分配一个唯一的 IP 地址。Pod 中的所有容器共享网络空间,包括 IP 地址和端口。Pod 内部的容器可以使用 localhost 互相通信。Pod 中的容器与外界通信时,必须分配共享网络资源,例如使用宿主机的端口映射。
veth 设备的特点
一个设备收到协议栈的数据发送请求后,会将数据发送到另一个设备上去。
- veth 和其它的网络设备都一样,一端连接的是内核协议栈。
- veth 设备是成对出现的,另一端两个设备彼此相连。
# 物理网卡eth0配置的IP为192.168.1.11
# 而veth0和veth1的IP分别是192.168.2.11和192.168.2.10
+----------------------------------------------------------------+
| |
| +------------------------------------------------+ |
| | Newwork Protocol Stack | |
| +------------------------------------------------+ |
| ↑ ↑ ↑ |
|..............|...............|...............|.................|
| ↓ ↓ ↓ |
| +----------+ +-----------+ +-----------+ |
| | eth0 | | veth0 | | veth1 | |
| +----------+ +-----------+ +-----------+ |
|192.168.1.11 ↑ ↑ ↑ |
| | +---------------+ |
| | 192.168.2.11 192.168.2.10 |
+--------------|-------------------------------------------------+
↓
Physical Network
Pod的网络通信
集群网络解决方案: Kubernetes + Flannel
Kubernetes 的网络模型假定了所有 Pod 都在一个直接连通的扁平的网络空间中,这在 GCE(Google Compute Engine)里面是现成的网络模型,Kubernetes 假定这个网络已经存在了。而在私有云搭建 Kubernetes 集群,就不能假定这个网络已经存在了。我们需要自己实现这个网络假设,将不同节点上的 Docker 容器之间的互相访问先打通,然后才能正常运行 Kubernetes 集群。
- 同一个
Pod内多个容器之间通过回环网络(lo - 127.0.0.1)进行通信。 - 各
Pod之间的通讯,则是通过Overlay Network网络进行通信。 - 而
Pod与Service之间的通讯,则是各节点的iptables或lvs规则。
Flannel 是 CoreOS 团队针对 Kubernetes 设计的一个网络规划服务,简单来说,它的功能就是让集群中的不同节点主机创建的 Docker 容器都具有全集群唯一的虚拟 IP 地址。而且它还能在这些 IP 地址之间建立一个覆盖的网络(Overlay Network),通过这个覆盖网络,将数据包原封不动地传递给目标容器内。
Pod网络通信方式
同一个
Pod内部通讯:同一个
Pod共享同一个网络命名空间,共享同一个Linux协议栈。不同
Pod之间通讯:
Pod1和Pod2在同一台Node主机,由docker0网桥直接转发请求到Pod2上面,- 不经过Flannel的转发。
Pod1和Pod2不在同一台Node主机,Pod的地址是与docker0在同一个网段的,但docker0网络与宿主机网卡是两个完全不同的 IP 网段,并且不同的Node之间的通讯只能通过宿主机的物理网卡进行。将Pod的IP地址和所在Node的IP地址关联起来,通过这个关联让Pod可以互相访问。
Pod至Service的网络目前基于性能考虑,全部为
iptables或lvs维护和转发。
Pod到外网
Pod向外网发送请求,查找路由表,转发数据包到宿主机的网卡,宿主机网卡完成路由选择之后,iptables或lvs执行Masquerade,把源IP地址更改为宿主机的网卡的IP地址,然后向外网服务器发送请求。外网访问
Pod通过
Service服务来向外部提供Pod服务。
ETCD 之于 Flannel 提供说明:
- 存储管理
Flannel可分配的 IP 地址段资源。 - 监控
ETCD中每一个Pod的实际IP地址,并在内存中建立维护Pod节点的路由表。
Pod的生命周期
Pod的生命周期涉及以下几个概念:
Init Container(初始化容器)。- 容器的两个钩子函数:
PostStart以及PreStop。 - 容器健康检查的两个探针:
liveness probe(存活探针)和readiness probe(就绪探针)
Init Container(初始化容器)
Init Container 就是用来做初始化工作的容器,可以是一个或者多个,如果有多个的话,这些容器会按定义的顺序依次执行,只有所有的 Init Container 执行完后,主容器才会被启动。我们知道一个 Pod 里面的所有容器是共享数据卷和网络命名空间的,所以 Init Container 里面产生的数据可以被主容器使用到的。
从Pod的生命周期图中我们可以直观的看到 PostStart 和 PreStop 包括 liveness 和 readiness 是属于主容器的生命周期范围内的,而 Init Container 是独立于主容器(Main C)之外的,当然他们都属于 Pod 的生命周期范畴之内的。
Init Container(初始化容器)应用场景
解决服务依赖
可以用来解决服务之间的依赖问题,比如我们有一个
Web服务,该服务又依赖于另外一个数据库服务,但是在我们启动这个Web服务的时候我们并不能保证依赖的这个数据库服务就已经启动起来了,所以可能会出现一段时间内Web服务连接数据库异常。 要解决这个问题的话我们就可以在Web服务的Pod中使用一个Init Container,在这个初始化容器中去检查数据库是否已经准备好了,准备好了过后初始化容器就结束退出,然后我们的主容器Web服务被启动起来,这个时候去连接数据库就不会有问题了。做初始化配置
比如集群里检测所有已经存在的成员节点,为主容器准备好集群的配置信息,这样主容器起来后就能用这个配置信息加入集群。
其它场景
如将 pod 注册到一个中央数据库、配置中心等。
服务依赖场景应用示例
下面以服务依赖的场景下初始化容器的使用方法,Pod 的定义方法:
apiVersion: v1
kind: Pod
metadata:
name: init-pod-demo
labels:
app: init
spec:
containers:
- name: init-container
image: busybox
command: ['sh', '-c', 'echo app is running! && sleep 600']
initContainers:
- name: init-myservice
image: busybox
command: ['sh', '-c', 'until nslookup myservice; do echo waiting for myservice; sleep 3; done;']
- name: init-mydb
image: busybox
command: ['sh', '-c', 'until nslookup mydb; do echo waiting for mydb; sleep 3; done;']
Service 对应的 yaml 文件 内容如下:
kind: Service
apiVersion: v1
metadata:
name: myservice
spec:
ports:
- protocol: TCP
port: 80
targetPort: 6376
---
kind: Service
apiVersion: v1
metadata:
name: mydb
spec:
ports:
- protocol: TCP
port: 80
targetPort: 6377
可以先创建上面的 Pod,查看 Pod 的状态,然后再创建 Service,看下前后状态有什么不一样。
在 Pod 启动过程中,初始化容器会按顺序在网络和数据卷初始化之后启动。每个容器必须在下一个容器启动之前成功退出。
如果由于运行时或失败退出,导致容器启动失败,它会根据 Pod 的 restartPolicy 策略进行重试。 如果 Pod 的 restartPolicy 设置为 Always,Init 容器失败时会不断的重启。
Pod 钩子函数
Pod 中通常有两种钩子函数可以使用:
PostStart:这个钩子在容器创建后会被执行。通常用于一些环境准备和资源部署等。不过需要注意的是如果钩子执行太长时间会导致容器不能正常运行,以至于被挂起的状态。PreStop:这个钩子在容器终止之前会被调用。它是阻塞的,意味着它是同步的, 所以它必须在删除容器的调用发出之前完成。主要用于优雅关闭应用程序、通知其他系统等。
如果
PostStart或者PreStop钩子执行过程中失败, 它会杀死相应的容器。所以我们应该让钩子函数尽可能的轻量。
实现 PostStart 与 PreStop 的方式:
Exec 方式:- 通过执行一段特定的命令来实现,该命令消耗的资源会被计入容器。HTTP 方式:对容器上特定的端点执行 HTTP 请求。
案例一:PostStart
在以下示例中,定义了一个 Nginx 的 Pod,其中设置了 PostStart 钩子函数,即在容器创建成功后,写入一句话到 /usr/share/message 文件中。
apiVersion: v1
kind: Pod
metadata:
name: hook-demo1
spec:
containers:
- name: hook-demo1
image: nginx
lifecycle:
postStart:
exec:
command: ["/bin/sh", "-c", "echo hello postStart handler > /usr/share/message"]
案例二:PreStop
当有请求需要删除含有 Pod 的资源对象时(比如 Deployment 等),为了让应用程序可以优雅的关闭(即让正在处理的程序完成后再关闭),K8S 提供两种信息通知来处理:
- 默认情况下
K8s会通知node执行docker stop命令,然后docker会先向容器中 PID 为 1 的进程发送系统信号SIGTERM,等待容器中的应用程序终止执行。如果等待时间达到设定的超时时间,或者默认超时时间(30s),则会继续发送SIGKILL的系统信号强行kill掉进程。 - 使用 PreStop 钩子函数:在发送终止信号之前会执行该钩子函数进行关闭资源。
比如我们定义了一个 Nginx 的 Pod,其中设置了 PreStop 钩子函数,即在容器退出之前,优雅的关闭 Nginx:
apiVersion: v1
kind: Pod
metadata:
name: hook-demo2
spec:
containers:
- name: hook-demo2
image: nginx
lifecycle:
preStop:
exec:
command: ["/usr/sbin/nginx","-s","quit"]
---
apiVersion: v1
kind: Pod
metadata:
name: hook-demo2
labels:
app: hook
spec:
containers:
- name: hook-demo2
image: nginx
ports:
- name: webport
containerPort: 80
volumeMounts:
- name: message
mountPath: /usr/share/
lifecycle:
preStop:
exec:
command: ['/bin/sh', '-c', 'echo hello preStop Handler > /usr/share/message']
Pod 健康检查(指针)
除了PostStart 与 PreStop这两个钩子函数以外,还有一项配置会影响到容器的生命周期的,那就是健康检查的探针,在 Kubernetes 集群当中,我们可以通过配置 liveness probe(存活探针)和 readiness probe(就绪探针)来影响容器的生存周期。
exec 方式检测容器存活
下面先来看下liveness probe(存活探针)的使用方法,首先我们用 exec 执行命令的方式来检测容器的存活,如下:
apiVersion: v1
kind: Pod
metadata:
name: liveness-exec
labels:
test: liveness
spec:
containers:
- name: liveness
image: busybox
args:
- /bin/sh
- -c
- touch /tmp/healthy; sleep 10; rm -rf /tmp/healthy; sleep 500
livenessProbe:
exec:
command:
- cat
- /tmp/healthy
initialDelaySeconds: 6
periodSeconds: 3 #
这里用到了 livenessProbe,然后通过 exec 执行一段命令,其中 periodSeconds 属性表示让 kubelet 每隔3s执行一次存活探针,也就是每3s执行一次上面的 cat /tmp/healthy 命令,如果命令执行成功了,将返回 0,那么 kubelet 就会认为当前这个容器是存活的并且是可监控,如果返回的是非 0 值,那么 kubelet 就会把该容器杀掉然后重启它。
再来看下 initialDelaySeconds 表示在第一次执行探针的时候要等待 6 秒,这样能够确保我们的容器能够有足够的时间启动起来。试想下,如果第一次执行探针等候的时间太短,就很有可能容器还没正常启动起来就进行存活探针检查,这样检查始终都是失败的,从而会不断的重启下去。所以设置一个合理的 initialDelaySeconds 就非常重要。
HTTP方式配置存活探针
另外我们可以使用 HTTP GET 请求来配置我们的存活探针,这里使用一个liveness镜像来验证下:
apiVersion: v1
kind: Pod
metadata:
labels:
test: liveness
name: liveness-http
spec:
containers:
- name: liveness
image: routeman/liveness
args:
- /server
livenessProbe:
httpGet:
path: /healthz
port: 8080
httpHeaders:
- name: X-Custom-Header
value: Awesome
initialDelaySeconds: 3
periodSeconds: 3
同样的,根据 periodSeconds 可以知道 kubelet 需要每隔3s执行一次 liveness probe,该探针将向容器中的 server 的 8080 端口发送一个 HTTP GET 请求。如果 server 的 /healthz 路径的 handler 返回一个成功的返回码,kubelet 就会认定该容器是健康存活的。
如果返回失败的返回码,kubelet 将杀掉该容器并重启。initialDelaySeconds 指定 kubelet 在该执行第一次探测之前需要等待3s。
从上面的 YAML 文件我们可以看出 readiness probe 的配置跟 liveness probe 大同小异。不同是使用 readinessProbe 而不是 livenessProbe。
两者如果同时使用的话就可以确保流量不会到达还未准备好的容器,如果应用程序出现了错误,则会重新启动容器。
这就是 liveness probe(存活探针)和 readiness probe(就绪探针)的使用方法。