k8s常用操作

一、kubectl命令

1.1 基本语法

在 Kubernetes 集群中,一切皆资源。无论是 Pod、Service 还是 Deployment,其定义都作为一条记录存储在 etcd 中。K8s 的核心机制在于“声明式 API”与“控制器模式”:用户声明资源的预期状态,而各种控制器则持续协调,将资源拉回到预期状态。

接下来将逐一解析:

  • 资源类型:它是什么?解决了什么业务问题?

  • 控制器:是谁在管理它?如何实现自愈与扩缩容?

语法:

kubectl [command] [TYPE] [NAME] [flags]
  1. command:子命令,准备做什么操作,例如 create、get、describe、delete 等(增删改查等)。

  2. TYPE:资源类型,准备操作什么类型的资源,区分大小写,并且可以简写,例如下述三个命令效果相同:

kubectl get pods xxx
kubectl get pod xxx
kubectl get po xxx
  1. 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
  1. 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 # 导出整张表
  1. 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定制插件非常简单。

  1. 创建一个可执行文件,文件名必须以kubectl-开头,该文件就是kubectl的插件

    文件名kubectl-hello,注意必须以kubectl-开头:

#!/bin/bash
echo "hello world"

必须添加可执行权限:

chmod +x kubectl-hello
  1. 将插件放到$PATH下,例如/usr/local/bin
mv ./kubectl-hello /usr/local/bin
  1. 然后就可以执行kubectl 来运行自定义插件了
kubectl hello  # 插件名需要去掉kubectl-前缀

想删除插件的话直接删就行:

rm -rf /usr/local/bin/kubectl-hello

我们可以将复杂的kubectl命令定制成插件以便使用,例如查看当前上下文环境(context)的用户名:

  1. 创建文件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 }}'
  1. 添加可执行权限:
chmod +x ./kubectl-whoami
  1. 拷贝到$PATH的路径下:
cp ./kubectl-whoami /usr/local/bin/
  1. 执行:
kubectl whoami
  1. 查看所有插件:
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

总结创建的过程:

  1. 用户通过kubectl创建Deployment
  2. Deployment创建ReplicaSet
  3. 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考虑几件事情。

  1. 流量转发的效率:service可以采用ipvs,iptables转发,我们安装的时候用了效率更高的ipvs。

  2. 副本的扩缩负载均衡配置的更新,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

十、自我修复

重新启动失败的容器,在节点死亡时替换并重新调度容器,杀死不响应用户定义的健康检查的容器,并且在它们准备好服务之前不会将它们公布给客户端。

故障修复的分类:

  1. pod级别:某个pod挂了,由控制器调谐

  2. 容器级别:pod内的某个容器挂了,引入重启机制

  3. 服务级别: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是否健康,怎么来定义健康呢?下述两种情况下服务均无法访问,为不健康状态:

  1. 当程序内部发生了错误已经不能对外提供服务了,但此时主程序仍在运行,这种情况就是不健康的
  2. 当容器主进程已经启动了,但是服务还没有准备好,这种情况也是不健康的

这就需要从应用层面来检查,K8S中定义了两种检查机制:

  • livenessProbe:存活检查,如果检查失败,将杀死容器,根据Pod的restartPolicy来操作。
  • readinessProbe:就绪检查,如果检查失败,Kubernetes会把Pod从Service endpoints中剔除,也就是让客户流量不打到readinessProbe检查失败的Pod上, 就绪检查不重启pod,之检查就绪状态。

具体的检查方式支持三种:

  • http Get:发送HTTP请求,返回200 - 400 范围状态码为成功
  • exec:执行Shell命令返回状态码是0为成功
  • tcpSocket:发起TCP Socket建立成功

实战思路:

  1. 备一个多个副本的Deployment和一个Service
  2. 在Pod容器里面准备一个/tmp/healthy用于响应就绪检查和存活检查,其中一个副本设置为不健康(exec方式命令执行失败)
  3. 检查两个事情:
    • 一是检查非健康容器是否根据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
posted @ 2026-07-02 22:56  cuckooyang  阅读(22)  评论(0)    收藏  举报