容器化技术(No.3) -- Kubernetes 进阶(一) Pod 对象与常用控制器
前言
今天的分享希望大家能够:
- 理解和掌握 Pod 对象的使用
- 深入了解和使用常用控制器的使用
回顾
Pod 对象
Pod 概述
Pod 是一个逻辑概念, 也是我们最长打交道的部分之一. 示例如下
...
template:
metadata:
labels:
app: ms-lbs
spec:
containers:
- image: ms-lbs-service:latest
imagePullPolicy: Always
name: lbs
- 最小部署单元
- 一组容器的集合
- 一个Pod中的容器共享网络命名空间
- Pod 是短暂的
- 为亲密性应用而存在
什么是亲密性
> - 两个应用之间发生文件交互
> - 两个应用需要通过127.0.0.1或者socket通信
> - 两个应用需要发生频繁的调用
Pod 详解
刚才说到, Pod 是一组容器的集合, 他是一个逻辑概念, 我们知道容器之间是通过 Namespace 隔离的, Pod要想解决上述应用场景, 那么就要让Pod里的容器之间高效共享.
具体分为两个部分:网络和存储. Kubernetes 是如何实现的呢?
实现策略
-
共享网络
kubernetes 的方案是这样的:会在每个 Pod 里先启动一个infrastructure container
容器pause
(700+KB), 然后让其他的容器连接进来这个网络命名空间, 然后其他容器看到的网络试图就完全一样了, 即网络设备、IP地址、Mac地址等, 这就是解决网络共享问题. 在 Pod 的 IP 地址就是infrastructure container
的 IP 地址.
每次启动 pod 时都会启动一个 pause
容器.
如下示例:
[aaa@qq.com ~]# docker ps|grep flannel
409bec1f9110 ff281650a721 "/opt/bin/flanneld -…" 3 weeks ago Up 3 weeks k8s_kube-flannel_kube-flannel-ds-amd64-wrhfc_kube-system_dc83ec9d-df8d-4757-9dd5-b0a087c2c7b2_9
4d58e801b677 registry.aliyuncs.com/google_containers/pause:3.1 "/pause" 3 weeks ago Up 3 weeks k8s_POD_kube-flannel-ds-amd64-wrhfc_kube-system_dc83ec9d-df8d-4757-9dd5-b0a087c2c7b2_9
-
共享存储
通过挂载共享 volume, 使得大家到共享目录的内容一样. 如下方示例:
apiVersion: v1
kind: Pod
metadata:
name: my-pod
spec:
containers:
- name: write
image: centos
command: ["bash","-c","for i in {1..100};do echo $i >> /data/count;sleep 1;done"]
volumeMounts:
- name: data
mountPath: /data
- name: read
image: centos
command: ["bash","-c","tail -f /data/count"]
volumeMounts:
- name: data
mountPath: /data
volumes:
- name: data
emptyDir: {}
上述示例中有两个容器, write 容器负责提供数据, read 消费数据, 通过数据卷将写入数据的目录和读取数据的目录都放到了该卷中, 这样每个容器都能看到该目录.
验证:
$ kubectl apply -f pod.yaml
$ kubectl logs my-pod -c read -f
- 在 Pod 中容器分为以下几个类型:
- Infrastructure Container:基础容器, 维护整个Pod网络空间, 对用户不可见, 即
pause
容器.
- InitContainers:初始化容器, 先于业务容器开始执行, 一般用于业务容器的初始化工作.
- Containers:业务容器, 具体跑应用程序的镜像, 并行启动.
镜像拉取策略
apiVersion: v1
kind: Pod
metadata:
name: nginx-pod
spec:
containers:
- name: nginx
image: nginx
imagePullPolicy: IfNotPresent
imagePullPolicy 字段有三个可选值:
- IfNotPresent:默认值, 镜像在宿主机上不存在时才拉取
- Always:每次创建 Pod 都会重新拉取一次镜像
- Never: Pod 永远不会主动拉取这个镜像
如果拉取公开的镜像, 直接按照上述示例即可, 但要拉取私有的镜像, 是必须认证镜像仓库才可以, 即 docker login
, 而在 Kubernetes 集群中会有多个 Node, 显然这种方式是很不放方便的!为了解决这个问题, Kubernetes 实现了自动拉取镜像的功能. 以 secret
方式保存到 Kubernetes 中, 然后传给 kubelet.
apiVersion: v1
kind: Pod
metadata:
name: nginx-pod
spec:
imagePullSecrets:
- name: nexus-registrykey
containers:
- name: nginx
image: nginx:latest
imagePullPolicy: IfNotPresent
上述中名为 nexus-registrykey
的 secret 是由 kubectl create secret docker-registry
命令创建:
$ kubectl create secret docker-registry nexus-registrykey --docker-username=admin --docker-password=aaa@qq.com --docker-email=aaa@qq.com --docker-server=192.168.0.13
–docker-server: 指定docke仓库地址
–docker-username: 指定docker仓库账号
–docker-password: 指定docker仓库密码
–docker-email: 指定邮件地址(选填)
资源限制
Pod 资源限制配置分为两类:
- 申请配额: 调度时使用, 参考是否有节点满足该配置
spec.containers[].resources.requests.cpu spec.containers[].resources.requests.memory
- 限制配额: 容器能使用的最大配置
spec.containers[].resources.limits.cpu spec.containers[].resources.limits.memory
示例:
apiVersion: v1
kind: Pod
metadata:
name: nginx
spec:
containers:
- name: nginx
image: nginx
resources:
requests:
memory: "64Mi"
cpu: "250m"
limits:
memory: "128M"
cpu: 0.5
其中cpu值比较抽象, 可以这么理解:
1核 = 1000m
1.5核 = 1500m
那上面限制配置就是1核的二分之一(500m), 即该容器最大使用半核CPU.
该值也可以写成浮点数, 更容易理解:
半核 = 0.5
1核 = 1
1.5核 = 1.5
启用资源限制, 避免某容器资源利用率异常突发影响其他容器, 可能会产生雪崩效应!
重启策略
apiVersion: v1
kind: Pod
metadata:
name: nginx
spec:
containers:
- name: nginx
image: nginx
restartPolicy: Always
restartPolicy 有三种选项:
- Always:默认策略. 当容器终止退出后, 总是重启容器.
- OnFailure:当容器异常退出(退出状态码非0)时, 才重启容器. 适于job
- Never:当容器终止退出, 从不重启容器. 适于job
健康检查
默认情况下, kubelet 根据容器状态作为健康依据(而不是容器中应用程序状态), 因此当出现类似程序假死的情况, 就会导致无法提供服务, 丢失流量的问题. 因此引入健康检查机制确保容器健康存活.
健康检查有两种类型:
- livenessProbe
如果检查失败, 将杀死容器, 根据Pod的restartPolicy来操作.- readinessProbe
如果检查失败, Kubernetes会把Pod从service endpoints中剔除.
这两种类型支持三种检查方法:
- httpGet
发送HTTP请求, 返回200-400范围状态码为成功.- exec
执行Shell命令返回状态码是0为成功.- tcpSocket
发起TCP Socket建立成功.
示例:
apiVersion: v1
kind: Pod
metadata:
labels:
test: liveness
name: liveness-exec
spec:
containers:
- name: liveness
image: busybox
args:
- /bin/sh
- -c
- touch /tmp/healthy; sleep 30; rm -rf /tmp/healthy; sleep 60
livenessProbe:
exec:
command:
- cat
- /tmp/healthy
initialDelaySeconds: 5
periodSeconds: 5
上述示例:启动容器第一件事创建文件, 停止30s, 删除该文件, 再停止60s, 确保容器还在运行中.
验证现象:容器启动正常, 30s后异常, 会restartPolicy策略自动重建, 容器继续正常, 反复现象.
调度策略
创建一个 Pod 的工作流程:
Pod根据调度器默认算法将Pod分配到合适的节点上, 一般是比较空闲的节点. 但有些情况我们希望将Pod分配到指定节点, 这里就需要用到如下调度策略.
- nodeName
- nodeSelector
- taint & tolerations
1. nodeName
nodeName 用于将 Pod 调度到指定的 Node 名称上.
例如:下面示例会绕过调度系统, 直接分配到 k8s-node1 节点.
apiVersion: v1
kind: Pod
metadata:
labels:
run: busybox
name: busybox
namespace: default
spec:
nodeName: k8s-node1
containers:
- image: busybox
name: bs
command:
- "ping"
- "www.baidu.com"
2. nodeSelector
nodeSelector用于将Pod调度到匹配Label的Node上.
先给规划node用途, 然后打标签, 例如将两台node划分给不同团队使用:
$ kubectl label nodes k8s-node1 group=lab
$ kubectl label nodes k8s-node2 group=hive
然后在创建Pod只会被调度到含有 group=lab
标签的节点上.
apiVersion: v1
kind: Pod
metadata:
name: busybox
namespace: default
spec:
nodeSelector:
group: lab
containers:
- image: busybox
name: test-box
command:
- "ping"
- "www.baidu.com"
3. taint(污点)与tolerations(容忍)
污点应用场景:节点独占, 例如具有特殊硬件设备的节点, 如GPU
设置污点命令:
kubectl taint node [node] key=value[effect]
其中[effect] 可取值:
- NoSchedule :一定不能被调度.
- PreferNoSchedule:尽量不要调度.
- NoExecute:不仅不会调度, 还会驱逐Node上已有的Pod.
示例:
先给节点设置污点, 说明这个节点不是谁都可以调度过来的:
$ kubectl taint node k8s-node1 nodelable=gpu:NoSchedule
查看污点:
$ kubectl describe node k8s-node1 |grep Taints
然后在创建Pod只有声明了容忍污点(tolerations), 才允许被调度到 nodelable=gpu 污点节点上.
apiVersion: v1
kind: Pod
metadata:
labels:
run: busybox
name: busybox3
namespace: default
spec:
tolerations:
- key: "nodelable"
operator: "Equal"
value: "gpu"
effect: "NoSchedule"
containers:
- image: busybox
name: busybox
command:
- "ping"
- "www.baidu.com"
如果不配置容忍污点, 则永远不会调度到k8s-node1.
去掉污点:
# kubectl taint node [node] key:[effect]-
$ kubectl taint node k8s-node1 abc:NoSchedule-
故障排查常用命令
# 查看事件, 可用于大部分资源
kubectl describe TYPE/NAME
# 如果pod启动失败, 先查看日志
kubectl logs TYPE/NAME [-c CONTAINER]
# 进入到容器中debug
kubectl exec POD [-c CONTAINER] -- COMMAND [args...]
控制器
Pod 与 controller 的关系
- controllers:在集群上管理和运行容器的对象 (有时也称为工作负载 - workload)
- 通过 label-selector 相关联, 如下图所示.
- Pod 通过控制器实现应用的运维, 如伸缩, 滚动升级等
无状态应用部署控制器 Deployment
Deployment功能:
- 部署无状态应用(无状态应用简单来讲, 就是Pod可以漂移任意节点, 而不用考虑数据和IP变化)
- 管理Pod和ReplicaSet(副本数量管理控制器)
- 具有上线部署、副本设定、滚动升级、回滚等功能
- 提供声明式更新, 例如只更新一个新的Image
应用场景:Web服务, 微服务
如下示例为 Deployment 标准 YAML, 通过标签与 Pod 关联.
# 控制器定义
apiVersion: apps/v1
kind: Deployment
metadata:
name: web
spec:
replicas: 3 # 设置副本数
selector:
matchLabels:
app: web
# 被控制对象
template:
metadata:
labels:
app: web
spec:
containers:
- image: nginx
name: nginx
将这个应用暴露到集群外部访问:
apiVersion: v1
kind: Service
metadata:
labels:
app: web
name: web
spec:
ports:
- port: 80 # 集群内容访问应用端口
protocol: TCP
targetPort: 8080 # 容器镜像端口
nodePort: 30008 # 对外暴露的端口
selector:
app: web
type: NodePort
查看资源:
$ kubectl get pods,svc
NAME READY STATUS RESTARTS AGE
pod/web-7dc5f5b565-cxh9t 1/1 Running 0 9s
pod/web-7dc5f5b565-gl4jz 1/1 Running 0 9s
pod/web-7dc5f5b565-qw6dq 1/1 Running 0 12s
NAME TYPE CLUSTER-IP EXTERNAL-IP PORT(S) AGE
service/kubernetes ClusterIP 10.99.103.5 <none> 443/TCP 1m
service/web NodePort 10.1.147.15 <none> 80:30008/TCP 48s
浏览器输入:http://NodeIP:30008 即可访问到该应用.
# 升级项目, 即更新最新镜像版本, 这里换一个nginx镜像为例:
$ kubectl set image deployment/web nginx=nginx:1.15
$ kubectl rollout status deployment/web # 查看升级状态
# 如果该版本发布失败想回滚到上一个版本可以执行:
$ kubectl rollout undo deployment/web # 回滚最新版本
# 也可以回滚到指定发布记录:
$ kubectl rollout history deployment/web # 查看发布记录
$ kubectl rollout undo deployment/web --revision=2 # 回滚指定版本
# 扩容/缩容:
$ kubectl scale deployment nginx-deployment --replicas=5
# --replicas设置比现在值大就是扩容, 反之就是缩容.
kubectl set image
会触发滚动更新.
滚动更新原理其实很简单, 利用新旧两个replicaset, 例如副本是 3 个, 首先 Scale Up 增加新 RS 副本数量为 1, 准备就绪后, Scale Down 减少旧 RS 副本数量为 2, 以此类推, 逐渐替代, 最终旧 RS 副本数量为 0, 新 RS 副本数量为 3, 完成本次更新. 这个过程可通过 kubectl describe deployment web 看到.
守护进程控制器 DaemonSet
DaemonSet功能:
- 在每一个Node上运行一个Pod
- 新加入的Node也同样会自动运行一个Pod
应用场景:Agent, 例如监控采集工具, 日志采集工具
任务控制器 Job & CronJob
Job: 一次性执行
应用场景: 离线数据处理, 视频解码等业务
apiVersion: batch/v1
kind: Job
metadata:
name: pi
spec:
template:
spec:
containers:
- name: pi
image: perl
command: ["perl", "-Mbignum=bpi", "-wle", "print bpi(2000)"]
restartPolicy: Never # 作业失败后会不再尝试创建新的Pod
backoffLimit: 4 # .spec.backoffLimit字段限制重试次数. 默认情况下, 这个字段默认值是6.
上述示例中将 π 计算到 2000 个位置并将其打印出来. 完成大约需要10秒.
查看任务:
$ kubectl get pods,job
CronJob: 定时任务, 像 Linux 的 Crontab 一样.
应用场景:通知, 备份
apiVersion: batch/v1beta1
kind: CronJob
metadata:
name: hello
spec:
schedule: "*/1 * * * *"
jobTemplate:
spec:
template:
spec:
containers:
- name: hello
image: busybox
args:
- /bin/sh
- -c
- date; echo Hello from the Kubernetes cluster
restartPolicy: OnFailure # 作业失败并返回状态码非0时, 尝试创建新的Pod运行任务
上述示例中将每分钟打印一次 Hello.
查看任务:
$ kubectl get pods,cronjob
总结
Pod 对象:
Pod 网络存储实现策略
镜像拉取策略
资源限制
重启策略
健康检查
调度策略
故障排查常用命令
常用控制器:
无状态应用部署控制器 Deployment
守护进程控制器 DaemonSet
任务控制器 Job & CronJob