Contents

Kubernetes 最简实践

什么是 Kubernetes


Kubernetes是用于管理容器化应用程序集群的工具,自动且持续地监视集群并对其组成进行调整。官方的描述:Kubernetes 是一个可移植的、可扩展的开源平台,用于管理容器化的工作负载和服务,可促进声明式配置和自动化。

下面图片说明了当前部署架构的变迁,也是为什么要把服务部署在容器,管理在k8s的原因 https://s21.ax1x.com/2024/05/11/pkZTW7R.png

Kubernetes 架构


Kubernetes 主要分两种角色的节点,一种是master节点,一种是 worker 节点。master节点中主要运行四个很关键的组件,分别是 API Server、etcd、kube-controller、kube-scheduler, worker节点主要运行 kubelet、docker、kube-proxy等三个主要的组件

https://s21.ax1x.com/2024/05/11/pkZThA1.png

Kubernetes 资源对象


在 Kubernetes 系统中,Kubernetes 对象 是持久化的实体。 Kubernetes 使用这些实体去表示整个集群的状态。特别地,它们描述了如下信息: - 哪些容器化应用在运行(以及在哪些节点上) - 可以被应用使用的资源 - 关于应用运行时表现的策略,比如重启策略、升级策略,以及容错策略

Kubernetes 核心对象概念

  • 工作负载(Deployment)

用来管理你的集群上的无状态应用,为 Pods 和 ReplicaSets 提供声明式的更新能力。你负责描述 Deployment 中的 目标状态,而 Deployment 控制器(Controller) 以受控速率更改实际状态, 使其变为期望状态。

  • 副本集(Replica Set,RS)

ReplicaSet 的目的是维护一组在任何时候都处于运行状态的 Pod 副本的稳定集合。 因此,它通常用来保证给定数量的、完全相同的 Pod 的可用性。

  • 服务(Service)

将运行在一组 Pods 上的应用程序公开为网络服务的抽象方法。Service 资源允许你对外暴露 Pods 中运行的应用程序,以支持来自于集群外部的访问。可以使用 Services 来发布仅供集群内部使用的服务。

  • 后台支撑服务集(DaemonSet)

DaemonSet 确保全部(或者某些)节点上运行一个 Pod 的副本。 当有节点加入集群时, 也会为他们新增一个 Pod 。 当有节点从集群移除时,这些 Pod 也会被回收 DaemonSet 的一些典型用法:

在每个节点上运行集群守护进程
在每个节点上运行日志收集守护进程
在每个节点上运行监控守护进程
  • 持久存储卷(Persistent Volume,PV)和持久存储卷声明(Persistent Volume Claim,PVC)

持久卷(PersistentVolume,PV)是集群中的一块存储,可以由管理员事先供应,或者 使用存储类(Storage Class)来动态供应。

持久卷申领(PersistentVolumeClaim,PVC)表达的是用户对存储的请求。概念上与 Pod 类似。 Pod 会耗用节点资源,而 PVC 申领会耗用 PV 资源。Pod 可以请求特定数量的资源(CPU 和内存)

  • 密钥对象(Secret)及 ConfigMap

Secret 对象类型用来保存敏感信息,例如密码、OAuth 令牌和 SSH 密钥。将这些信息放在 secret 中比放在 Pod 的定义或者 容器镜像 中来说更加安全和灵活。

ConfigMap 是一种 API 对象,用来将非机密性的数据保存到键值对中。ConfigMap 将您的环境配置信息和 容器镜像 解耦,便于应用配置的修改。

  • 服务帐户(Service Account)

服务账户为 Pod 中运行的进程提供了一个标识。当你创建 Pod 时,如果没有指定服务账户,Pod 会被指定给命名空间中的 default 服务账户

  • 名字空间(Namespace)

名字空间适用于存在很多跨多个团队或项目的用户的场景。对于只有几到几十个用户的集群,根本不需要创建或考虑名字空间。当需要名称空间提供的功能时,请开始使用它们。

