pod、容器和service的关系
pod与容器是包含关系,通常一个pod包含了多个容器,在pod对应的定义文件中会有具体的定义。pod本质上是一个最小部署单元,不是一个程序/进程,是一个环境,包括容器\存储、网络ip:port
为什么有了pod还要有service?
pod是临时性的,当pod中的进程结束、node故障、资源短缺时,pod都有可能被干掉,即pod的ip:port也是动态变化的,所以不能作为服务提供方。service会被分配一个固定的集群ip
挂载 app-volume 到自己的 webapps 目录下。
Pod初了解:是 Kubernetes 项目中最小的 API 对象,也是k8s中最小的调度粒度,一般会包含多个密切协作的容器。
为什么需要Pod?
一方面:某些应用之间有着密切的协作关系,使得它们必须部署在同一台机器(同一个容器)上。
例如:互相之间会发生直接的文件交换、使用 localhost 或者 Socket 文件进行本地通信、会发生非常频繁的远程调用、需要共享某些 Linux Namespace。
另一方面:容器的本质是一种特殊的进程,希望一个容器中仅仅跑一个应用进程。
怎么解决上面的矛盾呢?
Pod结构
infra容器 + 多个相关的业务容器,如图:
Pod 的实现需要使用一个中间容器,这个容器叫作 Infra 容器,永远都是第一个被创建的容器,而其他用户定义的容器,则通过 Join Network Namespace 的方式,与 Infra 容器关联在一起。同样的方式也可以声明共享同一个 Volume。
对于 Pod 里的容器 A 和容器 B 来说:
它们可以直接使用 localhost 进行通信;
它们看到的网络设备跟 Infra 容器看到的完全一样;
一个 Pod 只有一个 IP 地址,也就是这个 Pod 的 Network Namespace 对应的 IP 地址;
当然,其他的所有网络资源,都是一个 Pod 一份,并且被该 Pod 中的所有容器共享;
Pod 的生命周期只跟 Infra 容器一致,而与容器 A 和 B 无关。
所以:Pod其实是通过中间容器实现某些资源共享的容器组。
当用户想在一个容器里跑多个功能并不相关的应用时,应该优先考虑它们是不是更应该被描述成一个 Pod 里的多个容器。其实就是经典的容器设计模式:sidecar
即在 Pod 里面,可以定义一些专门的容器,来执行主业务容器所需要的一些辅助工作,比如:
1)业务镜像启动的准备工作,把镜像里的 WAR 包拷贝到共享目录里面
2)原本需要在容器里面执行 SSH 需要干的一些事情
3)日志收集
4)监控组件装到额外的小容器中获取业务容器的工作状态
docker自身也是支持多容器的网络和volume的共享的,跟Pod的实现有什么区别呢?
$ docker run --net=B --volumes-from=B --name=A image-A ...
容器 B 就必须比容器 A 先启动,这样一个 Pod 里的多个容器就不是对等关系,而是拓扑关系了。
所以:pod通过infra容器保证了业务容器的对等性。
存在如下场景
机器A: 2.5G内存,机器B: 3G内存
应用1 2 3容器分别需要1g内存,且因为要协作,需要部署到同一个机器。
swarm做法:另外两个容器上设置一个 affinity=1(与容器1有亲密性)的约束,即:它们俩必须和 容器1运行在同一台机器上。执行docker run后,1 和2进入机器a,此时容器2不行了。
k8s中Pod 是 Kubernetes 里的原子调度单位。三个容器组成的 Pod。Kubernetes 项目在调度时,自然就会去选择可用内存等于 3 GB 的机器a节点进行绑定,而根本不会考虑2。
所以:Pod 的设计更加有利于调度。
总结下pod带来的好处:
1)解决了有着密切的协作关系又不能运行在同一容器内的矛盾
2)引入了经典的设计模式:sidecar,更易于扩展和使用。
3)pod通过infra容器保证了业务容器的对等性
4)Pod 的设计更加有利于调度
pod的定义详解
pod级别的属性:调度、网络、存储,以及安全
对container的定义:Image(镜像)、Command(启动命令)、workingDir(容器的工作目录)、Ports(容器要开发的端口),以及 volumeMounts(容器要挂载的 Volume)
举个实例:
WAR 包与 Web 服务器:一个 Java Web 应用的 WAR 包,它需要被放在 Tomcat 的 webapps 目录下运行起来。
docker实现:
一种方法是,把 WAR 包直接放在 Tomcat 镜像的 webapps 目录下,做成一个新的镜像运行起来。可是,这时候,如果你要更新 WAR 包的内容,或者要升级 Tomcat 镜像,就要重新制作一个新的发布镜像,非常麻烦。
另一种方法是,你压根儿不管 WAR 包,永远只发布一个 Tomcat 容器。不过,这个容器的 webapps 目录,就必须声明一个 hostPath 类型的 Volume,从而把宿主机上的 WAR 包挂载进 Tomcat 容器当中运行起来。不过,这样你就必须要解决一个问题,即:如何让每一台宿主机,都预先准备好这个存储有 WAR 包的目录呢?这样来看,你只能独立维护一套分布式存储系统了。
有了 Pod 之后,这样的问题就很容易解决了。我们可以把 WAR 包和 Tomcat 分别做成镜像,然后把它们作为一个 Pod 里的两个容器“组合”在一起。这个 Pod 的配置文件如下所示:
pod定义实例与讲解:
apiVersion: v1 kind: Pod metadata: name: javaweb-2 spec: initContainers: - image: geektime/sample:v2 name: war command: ["cp", "/sample.war", "/app"] volumeMounts: - mountPath: /app name: app-volume containers: - image: geektime/tomcat:7.0 name: tomcat command: ["sh","-c","/root/apache-tomcat-7.0.42-v2/bin/start.sh"] volumeMounts: - mountPath: /root/apache-tomcat-7.0.42-v2/webapps name: app-volume ports: - containerPort: 8080 hostPort: 8001 volumes: - name: app-volume emptyDir: {} |
Pod 中,我们定义了两个容器,第一个容器使用的镜像是 geektime/sample:v2,这个镜像里只有一个 WAR 包(sample.war)放在根目录下。WAR 包容器的类型不再是一个普通容器,而是一个 Init Container 类型的容器。在 Pod 中,所有 Init Container 定义的容器,都会比 spec.containers 定义的用户容器先启动。并且,Init Container 容器会按顺序逐一启动,而直到它们都启动并且退出了,用户容器才会启动。
而第二个容器则使用的是一个标准的 Tomcat 镜像,挂载 app-volume 到自己的 webapps 目录下。
创建容器:kubctl create -f yaml文件
删除容器 kubectl delete pod podName
进入容器内部:kubectl exec -it podName /bin/bash
获取pod列表 kubectl get pods
获取某个pod的详情 kubectl describe pod podName
2.3.1 pod的定义详解
https://www.cnblogs.com/sseban/p/13084416.html
pod级别的属性:调度、网络、存储,以及安全
NodeSelector:是一个供用户将 Pod 与 Node 进行绑定的字段。
apiVersion: v1
kind: Pod
...
spec:
nodeSelector:
disktype: ssd
Pod 永远只能运行在携带了“disktype: ssd”标签(Label)的节点上;否则,它将调度失败。
NodeName:一旦 Pod 的这个字段被赋值,Kubernetes 项目就会被认为这个 Pod 已经经过了调度,调度的结果就是赋值的节点名字。所以,这个字段一般由调度器负责设置,但用户也可以设置它来“骗过”调度器,当然这个做法一般是在测试或者调试的时候才会用到。
HostAliases:定义了 Pod 的 hosts 文件(比如 /etc/hosts)里的内容,用法如下:
apiVersion: v1
kind: Pod
...
spec:
hostAliases:
- ip: "10.1.2.3"
hostnames:
- "foo.remote"
- "bar.remote"
...
Pod 启动后,/etc/hosts 文件的内容将如下所示:
cat /etc/hosts
# Kubernetes-managed hosts file.
127.0.0.1 localhost
...
10.244.135.10 hostaliases-pod
10.1.2.3 foo.remote
10.1.2.3 bar.remote
说明:如果直接修改了 hosts 文件的话,在 Pod 被删除重建之后,kubelet 会自动覆盖掉被修改的内容。
跟容器的 Linux Namespace 相关的属性,也一定是 Pod 级别的。
shareProcessNamespace=true :这个 Pod 里的容器要共享 PID Namespace。
凡是 Pod 中的容器要共享宿主机的 Namespace,也一定是 Pod 级别的定义,如:
apiVersion: v1
kind: Pod
metadata:
name: nginx
spec:
hostNetwork: true
hostIPC: true
hostPID: true
containers:
- name: nginx
image: nginx
- name: shell
image: busybox
stdin: true
tty: true
这个 Pod 里的所有容器,会直接使用宿主机的网络、直接与宿主机进行 IPC 通信、看到宿主机里正在运行的所有进程。
对container的定义:
Image(镜像)、Command(启动命令)、workingDir(容器的工作目录)、Ports(容器要开发的端口),以及 volumeMounts(容器要挂载的 Volume
还有:
ImagePullPolicy:值默认是 Always,即每次创建 Pod 都重新拉取一次镜像。另外,当容器的镜像是类似于 nginx 或者 nginx:latest 这样的名字时,ImagePullPolicy 也会被认为 Always。而如果它的值被定义为 Never 或者 IfNotPresent,则意味着 Pod 永远不会主动拉取这个镜像,或者只在宿主机上不存在这个镜像时才拉取。
Lifecycle :
例子:
apiVersion: v1
kind: Pod
metadata:
name: lifecycle-demo
spec:
containers:
- name: lifecycle-demo-container
image: nginx
lifecycle:
postStart:
exec:
command: ["/bin/sh", "-c", "echo Hello from the postStart handler > /usr/share/message"]
preStop:
exec:
command: ["/usr/sbin/nginx","-s","quit"]
postStart:在容器启动后,立刻执行一个指定的操作。在 Docker 容器 ENTRYPOINT 执行之后,但它并不严格保证顺序。也就是说,在 postStart 启动时,ENTRYPOINT 有可能还没有结束。
preStop发生的时机,则是容器被杀死之前,阻塞当前的容器杀死流程,直到操作完成之后,才允许容器被杀死,这跟 postStart 不一样。
2.3.2 pod的基本用法
对docker镜像的要求:必须以一个前台命令作为启动命令。原因如下:
如果镜像启动命令是后台执行程序,那么k8s创建包含该容器的pod之后,命令同时运行完成,就会认为Pod执行结束,会立刻销毁该pod。如果该pod定义了rc,那么k8s会再次创建一个新的Pod,从而进入死循环。
pod中容器定义的一个规则
如果两个容器应用为紧耦合的关系,应该组合成一个整体对外提供服务时,应该将两个容器打包为一个Pod。原因如下:
属于一个pod的多个容器应用之间通过localhost就可以直接通信。
实例:frontend容器需要调用redis容器的服务,那么pod的定义文件fronted-redis-pod.yaml定义如下:
apiVersion: v1
kind: Pod
metadata:
name: redis-php
labels:
name: redis-php
spec:
containers:
- name: fronted
image: php-frontend:localredis
ports:
- containerPort: 80
- name: redis
image: kubeguide/redis-master
ports:
- containerPort: 6379
在fronted对应的容器中,通过localhost:6379可以对同属一个pod的redis容器可以直接访问。
创建pod指令 kubectl create -f fronted-redis-pod.yaml
获取pod kubectl get pods
获取pod描述 kubectl describe pods
2.3.3 静态pod
不能通过apiServer进行管理,仅能通过kubelet管理,且无法与rc、deployment等进行关联。
创建静态pod的两种方式
1)配置文件方式
设置kubelet的启动参数“--config”,指定kubelet需要监控的配置文件的目录,kubelet会定期扫描该目录,根据目录中的.yaml或json文件进行创建操作。
设置配置目录为:--config=/etc/kubelet.d/,并重启kubelet服务。
在/etc/kubelet.d/目录下增加static-web.yml文件,内容如下:
apiVersion: v1
kind: Pod
metadata:
name: static-web
labels:
name: static-web
spec:
containers:
-name: static-web
image: nginx
ports:
-name: web
containerPort: 80
稍等一会,查看已经启动的容器:docker ps
master节点查看pod,kubectl get pods
删除pod(实际因为静态pod无法被apiserver直接管理,所以状态会变为pending,且不会被删除): kubectl delete pod static-web-node1
正取的删除方式:
删除/etc/kubelet.d/目录下的static-web.yml文件即可
2) http方式
设置kubelet的启动参数--manifest-url
2.3.4 pod容器共享Volume
同一个pod中的多个容器能够共享Pod级别的存储卷Volume。即定义一个卷,然后将该卷挂在为多个容器的内部目录。
如下pod实例,pod-volume.yml内容如下:
apiVersion: v1
kind: Pod
metadata:
name: volume-pod
spec:
containers:
- name: tomcat
image: tomcat
ports:
- containerPort: 8080
volumeMounts:
- name: app-logs #使用名称为app-logs的卷
mountPath: /usr/local/tomcat/logs #将卷挂载到该路径,这个路径是容器内的绝对路径
- name: busybox
image: busybox
command: ["sh", "-c", "tail -f /logs/catalinz*.log"]
volumeMounts:
- name: app-logs
mountPath: /logs
volumse: #定义pod中的公共volume
- name: app-logs #volume名称为app-logs
emptyDir: {} #
说明:pod中定义了名称为app-logs的卷,然后应用到多个pod中。
emptyDir:k8s不显式声明宿主机目录的 Volume。所以,Kubernetes 也会在宿主机上创建一个临时目录,这个目录将来就会被绑定挂载到容器所声明的 Volume 目录上。此时并不关心卷定义在宿主机的哪个目录,主要是通过该目录,实现Pod中多个容器对卷的共享。
Kubernetes 也提供了显式的 Volume 定义,它叫作 hostPath。比如下面的这个 YAML 文件:
...
volumes:
- name: nginx-vol
hostPath:
path: " /var/data"
Kubernetes 中,有几种特殊的 Volume:
存在的意义不是为了存放容器里的数据,也不是用来进行容器和宿主机之间的数据交换。这些特殊 Volume 的作用,是为容器提供预先定义好的数据,现在支持四种:
Secret; pod中创建保密信息
ConfigMap; 获取一般配置
Downward API;让 Pod 里的容器能够直接获取到这个 Pod API 对象本身的信息。
ServiceAccountToken。
Secret。它的作用,是帮你把 Pod 想要访问的加密数据,存放到 Etcd 中。然后,你就可以通过在 Pod 的容器里挂载 Volume 的方式,访问到这些 Secret 里保存的信息了。
Secret 最典型的使用场景,莫过于存放数据库的 Credential 信息,比如:
apiVersion: v1
kind: Pod
metadata:
name: test-projected-volume
spec:
containers:
- name: test-secret-volume
image: busybox
args:
- sleep
- "86400"
volumeMounts:
- name: mysql-cred
mountPath: "/projected-volume"
readOnly: true
volumes:
- name: mysql-cred
projected:
sources:
- secret:
name: user
- secret:
name: pass
声明挂载的 Volume,并不是常见的 emptyDir 或者 hostPath 类型,而是 projected 类型。而这个 Volume 的数据来源(sources),则是名为 user 和 pass 的 Secret 对象,分别对应的是数据库的用户名和密码。
Kubernetes 中创建secret对象
方法一:
$ cat ./username.txt
admin
$ cat ./password.txt
c1oudc0w!
$ kubectl create secret generic user --from-file=./username.txt
$ kubectl create secret generic pass --from-file=./password.txt
kubectl get secrets:查看这些 Secret 对象
方法二:yaml方式
apiVersion: v1
kind: Secret
metadata:
name: mysecret
type: Opaque
data:
user: YWRtaW4=
pass: MWYyZDFlMmU2N2Rm
说明:Secret 对象要求这些数据必须是经过 Base64 转码的,如下:
$ echo -n 'admin' | base64
YWRtaW4=
$ echo -n '1f2d1e2e67df' | base64
MWYyZDFlMmU2N2Rm
创建pod
$ kubectl create -f test-projected-volume.yaml
查看secret数据
$ kubectl exec -it test-projected-volume -- /bin/sh
$ ls /projected-volume/
user
pass
$ cat /projected-volume/user
root
$ cat /projected-volume/pass
1f2d1e2e67df
与 Secret 类似的是 ConfigMap,它与 Secret 的区别在于,ConfigMap 保存的是不需要加密的、应用所需的配置信息。
Service Account: Kubernetes 系统内置的一种“服务账户”,它是 Kubernetes 进行权限分配的对象。比如,Service Account A,可以只被允许对 Kubernetes API 进行 GET 操作,而 Service Account B,则可以有 Kubernetes API 的所有操作权限。
ServiceAccountToken。任何运行在 Kubernetes 集群上的应用,都必须使用这个 ServiceAccountToken 里保存的授权信息,也就是 Token,才可以合法地访问 API Server。
2.3.5 pod的配置管理 - ConfigMap
概述
configMap主要将应用所需的配置信息与程序分离,通过环境变量或者外挂文件的方式在创建容器时进行配置的注入。
典型用法:
1)生成为容器内的环境变量
2)设置容器启动命令的启动参数
3)以volume的形式挂载为容器内部的文件或目录
创建configMap资源对象
1)通过yaml配置文件
假设cm-appvars.yaml的内容如下:
apiVersion: v1
kind: ConfigMap
metadata:
name: cm-appvars
data:
apploglevel: info
appdatadir: /var/data
创建configMap: kubectl create -f cm-appvars.yaml
查看: kubectl get configmap
获取详情: kubectl describe configmap cm-appvars
2) 直接通过kubectl命令方式创建 -- 不推荐,难以维护和复用
在pod中使用configMap
1)通过环境白能量方式适用configMap
以使用前面定义的cm-appvars为例。在POd中,将ConifgMap中的内容以环境变量设置为容器内部的环境变量。pod定义cm-test-pod.yml如下:
apiVersion: v1
kind: Pod
metadata:
name: cm-test-pod
spec:
containers:
- name: cm-test
image: busybox
command: ["/bin/sh", "-c", "env | grep APP"]
env:
- name: APPLOGLEVEL #定义环境变量的名称
valueFrom: #定义环境变量的值
configMapKeyRef:
name: cm-appvare #环境变量取自名为cm-appvare的configMap
key: apploglevel #key为configMap中的apploglevel
kubectl create -f cm-test-pod.yml #创建pod
kubectl get pods --show-all
kubectl logs cm-test-pod 获取pod日志
2)通过volumeMount使用configMap
使用configMap的限制条件 先定义configMap后再Pod中使用,configmap受namspace限制,只有处于相同namespace的pod可以使用他
2.3.6 容器内获取Pod信息 (downward API)
pod成功创建之后,会被分配唯一的名字、ip、和特定的namespace,可以通过downward api在容器内使用这些信息。两种方式:
1)环境变量,用于的单个变量,将Pod信息和container信息注入容器内部
下面是通过downward API将Pod的IP\名称和所在namespace注入容器的环境变量中,容器应用使用env命令将全部环境变量大隐刀标准输出:
dapi-test-pod.yml
apiVersion: v1
kind: Pod
metadata:
name: dapi-test-pod
spec:
containers:
- name: test-container
image: busybox
command: ["/bin/sh", "-c", "env | grep APP"]
env:
- name: MY_POD_NAME
valueFrom: #Downward APi的语法
fieldRef:
fieldPath: metadata.name #Pod的名称
-name: MY_POD_NAMESPACE
valueFrom:
fieldRef:
fieldPath: metadata.namespace
-name: MY_POD_IP
valueFrom:
fieldRef:
fieldPath: status.podIP
创建Pod:kubectl create -f dapi-test-pod.yml
查看日志:kubectl logs dapi-test-pod
上面metadata.name metadata.namespace status.podIP是downward api提供的语法,分别对应到是pod名称、命名空间、ip
将容器资源注入容器,如
env:
- name: MY_CPU_REQUEST
valueFrom:
resourceFieldRef:
containerName: test-container
resource: requests.cpu #容器的cpu请求值
requests.cpu:容器的CPU请求值
limits.cpu:容器的CPU限制值
requests.memory:容器的内存请求值
limits.memory:容器内存限制值
2)Volume挂载,将数组类信息生成文件,挂载到容器内部
downward api的价值:
集群中节点需要将自身的标识及绑定IP地址等信息事先写入配置文件中,然后在进程启动的时候读取这些信息。
2.3.7 pod生命周期和重启策略
pod的生命周期(本质以容器的状态来决定)
pending:YAML 文件已经提交给了 Kubernetes,API 对象已经被创建并保存在 Etcd 当中。但是,这个 Pod 里有些容器因为某种原因而不能被顺利创建。比如,调度不成功。
running:Pod 已经调度成功,跟一个具体的节点绑定。它包含的容器都已经创建成功,并且至少有一个正在运行中。
succeeded:Pod 里的所有容器都正常运行完毕,并且已经退出了。这种情况在运行一次性任务时最为常见。
failed:Pod 里至少有一个容器以不正常的状态(非 0 的返回码)退出。意味着你得想办法 Debug 这个容器的应用,比如查看 Pod 的 Events 和日志。
unknown: Pod 的状态不能持续地被 kubelet 汇报给 kube-apiserver,这很有可能是主从节点(Master 和 Kubelet)间的通信出现了问题。
pod的重启策略,本质上也是应用于pod内的所有容器,pod的重启策略包括:
always:容器失效时,kubelet自动重启该容器
onFailure:容器非成功退出时,kubelet会重启该容器
never:kubelet从不重启容器
status可以再细分出一组conditions,值包括:PodScheduled、Ready、Initialized,以及 Unschedulable。
Pod 当前的 Status 是 Pending,对应的 Condition 是 Unschedulable,这就意味着它的调度出现了问题。
而其中,Ready 这个细分状态非常值得我们关注:它意味着 Pod 不仅已经正常启动(Running 状态),而且已经可以对外提供服务了。
当前可用于管理Pod的控制器包括ReplicationController、Job、DaemonSe,另外静态Pod有kubelet直接管理。
每种控制器对Pod的重启策略如下:
RC和DaemonSet:必须设置为always,需要保证该容器持续运行
Job:OnFailure或Never,保证容器执行完成后不再重启
kubelet:Pod失效时自动重启,不论将Restartpolicy设置为什么值,均不会对Pod进行健康检查
2.3.8 pod健康检查
对Pod的健康检查有两类探针:livenessProbe 和 ReadinessProbe
LivenessProbe探针:判断容器是否存活(running状态),如果探测到容器不健康,kubelet将杀掉该容器;并根据重启策略进行处理;如果不存在livenessProbe,那么按照success
ReadinessProbe探针:判断容器是否启动完成(ready状态)。如果检测到失败,则修改pod状态,endpointController将从service的Endpoint中删除容器所在Pod的endPoint。
Kubernetes 的 Pod 中,还有一个叫 readinessProbe 的字段。虽然它的用法与 livenessProbe 类似,但作用却大不一样。readinessProbe 检查结果的成功与否,决定的这个 Pod 是不是能被通过 Service 的方式访问到
pod定义如下:
apiVersion: v1
kind: Pod
metadata:
labels:
test: liveness
name: test-liveness-exec
spec:
containers:
- name: liveness
image: busybox
args:
- /bin/sh
- -c
- touch /tmp/healthy; sleep 30; rm -rf /tmp/healthy; sleep 600
livenessProbe:
exec:
command:
- cat
- /tmp/healthy
initialDelaySeconds: 5
periodSeconds: 5
说明:容器主要住了两件事:
第一件事,就是在 /tmp 目录下创建了一个 healthy 文件,以此作为自己已经正常运行的标志。而 30 s 过后,它会把这个文件删除掉。
二件事:livenessProbe(健康检查)。它的类型是 exec,这意味着,它会在容器启动后,在容器里面执行一条我们指定的命令,比如:“cat /tmp/healthy”。这时,如果这个文件存在,这条命令的返回值就是 0,Pod 就会认为这个容器不仅已经启动,而且是健康的。这个健康检查,在容器启动 5 s 后开始执行(initialDelaySeconds: 5),每 5 s 执行一次(periodSeconds: 5)。
$ kubectl create -f test-liveness-exec.yaml
$ kubectl get pod
NAME READY STATUS RESTARTS AGE
test-liveness-exec 1/1 Running 0 10s
30 s 之后,我们再查看一下 Pod 的 Events:
$ kubectl describe pod test-liveness-exec
Pod 在 Events 报告了一个异常:
FirstSeen LastSeen Count From SubobjectPath Type Reason Message
--------- -------- ----- ---- ------------- -------- ------ -------
2s 2s 1 {kubelet worker0} spec.containers{liveness} Warning Unhealthy Liveness probe failed: cat: can't open '/tmp/healthy': No such file or directory
再次查看一下这个 Pod 的状态:
$ kubectl get pod test-liveness-exec
NAME READY STATUS RESTARTS AGE
liveness-exec 1/1 Running 1 1m
RESTARTS 字段从 0 到 1 的变化,就明白原因了:这个异常的容器已经被 Kubernetes 重启了。
Pod 恢复机制,也叫 restartPolicy。它是 Pod 的 Spec 部分的一个标准字段(pod.spec.restartPolicy),默认值是 Always,即:任何时候这个容器发生了异常,它一定会被重新创建。
但一定要强调的是,Pod 的恢复过程,永远都是发生在当前节点上,而不会跑到别的节点上去。事实上,一旦一个 Pod 与一个节点(Node)绑定,除非这个绑定发生了变化(pod.spec.node 字段被修改),否则它永远都不会离开这个节点。
这也就意味着,如果这个宿主机宕机了,这个 Pod 也不会主动迁移到其他节点上去。而如果你想让 Pod 出现在其他的可用节点上,就必须使用 Deployment 这样的“控制器”来管理 Pod,哪怕你只需要一个 Pod 副本。
restartPolicy,改变 Pod 的恢复策略。除了 Always,它还有 OnFailure 和 Never 两种情况:
Always:在任何情况下,只要容器不在运行状态,就自动重启容器;
OnFailure: 只在容器 异常时才自动重启容器;
Never: 从来不重启容器。
livenessProbe 也可以定义为发起 HTTP 或者 TCP 请求的方式,定义格式如下:
...
livenessProbe:
httpGet:
path: /healthz
port: 8080
httpHeaders:
- name: X-Custom-Header
value: Awesome
initialDelaySeconds: 3
periodSeconds: 3
或
...
livenessProbe:
tcpSocket:
port: 8080
initialDelaySeconds: 15
periodSeconds: 20
三种方式均需要如下参数:
initialDelaySeconds:启动容器后进行首次健康检查的等待时间,单位s
timeOutSeconds:健康检查发送请求后等待响应的超时时间,单位为s。当超时发生时,kubelet会任务容器已经服务提供服务,江湖重启该容器。
Kubernetes 能不能自动给 Pod 填充某些字段呢?
PodPresent
开发人员定义的pod.yaml
apiVersion: v1
kind: Pod
metadata:
name: website
labels:
app: website
role: frontend
spec:
containers:
- name: website
image: nginx
ports:
- containerPort: 80
PodPreset 对象。在这个对象中,凡是他想在开发人员编写的 Pod 里追加的字段,都可以预先定义好。比如这个 preset.yaml:
apiVersion: settings.k8s.io/v1alpha1
kind: PodPreset
metadata:
name: allow-database
spec:
selector:
matchLabels:
role: frontend
env:
- name: DB_PORT
value: "6379"
volumeMounts:
- mountPath: /cache
name: cache-volume
volumes:
- name: cache-volume
emptyDir: {}
追加的定义仅作用与selector 所定义的、带有“role: frontend”标签的 Pod 对象,这就可以防止“误伤”。
假定运维人员先创建了这个 PodPreset,然后开发人员才创建 Pod:
$ kubectl create -f preset.yaml
$ kubectl create -f pod.yaml
Pod 运行起来之后,我们查看一下这个 Pod 的 API 对象:
$ kubectl get pod website -o yaml
apiVersion: v1
kind: Pod
metadata:
name: website
labels:
app: website
role: frontend
annotations:
podpreset.admission.kubernetes.io/podpreset-allow-database: "resource version"
spec:
containers:
- name: website
image: nginx
volumeMounts:
- mountPath: /cache
name: cache-volume
ports:
- containerPort: 80
env:
- name: DB_PORT
value: "6379"
volumes:
- name: cache-volume
emptyDir: {}
说明:PodPreset 里定义的内容,只会在 Pod API 对象被创建之前追加在这个对象本身上,而不会影响任何 Pod 的控制器的定义。比如,我们现在提交的是一个 nginx-deployment,那么这个 Deployment 对象本身是永远不会被 PodPreset 改变的,被修改的只是这个 Deployment 创建出来的所有 Pod
2.3.10 InitContainer 初始化容器
引入initCOntainer的原因,应用启动之前可能需要进行如下的初始化操作,如:
1)等待其他关联组件正取运行
2)基于环境变量或配置模板生成配置文件
3)从远程数据库获取本地所需配置,或者将自身注册到某个中央数据库。
所以k8s引入了初始化容器,用于在启动应用陈程序之前启动一个或多个”初始化“容器。Init container与应用容器本质上是一样的,但其是仅运行一次就结束的任务,并且在成功执行完成后,系统才能继续执行下一个容器。
下面是一个实例:
dapi-test-pod.yml
apiVersion: v1
kind: Pod
metadata:
name: nginx
spec:
initContainers: #初始化容器,负责容器的初始化
- name: install
image:busybox
command:
- wget
- "O"
- "/work-dir/index.html"
- http://kubenetes.io
volumeMounts:
- name: workdir
mountPath: "/work-dir"
containers:
- name: nginx
image: busybox
ports:
- containerPort: 80
volumeMounts:
- name: workdir
mountPath: /usr/share/nginx/html
volumes:
- name: workdir
emptyDir: {}
init container与应用容器的区别:
1)先于应用容器运行,如果有多个init container,则逐个运行。当所有的initContainer都成功运行后,k8s才会初始化pod的各种信息,并开始创建和运行应用容器
2)initContainer中也可以设置资源限制、volume的使用和安全策略2.3.11 Pod的升级和回滚
问题:
pod是怎么与service关联的?
pod的rc文件中:
labels:
app: mysql
service定义文件中:
spec:
ports:
- port: 3306 #Service提供服务的端口号
selector: #Service对应的Pod拥有这里定义的标签
app: mysql
附:k8s pod定义详解