k8s常用操作
一、kubectl命令
1.1 基本语法
在 Kubernetes 集群中,一切皆资源。无论是 Pod、Service 还是 Deployment,其定义都作为一条记录存储在 etcd 中。K8s 的核心机制在于“声明式 API”与“控制器模式”:用户声明资源的预期状态,而各种控制器则持续协调,将资源拉回到预期状态。
接下来将逐一解析:
-
资源类型:它是什么?解决了什么业务问题?
-
控制器:是谁在管理它?如何实现自愈与扩缩容?
语法:
kubectl [command] [TYPE] [NAME] [flags]
-
command:子命令,准备做什么操作,例如 create、get、describe、delete 等(增删改查等)。
-
TYPE:资源类型,准备操作什么类型的资源,区分大小写,并且可以简写,例如下述三个命令效果相同:
kubectl get pods xxx
kubectl get pod xxx
kubectl get po xxx
- NAME:具体要操作的资源对象名字,区分大小写,如果不指定具体的NAME,则默认返回全部资源,并且在一条命令里指定多个资源操作,例如:
kubectl get pod xxx yyy zzz
kubectl get pod/xxx deployment/aaa
kubectl get pod,deploy
kubectl apply -f 1.yaml -f 2.yaml
- flags:可选参数
# 例如
kubectl get pods xxx -o wide # 详细显示
kubectl get pods xxx -o yaml
kubectl get pods xxx -w # -w=watch , 动态显示,类似于tail -f
kubectl get pods -o yaml # 导出整张表
- apply 与 create 的区别。
# 只能用于创建新资源,如果资源已存在create -f会报错
kubectl create -f 1.yaml
# 创建或更新,所以推荐使用apply -f
kubectl apply -f 1.yaml
1.2 常用用法
1.2.1 查看
# 1、查看帮助
kubectl --help
kubectl apply --help # 查看二级帮助文档
kubectl api-resources # 查看资源类型
# 2、示例
kubectl get deploy,pods -w # 持续监控
kubectl get deploy,pods -o wide -n 某个名称空间 # -n可以直接跟在kubectl后
kubectl get deploy,pods -o wide -A # -A不能直接跟在kubectl后,要往后放,代表查看所有名称空间
kubectl get deploy 具体的deploy名 -o yaml
kubectl logs -f pod名
kubectl describe node node01
kubectl get deployment
kubectl get pods #获取pod信息
kubectl get pods -o wide #查看pod运行在哪个节点上
kubectl get deploy,pods -o wide # 一起查看
kubectl get pods -o wide -n 名称空间名字 # 查看某个名称空间下的pods
关于describe的结果示例:Controlled By 指明此 Pod 是由 ReplicaSet/xxx 创建。Events 记录了 Pod 的启动过程。如果操作失败(比如 image 不存在),也能在这里查看到原因。
1.2.2 增加
# 1、基于yaml创建
kubectl apply -f 1.yaml
# 2、直接用命令创建,
# 例如:启动临时容器,用于测试,exit退出后则删除
kubectl run -i --tty --image busybox:1.27 yss-test --restart=Never --rm sh
1.2.3 改
# 修改副本
kubectl scale deployment xxx --replicas=0
# 在线编辑
kubectl edit deployment xxx
1.2.4 删
kubectl delete deloyment xxx
# 删除带有标签<label-key>=<label-value>的pod和svc
# 查出标签:kubectl get pods --show-labels
kubectl delete pods,services -l <label-key>=<label-value>
kubectl delete -f 1.yaml
1.2.5 pod交互
kubectl exec -ti pod名字 bash
# 在容器里执行主机命令 nsenter
crictl ps
crictl inspect id | grep -i pid
crictl inspecti
nsenter -t pid -n "主机上的command"
# 指定pod里的容器
kubectl exec -it -c {c2} {pid_name}
kubectl logs -f -c {c2} {pod_name}
1.2.6 拷贝文件
# pod内文件拷贝到本机,必须指定文件名
kubectl cp pod名:/etc/fstab /tmp/a.txt
# 本机拷贝到pod内
kubectl cp /tmp/a.txt pod名:/tmp
1.3 kubectl插件
为了支持拓展kubectl功能,k8s1.8开始引入插件机制,1.14版之后达到稳定,为kubectl定制插件非常简单。
-
创建一个可执行文件,文件名必须以
kubectl-开头,该文件就是kubectl的插件文件名
kubectl-hello,注意必须以kubectl-开头:
#!/bin/bash
echo "hello world"
必须添加可执行权限:
chmod +x kubectl-hello
- 将插件放到$PATH下,例如/usr/local/bin
mv ./kubectl-hello /usr/local/bin
- 然后就可以执行kubectl 来运行自定义插件了
kubectl hello # 插件名需要去掉kubectl-前缀
想删除插件的话直接删就行:
rm -rf /usr/local/bin/kubectl-hello
我们可以将复杂的kubectl命令定制成插件以便使用,例如查看当前上下文环境(context)的用户名:
- 创建文件
kubectl-whoami,将常规命令写入:
#!/bin/bash
kubectl config view --template='{{ range .contexts }}{{ if eq .name "'$(kubectl config current-context)'" }}Current user: {{ printf "%s\n" .context.user }}{{ end }}{{ end }}'
- 添加可执行权限:
chmod +x ./kubectl-whoami
- 拷贝到$PATH的路径下:
cp ./kubectl-whoami /usr/local/bin/
- 执行:
kubectl whoami
- 查看所有插件:
kubectl plugin list
1.4 k8s更新资源原理(了解)
k8s通过 resourceversion(RV)的变化感知是否要更新资源。
1、前置知识
etcd底层是mvcc,每次写操作都会生成一个全局递增的revision。
k8s的每个资源对象都有一个 metadata.resourceVersion,记录的是该对象最后一次写入etcd时对应的etcd revision。
k8s中的各个组件通过apiServer对etcd做watch监听(并非是轮训etcd),首次先list一次拿到全量的对象(包括当前的resourceVersion),然后发起watch长连接,一旦revision变化就通知对应的组件,保证组件能拿到最新的resourceversion。
2、kubelet如何更新资源
kubectl只是一个命令,本身不做watch。kubectl 发出apply时,会先向apiserver发出get请求获取etcd里关于目标对象的全部信息,并存到内存。然后做合并。
- last-applied:上次apply时,k8s存下的旧配置
- live:刚get回来的真对象信息
- new:这次yaml里写的配置
kubectl 将这三个配置合并,并且最终使用live版本中的 resourceversion,发给apiserver。(kubectl本身不会生成rv,就是把get到的rv原封不动带在身上)。
apiserver收到后,传给etcd,etcd判断当前的revision能对应传过来的rv,然后将新的配置写入etcd中,并生成新的revision。如果对应不上,会报错过期。
如果同时有多个客户端提交更新,kubectl 不小心用里刚过期的RV,会自动重试直到拿到最新的revision然后提交成功(控制器也会改变RV)。
- kubectl edit 也是同样的逻辑。
3、控制器协调怎样更新资源对象
跟kubectl一样的地方:控制器也必须带上RV,和kubectl没什么区别。
跟kubectl不一样的地方:
- kubectl是人为触发一次,一般改spec,走三个配置合并
- 控制器是调谐循环自动跑,一般改status,走get--对比--改--update(冲突就重试)。控制器拿到的信息是watch流推送过来的。
控制器会更新RV的场景:
- 1、控制器主动改spec(很少),比如hpa缩容:副本数会变化
- 2、控制器主动改status(多)。
二、创建与删除资源
K8S 的资源有 Pod、Service、Volume、Namespace、ReplicaSet、Deployment、StatefulSet、DaemonSet、Job等等,如下:
| 类别 | 名称 |
|---|---|
| 工作负载型资源对象 | Pod、Replicaset、ReplicationController、Deployments、StatefulSets、Daemonset、Job、CronJob |
| 服务发现及负载均衡 | Service、Ingress |
| 配置与存储 | Volume、Persistent Volume、CSI、configmap、secret |
| 集群资源 | Namespace、Node、Role、ClusterRole、RoleBinding、ClusterRoleBinding |
| 元数据资源 | HPA、PodTemplate、LimitRange |
2.1 K8S创建资源的两种方式
我们通常不会单独拉起一个裸pod。而是会通过各种控制器拉起pod,因为我们要用的就是控制器那优秀的功能!
Kubernetes 支持两种方式创建资源:
(1)用kubectl命令直接创建,在命令行中通过参数指定资源的属性。此方式简单直观,比较适合临时测试或实验使用。
# 该命令只能创建pod,创建了一个名为pod-test的pod资源
[root@master01 ~]# kubectl run pod-test --image=nginx:1.8.1
# 也可以启用临时容器进行测试,exit退出后则删除
[root@master01 ~]# kubectl run alpine --rm -ti --image=alpine -- /bin/sh
[root@master01 ~]# kubectl run net-test --image=alpine sleep 36000
# 若非测试,不推荐使用kubectl run,因为该命令只创建pod,该pod没有被管理,一旦挂掉了就挂掉了
# 我们还是建议使用kubectl create来创建控制器pod,pod会被控制器管理起来,死掉了也可以被控制器重启
kubectl create deployment web --image=nginx:1.14
(2)通过yaml配置文件和kubectl create创建。在配置文件中描述了应用的信息和需要达到的预期状态。
kubectl apply -f nginx-deployment.yaml
kubectl create -f nginx-deployment.yaml
总结创建的过程:
- 用户通过kubectl创建Deployment
- Deployment创建ReplicaSet
- ReplicaSet创建Pod
2.2 yaml文件简介
[root@egon ~]# vim nginx-deployment.yaml #使用yaml的方式进行创建应用
apiVersion: apps/v1 #apiVersion是当前配置格式的版本
kind: Deployment #kind是要创建的资源类型,这里是Deploymnet
metadata: #metadata是该资源的元数据,name是必须的元数据项
name: nginx-deployment
labels:
app: nginx
spec: #spec部分是该Deployment的规则说明
replicas: 3 #relicas指定副本数量,默认为1,可以设置多个以达到负载均衡的效果
selector:
matchLabels:
app: nginx
template: #template定义Pod的模板,这是配置的重要部分
metadata: #metadata定义Pod的元数据,至少要顶一个label,label的key和value可以任意指定
labels:
app: nginx
spec: #spec描述的是Pod的规则,此部分定义pod中每一个容器的属性,name和image是必需的
containers:
- name: nginx
image: nginx:1.18 # 镜像地址
ports:
- containerPort: 80
apply vs create
| 比较项 | apply | create |
|---|---|---|
| 资源创建 | 支持 | 支持 |
| 资源更新 | 支持 | 无 |
| 重复创建 | 支持 | 无 |
其他类似的命令:kubectl replace、kubectl edit 和 kubectl patch
为避免造成不必要的困扰,我们会尽量只使用 kubectl apply。
此命令已经能够应对超过 90% 的场景,事半功倍。
2.3 删除资源
# 方式一
kubectl delete deployment nginx-deployment
# 方式二
kubectl delete -f nginx-deployment.yaml
三、水平扩缩
水平扩缩:deployment 管理 replicaset,再由replicaset去管理pod。所以水平扩缩时,实际上是由deployment生成一个新的replicaset,启动一套新的pod,同时关闭旧的pod,实现平滑扩缩。
3.1 快速生成一个yaml文件
kubectl create deployment web --image=nginx:1.14 --dry-run -o yaml > web.yaml
#新版本
kubectl create deployment web --image=nginx:1.14 --dry-run=client -o yaml > web.yaml
kubectl apply -f web.yaml
可以用 explain 查看 yaml 各个字段详细信息
kubetl explain deployment.spec.template #要看什么往后加就可以
3.2 水平扩缩操作的两种方法
kubectl apply -f web.yaml # 增加web.yaml中的replicas完成扩容
kubectl apply -f web.yaml # 减少web.yaml中的replicas完成缩容
kubectl scale deploy web --replicas=10 # 扩容的偷懒做法
kubectl scale deploy web --replicas=1 # 缩容的偷懒做法
四、节点污点
污点用于标记哪些物理节点不要被调度。
4.1 查看污点
[root@k8s-node-01 ~]# kubectl get nodes
NAME STATUS ROLES AGE VERSION
k8s-master-01 Ready control-plane 6h25m v1.30.3
k8s-node-01 Ready <none> 6h24m v1.30.3
k8s-node-02 Ready <none> 6h24m v1.30.3
kubectl describe node k8s-master-01 | grep Taints
kubectl describe node k8s-node-01 | grep Taints
kubectl describe node k8s-node-02 | grep Taints
污点的类型:
- NoSchedule:一定不被调度,但不驱逐。
- PreferNoSchedule:尽量不被调度(也有被调度的几率)
- NoExecute:不会调度,并且还会驱逐Node已有Pod
4.2 添加污点与去除污点
# 打污点
kubectl taint nodes 10.1.1.104 node-role.kubernetes.io/control-plane:NoSchedule
# 去掉污点
kubectl taint nodes 10.1.1.104 node-role.kubernetes.io/control-plane:NoSchedule-
kubectl taint nodes 10.1.1.104 node-role.kubernetes.io/master:NoExecute
kubectl taint nodes 10.1.1.104 node-role.kubernetes.io/master:NoExecute-
五、pod调度策略
调度策略决定 pod 应该调度到哪些物理节点上,需要注意,污点比调度策略的优先级更高。
5.1 根据资源需求调度
Pod声明的requests和limits,前者表示Pod需要多少资源,后者表示Pod最多用多少资源,资源比如CPU内存等。修改web.yaml文件的resources: {}配置项:
resources:
requests:
memory: "3Gi" # 声明需要3G内存
limits:
memory: "4Gi" # 声明最大4G内存
[root@master01 ~]# kubectl apply -f web.yaml
查看Pod,发现Pod一直在挂起状态中。
这是为什么?因为我声明的需要3G内存,而我的虚拟机最多就2G内存,所以资源不满足,影响了Pod调度。
5.2 节点标签选择器
节点标签选择器,会根据标签选择符合的节点进行调度,参数:nodename。
为node02上打标签,就是键值对,随便起名字:
kubectl label node 10.1.1.104 xxx=yyy
然后在web.yaml中新增nodeSelector声明:
[root@master01 ~]# cat web.yaml
apiVersion: apps/v1
kind: Deployment
metadata:
creationTimestamp: null
labels:
app: web
name: web
spec:
replicas: 1
selector:
matchLabels:
app: web
strategy: {}
template:
metadata:
creationTimestamp: null
labels:
app: web
spec:
containers:
- image: nginx:1.14
name: nginx
nodeSelector:
xxx: yyy # -------》注意缩进,xxx:后有一个空格
status: {}
5.3 节点亲和性
亲和性和节点选择器类似,只不过多了操作符表达式:In、NotIn、Exists、Gt、Lt、DoesNotExists。
kubectl label node 10.1.1.104 xxx=yyy
kubectl label node 10.1.1.105 xxx=zzz
[root@master01 ~]# cat web.yaml
apiVersion: apps/v1
kind: Deployment
metadata:
creationTimestamp: null
labels:
app: web
name: web
spec:
replicas: 10
selector:
matchLabels:
app: web
strategy: {}
template:
metadata:
creationTimestamp: null
labels:
app: web
spec:
containers:
- image: nginx:1.14
name: nginx
affinity:
nodeAffinity:
requiredDuringSchedulingIgnoredDuringExecution: # 硬策略:必须满足,否则一直pending
nodeSelectorTerms:
- matchExpressions:
- key: xxx
operator: In
values:
- yyy
- zzz
preferredDuringSchedulingIgnoredDuringExecution: # 软策略:尽量满足,没有任何一个节点满足,还是会调度到某个节点上
- weight: 1
preference:
matchExpressions:
- key: com
operator: In
values:
- wolegequ
status: {}
其他示范:
apiVersion: apps/v1
kind: Deployment
metadata:
name: node-affinity
labels:
app: node-affinity
spec:
replicas: 8
selector:
matchLabels:
app: node-affinity
template:
metadata:
labels:
app: node-affinity
spec:
containers:
- name: nginx
image: nginx:1.7.9
ports:
- containerPort: 80
name: nginxweb
affinity:
nodeAffinity:
requiredDuringSchedulingIgnoredDuringExecution: # 硬策略
nodeSelectorTerms:
- matchExpressions:
- key: kubernetes.io/hostname
operator: NotIn
values:
- xxx-node3
preferredDuringSchedulingIgnoredDuringExecution: # 软策略
- weight: 1
preference:
matchExpressions:
- key: com
operator: In
values:
- yyy-zzz-mmm
上面的亲和性表示如下含义:
requiredDuringSchedulingIgnoredDuringExecution:硬亲和,test123_env等于dev或者test,必须满足preferredDuringSchedulingIgnoredDuringExecution:软亲和,group等于ttttest,非必须满足
K8S搞这么多策略有啥用呢?又是节点污点、节点标签、Pod调度策略之类的,目的当然是提供最大的灵活性,最终提高整体资源利用率,这就是自动装箱。
此外强调: preferredDuringSchedulingIgnoredDuringExecution和requiredDuringSchedulingIgnoredDuringExecution名字中后半段字符串IgnoredDuringExecution表示的是,在Pod资源基于节点亲和性规则调度到某个节点之后,如果节点的标签发生了改变,调度器不会将Pod对象从该节点上移除,因为该规则仅对新建或更新Pod有效。
六、Secret和配置管理
部署和更新 Secrets 和应用程序的配置而不必重新构建容器镜像,且不必将软件堆栈配置中的秘密信息暴露出来。
Secret意为秘密,那在K8S中是啥意思呢?在K8S中表示一个存储在etcd中的配置,这个配置是秘密的,是安全的,通常用Base64编码,此配置可以通过挂载卷或者环境变量的方式供Pod访问。首先定义一个Secret。
首先将明文转换成base64编码:
# 首先将明文转换成base64编码
echo -n 'yss' | base64 # 结果是ZWdvbg==
echo -n '123456' | base64 # 结果是MTIzNDU2 可以解开:echo MTIzNDU2 | base64 -d
通过下面的secret.yaml声明创建一个Secret:
apiVersion: v1
kind: Secret
metadata:
name: test-secret
data:
username: ZWdvbg==
password: MTIzNDU2
kubectl apply -f secret.yaml
kubectl get secret
kubectl get secret test-secret
6.1 挂载卷的方式
指定alpine镜像,注意用args执行sleep 36000秒,否则容器立即结束,写入yaml文件后,注释的信息记得删除掉,检查一下缩进:
[root@master01 ~]# cat test.yaml
apiVersion: apps/v1
kind: Deployment
metadata:
creationTimestamp: null
labels:
app: test
name: test
spec:
replicas: 3
selector:
matchLabels:
app: test
strategy: {}
template:
metadata:
creationTimestamp: null
labels:
app: test
spec:
containers:
- image: alpine
name: alpine
args: ["sleep","36000"]
# 挂载到容器内
volumeMounts:
- name: xxx
mountPath: /etc/secret-volume
# 卷声明
volumes:
- name: xxx
secret:
secretName: test-secret
status: {}
验证:
kubectl apply -f test.yaml
# 然后跑到相应的节点
docker container exec -ti 9023b9cfa886 sh
# 查看
cat /etc/secret-volume/username # 显示egon
cat /etc/secret-volume/password # 显示123456
6.2 环境变量的方式
[root@master01 ~]# cat test.yaml
apiVersion: apps/v1
kind: Deployment
metadata:
creationTimestamp: null
labels:
app: test
name: test
spec:
replicas: 3
selector:
matchLabels:
app: test
strategy: {}
template:
metadata:
creationTimestamp: null
labels:
app: test
spec:
containers:
- image: alpine
name: alpine
args: ["sleep","36000"]
env:
- name: BIG_SB
valueFrom:
secretKeyRef:
name: test-secret
key: username
- name: BIG_NB
valueFrom:
secretKeyRef:
name: test-secret
key: password
status: {}
验证:
kubectl apply -f test.yaml
# 然后跑到相应的节点
[root@node02 ~]# docker container exec -ti 852bce7cbdad sh
/ #
echo $BIG_SB
egon
/ # echo $BIG_NB
123456
七、ConfigMap
ConfigMap可以看做是不需要加密,不需要安全属性的Secret,也是和配置相关的。创建ConfigMap的过程如下,首先创建一个配置文件。
文件名:redis.conf,包含如下内容:
redis.port=127.0.0.1
redis.port=6379
redis.password=123456
以下命令从文件redis.properties创建了一个名为redis-config的ConfigMap:
kubectl create configmap redis-config --from-file=redis.conf
[root@k8s-master-01 ~]# kubectl get cm redis-config -o yaml
apiVersion: v1
data:
redis.conf: | # -------------------> 这个名字很重要,对应的就是你pod内的挂载点
redis.port=127.0.0.1
redis.port=6379
redis.password=123456
kind: ConfigMap
...........
使用命令kubectl get configmap可以查看刚才创建的ConfigMap,当然ConfigMap也有挂载卷和设置环境变量的方式供Pod调用,与secret的用法类似。
apiVersion: v1
kind: Pod
metadata:
name: app-pod
spec:
containers:
- name: app-container
image: your-image
volumeMounts:
- name: config-volume
mountPath: /etc/redis.conf # 这个redis.conf就对应你在cm中定义的文件名
subPath: redis.conf # 必须指定subPath,否则会被挂载成一个目录
volumes:
- name: config-volume
configMap:
name: reids-config # 这个就是你创建的cm名字
用env的形式加载配置也可以,不啰嗦了,详细可以看https://kubernetes.io/zh-cn/docs/tasks/configure-pod-container/configure-pod-configmap/
八、存储编排
容器的存储基于overlay2存储驱动,通过联合挂载将多层目录合并为一个统一的文件视图。
- lowerdir:只读底层目录,对应为镜像层,不可修改。
- upperdir:可写层,对应为容器运行时可写层,仅当前容器可见。
但upperdir中的数据是短暂的,为了持久化存储,k8s提供了存储编排方案。
k8s的自动挂载可以选择的存储系统:包括本地存储、诸如 GCP 或 AWS 之类公有云提供商所提供的存储或者诸如 NFS、iSCSI、Gluster、Ceph、Cinder 或 Flocker 这类网络存储系统。
提到存储就不得不说K8S中的PV和PVC了,解释如下
- PV:PersistentVolume,持久化卷,用于声明底层存储类型,关联的是底层存储,例如存储路径
- PVC:PersistentVolumeClaim,持久化卷声明,用于上层pod定制自己的对存储的需求参数,例如大小(不关心底层存储是什么)
PV说白了就是一层存储的抽象,底层的存储可以是本地磁盘,也可以是网络磁盘比如NFS、Ceph之类。既然有了PV那为什么又要搞一个PVC呢?
PVC其实在Pod和PV之前又增加了一层抽象,这样做的目的在于将Pod的存储行为于具体的存储设备解耦,好处是:如果哪天我们变更底层存储(例如更换路径、IP、或者换成其他存储),只需要变更PV即可,POD对存储的需求参数都放在PVC里根本不需要变动,如此,便十分灵活。
8.1 本地存储hostPath
本地存储即对应K8S中的hostPath。
8.1.1 创建PV
首先声明一个PV.yaml,内容如下,声明的本地存储路径为/data/hostpath:
apiVersion: v1
kind: PersistentVolume
metadata:
name: my-pv
labels:
type: local
spec:
storageClassName: manual
capacity:
storage: 1Gi
accessModes:
- ReadWriteMany
hostPath: # 声明本地存储
path: /data/hostpath
kubectl apply -f PV.yaml
通过kubectl get pv查看:
[root@master01 ~]# kubectl get pv
NAME CAPACITY ACCESS MODES RECLAIM POLICY STATUS CLAIM STORAGECLASS REASON AGE
my-pv 1Gi RWX Retain Available manual 39s
可以看到创建成功,并且状态是Available,说明还没有被PVC绑定。注意声明的hostPath并不是只有Master节点才有,所有的节点都有!
- Capacity(存储能力):一般来说,一个 PV 对象都要指定一个存储能力,通过 PV 的 capacity 属性来设置的,目前只支持存储空间的设置,就是我们这里的 storage=1Gi,不过未来可能会加入 IOPS、吞吐量等指标的配置。
- AccessModes(访问模式):用来对 PV 进行访问模式的设置,用于描述用户应用对存储资源的访问权限,访问权限包括下面几种方式:
- ReadWriteOnce(RWO):读写权限,但是只能被单个节点挂载
- ReadOnlyMany(ROX):只读权限,可以被多个节点挂载
- ReadWriteMany(RWX):读写权限,可以被多个节点挂载
8.1.2 创建PVC
再声明一个PVC.yaml,内容如下:
apiVersion: v1
kind: PersistentVolumeClaim
metadata:
name: my-pvc
spec:
storageClassName: manual
accessModes:
- ReadWriteMany
resources:
requests:
storage: 1Gi
kubectl apply -f PVC.yaml
通过kubectl get pvc查看:
persistentvolumeclaim/my-pvc created
[root@master01 ~]# kubectl get pvc
NAME STATUS VOLUME CAPACITY ACCESS MODES STORAGECLASS AGE
my-pvc Bound my-pv 1Gi RWX manual 13s
[root@master01 ~]# kubectl get pv
NAME CAPACITY ACCESS MODES RECLAIM POLICY STATUS CLAIM STORAGECLASS REASON AGE
my-pv 1Gi RWX Retain Bound default/my-pvc manual 3m36s
可以看到PV的状态变成了Bound,说明PV被PVC绑定了(注意:创建 PVC 之后,Kubernetes 就会去查找满足我们声明要求的 PV,比如 storageClassName、accessModes 以及容量这些是否满足要求,如果满足要求就会将 PV 和 PVC 绑定在一起,目前 PV 和 PVC 之间是一对一绑定的关系,也就是说一个 PV 只能被一个 PVC 绑定)。
8.1.3 创建pod绑定pvc
最后通过如下web-test.yaml声明创建deploy
apiVersion: apps/v1
kind: Deployment
metadata:
labels:
app: web-test
name: web-test
spec:
replicas: 1
selector:
matchLabels:
app: web-test
strategy: {}
template:
metadata:
labels:
app: web-test
spec:
containers:
- image: nginx:1.14
name: nginx
# 挂载到容器内
volumeMounts:
- name: wwwroot
mountPath: /usr/share/nginx/html
# PVC声明
volumes:
- name: wwwroot
persistentVolumeClaim:
claimName: my-pvc # POD关联的是PVC,而不管PV是谁
status: {}
应用:
kubectl apply -f web-test.yaml
此声明将Pod内的/usr/share/nginx/html绑定到主机的/data/hostpath(通过PV声明的),如果此时访问一下nginx会报403 Forbidden错误,因为主机内的/data/hostpath/index.html并没有,先创建一个(注意:Pod在哪个节点上就在哪个节点上创建)。
创建本地路径:(注意:Pod在哪个节点上就在哪个节点上创建)
# 创建文件夹
mkdir -p /data/hostpath
# 写入index.html
cat > /data/hostpath/index.html <<EOF
Hello yss
EOF
验证:
[root@master01 ~]# kubectl get deploy,pods -o wide | grep web-test
deployment.apps/web-test 1/1 1 1 2m11s nginx nginx:1.14 app=web-test
pod/web-test-88bc96645-8mrgd 1/1 Running 0 2m11s 10.2.73.23 10.1.1.103 <none> <none>
# 然后验证
[root@node02 ~]# curl 10.2.73.23
Hello egon
8.2 网络存储
一般来讲,不会通过本地存储来持久化数据,因为Pod的调度不是固定的,所以会通过网络的方式来存储数据,比如NFS。这里我们新增加一台虚拟机(192.168.108.100)来充当NFS服务器,安装NFS命令如下(本例中,我们在mananger节点安装nfs)。
# 服务端软件安装
yum install -y nfs-utils rpcbind # 安装nfs-utils和rpcbind两个包
# 客户端软件安装(在所有node节点安装)
yum install -y nfs-utils
# 创建共享目录
mkdir -p /data/nfs
# 配置共享目录
cat > /etc/exports <<EOF
/data/nfs *(rw,no_root_squash)
EOF
# 启动nfs服务
systemctl start nfs
# 查看服务是否启动成功
ps aux | grep nfs
如果有如下回显,说明启动成功:
root 75267 0.0 0.0 0 0 ? S< 08:44 0:00 [nfsd4_callbacks]
root 75273 0.0 0.0 0 0 ? S 08:44 0:00 [nfsd]
接下来就是改PV,只需要改动挂载类型即可,如下:
[root@master01 ~]# cat PV.yaml
apiVersion: v1
kind: PersistentVolume
metadata:
name: my-pv
labels:
type: remote # 改为remote
spec:
storageClassName: manual
capacity:
storage: 1Gi
accessModes:
- ReadWriteMany
nfs: #声明nfs存储
path: /data/nfs # 可以指定为子目录/data/nfs/aaa,但必须事先创建好
server: 10.1.1.106 # server指定为nfs服务端的物理机ip地址
PVC和web.yaml都不用改,然后应用:
# 先删除,否则会提示重名
kubectl delete -f web-test.yaml
kubectl apply -f PV.yaml
kubectl apply -f PVC.yaml
kubectl apply -f web-test.yaml
在NFS服务端写入文件:
[root@manager ~]# echo "hello big yss" > /data/nfs/a.txt
然后进行测试:
[root@node01 ~]# curl 10.2.73.25/a.txt
hello big egon
九、服务发现与负载均衡
针对同一个应用的多个pod副本,要访问它们必然要引入负载均衡,k8s引入了service实现负载均衡,我们需要service考虑几件事情。
-
流量转发的效率:service可以采用ipvs,iptables转发,我们安装的时候用了效率更高的ipvs。
-
副本的扩缩负载均衡配置的更新,service代理的pod会自动更新。
kube-proxy组件负责管理service这种智能负载均衡。service的工作流如下:
- service --> endpoint --> pod
- endpoint指的是pod ip + port 组合而成的入口
- service 是基于 ip port 的四层代理
# #service基于 ipvs创建转发条目,会创建在每个节点上,是由kube-proxy维护的。可通过ipvsadm查看。
ipvsadm -Ln
service 会创建一个clusterIP, 用于负载均衡,同时调用coreDNS做内部的域名解析。
#svc的域名:svc_name.default.svc.cluster.local
9.1 引入service
到目前为止,我们的Pod已经可以实现水平扩缩、自动装箱、配置管理、存储编排了,但是访问还是个大问题。
问题1:pod的地址是动态变化的
[root@master01 ~]# kubectl create deployment nginx-deploy --image=nginx:1.14-alpine #创建一个nginx的应用,副本数为1
deployment.apps/nginx-deploy created
[root@master01 ~]# kubectl get deployment
NAME READY UP-TO-DATE AVAILABLE AGE
nginx-deploy 1/1 1 1 6s
[root@master01 ~]# kubectl get pods #获取pod信息
NAME READY STATUS RESTARTS AGE
nginx-deploy-788b9c6b69-sn876 1/1 Running 0 13s
[root@master01 ~]# kubectl get pods -o wide # 查看pod运行在哪个节点上
NAME READY STATUS RESTARTS AGE IP NODE NOMINATED NODE READINESS GATES
nginx-deploy-788b9c6b69-sn876 1/1 Running 0 17s 10.2.43.104 10.1.1.104 <none> <none>
从上面创建的应用可以得知,nginx-deploy应用的pod的ip为10.2.43.104,这是一个pod ip,仅仅可以在安装有kubelet的节点上进行访问,如下:
[root@node01 ~]# curl 10.2.43.104 -I # 可以访问到
HTTP/1.1 200 OK
Server: nginx/1.14.2
Date: Sun, 29 Aug 2021 08:53:10 GMT
Content-Type: text/html
Content-Length: 612
Last-Modified: Wed, 10 Apr 2019 01:08:42 GMT
Connection: keep-alive
ETag: "5cad421a-264"
Accept-Ranges: bytes
[root@node02 ~]# curl 10.2.43.104 -I # 可以访问到,同上
[root@node03 ~]# curl 10.2.43.104 -I # 可以访问到,同上
这里要注意的是pod的客户端有2类,1类是其他pod,1类是集群外部客户端,那么集群外部的客户端如何访问到pod呢?pod的地址是随时变化的,假设先删除创建的pod然后重新创建:
kubectl delete pods nginx-deploy-788b9c6b69-sn876
要明白pod是通过控制器进行管理的,当控制器发现pod的状态不满足预期的状态时,将会重新创建一个pod,此时可以看到新建的pod的ip地址已经更改了:
[root@master01 ~]# kubectl get pods -o wide
NAME READY STATUS RESTARTS AGE IP NODE NOMINATED NODE READINESS GATES
nginx-deploy-788b9c6b69-sn876 1/1 Running 0 2m51s 10.2.43.104 10.1.1.104 <none> <none>
[root@master01 ~]#
[root@master01 ~]# kubectl delete pods nginx-deploy-788b9c6b69-sn876 # 删除
pod "nginx-deploy-788b9c6b69-sn876" deleted
[root@master01 ~]#
[root@master01 ~]# kubectl get pods -o wide # 发现又创建了新的pod,ip地址变为10.2.43.105,创建于10.1.1.104这个物理节点上
NAME READY STATUS RESTARTS AGE IP NODE NOMINATED NODE READINESS GATES
nginx-deploy-788b9c6b69-8kvrr 1/1 Running 0 13s 10.2.43.105 10.1.1.104 <none> <none>
问题2:扩容某个应用下的pod后,应该访问哪一个?
扩容后这么多Pod应该访问哪一个?你会发现访问哪一个都不合适,负载均衡最好。
综合上述两个问题,如果能够自动将流量分配到不同的Pod上(负载均衡);并且当扩容或者缩容的时候能够动态的将Pod添加或者剔除出负载均衡的范围,简而言之就是服务发现,如果有这样的功能就好了。
那么在K8S中有没有东西可以做到服务发现和负载均衡呢?答案是有,这就是Service。无需修改你的应用程序即可使用陌生的服务发现机制。Kubernetes 为容器提供了自己的 IP 地址和一个 DNS 名称,并且可以在它们之间实现负载均衡。Service有4种类型:
-
ClusterIp:集群内部访问(默认),通过集群的内部 IP 暴露服务,选择该值,服务只能够在集群内部可以访问,这也是默认的 ServiceType。
-
NodePort:集群外部访问(包含了ClusterIp)
NodePort 服务会路由到 ClusterIP 服务,这个 ClusterIP 服务会自动创建。
集群外部通过请求<NodeIP>:<NodePort>
会路由到<Clusterip>:<Port>
然后转发到<Podip>:<TargetPort>虽然监听在宿主机上,但是,但是,本身不占用端口号,就像ipvs一样。
-
LoadBalancer:对外访问应用使用,公有云
使用云提供商的负载均衡器,可以向外部暴露服务。外部的负载均衡器可以路由到 NodePort 服务和 ClusterIP 服务。 -
ExternalName:通过返回 CNAME 和它的值,可以将服务映射到 externalName 字段的内容(例如, foo.bar.example.com)。
没有任何类型代理被创建,这只有 Kubernetes 1.7 或更高版本的 kube-dns 才支持。
下面我们主要演示第二种,首先应用nginx-balance.yaml创建Pod,副本数量可以多一点。
9.2 service资源与应用案例
先创建应用:
[root@master01 ~]# kubectl create deployment nginx-ba --image=nginx:1.14 --dry-run=client -o yaml > nginx-balance.yaml
[root@master01 ~]# vim nginx-balance.yaml #修改副本数为5,如下
[root@master01 ~]# cat nginx-balance.yaml
apiVersion: apps/v1
kind: Deployment
metadata:
labels:
app: nginx-ba
name: nginx-ba
spec:
replicas: 5
selector:
matchLabels:
app: nginx-ba
strategy: {}
template:
metadata:
labels:
app: nginx-ba
spec:
containers:
- image: nginx:1.14
name: nginx
status: {}
应用:
[root@master01 ~]# kubectl apply -f nginx-balance.yaml
[root@master01 ~]# kubectl get deployment
NAME READY UP-TO-DATE AVAILABLE AGE
nginx-ba 5/5 5 5 23m
[root@master01 ~]# kubectl get pods -o wide
NAME READY STATUS RESTARTS AGE IP NODE NOMINATED NODE READINESS GATES
nginx-ba-65c5d67d8b-8xrqm 1/1 Running 0 23m 10.2.43.145 10.1.1.104 <none> <none>
nginx-ba-65c5d67d8b-s5hfb 1/1 Running 0 21m 10.2.60.82 10.1.1.105 <none> <none>
nginx-ba-65c5d67d8b-s8xpn 1/1 Running 0 21m 10.2.43.146 10.1.1.104 <none> <none>
nginx-ba-65c5d67d8b-sxqx2 1/1 Running 0 21m 10.2.60.81 10.1.1.105 <none> <none>
nginx-ba-65c5d67d8b-tdbdb 1/1 Running 0 23m 10.2.60.80 10.1.1.105 <none> <none>
每个Pod设置一下显示的内容,这样才能看出负载均衡的效果,名字来(下述命令不要一下子复制粘贴执行,最好是一条一条执行):
kubectl exec -it nginx-ba-65c5d67d8b-8xrqm -- bash -c 'echo 1 > /usr/share/nginx/html/index.html'
kubectl exec -it nginx-ba-65c5d67d8b-s5hfb -- bash -c 'echo 2 > /usr/share/nginx/html/index.html'
kubectl exec -it nginx-ba-65c5d67d8b-s8xpn -- bash -c 'echo 3 > /usr/share/nginx/html/index.html'
kubectl exec -it nginx-ba-65c5d67d8b-sxqx2 -- bash -c 'echo 4 > /usr/share/nginx/html/index.html'
kubectl exec -it nginx-ba-65c5d67d8b-tdbdb -- bash -c 'echo 5 > /usr/share/nginx/html/index.html'
创建一个Service:为nginx-ba这个deployment资源创建一个名为nginx-svc-test的service资源。
[root@master01 ~]# kubectl expose deployment nginx-ba --name=nginx-svc-test --port=8080 --target-port=80 --protocol=TCP --type=NodePort
我们可以用--name指定创建的service资源的名字也可以不指定,不指定则默认与deployment名字一样,依然老套路 --dry-run,试运行生成yaml后再应用
[root@master01 ~]# kubectl expose deployment nginx-ba --port=8080 --target-port=80 --protocol=TCP --type=NodePort --dry-run -o yaml > service.yaml
expose:指定这是要暴露一个deployment,后面的web是服务的名字
--port:指定集群内部访问的端口
--target-port:指定容器本身端口,比如nginx是80,所以
--type:表明这是NodePort类型,可以被集群外部访问
查看一下生成的service.yaml的内容:
[root@master01 ~]# cat service.yaml
apiVersion: v1
kind: Service
metadata:
creationTimestamp: null
labels:
app: nginx-ba
name: nginx-ba
spec:
ports:
- port: 8080
protocol: TCP
targetPort: 80
# nodePort: 9999 # 不指定则会生成一个默认端口
selector:
app: nginx-ba
type: NodePort
status:
loadBalancer: {}
# 注意
# 1、'nodePort' 的取值范围在 30000-32767 之间
# 2、可以修改/etc/kubernetes/manifests/kube-apiserver.yaml 添加参数- --service-node-port-range=1024-65535来定制端口范围
强调: 在旧版k8s中,nodePort的端口你直接在所有物理节点用netstat就能看到,即占用的就是物理机的端口,但是新版k8s则看不到了,并且仍可以使用(不再占用物理机端口,这是一个极大的优化,可以放心大胆的使用nodePort模式,不必再像过去那样唯唯诺诺生怕nodePort指定的端口与物理机某个端口冲突)。
应用service.yaml:
kubectl apply -f service.yaml
查看刚刚创建的Service:
[root@master01 ~]# kubectl get svc -o wide
NAME TYPE CLUSTER-IP EXTERNAL-IP PORT(S) AGE SELECTOR
kubernetes ClusterIP 10.0.0.1 <none> 443/TCP 10d <none>
nginx-ba NodePort 10.0.40.95 <none> 8080:27028/TCP 5s app=nginx-ba
集群内部访问的地址,即ClusterIP+8080:10.0.40.95:8080
集群外部访问的地址:运行有kubelet的任意一台物理节点的物理ip地址:27028,端口27028是K8S随机生成的。
[root@master01 ~]# kubectl describe svc nginx-ba # 查看详细信息
Name: nginx-ba
Namespace: default
Labels: app=nginx-ba
Annotations: <none>
Selector: app=nginx-ba
Type: NodePort
IP: 10.0.40.95
Port: <unset> 8080/TCP
TargetPort: 80/TCP
NodePort: <unset> 27028/TCP
Endpoints: 10.2.43.145:80,10.2.43.146:80,10.2.60.80:80 + 2 more... #会根据资源变化改变,但是实际访问的service依旧有效
Session Affinity: None
External Traffic Policy: Cluster
Events: <none>
在运行有kubelet的任意一个节点上都可以访问clusterIp+8080:10.0.40.95:8080,但是clusterip也只能在集群内部用,集群外部无法访问。
# 在node01上
[root@node01 ~]# curl 10.0.40.95:8080
3
# 在node02上
[root@node02 ~]# curl 10.0.40.95:8080
1
# 在node03上
[root@node03 ~]# curl 10.0.40.95:8080
3
# 在master01上(注意我们的master01并没有join加入k8s集群成为node节点,k8s相关的网络组件也都没有部在master01上)
[root@master01 ~]# curl 10.0.40.95:8080 # 无法访问
而27028则是在安装有kubelet的物理节点(称之为计算节点)上监听的端口,只要客户端主机能够连同计算节点的物理ip则可以访问。注意,我们的master01上并没有安装kubelet,但是可以访问node01、node02、node03的物理ip。
[root@master01 ~]# curl node01:27028
1
[root@master01 ~]# curl node01:27028
4
[root@master01 ~]# curl node01:27028
5
[root@master01 ~]#
[root@master01 ~]# curl node02:27028
4
[root@master01 ~]# curl node03:27028
1
[root@master01 ~]#
写个脚本访问一下:
[root@master01 ~]# while true;do curl node01:27028;sleep 1;done
2
3
1
4
5
...
可以看到负载均衡已经生效,当然此时如果将副本的数量增加或者减少Service也是能感知到的,比如将副本的数量减少为1,可以看到如下效果:
[root@master01 ~]# kubectl scale deploy nginx-ba --replicas=1
[root@master01 ~]# while true;do curl node01:27028;sleep 1;done
...
4
5
2
3
1
curl: (7) Failed connect to node01:27028; 没有到主机的路由
1
1
1
1
1
1
1
1
1
1
1
那么pod的增删改,service又是如何确定对pod的访问呢?这就需要通过标签选择器进行选定,无论pod的ip如何变化,但是标签不会变化,从而达到固定端点的访问效果。查看一下pod的标签:
[root@node01 ~]# kubectl get pods --show-labels
NAME READY STATUS RESTARTS AGE LABELS
nginx-ba-65c5d67d8b-8xrqm 1/1 Running 0 26m app=nginx-ba,pod-template-hash=65c5d67d8b
app=nginx-ba就是这个应用的标签,所以当pod的改变,并不会影响service的访问。
9.3 k8s的端口监听原理
K8S使用NodePort时,无法在主机上看到监听的端口 - 探究原因
1、前言
在新版K8S中,即便 Service 使用 nodeport 暴露,在 node 中使用netstat -anp 或者ss -nlt命令上也看不到 kube-proxy 监听的端口了,但是 nodeport 访问是正常的。
Kubernetes服务不是作为侦听特定端口的进程来实现的。取而代之的是使用iptables (或IPVS),服务基本上是iptables规则。这就是为什么它们不会出现在你的netstat中。
详细查看:https://kubernetes.io/docs/concepts/services-networking/service/#proxy-mode-iptables
2、你需要先了解kube-proxy的三种工作模式
2.1、userspace
userspace模式是 kube-proxy 使用的第一代模式,该模式在 kubernetes v1.0 版本开始支持使用,该模式kube-proxy会为每一个Service创建一个监听端口,发向Cluster IP的请求被Iptables规则重定向到kube-proxy监听的端口上,kube-proxy根据LB算法选择一个提供服务的Pod并和其建立链接,以将请求转发到Pod上。该模式下,kube-proxy充当了一个四层负责均衡器的角色。由于kube-proxy运行在userspace中,在进行转发处理时会增加内核和用户空间之间的数据拷贝,虽然比较稳定,但是效率比较低,不推荐使用。
2.2、iptables
iptables 模式是 kube-proxy 使用的第二代模式,该模式在 kubernetes v1.1 版本开始支持,从 v1.2 版本开始成为 kube-proxy 的默认模式。
该模式下kube-proxy为service后端的每个Pod创建对应的iptables规则,直接将发向Cluster IP的请求重定向到一个Pod IP。该模式的优点是较userspace模式效率更高,但不能提供灵活的LB策略,当后端Pod不可用时也无法进行重试。
2.3、ipvs
ipvs 模式被 kube-proxy 采纳为第三代模式,模式在 kubernetes v1.8 版本开始引入,在 v1.9 版本中处于 beta 阶段,在 v1.11 版本中正式开始使用。
ipvs模式和iptables类似,kube-proxy监控Pod的变化并创建相应的ipvs规则。ipvs相对iptables转发效率更高。除此以外,ipvs支持更多的LB算法。如果要设置Kube-proxy为ipvs模式,必须在操作系统中安装IPVS内核模块,否则会降级为iptables。
3、如何查看自己k8s集群的kube-proxy使用的是iptables还是ipvs?
方法1:通过 `ps -ef | grep kube-proxy` 查看--proxy-mode参数内容。**
方法2:查看kube-proxy运行日志。**
4、查找防火墙的端口转发
4.1 如果是iptables,查找nodeport 31000端口:
[root@k8s-master01 ~]# iptables -tnat -L | grep 31000
KUBE-EXT-FC2LKBLU44TPG3GB tcp -- anywhere anywhere /* hellok8s/hellok8s:hellosvcp */ tcp dpt:31000
4.2 如果是ipvs,查找nodeport 31000端口:
[root@k8s-master01 ~]# ipvsadm -Ln | grep 31000
TCP 172.16.10.20:31000 rr
9.4 不同pod基于service通信
注意,如果有两个pod,一个是pod1,另外一个是pod2,pod1想访问pod2,有两种方式:
1、肯定可以通过service ip(也就是clusterip)访问,但通常不会这么做,因为ip是会动态变化的,所以此处简单了解即可。
# 我们之前已经创建过一个pod了,所以我们在创建一个新的
[root@master01 ~]# kubectl run client --image=alpine sleep 36000 # 只创建pod
pod/client created
[root@master01 ~]# kubectl get pods -o wide
NAME READY STATUS RESTARTS AGE IP NODE NOMINATED NODE READINESS GATES
client 1/1 Running 0 46s 10.2.73.38 10.1.1.103 <none> <none>
nginx-deploy-788b9c6b69-8kvrr 1/1 Running 0 53m 10.2.43.105 10.1.1.104 <none> <none>
[root@master01 ~]# kubectl exec -it client /bin/sh
kubectl exec [POD] [COMMAND] is DEPRECATED and will be removed in a future version. Use kubectl kubectl exec [POD] -- [COMMAND] instead.
/ # ping 10.0.162.13
PING 10.0.162.13 (10.0.162.13): 56 data bytes
64 bytes from 10.0.162.13: seq=0 ttl=64 time=0.057 ms
64 bytes from 10.0.162.13: seq=1 ttl=64 time=0.066 ms
2、还可以通过service的名称进行访问,但是前提是需要对service的名称能够进行解析。注意了,我们是要在一个pod内解析到另外一个pod的clusterip,pod的dns都是指向coredns服务的,所以我们需要往coredns内添加解析记录。如果是你想在物理机能够通过名称访问到clusterip,那么你就在物理机指向的dns里添加解析记录,就是这么个理。
#下面查看一下coredns的ip地址:
[root@master01 ~]# kubectl get pods -n kube-system -o wide
NAME READY STATUS RESTARTS AGE IP NODE NOMINATED NODE READINESS GATES
coredns-8585c9f5dd-2wlrz 1/1 Running 0 5h50m 10.2.60.74 10.1.1.105 <none> <none>
coredns-8585c9f5dd-5rnlt 1/1 Running 0 5h50m 10.2.60.71 10.1.1.105 <none> <none>
kubernetes-dashboard-869b75bf66-lnffq 0/1 CrashLoopBackOff 68 5h51m 10.2.60.64 10.1.1.105 <none> <none>
kubernetes-metrics-scraper-86f6785867-mkmvj 1/1 Running 0 5h51m 10.2.60.68 10.1.1.105 <none> <none>
#而一般,也不会直接通过coredns的这个pod ip地址进行访问,而是通过service进行访问,查看一下coredns的service:
[root@master01 ~]# kubectl get svc -n kube-system
NAME TYPE CLUSTER-IP EXTERNAL-IP PORT(S) AGE
coredns ClusterIP 10.0.0.2 <none> 53/UDP,53/TCP 2d6h
dashboard-metrics-scraper ClusterIP 10.0.153.11 <none> 8000/TCP 2d2h
kubernetes-dashboard NodePort 10.0.12.247 <none> 443:30001/TCP 2d2h
得知coredns的clusterip为10.0.0.2,每个pod内指向的dns地址就是它。
[root@master01 ~]# kubectl exec -it client /bin/sh
/ #
/ # cat /etc/resolv.conf
nameserver 10.0.0.2 # 该ip就是kube-system名称空间下coredns的svc地址
search default.svc.cluster.local. svc.cluster.local. cluster.local. localdomain
options ndots:5
墨迹了半天,其实coredns里默认就添加了nginx-svc-test这个service资源与其clusterip:10.0.162.13的解析记录。
[root@master01 ~]# kubectl exec -it client /bin/sh
kubectl exec [POD] [COMMAND] is DEPRECATED and will be removed in a future version. Use kubectl kubectl exec [POD] -- [COMMAND] instead.
/ #
/ # ping nginx-svc-teste
^C
/ # ping nginx-svc-test # 或者带着后缀.名称空间.svc.cluster.local
PING nginx-svc-test (10.0.162.13): 56 data bytes
64 bytes from 10.0.162.13: seq=0 ttl=64 time=0.039 ms
64 bytes from 10.0.162.13: seq=1 ttl=64 time=0.076 ms
64 bytes from 10.0.162.13: seq=2 ttl=64 time=0.067 ms
我们可以在安装有kubelet的节点上查看coredns的解析:
[root@master01 ~]# yum install -y bind-utils
[root@master01 ~]# dig -t A nginx-svc-test.default.svc.cluster.local @10.0.0.2 #注意这里需要使用完整的服务名称,否则会因为dns搜索域的问题而导致无法解析成功
service别名:
不同名称空间怎么访问,有下述三种写法,效果一样:
- service名称.名称空间
- service名称.名称空间.svc
- service名称.名称空间.svc.cluster.local
9.5 小结
综上,我们总结两个重点:
1、端口
- nodeport:物理节点监听的端口号,配合物理节点的ip地址用
- port:service资源对应的端口,配合clusterip地址用
- targetport:pod内容器监听的端口,被service资源代理/负载均衡
2、service资源所对应的clusterip(例如10.0.162.13)
这网段依然是集群内部的网段,只能被集群内部(安装有kubelet的物理节点)所能访问,外部是无法通过service的ip进行访问的。若外部想访问,我们可以通过物理节点ip+nodeport进行访问(前提是service资源的type类型为nodeport)。
9.6 小练习
结合service练习一下应用副本的动态伸缩,算是把之前所学的知识点复习一下。
#(1)创建应用myapp
kubectl create deployment nginx-deploy --image=nginx:1.14-alpine
#(2)查看deployment
kubectl get deployment
#(3)查看pod详细信息
kubectl get pods -o wide
#(4)配置service端点
kubectl expose deployment nginx-deploy --name=nginx-svc-test --port=6666 --target-port=80 --protocol=TCP --type=NodePort
#(5)查看服务信息
kubectl get svc
#(6)Pod客户端访问
kubectl run client --image=alpine sleep 36000 # 只创建pod
kubectl exec -it client /bin/sh
/ # wget -O - -q http://nginx-svc-test:6666
#(7)副本增加到5
kubectl scale --replicas=5 deployment nginx-deploy
#(8)查看
kubectl get pods
#(9)副本收缩到3
kubectl scale --replicas=3 deployment nginx-deploy
十、自我修复
重新启动失败的容器,在节点死亡时替换并重新调度容器,杀死不响应用户定义的健康检查的容器,并且在它们准备好服务之前不会将它们公布给客户端。
故障修复的分类:
-
pod级别:某个pod挂了,由控制器调谐
-
容器级别:pod内的某个容器挂了,引入重启机制
-
服务级别:pod不变,容器也没有挂,但容器内的服务挂了,没法对外提供服务了。引入pod的健康检查机制(就绪检查,存活检查),检查完之后进行的动作:
-
方案1:从service中清理掉该pod代理
-
方案2:重启pod。
-
10.1 Pod重启机制
当Pod异常停止时,就会触发Pod的重启机制,根据重启策略会表现出不同的行为。
重启策略主要分为以下三种:
- Always:当容器终止退出后,总是重启容器,默认策略
- OnFailure:当容器异常退出(退出状态码非0)时,才重启
- Never:当容器终止退出,从不重启容器,
实战思路: 准备一个多个副本的Deployment和一个Service,不断访问Service,然后尝试停止Pod,看两个事情:
- 第一个是被停止的Pod是否按照restartPolicy指定的策略执行
- 第二个事情是不停的访问Service看是否受到影响。
- 单论pod来说有这三个,但是一旦用deployment就只能用always,不支持设置其他。
nginx-balance.yaml
[root@master01 ~]# cat nginx-balance.yaml
apiVersion: apps/v1
kind: Deployment
metadata:
labels:
app: nginx-ba
name: nginx-ba
spec:
replicas: 5
selector:
matchLabels:
app: nginx-ba
strategy: {}
template:
metadata:
labels:
app: nginx-ba
spec:
containers:
- image: nginx:1.14
name: nginx
# 重启策略
restartPolicy: Always
status: {}
service.yaml(保持不变即可):
[root@master01 ~]# cat service.yaml
apiVersion: v1
kind: Service
metadata:
creationTimestamp: null
labels:
app: nginx-ba
name: nginx-ba
spec:
ports:
- port: 8080
protocol: TCP
targetPort: 80
selector:
app: nginx-ba
type: NodePort
status:
loadBalancer: {}
应用上面两个资源:
kubectl apply -f web.yaml,svc.yaml
# 查看生成的资源
kubectl get deploy,po,svc
更改每个Pod返回内容用以区分:
kubectl exec -it nginx-ba-6b48bd744f-6j54q -- bash -c 'echo 1 > /usr/share/nginx/html/index.html'
kubectl exec -it nginx-ba-6b48bd744f-c6tm9 -- bash -c 'echo 2 > /usr/share/nginx/html/index.html'
kubectl exec -it nginx-ba-6b48bd744f-ddfd2 -- bash -c 'echo 3 > /usr/share/nginx/html/index.html'
kubectl exec -it nginx-ba-6b48bd744f-gqkqx -- bash -c 'echo 4 > /usr/share/nginx/html/index.html'
kubectl exec -it nginx-ba-6b48bd744f-hkvcs -- bash -c 'echo 5 > /usr/share/nginx/html/index.html'
新开一个session不停的访问Service开出来的IP:
[root@master01 ~]# while true;do curl node01:30714;sleep 1;done
1
3
5
2
4
1
3
5
...
到部署的节点上面随机停掉一个容器(根据具体的容器ID来):
[root@node01 ~]# docker container ls | grep nginx-ba
9415c241e2d0 295c7be07902 "nginx -g 'daemon of…" 34 minutes ago Up 34 minutes k8s_nginx_nginx-ba-6b48bd744f-gqkqx_default_43d951b1-5f4d-4dcd-bfa3-98e6f3c918d4_0
0e0a5b5076d4 mirrorgooglecontainers/pause-amd64:3.0 "/pause" 34 minutes ago Up 34 minutes k8s_POD_nginx-ba-6b48bd744f-gqkqx_default_43d951b1-5f4d-4dcd-bfa3-98e6f3c918d4_0
[root@node01 ~]# docker container stop 9415c241e2d0
9415c241e2d0
[root@node01 ~]#
观看Pod状态,重点看RESTARTS字段,可以看到有一个Pod的值为1,并且该Pod是Running状态,说明重启了:
[root@master01 ~]# kubectl get pods
NAME READY STATUS RESTARTS AGE
......
nginx-ba-6b48bd744f-gqkqx 1/1 Running 1 35m
......
在看看访问的输出:
1
3
curl: (7) Failed connect to 10.1.1.103:8080; Connection refused
5
2
<!DOCTYPE html>
<html>
<head>
<title>Welcome to nginx!</title>
<style>
body {
width: 35em;
margin: 0 auto;
font-family: Tahoma, Verdana, Arial, sans-serif;
}
</style>
</head>
<body>
<h1>Welcome to nginx!</h1>
<p>If you see this page, the nginx web server is successfully installed and
working. Further configuration is required.</p>
<p>For online documentation and support please refer to
<a href="http://nginx.org/">nginx.org</a>.<br/>
Commercial support is available at
<a href="http://nginx.com/">nginx.com</a>.</p>
<p><em>Thank you for using nginx.</em></p>
</body>
</html>
1
3
怎么回事,还有访问失败的?这符合自我修复的功能描述吗?答案是符合的,试想一下刚才的操作是强制停止容器,假设停止容器的同时正在访问容器,容器停止之后自然无法访问,但是容器被重启之后就可以接入服务正常访问了,后面输出的Welcome to nginx!可以证明已经被重启过了,对客户的感知就是刚才网"卡"了一下,再次刷新就可以访问了。
其他的策略感兴趣的同学可自行尝试。
10.2 Pod服务健康检查
健康检查顾名思义就是检查Pod是否健康,怎么来定义健康呢?下述两种情况下服务均无法访问,为不健康状态:
- 当程序内部发生了错误已经不能对外提供服务了,但此时主程序仍在运行,这种情况就是不健康的
- 当容器主进程已经启动了,但是服务还没有准备好,这种情况也是不健康的
这就需要从应用层面来检查,K8S中定义了两种检查机制:
- livenessProbe:存活检查,如果检查失败,将杀死容器,根据Pod的restartPolicy来操作。
- readinessProbe:就绪检查,如果检查失败,Kubernetes会把Pod从Service endpoints中剔除,也就是让客户流量不打到readinessProbe检查失败的Pod上, 就绪检查不重启pod,之检查就绪状态。
具体的检查方式支持三种:
- http Get:发送HTTP请求,返回200 - 400 范围状态码为成功
- exec:执行Shell命令返回状态码是0为成功
- tcpSocket:发起TCP Socket建立成功
实战思路:
- 备一个多个副本的Deployment和一个Service
- 在Pod容器里面准备一个/tmp/healthy用于响应就绪检查和存活检查,其中一个副本设置为不健康(exec方式命令执行失败)
- 检查两个事情:
- 一是检查非健康容器是否根据restartPolicy重启
- 二是检查通过Service是否能访问到非就绪的容器。
health-test.yaml:
apiVersion: apps/v1
kind: Deployment
metadata:
labels:
app: health-test
name: health-test
spec:
replicas: 3
selector:
matchLabels:
app: health-test
strategy: {}
template:
metadata:
labels:
app: health-test
spec:
containers:
- image: nginx:1.14
name: nginx
# 就绪检查
readinessProbe:
exec:
command:
- cat
- /tmp/healthy1
# 初始化检查延迟时间
initialDelaySeconds: 90
# 隔多少秒检查一次
periodSeconds: 5
# 存活检查
livenessProbe:
exec:
command:
- cat
- /tmp/healthy2
# 初始化检查延迟时间,一般设置比就绪检查的该时间大一点
initialDelaySeconds: 95
# 隔多少秒检查一次
periodSeconds: 5
# 重启策略
restartPolicy: Always
status: {}
SVC.yaml:
apiVersion: v1
kind: Service
metadata:
labels:
app: health-test
name: health-test
spec:
ports:
# 端口不要冲突
- port: 8081
protocol: TCP
targetPort: 80
selector:
app: health-test
type: NodePort
status:
loadBalancer: {}
应用上面两个资源:
kubectl apply -f health-test.yaml,SVC.yaml
# 查看生成的资源
kubectl get deploy,po,svc
initialDelaySeconds: 90,初始化检查延迟时间设置成比较长,是因为在此期间执行下面的命令将健康检查的值设进去,此时快速访问一下Service暴露出来的内网IP是访问不到的,因为应用还没准备好。
下面设置健康检查的状态,注意第二个Pod不健康,因为/tmp/healthy文件不存在,cat /tmp/healthy会返回非0:
# 健康的
kubectl exec -it health-test-56d4df4855-5b9pw -- bash -c 'echo 1 > /usr/share/nginx/html/index.html && touch /tmp/healthy'
# 不健康,因为没有/tmp/healthy文件
kubectl exec -it health-test-56d4df4855-5h9ww -- bash -c 'echo 2 > /usr/share/nginx/html/index.html'
# 健康的
kubectl exec -it health-test-56d4df4855-brm4q -- bash -c 'echo 3 > /usr/share/nginx/html/index.html && touch /tmp/healthy'
访问一下Service,没有返回2,说明符合预期:
[root@master01 ~]# while true;do curl node01:28227; sleep 1;done
3
1
3
1
3
1
3
看一下不健康Pod的信息,看到Readiness和Liveness都失败,并且该Pod在不停的重启中,也符合预期:
[root@master01 ~]# kubectl describe po health-test-56d4df4855-5h9ww
...
... Liveness probe failed: cat: /tmp/healthy: No such file or directory
... Container nginx failed liveness probe, will be restarted
... Started container nginx
... Readiness probe failed: cat: /tmp/healthy: No such file or directory
... Back-off restarting failed container
十一、自动上线与回滚
Kubernetes 会分步骤地将针对应用或其配置的更改上线,同时监视应用程序运行状况以确保你不会同时终止所有实例。如果出现问题,Kubernetes 会为你回滚所作更改。你应该充分利用不断成长的部署方案生态系统。
11.1 升级
实战思路: 创建一个多副本的Deployment和一个Service,不断的访问Service,然后更改镜像版本,查看Service是否停止服务。
nginx-balance.yaml:
[root@master01 ~]# cat nginx-balance.yaml
apiVersion: apps/v1
kind: Deployment
metadata:
labels:
app: nginx-ba
name: nginx-ba
spec:
replicas: 5
selector:
matchLabels:
app: nginx-ba
strategy: {}
template:
metadata:
labels:
app: nginx-ba
spec:
containers:
- image: nginx:1.14
name: nginx
status: {}
service.yaml:
[root@master01 ~]# cat service.yaml
apiVersion: v1
kind: Service
metadata:
creationTimestamp: null
labels:
app: nginx-ba
name: nginx-ba
spec:
ports:
- port: 8080
protocol: TCP
targetPort: 80
selector:
app: nginx-ba
type: NodePort
status:
loadBalancer: {}
应用:
kubectl apply -f web.yaml,svc.yaml
# 查看升级过程
kubectl rollout status deployment myapp #查看更新过程
# 查看生成的资源
kubectl get deploy,po,svc
新开一个session不断访问:
[root@master01 ~]# while true;do curl node01:27028; sleep 1;done
将版本更新为1.15,nginx:1.14变更为nginx:1.15,再次应用web.yaml:
kubectl apply -f nginx-balance.yaml
查看Service发现并没有停止服务,符合预期,快速查看一下Pod,发现新启动了一个Pod在Running,之前的Pod在Terminating了,说明是滚动发布的:
[root@master01 ~]# kubectl get deploy,pods -o wide | grep nginx-ba
deployment.apps/nginx-ba 5/5 5 5 78m nginx nginx:1.15 app=nginx-ba
pod/nginx-ba-65c5d67d8b-b8rhb 1/1 Terminating 0 3m2s 10.2.60.48 10.1.1.105 <none> <none>
pod/nginx-ba-65c5d67d8b-jj2qh 1/1 Terminating 0 55s 10.2.60.49 10.1.1.105 <none> <none>
pod/nginx-ba-65c5d67d8b-kf55q 1/1 Running 0 3m3s 10.2.73.30 10.1.1.103 <none> <none>
pod/nginx-ba-65c5d67d8b-p7c42 0/1 Pending 0 2s <none> <none> <none> <none>
pod/nginx-ba-65c5d67d8b-r7cbm 1/1 Running 0 3m3s 10.2.43.87 10.1.1.104 <none> <none>
pod/nginx-ba-65c5d67d8b-sfkwj 0/1 Pending 0 4s <none> 10.1.1.103 <none> <none>
pod/nginx-ba-65c5d67d8b-stvl9 1/1 Running 0 105s 10.2.43.88 10.1.1.104 <none> <none>
pod/nginx-ba-6b48bd744f-ddfd2 0/1 Terminating 0 53m 10.2.60.46 10.1.1.105 <none> <none>
pod/nginx-ba-6b48bd744f-gqkqx 0/1 Terminating 1 78m 10.2.73.28 10.1.1.103 <none> <none>
还可以再node上查看一下镜像版本是不否是1.15:
# 查看nginx-ba的升级版本
[root@master01 ~]# kubectl rollout status deploy nginx-ba
Waiting for deployment "nginx-ba" rollout to finish: 4 of 5 updated replicas are available...
deployment "nginx-ba" successfully rolled out
# 查看nginx-ba的历史版本
[root@master01 ~]# kubectl rollout history deployment nginx-ba
deployment.apps/nginx-ba
REVISION CHANGE-CAUSE
1 <none>
2 <none>
# 查看指定版本的
kubectl rollout history deployment nginx-ba --revision=3
ps: 也可以不用yaml文件,直接用命令来升级,了解即可不建议这么做:
# 语法
kubectl set image (-f FILENAME | TYPE NAME) CONTAINER_NAME_1=CONTAINER_IMAGE_1 ... CONTAINER_NAME_N=CONTAINER_IMAGE_N
# 例
kubectl set image deployment myapp myapp=ikubernetes/myapp:v2
11.2 回滚
先查看当前pod中的nginx版本为1.15.12:
[root@master01 ~]# kubectl get deploy,pods -o wide | grep nginx-ba | awk 'NR==2{print $1}'
pod/nginx-ba-65c5d67d8b-kf55q
[root@master01 ~]# kubectl exec -it nginx-ba-65c5d67d8b-kf55q -- bash -c '/usr/sbin/nginx -v'
nginx version: nginx/1.15.12
回滚就比较简单了,回滚到上一个版本用下面命令,回滚和升级一样也是滚动的,Service端无感知:
kubectl rollout undo deploy nginx-ba
查看回滚状态:
[root@master01 ~]# kubectl rollout status deploy nginx-ba
Waiting for deployment "nginx-ba" rollout to finish: 1 old replicas are pending termination...
Waiting for deployment "nginx-ba" rollout to finish: 1 old replicas are pending termination...
deployment "nginx-ba" successfully rolled out
查看回滚后的nginx版本为1.14.2:
[root@master01 ~]# kubectl get deploy,pods -o wide | grep nginx-ba | awk 'NR==2{print $1}'
pod/nginx-ba-6b48bd744f-p9j98
[root@master01 ~]# kubectl exec -it nginx-ba-6b48bd744f-p9j98 -- bash -c '/usr/sbin/nginx -v'
nginx version: nginx/1.14.2
回滚到指定版本命令如下:
kubectl rollout undo deploy nginx-ba --to-revision=2

浙公网安备 33010602011771号