Deployment yaml例子

# kubectl apply -f xxx.yaml 来更新或者部署deployment   
# yaml格式的pod定义文件完整内容:
apiVersion: v1                      # 必选,版本号,例如v1
kind: Pod                       # 必选,Pod
metadata:                       # 必选,元数据
  name: string                      # 必选,Pod名称
  namespace: string                 # 必选,Pod所属的命名空间
  labels:                       # 自定义标签
    - name: string                  # 自定义标签名字
  annotations:                      # 自定义注释列表
    - name: string
spec:                               # 必选,Pod中容器的详细定义
  containers:                       # 必选,Pod中容器列表
  - name: string                    # 必选,容器名称
    image: string                   # 必选,容器的镜像名称
    imagePullPolicy: [Always | Never | IfNotPresent]  # 获取镜像的策略 Alawys表示下载镜像 IfnotPresent表示优先使用本地镜像,否则下载镜像,Nerver表示仅使用本地镜像
    command: [string]               # 容器的启动命令列表,如不指定,使用打包时使用的启动命令
    args: [string]                  # 容器的启动命令参数列表
    workingDir: string              # 容器的工作目录
    volumeMounts:                   # 挂载到容器内部的存储卷配置
    - name: string                  # 引用pod定义的共享存储卷的名称,需用volumes[]部分定义的的卷名
      mountPath: string             # 存储卷在容器内mount的绝对路径,应少于512字符
      readOnly: boolean             # 是否为只读模式
    ports:                          # 需要暴露的端口库号列表
    - name: string                  # 端口号名称
      containerPort: int            # 容器需要监听的端口号
      hostPort: int                 # 容器所在主机需要监听的端口号,默认与Container相同
      protocol: string              # 端口协议,支持TCP和UDP,默认TCP
    env:                            # 容器运行前需设置的环境变量列表
    - name: string                  # 环境变量名称
      value: string                 # 环境变量的值
    resources:                      # 资源限制和请求的设置
      limits:                       # 资源限制的设置
        cpu: string                 # Cpu的限制,单位为core数,将用于docker run --cpu-shares参数
        memory: string              # 内存限制,单位可以为Mib/Gib,将用于docker run --memory参数
      requests:                     # 资源请求的设置
        cpu: string                 # Cpu请求,容器启动的初始可用数量
        memory: string              # 内存清楚,容器启动的初始可用数量
    livenessProbe:                  # 对Pod内个容器健康检查的设置,当探测无响应几次后将自动重启该容器,检查方法有exec、httpGet和tcpSocket,对一个容器只需设置其中一种方法即可
      exec:                         # 对Pod容器内检查方式设置为exec方式
        command: [string]           # exec方式需要制定的命令或脚本
      httpGet:                      # 对Pod内个容器健康检查方法设置为HttpGet,需要制定Path、port
        path: string
        port: number
        host: string
        scheme: string
        HttpHeaders:
        - name: string
          value: string
      tcpSocket:                    # 对Pod内个容器健康检查方式设置为tcpSocket方式
         port: number
       initialDelaySeconds: 0       # 容器启动完成后首次探测的时间,单位为秒
       timeoutSeconds: 0            # 对容器健康检查探测等待响应的超时时间,单位秒,默认1秒
       periodSeconds: 0             # 对容器监控检查的定期探测时间设置,单位秒,默认10秒一次
       successThreshold: 0
       failureThreshold: 0
       securityContext:
         privileged: false
    restartPolicy: [Always | Never | OnFailure] # Pod的重启策略,Always表示一旦不管以何种方式终止运行,kubelet都将重启,OnFailure表示只有Pod以非0退出码退出才重启,Nerver表示不再重启该Pod
    nodeSelector: obeject           # 设置NodeSelector表示将该Pod调度到包含这个label的node上,以key:value的格式指定
    imagePullSecrets:               # Pull镜像时使用的secret名称,以key:secretkey格式指定
    - name: string
    hostNetwork: false              # 是否使用主机网络模式,默认为false,如果设置为true,表示使用宿主机网络
    volumes:                        # 在该pod上定义共享存储卷列表
    - name: string                  # 共享存储卷名称 (volumes类型有很多种)
      emptyDir: {}                  # 类型为emtyDir的存储卷,与Pod同生命周期的一个临时目录。为空值
      hostPath: string              # 类型为hostPath的存储卷,表示挂载Pod所在宿主机的目录
        path: string                # Pod所在宿主机的目录,将被用于同期中mount的目录
      secret:                       # 类型为secret的存储卷,挂载集群与定义的secre对象到容器内部
        scretname: string  
        items:     
        - key: string
          path: string
      configMap:                    # 类型为configMap的存储卷,挂载预定义的configMap对象到容器内部
        name: string
        items:
        - key: string
          path: string

