Kubernetes 最简实践
什么是 Kubernetes
Kubernetes是用于管理容器化应用程序集群的工具,自动且持续地监视集群并对其组成进行调整。官方的描述:Kubernetes 是一个可移植的、可扩展的开源平台,用于管理容器化的工作负载和服务,可促进声明式配置和自动化。
下面图片说明了当前部署架构的变迁,也是为什么要把服务部署在容器,管理在k8s的原因
Kubernetes 架构
Kubernetes 主要分两种角色的节点,一种是master节点,一种是 worker 节点。master节点中主要运行四个很关键的组件,分别是 API Server、etcd、kube-controller、kube-scheduler, worker节点主要运行 kubelet、docker、kube-proxy等三个主要的组件
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)。 无需创建任何类型代理