第八课 Kubernetes生产级实践-k8s服务调度编排和Pod深入讲解
tags:
- k8s
- 慕课网
categories:
- 健康检查
- 调度器
- 污点容忍
- 部署策略
- Pod深入
- 投射数据卷
- Secret
- ConfigMap
- DownwardAPI
第一节 健康检查
1.1 健康检查-通过Command方式
- 在没有健康检查时默认:入口程序不退出那么k8s就认为是正常的。容器中pid为1的程序或者
ENTRYPOINT指定的程序就是入口程序。显然这种健康方式过于简单。 - 例子, 健康检查时容器级别的:
- initialDelaySeconds: 等待容器启动10秒之后再执行健康检查的命令
- periodSeconds: 每隔10秒检查一次
- failureThreshold: 失败的门槛,失败2次我认为它失败了 进行容器重启
- successThreshold: 从错误到正确只需要通过1次
- timeoutSeconds: 每次执行健康检查命令时 最长等待时间5秒
spec:
containers:
- name: web-demo
image: 192.168.242.130/k8s/web:v1
ports:
- containerPort: 8080
livenessProbe:
exec:
command:
- /bin/sh
- -c
- ps -ef|grep java|grep -v grep
initialDelaySeconds: 10
periodSeconds: 10
failureThreshold: 2
successThreshold: 1
timeoutSeconds: 5
- 测试一下。
kubectl apply -f web-dev-cmd.yaml
kubectl get pods -n dev
kubectl exec -it -n dev bash
kubectl exec -it web-demo-79cf5b99c8-gx2s7 -n dev bash
# 查看进程 手动健康检查命令
ps -ef
ps -ef|grep java|grep -v grep
# 查看命令结果
echo $?
# 容器内强制杀掉进程tail -f /usr/local/tomcat/logs/catalina.out
kill -9 13
# 发现容器重启了一次
kubectl get pods -n dev
1.2 健康检查-通过HTTP方式
- path: 应用要访问的路径
- port: 容器本身的真实端口
- 返回200正常,其他返回失败
spec:
containers:
- name: web-demo
image: 192.168.242.130/k8s/web:v1
ports:
- containerPort: 8080
livenessProbe:
httpGet:
path: /examples/index.html
port: 8080
scheme: HTTP
initialDelaySeconds: 5
periodSeconds: 5
failureThreshold: 1
successThreshold: 1
timeoutSeconds: 5
- 测试一下
kubectl get pods -n dev
# 发现pod在不断的重启 重启原因是http检测超时
kubectl describe pod web-demo-577b5d88f6-x5fts -n dev
# 上面initialDelaySeconds:10 时间设置长一点呀 要不容器没启动你就检查 能检查到吗
# failureThreshold也要多给两次机会
1.3 健康检查-通过TCP方式
- 补充就绪探针, 例子:
spec:
containers:
- name: web-demo
image: 192.168.242.130/web:v1
ports:
- containerPort: 8080
livenessProbe:
tcpSocket:
port: 8080
initialDelaySeconds: 20
periodSeconds: 10
failureThreshold: 2
successThreshold: 1
timeoutSeconds: 5
readinessProbe:
tcpSocket:
port: 8080
initialDelaySeconds: 20
periodSeconds: 10
failureThreshold: 2
successThreshold: 1
timeoutSeconds: 5
- 测试一下。
kubectl create -f web-dev-tcp.yaml
kubectl get pods -n dev -o wide
# readinessProbe 决定下面的available字段 可以提供服务
kubectl get deploy -n dev -o wide
- 这里有一些工作中的策略。
- 如果restart频率比较低,几天几十天一次。不让它重启,保留上下文去解决问题。
- 如果是稳定复现的问题。去掉livenessProbe,让它一直成功。进入环境当成沙箱环境,先排查问题。
第二节 POD的调度策略
2.1 调度流程
- ApiServer向ETCD数据中心交互。优先级队列用于存储等待调度的优先级列表。Informer用来监听ApiServer的变化。
# nodeName这个字段,在pod刚刚创建时是不存在的只有在调度器调度后,它才会把字段加上去。
kubectl get pod web-demo-79cd8f9f4-bqp72 -n dev -o yaml
- 如果每次调度pod就去ApiServer请求信息,性能必然会差。k8s设计了一个Cache, 它从ApiServer取信息保存起来。
- **预选策略,**初步过滤掉不符合的节点。剩余内存,cpu,端口,volume类型,nodeSeletor必须匹配。
- 优选策略,上一步筛选的node进行评分。
- 然后pod和Node进行绑定,把绑定信息告诉ApiServer。然后更新pod的那个字段nodeName。
- 然后指派那个节点的kubelet把服务调度起来
2.2 Scheduler节点调度的配置
- affinity亲和性。nodeAffinity,节点亲和性
- requiredDuringSchedulingIgnoredDuringExecution:必须满足下面条件才能调度
- nodeSelectorTerms: 节点的选择策略。多个nodeSelectorTerms 是或的关系
- matchExpressions:如果有多个,是并且的关系
- key: 节点的label名字
- preferredDuringSchedulingIgnoredDuringExecution: 最好选择那些节点
- weight: 1 最好的权重
affinity:
nodeAffinity:
requiredDuringSchedulingIgnoredDuringExecution:
nodeSelectorTerms:
- matchExpressions:
- key: beta.kubernetes.io/arch
operator: In
values:
- amd64
preferredDuringSchedulingIgnoredDuringExecution:
- weight: 1
preference:
matchExpressions:
- key: disktype
operator: NotIn
values:
- ssd
- 运行测试
kubectl apply -f web-dev-node.yaml
kubectl get pods -n dev -o wide
# 因为最好不在ssd标签的节点上 所以选择s1
kubectl get nodes --show-labels
2.3 pod调度的配置
affinity:
podAffinity:
requiredDuringSchedulingIgnoredDuringExecution:
- labelSelector:
matchExpressions:
- key: app
operator: In
values:
- web-demo
topologyKey: kubernetes.io/hostname
preferredDuringSchedulingIgnoredDuringExecution:
- weight: 100
podAffinityTerm:
labelSelector:
matchExpressions:
- key: app
operator: In
values:
- web-demo-node
topologyKey: kubernetes.io/hostname
- pod的亲和性。 podAffinity, pod想和那些pod运行在一起,不想和某些pod运行在一起
- 上面例子说:要跟app=web-demo的pod运行在一同一节点
- topologyKey 对应节点上label的名字
- 更好的话:跟app=web-demo-node的pod运行在同一节点
- podAntiAffinity 反亲和性调度
- 运行测试
kubectl apply -f web-dev-pod.yaml
kubectl get pods -n dev -o wide
2.4 污点和污点容忍
- 给节点打上一个污点
- 调度效果:NoSchedule 节点不会把pod调度到这
- 调度效果:PreferNoSchedule 节点最好不把pod调度到这
- 调度效果:NoExcute 除了不调度之外 调度了的也给你干掉
- 如果没有配置污点容忍,pod不会部署到该节点上的。
kubectl taint nodes s1 gpu=true:NoSchedule
# 删除污点
kubectl taint nodes s1 gpu=true:NoSchedule-
- 污点容忍例子:
spec:
containers:
- name: web-demo-taint
image: 192.1687.242.130/k8s/web:v1
ports:
- containerPort: 8080
#第一种表达方式,effect可选值:NoSchedule、NoExecute
tolerations:
- key: "gpu"
operator: "Equal"
value: "true"
effect: "NoSchedule"
#第二种表达方式
#tolerations:
#- key: "key"
# operator: "Exists"
# effect: "NoSchedule"
- tolerations污点容忍,gpu=true。 effect一定要配置上
第三节 部署策略实践
3.1 部署策略
- 滚动跟新:Rolling update, 之前修改yaml后重新apply 就是这种方式
- 重新创建 : Recreate,先停止旧的服务 在启动新服务
- 蓝绿部署: 利用Service的Selector选择不同版本的服务
- 金丝雀部署: 通过Ingress,轮询访问不同的服务。之前刷新一下,变一下就是这个
- 前面两种是k8s支持的不同的重启策略,后面两种是我们利用了Service的特征结合Deployment完成的部署方式。
3.2 部署实践-重新创建
- Recrete 重新创建
strategy:
type: Recreate
- Recrete测试一下
# 使用场景不多 更新服务过程中 服务会断掉的 或许在资源不太充足的场景下 每个节点只能有一个实例时使用
kubectl apply -f web-recreate.yaml
kubectl delete -f web-recreate.yaml
3.3 部署实践-滚动部署
- 滚动部署 Rolling update
- maxSurge 最大可以超出服务实例数的百分比 比如有四个实例:我们最多可以多启动1个实例
- maxUnavailable 最大不可用的实例数百分比 有四个实例 必须三个实例可用才行
spec:
strategy:
rollingUpdate:
maxSurge: 25%
maxUnavailable: 25%
type: RollingUpdate
- 测试下滚动部署
kubectl apply -f web-rollingupdate.yaml
kubectl get pods -n dev
# 修改镜像从web到spring-web 滚动更新
kubectl apply -f web-rollingupdate.yaml
# 暂停更新
kubectl rollout pause deploy web-rollingupdate -n dev
# 开个窗口一直访问服务 可以观察到新版本到旧版本的过程
while sleep 0.2;do curl "http://web-rollingupdate.mooc.com/hello?name=michael";echo "";done
# 继续更新
kubectl rollout resume deploy web-rollingupdate -n dev
# 发现这个版本不适用 回滚到上一个版本 undo 就可以
kubectl rollout undo deploy web-rollingupdate -n dev
3.4 部署实践-蓝绿部署
template:
metadata:
labels:
app: web-bluegreen
version: v1.0
- 这里多了个version字段。通过这个字段来控制版本。
- 蓝绿部署的过程。
# 1. 第一步 部署1.0版本的应用
kubectl apply -f web-bluegreen.yaml
kubectl apply -f bluegreen-service.yaml
# 2. 第二部 部署2.0版本的web-bluegreen.yaml
# 这里修改镜像springboot-web变为web version: v2.0 name: web-bluegreen-v2 这三个地方
kubectl apply -f web-bluegreen.yaml
# 3. 第三部 pod都运行起来后 只需修改bluegreen-service.yaml切换流量.把1.0改成2.0
# 效果没有过渡的痕迹,直接切换流量。使用于新版本线上测试完成后删除旧版本
kubectl apply -f bluegreen-service.yaml
while sleep 0.2;do curl "http://web-bluegreen.mooc.com/hello?name=michael";echo "";done
3.5 部署实践-金丝雀部署
- 在蓝绿部署的继承上简单修改Selector就变成了金丝雀部署。
- 只要把bluegreen-service.yaml中的version字段去掉即可。
- 所有的pod都会被轮训交替访问。同时访问两个或者多个版本。
- 比如我新开发个功能,但是我不确定是否好用,可以给一些pod让别人尝试。也是人常说的AB测试。
第四节 Pod的深入学习
4.1 Pod的设计
- Pod是k8s最小的调度单位。
- 本质上还是容器的隔离,pod是一个逻辑的概念,物理机上没有一个真实的东西叫pod。pod的本质是共享了network,namespace同一个volume。
# docker这种指定方式对容器的启动顺序是有要求的 容器间不是一种对等的关系
docker run --net=xxX --volumes-from=
- Pause容器。不需要我们显式声明每个pod都会有。
4.2 Pod的资源学习
- volume资源挂载。它的定义是pod层面的。把目录/shared-volume-data挂载到容器的/shared-dubbo和/shared-web中。
apiVersion: v1
kind: Pod
metadata:
name: pod-volume
spec:
hostNetwork: true
hostPID: true
hostAliases:
- ip: "192.168.242.130"
hostnames:
- "web.mooc.com"
containers:
- name: web
image: 192.168.242.130/k8s/web:v1
ports:
- containerPort: 8080
volumeMounts:
- name: shared-volume
mountPath: /shared-web
lifecycle:
postStart:
exec:
command: ["/bin/sh", "-c", "echo web starting... >> /var/log/message"]
preStop:
exec:
command: ["/bin/sh", "-c", "echo web stoping... >> /var/log/message && sleep 3"]
- name: dubbo
env:
- name: DUBBO_PORT
value: "20881"
image: 192.168.242.130/k8s/dubbo:v1
ports:
- containerPort: 20881
hostPort: 20881
protocol: TCP
volumeMounts:
- name: shared-volume
mountPath: /shared-dubbo
volumes:
- name: shared-volume
hostPath:
path: /shared-volume-data
- 测试一下 。POD级别参数
- hostAliases
- hostPID
- hostNetwork
- 容器级别参数
- lifecycle
- postStart 注意下 容器执行ENTRYPOINT 命令时 同时执行它 并行执行
- preStop 停止之前做的事 串行执行 它执行完在停止容器。有个超时
- lifecycle
kubectl create -f pod-volume.yaml
kubectl get pods -o wide
# 进入到容器中查看挂载目录 随便创建个文件 touch qnhyn.sh
docker ps | grep volume
docker exec -it 65fdf2ef2b36 bash
# 到挂载目录
docker exec -it 3b886defe598 bash
# 这两个容器的hosts文件一样 它是由pod给我们统一管理的
docker exec -it 65fdf2ef2b36 cat /etc/hosts
docker exec -it 3b886defe598 cat /etc/hosts
# 如何自定义自己的hosts文件 通过如下定义hostAliases
# 是否使用宿主机的网络 hostNetwork: true
# 是否使用宿主机的进程空间: hostPID: true
apiVersion: v1
kind: Pod
metadata:
name: pod-volume
spec:
hostNetwork: true
hostPID: true
hostAliases:
- ip: "192.168.242.130"
hostnames:
- "web.mooc.com"
# pod的字段修改和deployment不一样 很多字段不能直接修改 所以应用时先删除再创建
kubectl delete -f pod-volume.yaml
kubectl create -f pod-volume.yaml
# 测试一下
docker exec -it 4d1995a2f5b0 cat /etc/hosts
docker exec -it 4d1995a2f5b0 sh
ps -ef # 发现很多进程 我们使用了宿主机的进程空间
netstat -ntlp # 发现很多监听 我们使用了宿主机的network namespace
tail -f /var/log/message
- 容器的状态:
- Pendding 还没有被调度的的状态。如果长时间这个状态可能资源不足,镜像没下载完等
- containerCreating 被调度了 等待容器创建
- Running
- Succeeded && Failed
- Ready 通过健康检查
- CrashLoopBackOff 如果没有通过健康检查的话 等待时间过长则一直处于启动失败中
- UnKnown 未知状态 kubelet和apiserver通讯的问题
4.3 Pod的投射数据卷ProjectedVolume介绍
- 投射数据卷ProjectedVolume: 它是轻量级的volume,通过apiServer投射到pod中的。比如:知道pod需要什么文件,在启动时给你扔过来。
- 常用的三种使用方式:
- Secret
- ConfigMap
- DownwardAPI
kubectl get secret
# 查看它的具体定义 发现数据都是base64加密的 type: kubernetes.io/service-account-token
kubectl get secret default-token-bvh9v -o yaml
# k8s实际上会把serviceaccount自动加入到每一个pod中 随便一个pod进行查看
kubectl get pods pod-volume -o yaml
# 里面有volume.secretName: default-token-bvh9v 挂载到/var/run/secrets/kubernetes.io/serviceaccount 文件夹下 可以到容器中查看
4.4 Secret创建和应用
- 创建一个secret Opaque是不透明的意思
apiVersion: v1
kind: Secret
metadata:
name: dbpass
type: Opaque
data:
username: aW1vb2M=
passwd: aW1vb2MxMjM=
# base64加密 aW1vb2M=
echo -n imooc|base64
# 把它写到etcd中
kubectl apply -f secret.yaml
- Pod使用上面的Secret
apiVersion: v1
kind: Pod
metadata:
name: pod-secret
spec:
containers:
- name: springboot-web
image: 192.168.242.130/k8s/springboot-web:v1
ports:
- containerPort: 8080
volumeMounts:
- name: db-secret
mountPath: /db-secret
readOnly: true
volumes:
- name: db-secret
projected:
sources:
- secret:
name: dbpass
- 测试用下。可以动态修改容器中的密码。
kubectl apply -f pod-secret.yaml
kubectl get pod -o wide
# 到执行机上
docker ps | grep secret
docker exec -it 6ac784681b7a sh
cd db-secret/
# 这个用户名密码可以用在数据库
cat username
cat passwd
# 如果这个用户名密码错了 直接修改secret.yaml重新应用即可
# pod上会延迟自动更新
4.5 ConfigMap创建和应用
- 它和secret的应用和部署非常相似,只是它存储的数据不需要加密。比如一些启动参数,参数的配置等等。
- 创建ConfigMap
# 从配置文件game.properties创建ConfigMap
kubectl create configmap web-game --from-file game.properties
# ConfigMap简写cm
kubectl get cm web-game -o yaml
apiVersion: v1
kind: Pod
metadata:
name: pod-game
spec:
containers:
- name: web
image: 192.168.242.130/k8s/springboot-web:v1
ports:
- containerPort: 8080
volumeMounts:
- name: game
mountPath: /etc/config/game
readOnly: true
volumes:
- name: game
configMap:
name: web-game
- 测试使用下
kubectl apply -f pod-game.yaml
# 节点上
docker ps | grep game
docker exec -it 17b6fadf3f4f sh
cd /etc/config/game/
cat -n game.properties
# 试下是否可以直接修改
kubectl edit cm web-game
# 每隔5秒看下配置 发现很快就修改完了
watch -n 5 cat game.properties
- 另外的方式使用。通过yaml定义配置, 通过环境变量的方式使用
apiVersion: v1
kind: ConfigMap
metadata:
name: configs
data:
JAVA_OPTS: -Xms1024m
LOG_LEVEL: DEBUG
- 环境变量方式使用。env环境变量
apiVersion: v1
kind: Pod
metadata:
name: pod-env
spec:
containers:
- name: web
image: 192.168.242.130/k8s/springboot-web:v1
ports:
- containerPort: 8080
env:
- name: LOG_LEVEL_CONFIG
valueFrom:
configMapKeyRef:
name: configs
key: LOG_LEVEL
- 测试使用下
kubectl apply -f pod-env.yaml
# 节点上
docker ps | grep env
docker exec -it d0a2c845503f sh
env | grep LOG
- 还可以把环境变量应用到我们的启动命令中。
apiVersion: v1
kind: Pod
metadata:
name: pod-cmd
spec:
containers:
- name: web
image: 192.168.242.130/k8s/springboot-web:v1
command: ["/bin/sh", "-c", "java -jar /springboot-web.jar -DJAVA_OPTS=$(JAVA_OPTS)"]
ports:
- containerPort: 8080
env:
- name: JAVA_OPTS
valueFrom:
configMapKeyRef:
name: configs
key: JAVA_OPTS
4.6 DownwardAPI创建和应用
- DownwardAPI的作用:是可以让我们在程序中可以取到Pod对象本身的相关信息
apiVersion: v1
kind: Pod
metadata:
name: pod-downwardapi
labels:
app: downwardapi
type: webapp
spec:
containers:
- name: web
image: 192.168.242.130/k8s/springboot-web:v1
ports:
- containerPort: 8080
volumeMounts:
- name: podinfo
mountPath: /etc/podinfo
volumes:
- name: podinfo
projected:
sources:
- downwardAPI:
items:
- path: "labels"
fieldRef:
fieldPath: metadata.labels
- path: "name"
fieldRef:
fieldPath: metadata.name
- path: "namespace"
fieldRef:
fieldPath: metadata.namespace
- path: "cpu-request"
resourceFieldRef:
containerName: web
resource: limits.memory
- 测试使用下
kubectl apply -f pod-downwardapi.yaml
docker ps | grep downward
docker exec -it 6e66ce799088 sh
cd /etc/podinfo && ls
cat -n labels