Kubernetes 常见命令

# 详细命令可看官方文档,或者:https://hardocs.com/d/kubernetes/136-Command%20Reference.html、https://juejin.cn/post/6844904008436416519
 
# 查看k8s集群状态
➜  ~ kubectl cluster-info
Kubernetes control plane is running at https://kubernetes.docker.internal:6443
 
# 查看k8s节点
➜  ~ kubectl get nodes
 
# 查看命名空间
➜  ~ kubectl get namespaces
NAME                   STATUS   AGE
default                Active   7d5h
kube-node-lease        Active   7d5h
kube-public            Active   7d5h
kube-system            Active   7d5h
kubernetes-dashboard   Active   3m50s
 
# 查看某个命名空间的所有pod, -o 表示查看pod的更具体信息,如pod ip 或者 node
➜  ~ kubectl get pods -n <namespace> -o wide
 
# 查看pod的所有标签
➜  ~ kubectl get pod --show-labels
 
# 查看 pod 的 yaml 配置
➜  ~ kubectl get pod <podname> -o yaml
 
# 如何查看一个 deployment 多个pod的日志? -l 表示指定的标签
➜  ~ kubectl logs -l app=nginx
      # Usage:
        kubectl logs [-f] [-p] (POD | TYPE/NAME) [-c CONTAINER] [options]
      # Examples: 
      kubectl logs my-pod                              
      # 输出一个单容器pod my-pod的日志到标准输出
      kubectl logs nginx-78f5d695bd-czm8z -c nginx     
      # 输出多容器pod中的某个nginx容器的日志
      kubectl logs -l app=nginx                        
      # 输出所有包含app-nginx标签的pod日志
      kubectl logs -f my-pod                           
      # 加上-f参数跟踪日志,类似tail -f
      kubectl logs my-pod  -p                          
      # 输出该pod的上一个退出的容器实例日志。在pod容器异常退出时很有用
      kubectl logs my-pod  --since-time=2018-11-01T15:00:00Z
      # 指定时间戳输出日志            
      kubectl logs my-pod  --since=1h 
      # 指定时间段输出日志,单位s/m/h
 
# 快速创建一个depolyment
➜  ~ kubectl run --image=nginx:alpine nginx-app --port=8180
 
# 进入容器内部, 双破折号 "--" 用于将要传递给命令的参数与 kubectl 的参数分开。后面加上 -i 和 -t 来运行一个连接到你的终端的 Shell,
➜  ~ kubectl exec -it -n <namespace> <pod_name> -- /bin/bash
 
  -c, --container="": 容器名。如果未指定,使用pod中的一个容器。
  -p, --pod="": Pod名。
  -i, --stdin[=false]: 将控制台输入发送到容器。
  -t, --tty[=false]: 将标准输入控制台作为容器的控制台输入。。
 
# 给k8s对象设置标签
# 设置标签
➜  ~ kubectl label pod $POD_NAME app=v1
# 设置之后, 可如下使用: 
➜  ~ kubectl get pods -l app=v1
 
# 删除 k8s 对象
➜  ~ kubectl delete -n <namespace> <对象名称>
 
# 从 yaml 创建对象
➜  ~ kubectl create -f xxx.yaml
 
# 扩容, 指定扩大副本数到什么个
➜  ~ kubectl scale deployment <deploy_name> --replicas 10
 
# 自动扩容
➜  ~ kubectl autoscale deployment <deploy_name> --min=10 --max=15 --cpu-percent=80
 
# 更新镜像
➜  ~ kubectl set image deployment/<deploy_name> nginx=nginx:1.9.1
 
# 回滚版本
➜  ~ kubectl rollout undo deployment/<deploy_name>
 
#  对指定的replication controller执行滚动升级。
➜  ~ kubectl rolling-update <pod_name> -image=image:v2
 
# 拷贝本地文件到pod内部,用于pod和外部的文件交换,比如如下示例了如何在进行内外文件交换。
➜  ~ kubectl cp <pod_name>:/tmp/message.log message.log
 
# --watch 参数可以监控资源的状态,在状态变换时输出。在跟踪服务部署情况时很有用
➜  ~ kubectl get deployment <deploy_name> --watch

Kubernetes 排障

服务跑不起来怎么办?

# 服务跑不起来怎么办?
 
# 1、查看 deployment 是否ready
➜  ~ kubectl get deploy -n <namespace>
NAMESPACE              NAME                        READY   UP-TO-DATE   AVAILABLE   AGE
kube-system            kubernetes-dashboard        1/1     1            1           4h
 
# 2、查看 Pod 状态以及运行节点, pod 如果有问题,一般状态都是 CrashLoopBackOff 或者 failed 
➜  ~ kubectl get pods -n <namespace> -o wide
# 常见的错误
CrashLoopBackOff: 容器退出,kubelet正在将它重启
InvalidImageName: 无法解析镜像名称
ImageInspectError: 无法校验镜像
ErrImageNeverPull: 策略禁止拉取镜像
ImagePullBackOff: 正在重试拉取
RegistryUnavailable: 连接不到镜像中心
ErrImagePull: 通用的拉取镜像出错
 
# 3、查看 pod event 事件
➜  ~ kubectl describe pod -n <namespace> <pod_name>
Events:
  Type     Reason     Age                    From               Message
  ----     ------     ----                   ----               -------
  Normal   Scheduled  3h41m                  default-scheduler  Successfully assigned kubernetes-dashboard/kubernetes-dashboard-658485d5c7-qh94f to docker-desktop
  Normal   Pulled     3h41m                  kubelet            Successfully pulled image "kubernetesui/dashboard:v2.3.1" in 15.7084595s
  Normal   Pulled     3h41m                  kubelet            Successfully pulled image "kubernetesui/dashboard:v2.3.1" in 1.1687069s
  Warning  Unhealthy  3h40m                  kubelet            Liveness probe failed: Get "https://10.1.0.120:8443/": dial tcp 10.1.0.120:8443: connect: connection refused
 
# 可通过下面命令,查看一个命名空间所有的事件
➜  ~ kubectl get events -n <namespace>
 
 
# 4、如果pod正常访问,服务无法访问, 可以通过命令查看pod的日志,查看是否有报错信息,这也是我们平时业务查看日志最直接的一种方式,另外一种是通过 granfa 平台查看
➜  ~ kubectl logs -f <pod_name> -n <namespace>

# 5、如果不是业务报错,还可以查看一下 k8s node 的状态,看看是不是因为 node not ready 导致pod运行不起来
➜  ~ kubectl get no -o wide
NAME             STATUS   ROLES    AGE    VERSION   INTERNAL-IP    EXTERNAL-IP   OS-IMAGE         KERNEL-VERSION     CONTAINER-RUNTIME
docker-desktop   Ready    <none>   7d9h   v1.19.3   192.168.65.4   <none>        Docker Desktop   5.10.25-linuxkit   docker://20.10.7
 
# 6、如果 node 的状态不是 Ready,可以查看node的 event信息, 可看看 node 的具体负载情况,包括cpu、memory、disk是否有压力等情况
➜  ~ kubectl describe node <node_name>
 
# 7、查看 kubelet 是否存在异常日志(这个找 sre 看,因为这个要登到节点上查看)
➜  ~ journalctl -u kubelet -f 
 
# 8、查看网络插件是否启动(calico、flannel、weave),这些组件一般以 deployment 运行在 k8s集群内部

pod 的几种状态

  • Pending 创建pod的请求已经被k8s接受,但是容器并没有启动成功,可能处在:写数据到etcd,调度,pull镜像,启动容器这四个阶段中的任何一个阶段,pending伴随的事件通常会有:ADDED, Modified这两个事件的产生。

  • Running pod已经绑定到node节点,并且所有的容器已经启动成功,或者至少有一个容器在运行,或者在重启中。

  • Succeeded pod中的所有的容器已经正常的自行退出,并且k8s永远不会自动重启这些容器,一般会是在部署job的时候会出现。

  • Failed pod中的所有容器已经终止,并且至少有一个容器已经终止于失败(退出非零退出代码或被系统停止)。

  • Unknown 由于某种原因,无法获得pod的状态,通常是由于与pod的主机通信错误。

Service 无法访问?

# 1、检查 service 是否存在相应的endpoints,endpoints == pod_ip:port,如果该列表为空,则有可能是该 Service 的 LabelSelector 配置错误,可以用下面的方法确认一下
kubectl get endpoints <service-name>
 
# 2、查询 Service 的 LabelSelector
kubectl get svc <service-name> -o jsonpath='{.spec.selector}'
 
# 3、查询匹配 LabelSelector 的 Pod
kubectl get pods -l key1=value1,key2=value2
 
# 4、如果 Endpoints 正常,可以进一步检查, Pod 的 containerPort 与 Service 的 containerPort 是否对应直接访问 podIP:containerPort 是否正常
 
# 5、还有其他的原因会导致 Service 无法访问,比如
Pod 内的容器有可能未正常运行或者没有监听在指定的 containerPort 上
CNI 网络或主机路由异常也会导致类似的问题
kube-proxy 服务有可能未启动或者未正确配置相应的 iptables 规则,比如正常情况下名为 hostnames 的服务会配置以下 iptables 规则

Kubernetes 负载均衡

目前我们业务上pod 与 pod 之间的网络通信是通过 service提供服务的,因为 pod的 IP是不固定的,所以需要一个固定的IP为服务提供访问入口。原理其实是宿主机的kube-proxy生成的iptables规则 ,及core-dns生成的DNS记录, Service通过label标签选中Pod,被选中的的Pod称为Service的Endpoints(官方文档:https://kubernetes.io/zh/docs/concepts/services-networking/service/)

service 提供了四种访问方式,我们一般默认设置是 ClusterIP 方式:

  • ClusterIP:通过集群的内部 IP 暴露服务,选择该值时服务只能够在集群内部访问。

  • NodePort:通过每个节点上的 IP 和静态端口(NodePort)暴露服务。 NodePort 服务会路由到自动创建的 ClusterIP 服务。 通过请求 <节点 IP>:<节点端口>,你可以从集群的外部访问一个 NodePort 服务。

  • LoadBalancer:使用云提供商的负载均衡器向外部暴露服务。 外部负载均衡器可以将流量路由到自动创建的 NodePort 服务和 ClusterIP 服务上。

  • ExternalName:通过返回 CNAME 和对应值,可以将服务映射到 externalName 字段的内容(例如,foo.bar.example.com)。 无需创建任何类型代理

https://s21.ax1x.com/2024/05/11/pkZTx4P.png

参考

官方文档

Kubernetes 中文指南

排障指南:Kubernetes 故障排除指南(中文版).pdf