Kubernetes 笔记

Kubernetes 笔记

_

基础

各个服务架构一句话总结: Kubernetes 是由容器和进程互相配合实现的

这句话的意思是,部分操作,比如 apiserver,scheduler 是由容器来完成的,而像 Kubelet 是一个进程。Kubelet 实现了集群中最重要的关于 Node 和 Pod 的控制功能

(kubelet 等组件之所以不采用容器部署,是应为要操作宿主机的原因,比如分配资源,网络划分,隔着容器不好做这些事情)

协议规范:

CNI(Container Network Interface) CSI(Container Storage Interface) CRI(Container Runtime Interface)

etcd: 是一个高可用的分布式键值 (key-value) 数据库。etcd 内部采用 raft 协议作为一致性算法,有点像 zk,k8s 集群使用 etcd 作为它的数据后端,etcd 是一种无状态的分布式数据存储集群。数据以 key-value 的形式存储在其中

swap: 在 k8s 中最好禁用 swap,以免发生集群无法调度问题

flannel: 用来通信的网络插件。要符和 CNI 规范

k8s 用的的镜像示例

master 节点

CommandDescription
REPOSITORYTAGIMAGEIDCREATEDSIZE
registry.aliyuncs.com/google_containers/kube-proxyv1.14.120a2d703516510monthsago82.1MB
registry.aliyuncs.com/google_containers/kube-apiserverv1.14.1cfaa4ad74c3710monthsago210MB
registry.aliyuncs.com/google_containers/kube-controller-managerv1.14.1efb3887b411d10monthsago158MB
registry.aliyuncs.com/google_containers/kube-schedulerv1.14.18931473d5bdb10monthsago81.6MB
quay-mirror.qiniu.com/coreos/flannelv0.11.0-amd64ff281650a72112monthsago52.6MB
quay.io/coreos/flannelv0.11.0-amd64ff281650a72112monthsago52.6MB
registry.aliyuncs.com/google_containers/coredns1.3.1eb516548c18012monthsago40.3MB
registry.aliyuncs.com/google_containers/etcd3.3.102c4adeb21b4f14monthsago258MB
registry.aliyuncs.com/google_containers/pause3.1da86e6ba6ca12yearsago742kB

node 节点

CommandDescription
REPOSITORYTAGIMAGEIDCREATEDSIZE
registry.aliyuncs.com/google_containers/kube-proxyv1.14.120a2d703516510monthsago82.1MB
quay-mirror.qiniu.com/coreos/flannelv0.11.0-amd64ff281650a72112monthsago52.6MB
quay.io/coreos/flannelv0.11.0-amd64ff281650a72112monthsago52.6MB
registry.aliyuncs.com/google_containers/pause3.1da86e6ba6ca12yearsago742kB

除了容器外,还需要 kubelet 才是完整的

常用命令

kubectl explain node

kubectl get pods -n kube-system -o wide kubectl get nodes -o wide

kubectl get node 可以使用 -o 配合 wide json yaml

配合 jq 做内容提取

kubectl get nodes -o json | jq ".items[] | {name: .metadata.name} + .status.nodeInfo"

-w 参数

可以通过加 -w 开启资源监控,比如 kubectl get pod -w -oyaml 当 pod 被修改后,可以观察到 yaml 文件的变化

与k8s交互

在 K8S 中进行部署或者说与 K8S 交互的方式主要有三种:

命令式 命令式对象配置 声明式对象配置

镜像拉取策略

默认值是 IfNotPresent(在 initContainers 中,发现不配置并不会去使用本地的,所以最好显示的配置一下)

Always 总是拉取: 首先获取仓库镜像信息,如果仓库中的镜像与本地不同,那么仓库中的镜像会被拉取并覆盖本地。 如果仓库中的镜像与本地一致,那么不会拉取镜像。 如果仓库不可用,那么 pod 运行失败。

IfNotPresent 优先使用本地: 如果本地存在镜像,则使用本地的, 不管仓库是否可用。 不管仓库中镜像与本地是否一致。

Never 只使用本地镜像,如果本地不存在,则 pod 运行失败

固定node发布

deploy 上用选择器,就可以固定在一个 node 上

资源删除

一般是 delete -f .yaml 删除对应的资源,然后 apply 再应用,资源生效

删除 pod: 先删除 pod,然后再删除 deployment。只删除 pod,去查看的时候 pod 还是存在的,再去删除 dep,查看 pod 消失

service 和 deployment

deployment 负责创建 pod,只有 de 存在,pod 就会维持住,pod 被删除了也会被 dev 拉起。这样 pod 的服务是不确定的,它的 ip 一直在变化

所以需要 service

Service 是 Kubernetes 中的一种服务发现机制:

Pod 有自己的 IP 地址 Service 被赋予一个唯一的 dns name Service 通过 label selector 选定一组 Pod Service 实现负载均衡,可将请求均衡分发到选定这一组 Pod 中

标签操作

kubectl get pods --show-labels  # 查看 pod 所有标签信息 kubectl get pods -l app  # 过滤包含 app 的标签 kubectl get pods -l app #过滤包含 app 的标签及显示值 kubectl label pods pod-demo release=canary  # 给 pod-demo 增加标签 kubectl label pods pod-demo release=stable --overwrite  # 修改标签

通过标签来过滤资源

kubectl get pods -l run=myapp kubectl get pods -l run=myapp --show-labels kubectl get pods -l run!=client --show-labels

通过标签部署到指定节点

nodeSelector 写上标签,调度的时候就只会调度到这几个有标签的节点 (该特性在官方准备在新版本中去除了)

nodeName: 指定主机名称,比如 k8s-09

kubernetes.io/hostname=k8s-09 一般节点都会打上这个标签,描述了主机名称信息

spec:
  # priorityClassName: system-cluster-critical
  nodeSelector:
    kubernetes.io/hostname: k8s-worker06

命名空间

资源创建的时候,可以指定命名空间,命名空间内各资源可以互相访问 如果 pod 和 service 在不同的命名空间,是不能直接访问的,这时候要跨命名空间访问

使用 服务加命名空间的方式 比如服务 是 oap,那么 web-b 命名空间想访问 web-f 命名空间的 oap service,使用oap.web-f

查看pod日志

使用 logs 命令,加上 pod 实例,如果 pod 里面有多个容器,还要加上容器名称,举例如下:

kubectl -n kube-system logs alertmanager-54f5b4447b-2jvzz prometheus-alertmanager

引用pod的信息

env:
    - name: MY_NODE_NAME
      valueFrom:
        fieldRef:
          fieldPath: spec.nodeName
    - name: MY_POD_NAME
      valueFrom:
        fieldRef:
          fieldPath: metadata.name
    - name: MY_POD_NAMESPACE
      valueFrom:
        fieldRef:
          fieldPath: metadata.namespace
    - name: MY_POD_IP
      valueFrom:
        fieldRef:
          fieldPath: status.podIP
    - name: MY_POD_SERVICE_ACCOUNT
      valueFrom:
        fieldRef:
          fieldPath: spec.serviceAccountName

通过环境变量,引用当前 pod 的 container 的信息注入到外部

spec.nodeName : pod 所在节点的 IP、宿主主机 IP

status.podIP :pod IP

metadata.namespace : pod 所在的 namespace

更多参数: https://kubernetes.io/docs/tasks/inject-data-application/environment-variable-expose-pod-information/

https://github.com/kubernetes/kubernetes/issues/24657

单个文件的方式挂载 configMap

主要是用了 subPath 这个配置,另外记得 volumes 挂载不要用驼峰命名,可以用横杆加小写

挂载多个文件

volumeMounts:
- mountPath: /skywalking/config/application.yml
    name: config
    subPath: application.yml
- mountPath: /skywalking/config/alarm-settings.yml
    name: alarm-config
    subPath: alarm-settings.yml

volumes:
- name: application-config
  configMap:
    defaultMode: 420
    name: oap-config
    items:
      - key: application.yml
        path: application.yml
- name: alarm-config
  configMap:
    defaultMode: 420
    name: oap-config
    items:
      - key: alarm-settings.yml
        path: alarm-settings.yml

调度策略PriorityClass

参考:https://www.ibm.com/support/knowledgecenter/zh/bluemix_stage/containers/cs_pod_priority.html

除了常用的 nodeSelector,k8s 还提供了优先级调度,也就是抢占式调度,优先级高的 pod 会先调度,在资源不足的情况下会牺牲掉低优先级的 pod 先把高优先级的 pod 先调度

具体做法就是在资源定义的时候关联一个 PriorityClass 对象,这个对象会设置优先级

apiVersion: scheduling.k8s.io/v1alpha1
kind: PriorityClass
metadata:
  name: high-priority
value: 1000000
globalDefault: false
description: "This priority class should be used for XYZ service pods only."
globalDefault

	可选:将此字段设置为 true 可将此优先级类设置为全局缺省值,此缺省值将应用于安排的未指定 priorityClassName 值的每个 pod。集群中只能有 1 个优先级类可设置为全局缺省值。如果没有全局缺省值,那么没有指定 priorityClassName 的 pod 的优先级为零 (0)。

缺省优先级类不会设置 globalDefault。如果在集群中创建了其他优先级类,那么可以通过运行 kubectl describe priorityclass <name>.

在 containers 配置中加上 priorityClassName: high-priority

系统默认的优先级类

名称	设置方	优先级值	用途
system-node-critical	Kubernetes	2000001000	选择在创建集群时部署到 kube-system 名称空间中的 pod 会使用此优先级类来保护工作程序节点的关键功能,例如联网、存储器、日志记录、监视和度量值 pod。
system-cluster-critical	Kubernetes	2000000000	选择在创建集群时部署到 kube-system 名称空间中的 pod 会使用此优先级类来保护集群的关键功能,例如联网、存储器、日志记录、监视和度量值 pod。
ibm-app-cluster-critical	IBM	900000000	选择在创建集群时部署到 ibm-system 名称空间中的 pod 会使用此优先级类来保护应用程序的关键功能,例如负载均衡器 pod

kubectl get priorityclasses kubectl get priorityclass <priority_class> -o yaml > Downloads/priorityclass.yaml

Taints与Tolerations 污点和容忍

kubectl taint node [node] key=value[effect]   
     其中[effect] 可取值: [ NoSchedule | PreferNoSchedule | NoExecute ]
      NoSchedule: 一定不能被调度
      PreferNoSchedule: 尽量不要调度
      NoExecute: 不仅不会调度, 还会驱逐Node上已有的Pod

kubectl taint node node1 key1=value1:NoSchedule
kubectl taint node node1 key1=value1:NoExecute
kubectl taint node node1 key2=value2:NoSchedule

kubectl describe node node1

删除

kubectl taint node node1 key1:NoSchedule- # 这里的 key 可以不用指定 value kubectl taint node node1 key1:NoExecute- kubectl taint node node1 key1- 删除指定 key 所有的 effect kubectl taint node node1 key2:NoSchedule-

master 节点设置

kubectl taint nodes master1 node-role.kubernetes.io/master=:NoSchedule

容忍 tolerations 主节点的 taints

以上面为 master1 设置的 taints 为例, 你需要为你的 yaml 文件中添加如下配置, 才能容忍 master 节点的污点

在 pod 的 spec 中设置 tolerations 字段

tolerations:
- key: "node-role.kubernetes.io/master"
  operator: "Equal"
  value: ""
  effect: "NoSchedule"

Jsonnet

Jsonnet 是一个帮助你定义 JSON 的数据的特殊配置语言。Jsonnet 可以对 JSON 结构里面的元素进行运算,就像模版引擎给纯文本带来好处一样

// Jsonnet Example
{
    person1: {
        name: "Alice",
        welcome: "Hello " + self.name + "!",
    },
    person2: self.person1 { name: "Bob" },
}

转换编译成 JSON 是如下:

{
   "person1": {
      "name": "Alice",
      "welcome": "Hello Alice!"
   },
   "person2": {
      "name": "Bob",
      "welcome": "Hello Bob!"
   }
}

谷歌:建议使用 Jsonnet 来增强 JSON。Jsonnet 是完全向后兼容 JSON 的,引入了很多新特效,譬如注释、引用、算术运算、条件操作符,数组和对象内含,引入,函数,局部变量,继承等

Kubernetes Custom Resources

自定义资源,在 kubernetes 中,我们用资源来概括部署在集群中各种东西,比如 deployment 就是一种资源

在生命 deployment 这种资源的时候,有一个 kind 的配置,表明了资源类型

apiVersion: apps/v1
kind: Deployment

Kubernetes Custom Resources 就是自定义资源用的,可以扩展 k8s,不过这属于比较高级的话题,目前在 Prometheus-operator 中有见到过

apiVersion: monitoring.coreos.com/v1
kind: Prometheus

它会定义一些 CustomResourceDefinition 之类的 yaml 文件,所以当我们看到 kind 不是我们熟悉的类型的时候,很可能就是做了扩展

由于平时也不用,不做展开了,官方参考

https://kubernetes.io/zh/docs/tasks/extend-kubernetes/custom-resources/custom-resource-definition-versioning/

Kubernetes中的开放接口CRI、CNI、CSI

参考 https://zhuanlan.zhihu.com/p/33390023

CRI(Container Runtime Interface):容器运行时接口,提供计算资源 CNI(Container Network Interface):容器网络接口,提供网络资源 CSI(Container Storage Interface):容器存储接口,提供存储资源

容器重启策略

Pod 的 spec 中包含一个 restartPolicy 字段,其可能取值包括 Always、OnFailure 和 Never。默认值是 Always

restartPolicy 适用于 Pod 中的所有容器 restartPolicy 仅针对同一节点上 kubelet 的容器重启动作 当 Pod 中的容器退出时,kubelet 会按指数回退 方式计算重启的延迟(10s、20s、40s、...),其最长延迟为 5 分钟。 一旦某容器执行了 10 分钟并且没有出现问题,kubelet 对该容器的重启回退计时器执行 重置操作

参考 https://kubernetes.io/zh/docs/concepts/workloads/pods/pod-lifecycle/

pod 调度不均衡

https://www.cnblogs.com/rancherlabs/p/12228019.html

https://cloud.tencent.com/developer/article/1671811

静态Pod

静态 Pod 在指定的节点上由 kubelet 守护进程直接管理,不需要 API 服务器 监管。 与由控制面管理的 Pod(例如,Deployment) 不同;kubelet 监视每个静态 Pod(在它崩溃之后重新启动)。

静态 Pod 永远都会绑定到一个指定节点上的 Kubelet。

kubelet 会尝试通过 Kubernetes API 服务器为每个静态 Pod 自动创建一个 镜像 Pod。 这意味着节点上运行的静态 Pod 对 API 服务来说是不可见的,但是不能通过 API 服务器来控制

删除被驱逐节点

kubectl -n eos-bpm get pods | grep Evicted |awk '{print$1}'|xargs kubectl -n eos-bpm delete pods

Kubernetes Resource QoS Classes

QoS 是容器资源配置的一个分类,驱逐策略将根据不同的分类来决定先驱逐谁

Guaranteed:每个容器都必须设置 CPU 和内存的限制和请求(最大和最小) Burstable:在不满足 Guaranteed 的情况下,至少设置一个 CPU 或者内存的请求 BestEffort:什么都不设置,可用使用到系统的最大资源

最小被驱逐的就是 BestEffort 类型

关于 request 和 limit

必须要满足这个条件

0 <= request <= limit

limit 0 就是资源没有限制,理解 request 是容器启动的最小资源,为 0 就是没有资源也可用启动,当然没有资源是起不来的 request 只是保证被调度的时候,工作节点需要有这么多的资源。而 limit 会限制能使用的最大资源

查看所有pod

kubectl get pods --all-namespaces

ResourceQuota(配额)

资源配额的支持在很多 Kubernetes 版本中是默认开启的。当 API 服务器的 --enable-admission-plugins= 参数中包含 ResourceQuota 时,资源配额会被启用

kubectl get resourcequota -n namespace kubectl describe resourcequota -n namespace

kubectl get ResourceQuota -A

kubectl get resourcequota namespace --namespace=namespace --output=yaml

当一个集群有分配 ResourceQuota 和对应的 Namespace 时,部署的 pod 需要声明 request 和 limit,否则 pod 启动失败 开启了 resource quota 时,用户创建 pod,必须指定 cpu、内存的 requests or limits ,否则创建失败。resourceQuota 搭配 limitRanges 使用:limitRange 可以配置创建 Pod 的默认 limit/reques

在一个多用户、多团队的 k8s 集群上,通常会遇到一个问题,如何在不同团队之间取得资源的公平,即,不会因为某个流氓团队占据了所有资源,从而导致其他团队无法使用 k8s。 k8s 的解决方法是,通过 RBAC 将不同团队(or 项目)限制在不同的 namespace 下,通过 resourceQuota 来限制该 namespace 能够使用的资源

配置参考,通过命名空间去绑定,如果要制空配额设置,可用删除资源对象,或者把 spec 部分注释掉

apiVersion: v1
kind: ResourceQuota
metadata:
  name: quota-test
  namespace: test
spec:
  hard:
    requests.cpu: "2"
    requests.memory: 2Gi
    limits.cpu: "4"
    limits.memory: 4Gi
    requests.nvidia.com/gpu: 4
    pods: "3"
    services: "6"

可以通过 describe 查看 used,也就是当前配额的占用 (只有 pod 声明了 resources,并且不是无限制的,才会被配额控制和统计到,也就是 pod resources 不要设置 0,这种做法不合规范)

场景举例

  1. 查看 ResourceQuota 的 describe,used 字段只会统计配置了资源限制的 pod,也就是 pod 不声明 resources,或者设置无限制这里 used 不会统计到 一般会认为 used 会把当前命名空间的资源占用统计到,但是从设计的角度来说,直接拉取 pod 的 resources 是最容易的,具体使用到多少资源属于监控的范畴,减少了 API 的压力

  2. 已配置 ResourceQuota,创建新的 pod 资源占用超过配额。此时 pod 无法创建,replicaset 状态不满足。删除配额声明或修改上限,等会 pod 会继续创建

  3. pod 已存在,没有配额声明,但是资源声明 2Gi,此时创建配额声明限制是 1Gi,配额可以创建,已存在的 pod 也不会受到影响。配额的 used 字段显示是 2Gi

  4. 配额限制声明 0,pod resources 声明是 0,可以创建 pod,假如 pod 声明是 1Gi,不能创建 (这种情况是比较坑的,最好避免)

  5. 配额的声明只管 pod 设置了 resources,如果 pod 的 resources 是无限制,也就是都是 0,那么不受配额的影响 (也是要避免出现这种情况)

  6. 总之在使用了配额后,pod 就必须声明资源限制,然后不要设置为 0,这样才能更好的发挥 ResourceQuota 的作用,可以配合 limitRange 来对 pod 添加默认的 resources

参考

https://blog.csdn.net/sinron_wu/article/details/106518824

CPU和内存单位

在资源配置的时候用到

CPU: 2, 200m memory: 100Mi 2Gi

cpu 1000m = 1 不带单位

设置namespace上下文

kubectl config set-context $(kubectl config current-context) --namespace=sy

k8s中的各种ip

  1. NODE IP

也称为 INTERNAL-IP,通过 kubectl get node -o wide 查看到的就是 INTERNAL-IP

  1. POD IP

pod 网络的 IP 地址,是每个 POD 分配的虚拟 IP,可以使用 kubectl get pod -o wide 来查看

  1. CLUSTER-IP

它是 Service 的地址, 是一个虚拟地址(无法 ping),是使用 kubectl create 时,--port 所指定的端口绑定的 IP, 各 Service 中的 pod 都可以使用 CLUSTER-IP:port 的方式相互访问(当然更应该使用 ServiceName:port 的方式)可以使用kubectl get svc -o wide进行查看

操作工作负载的方式

rolling-update 版本操作

patch 方式,patch 不会去重建 Pod,Pod 的 IP 可以保持。kubectl patch -f node.json -p '{"spec":{"unschedulable":true}}'

create 会从完整的 yaml 文件操作,先删除资源对象再次创建(测试并不会删除,如果对一个已存在的资源对象操作,会报错该资源已存在)

apply 只会更新 yaml 文件中声明的部分,所以 apply 操作的 yaml 文件可用不完整,但是 create 要求完整

kubectl proxy 让外部网络访问K8S

基本使用 kubectl proxy --port=8009

开放地址 kubectl proxy --address=0.0.0.0 --port=8009

授权其它主机 (相当于其它服务都能随意服务,可以限制 accept 的范围) kubectl proxy --address='0.0.0.0' --accept-hosts='^*$' --port=8009

kubelet 配置

https://v1-18.docs.kubernetes.io/zh/docs/tasks/administer-cluster/kubelet-config-file/

Headless Service

通过 service 访问 pod

  1. 以 Service 的 VIP(Virtual IP, 即: 虚拟 IP) 方式. 比如: 当我访问 192.168.0.1 这个 Service 的 IP 地址时, 它就是一个 VIP. 在实际中, 它会把请求转发到 Service 代理的具体 Pod 上.

  2. 以 Service 的 DNS 方式. 在这里又分为两种处理方法:

第一种是 Normal Service. 这种情况下, 当访问 DNS 记录时, 解析到的是 Service 的 VIP.

第二种是 Headless Service. 这种情况下, 访问 DNS 记录时, 解析到的就是某一个 Pod 的 IP 地址.

可以看到,Headless Service 不需要分配一个 VIP, 而是可以直接以 DNS 记录的方式解析出被代理 Pod 的 IP 地址. 这样设计有什么好处呢? 这样设计可以使 Kubernetes 项目为 Pod 分配唯一 "可解析身份". 而有了这个身份之后, 只要知道了一个 Pod 的名字以及它对应的 Service 的名字, 就可以非常确定地通过这条 DNS 记录访问到 Pod 的 IP 地址.

PVC访问模式,状态,回收策略

PVC 是有命名空间的,而 PV 是集群级别的资源对象

访问模式包括:
  ▷ ReadWriteOnce —— 该volume只能被单个节点以读写的方式映射
  ▷ ReadOnlyMany —— 该volume可以被多个节点以只读方式映射
   ▷ ReadWriteMany —— 该volume只能被多个节点以读写的方式映射    
 状态
  ▷ Available:空闲的资源,未绑定给PVC 
  ▷ Bound:绑定给了某个PVC 
  ▷ Released:PVC已经删除了,但是PV还没有被集群回收 
  ▷ Failed:PV在自动回收中失败了 
 当前的回收策略有:
  ▷ Retain:手动回收
  ▷ Recycle:需要擦出后才能再使用
  ▷ Delete:相关联的存储资产,如AWS EBS,GCE PD,Azure Disk,or OpenStack Cinder卷都会被删除
   当前,只有NFS和HostPath支持回收利用,AWS EBS,GCE PD,Azure Disk,or OpenStack Cinder卷支持删除操作。

PV与PVC绑定

用户创建包含容量、访问模式等信息的 PVC,向系统请求存储资源。系统查找已存在 PV 或者监控新创建 PV,如果与 PVC 匹配则将两者绑定。如果 PVC 创建动态 PV,则系统将一直将两者绑定 PV 与 PVC 的绑定是一一对应关系,不能重复绑定与被绑定。如果系统一直没有为 PVC 找到匹配 PV,则 PVC 无限期维持在 "unbound" 状态,直到系统找到匹配 PV。实际绑定的 PV 容量可能大于 PVC 中申请的容量

PV 的生命周期独立于 Pod Pod 消耗的是节点资源,PVC 消耗的是 PV 资源

存储对象使用中保护

如果启用了存储对象使用中保护特性,则不允许将正在被 pod 使用的活动 PVC 或者绑定到 PVC 的 PV 从系统中移除,防止数据丢失。活动 PVC 指使用 PVC 的 pod 处于 pending 状态并且已经被指派节点或者处于 running 状态。

如果已经启用存储对象使用中保护特性,且用户删除正在被 pod 使用的活动 PVC,不会立即删除 PVC,而是延后到其状态变成非活动。同样如果用户删除已经绑定的 PV,则删除动作延后到 PV 解除绑定后。

当 PVC 处于保护中时,其状态为 "Terminating" 并且其 "Finalizers" 包含 "kubernetes.io/pvc-protection"

PVC回收策略

pvc 不会和 pod 联动删除,也就是说 pvc 要手动删除。pv 才是集群的资源,通过策略决定当 pvc 删除的时候,pv 做何处理 Retain 的话,则 pv 存在,可以下次再由新的 pvc 绑定上去。Delete 就是删除 pvc 的时候会把 pv 也删除了,这样数据就不存在了

如果是动态创建的 pv,比如使用 storageClass,pv 的回收策略由 storageClass 决定

基于环境变量的服务发现

通常,创建 service 后,可以通过 dns 做服务发现。还可以基于环境变量的服务发现

REDIS_MASTER_SERVICE_HOST=10.0.0.11
REDIS_MASTER_SERVICE_PORT=6379
REDIS_MASTER_PORT=tcp://10.0.0.11:6379
REDIS_MASTER_PORT_6379_TCP=tcp://10.0.0.11:6379
REDIS_MASTER_PORT_6379_TCP_PROTO=tcp
REDIS_MASTER_PORT_6379_TCP_PORT=6379
REDIS_MASTER_PORT_6379_TCP_ADDR=10.0.0.11

环境变量的服务发现有个前提,必须先创建 service 后创建 pod,才能在 pod 中获取到注入的环境变量 假如顺序反了,但是之后 pod 被删除了,此时会创建新的 pod,然后这个时候 service 已经存在了,就可以注入环境变量

扩缩容scale

deployment 用 rc statefulSet 用 sts

kubectl -n monitoring scale rc prometheus-k8s --replicas=1 kubectl -n monitoring scale sts prometheus-k8s --replicas=1

StorageClass

StorageClass 是集群的概念,并且创建后不能随意修改一些配置,比如 provisioner

terminationGracePeriodSeconds

K8S 会给老 POD 发送 SIGTERM 信号,并且等待 terminationGracePeriodSeconds 这么长的时间 (默认为 30 秒),然后终止 pod terminationGracePeriodSeconds 是留给程序的缓冲时间

启动命令和参数

command: ["/bin/sh", "-c", "my.py xxx"]

建议使用这种方式,前 0,1 个元素固定不变,把执行命令和参数写到第 3 个元素位置

复杂一点的情况,可以使用 args,命令的全部都算成一个元素,注意 - 只用了一个,里面的内容都会作为一个脚本的内容被执行

command: ["/bin/sh"]
args:
  - "-c"
  - redis-trib.py replicate
    --password qwe123456
    --master-addr `dig +short redis-cluster5-redis-0.redis-cluster5-headless-service.redis.svc.cluster.local`:6379
    --slave-addr `dig +short redis-cluster5-redis-3.redis-cluster5-headless-service.redis.svc.cluster.local`:6379
command: ['sh']
args:
- "-c"
- |
  set -ex
  cp /configmap/* /etc/rabbitmq
  rm -f /var/lib/rabbitmq/.erlang.cookie
  {{- if .Values.forceBoot }}
  if [ -d "${RABBITMQ_MNESIA_DIR}" ]; then
    touch "${RABBITMQ_MNESIA_DIR}/force_load"
  fi
  {{- end }}

如果要执行多组命令,用;分割

command: ["/bin/sh"]
args:
  - "-c"
  - redis-trib.py replicate
    --password qwe123456
    --master-addr `dig +short redis-cluster5-redis-1.redis-cluster5-headless-service.redis.svc.cluster.local`:6379
    --slave-addr `dig +short redis-cluster5-redis-4.redis-cluster5-headless-service.redis.svc.cluster.local`:6379;
    redis-trib.py replicate
    --password qwe123456
    --master-addr `dig +short redis-cluster5-redis-2.redis-cluster5-headless-service.redis.svc.cluster.local`:6379
    --slave-addr `dig +short redis-cluster5-redis-5.redis-cluster5-headless-service.redis.svc.cluster.local`:6379

参考:

https://kubernetes.io/zh/docs/tasks/inject-data-application/define-command-argument-container/

删除K8s Namespace时卡在Terminating状态

kubectl get namespace redis -o json > tmp.json kubectl get namespace test-rabbitmq -o json > tmp.json

修改 tmp.json 删除其中的 spec 字段,当前目录执行命令

curl -H "Authorization: Bearer tokenXXX" -k -H "Content-Type: application/json" -X PUT --data-binary @tmp.json https://xxx:6443/api/v1/namespaces/redis/finalize

本次演示的是删除 redis 的命名空间

在deployment中使用pvc,pv通过storageClass创建

apiVersion: v1
kind: PersistentVolumeClaim
metadata:
  name: test-pvc
  annotations:
    volume.beta.kubernetes.io/storage-class: "course-nfs-storage" # storageClass名称
spec:
  accessModes:
  - ReadWriteMany
  resources:
    requests:
      storage: 1Mi
volumes:
  - name: nfs-pvc
    persistentVolumeClaim:
      claimName: test-pvc # pvc名称

securityContext

我们可用通过 securityContext 获得较高的权限,执行一些命令

比如在容器启动前,修改 somaxconn

initContainers:
   - name: sysctl
      image: alpine:3.10
      securityContext:
          privileged: true
       command: ['sh', '-c', "echo 511 > /proc/sys/net/core/somaxconn"]

Configmap 动态更新的实现

Configmap 或 Secret 使用有两种方式,一种是 env 系统变量赋值,一种是 volume 挂载赋值,env 写入系统的 configmap 是不会热更新的,而 volume 写入的方式支持热更新!

对于 env 环境的,必须要滚动更新 pod 才能生效,也就是删除老的 pod,重新使用镜像拉起新 pod 加载环境变量才能生效。 对于 volume 的方式,虽然内容变了,但是需要我们的应用直接监控 configmap 的变动,或者一直去更新环境变量才能在这种情况下达到热更新的目的。

应用不支持热更新,可以在业务容器中启动一个 sidercar 容器,监控 configmap 的变动,更新配置文件,或者也滚动更新 pod 达到更新配置的效果。

可用使用 Reloader 来实现 pod 的滚动更新,生效 configMap 参考: https://juejin.cn/post/6897882769624727559

获取API版本

kubectl api-versions

K8S Pod 保护之 PodDisruptionBudget

这是 k8s 保证服务 SLA 的一种策略,当自愿中断(或者叫主动逃离,即显示的命令操作等,比如删除控制器,删除 Pod。与之相对的非自愿中断就是比如 OOM 一类的)发生的过程中,PDB 会保证 Pod 至少有一定的数量可用

比如 10 个 Pod,PDB 是 2,那么因为滚动升级,k8s 会保证至少有 2 个 pod 能够使用

1、minAvailable设置成了数值5:应用POD集群中最少要有5个健康可用的POD,那么就可以进行操作。

2、minAvailable设置成了百分数30%:应用POD集群中最少要有30%的健康可用POD,那么就可以进行操作。

3、maxUnavailable设置成了数值5:应用POD集群中最多只能有5个不可用POD,才能进行操作。

4、maxUnavailable设置成了百分数30%:应用POD集群中最多只能有30%个不可用POD,才能进行操作。

参考: https://www.jianshu.com/p/7da065228a04

/etc/resolv.conf

/etc/resolv.conf 它是 DNS 客户机配置文件,用于设置 DNS 服务器的 IP 地址及 DNS 域名,还包含了主机的域名搜索顺序。 该文件是由域名解析 器(resolver,一个根据主机名解析 IP 地址的库)使用的配置文件。 它的格式很简单,每行以一个关键字开头,后接一个或多个由空格隔开的参数。

resolv.conf 的关键字主要有四个,分别是:

nameserver    //定义DNS服务器的IP地址
domain        //定义本地域名
search        //定义域名的搜索列表
sortlist      //对返回的域名进行排序

一般用的最多的是 nameserver

  1. nameserver  表明 DNS 服务器的 IP 地址。可以有很多行的 nameserver,每一个带一个 IP 地址。在查询时就按 nameserver 在本文件中的顺序进行,且只有当第一个 nameserver 没有反应时才查询下面的 nameserver。

  2. domain    声明主机的域名。很多程序用到它,如邮件系统;当为没有域名的主机进行 DNS 查询时,也要用到。如果没有域名,主机名将被使用,删除所有在第一个点 (.) 前面的内容。

  3. search    它的多个参数指明域名查询顺序。当要查询没有域名的主机,主机将在由 search 声明的域中分别查找。domain 和 search 不能共存;如果同时存在,后面出现的将会被使用。

  4. sortlist   允许将得到域名结果进行特定的排序。它的参数为网络 / 掩码对,允许任意的排列顺序。

k8s的运用:k8s 默认会给 /etc/resolv.conf 文件新增 4 个搜索域,查看 pod 的配置,可以找到以下内容

nameserver 10.96.0.10
search eos-mid-public.svc.cluster.local svc.cluster.local cluster.local
options ndots:5

Node 节点警告事件,如果宿主机配置了搜索域,会收到以下警告事件。宿主机的搜索域被注到容器中了。解决方法是删除这个 search 配置

Warning  CheckLimitsForResolvConf  6m20s (x70 over 20h)  kubelet, k8s-worker07  Resolv.conf file '/etc/resolv.conf' contains search line consisting of more than 3 domains!

还有一种是 nameserver 超出 3 个的报错,删掉宿主机多余的 nameserver,参考:https://www.cnblogs.com/cuishuai/p/10980852.html

java k8s API Unknown apiVersionKind

在本地开发是正常的,打成 jar 包后就会出现这个问题,主要是类没被装载到。ClassPath.getTopLevelClasses() returns empty list

参考 issues: https://github.com/kubernetes-client/java/issues/365

解决方法

<plugin>
    <groupId>org.springframework.boot</groupId>
    <artifactId>spring-boot-maven-plugin</artifactId>
    <configuration>
        <!-- Enable io.kubernetes:client-java to find its model classes. -->
        <requiresUnpack>
            <dependency>
                <groupId>io.kubernetes</groupId>
                <artifactId>client-java-api</artifactId>
            </dependency>
        </requiresUnpack>
    </configuration>
</plugin>

hostNetwork

主机网络模式,可以让 pod 把端口直接绑定到宿主机

通常配合 dnsPolicy: ClusterFirstWithHostNet 一起使用,不然 dns 解析可能会有问题,比如服务发现

hostNetwork: true
dnsPolicy: ClusterFirstWithHostNet

kubeadm 重新生成token

  1. 先获取token
  • 列出 token,可能会有找不到的情况 kubeadm token list | awk -F""'{print $1}' |tail -n 1

  • 如果过期可先执行此命令 kubeadm token create #重新生成 token

  1. 获取CA公钥的哈希值

openssl x509 -pubkey -in /etc/kubernetes/pki/ca.crt | openssl rsa -pubin -outform der 2>/dev/null | openssl dgst -sha256 -hex | sed 's/^ .* //'

  1. 从节点加入集群

kubeadm join 192.168.40.xx:6443 --token token 填这里 --discovery-token-ca-cert-hash sha256: 哈希值填这里

  1. 节点加入集群的其它方式

kubeadm token create --print-join-command 直接得到 join 命令的全部内容,适用于 node 节点加入

kubeadm join 10.90.16.xx:6443 --token #token --discovery-token-ca-cert-hash #sha256

集群加入新的 master,需要先获取证书,执行下面命令

旧版本: kubeadm init phase upload-certs --experimental-upload-certs 新版本: kubeadm init phase upload-certs --upload-certs

得到一个 certificate-key,执行下面的命令

旧版本: kubeadm join 10.90.16.xx:6443 --token #token --discovery-token-ca-cert-hash #sha256 --experimental-control-plane --certificate-key #certificate-key 新版本: kubeadm join 10.90.16.xx:6443 --token #token --discovery-token-ca-cert-hash #sha256 --control-plane --certificate-key #certificate-key

unknown flag: --experimental-upload-certs,新版本不再支持此参数了,变更为:--upload-certs

Pod状态

Pending API Server 已经创建该 Pod,但在 Pod 内还有一个或多个容器的镜像没有创建,包括正在下载镜像的过程 Runnung Pod 内所有容器均已创建,且至少有一个容器处于运行状态、正在启动状态或正在重启状态 Succeeded Pod 内所有容器均成功执行后退出,且不会再重启 Failed Pod 内所有容器均已退出,但至少有一个容器退出为失败状态 Unknown 由于某种原因无法获取该 Pod 的状态,可能由于网络通信不畅导致

Pod重启策略

Pod 的重启策略(RestartPolicy)应用与 Pod 内所有容器,并且仅在 Pod 所处的 Node 上由 kubelet 进行判断和重启操作 当某个容器异常退出或者健康检查失败时,kubelet 将根据 RestartPolicy 的设置来进行相应的操作

Pod 的重启策略包括:Always、OnFailure 和 Never,默认值为 Always

Always:当容器失效时,由 kubelet 自动重启该容器 OnFailure:当容器终止运行且退出码不为 0 时,由 kubelet 自动重启该容器 Never:不论容器运行状态如何,kubelet 都不会重启该容器

readiness和liveness

  1. readiness和liveness的核心区别

实际上 readiness 和 liveness 就如同字面意思 readiness 就是意思是否可以访问,liveness 就是是否存活 如果一个 readiness 为 fail 的后果是把这个 pod 的所有 service 的 endpoint 里面的改 pod ip 删掉,意思就这个 pod 对应的所有 service 都不会把请求转到这 pod 来了 但是如果 liveness 检查结果是 fail 就会直接 kill container,当然如果你的 restart policy 是 always 会重启 pod

  1. 什么样才叫readiness/liveness检测失败呢

实际上 k8s 提供了 3 中检测手段

  • http get 返回200-400算成功,别的算失败
  • tcp socket 你指定的tcp端口打开,比如能telnet 上
  • cmd exec 在容器中执行一个命令 推出返回0 算成功

每中方式都可以定义在 readiness 或者 liveness 中 比如定义 readiness 中 http get 就是意思说如果我定义的这个 path 的 http get 请求返回 200-400 以外的 http code 就把我从所有有我的服务里面删了吧,如果定义在 liveness 里面就是把我 kill 了

  1. 什么时候用readiness 什么时候用readiness

比如如果一个 http 服务你想一旦它访问有问题我就想重启容器。那你就定义个 liveness 检测手段是 http get。反之如果有问题我不想让它重启,只是想把它除名不要让请求到它这里来。就配置 readiness

注意,liveness不会重启pod,pod是否会重启由你的restart policy 控制

exec 多容器 情况

使用 -c 指定 container

kubectl -n kong2 exec -it ingress-kong2-55bb4845b4-zzq46 -c proxy env | grep HOSTNAME

内存

kubernetes 上报的 container_memory_working_set_bytes

container_memory_working_set_bytes 是取自 cgroup memory.usage_in_bytes 与 memory.stat total_inactive_file(静态不活跃的内存)

memory.usage_in_bytes 的统计数据是包含了所有的 file cache 的 total_active_file 和 total_inactive_file 都属于 file cache 的一部分,并且这两个数据并不是业务真正占用的内存,只是系统为了提高业务的访问 IO 的效率,将读写过的文件缓存在内存中,file cache 并不会随着进程退出而释放,只会当容器销毁或者系统内存不足时才会由系统自动回收

patch 更新资源对象

path 是个很有用的操作,有些修改 apply 并不会重启容器,这导致修改失效,通常这种情况是我们操作不符合规范,应该使用 path 去更新资源对象

kubectl patch service istio-ingressgateway -n istio-system -p '{"spec":{"type":"NodePort"}}' kubectl -n istio-system patch deployment prometheus -p '{"spec":{"template":{"spec":{"nodeSelector":{"nodeType": "mid"}}}}}'

{"spec":{"template":{"spec":{"nodeSelector":{"resources":{"limits": {"cpu": "1000m"}}}}}}}

部署ELB

私有云一般很少用到 ELB,但是我们也可以去为集群做一个 ELB,硬件负载均衡或者软件负载均衡都可以

用 nginx 来实现,比如在部署 istio 的时候,LoadBalancer 处于 pending 状态的。15021:31929/TCP,80:30186/TCP,443:32085/TCP,31400:32543/TCP,15443:30676/TCP 需要暴露这几个端口

找一台服务器部署 nginx

stream {
    server {
        listen 80;
        proxy_pass 192.168.120.110:30186;
    }
    server {
        listen 443;
        proxy_pass 192.168.120.110:32085;
    }
    server {
        listen 31400;
        proxy_pass 192.168.120.110:32543;
    }
    server {
        listen 15021;
        proxy_pass 192.168.120.110:31929;
    }
    server {
        listen 15443;
        proxy_pass 192.168.120.110:30676;
    }
}

比如 15021:31929/TCP,在 nginx 中,监听当前的 15021,并将请求发到 192.168.120.110:31929,这个是 k8s 的 nodeport 地址

域名解析验证

  1. 启动一个busybox容器

kubectl run -i --tty --image busybox dns-test --restart=Never --rm /bin/sh

  1. 执行nslookup命令

nslookup redis-app-1.redis-service.default.svc.cluster.local

可能会返回这个信息,也是代表正常的 Can't find redis-app-1.redis-service.default.svc.cluster.local: No answer

删除节点

删除这个 node 节点

kubectl delete nodes node06

然后在 node06 这个节点上执行如下命令:

kubeadm reset 重置配置,根据输出提示,删除一些必要的文件即可

根据SA获取rolebinding

比如查询 SA 是 dashboard-admin,会返回对应的 rolebinding

kubectl get rolebinding,clusterrolebinding 
--all-namespaces -o jsonpath='{range .items[?(@.subjects[0].name=="dashboard-admin")]}[{.roleRef.kind},{.roleRef.name}]{end}' 

kubectl 信息命令

kubectl cluster-info kubectl cluster-info dump

kubectl config view

kubectl api-resources -o wide kubectl api-versions

kubectl drain 驱逐

kubectl drain itk8s-mid01 --delete-local-data --ignore-daemonsets --force

强制删除pod

解决:加参数 --force --grace-period=0,grace-period 表示过渡存活期,默认 30s,在删除 POD 之前允许 POD 慢慢终止其上的容器进程,从而优雅退出,0 表示立即终止 POD

kubectl delete po <your-pod-name> -n <name-space> --force --grace-period=0

设置默认的StorageClass

并不是默认命名空间下的 sc 就是默认 sc,需要我们配置一下

选择一个 sc,标记为默认

kubectl patch storageclass <your-class-name> -p '{"metadata": {"annotations":{"storageclass.kubernetes.io/is-default-class":"true"}}}'

如果用 false 则取消默认,只能有一个 sc 被标记为默认。如果它们中有两个或多个被标记为默认,Kubernetes 将忽略这个注解, 也就是它将表现为没有默认 StorageClass

我们 get sc 的时候,可以看到 gold (default) 这种输出则是配置了默认 sc

修改 nodePort 端口范围

k8s 集群 nodePort 分配的端口范围为:30000-32767,这是默认值,如果配置不在这个范围内的端口会报错 /etc/kubernetes/manifests/kube-apiserver.yaml 调整静态 pod 的配置文件可以修改端口范围(不是很建议这么去做)

Server-side Apply 特性

k8s 1.16 新特性,之前我们通过 k8s 创建资源对象之后,之后的修改信息,会在 annotation 记录完整的内容

而 Server-side Apply 简单的说就是多个 Controller 控制一个资源 多个 Controller 控制一个资源, 通过 managedFields 来记录哪个 Field 被哪个资源控制,我们通过 kubelet edit 去查看资源对象,就会发现有一个 managedFiedld 字段,每组数据大致包括下面一些描述字段:

1. manager :指执行本次操作的主体。

2. operation :指执行本次操作的类型。

3. time :指执行本次操作的时间。

这个特性,一般发生在,比如 kubelet appply deployment 创建的资源对象,如果 HPA 要修改副本数,那么当前 deployment 就出现多个 controller 控制一个资源的场景,然后可以分为一下几种情况:

情况一:覆盖这个值,成为唯一的 Manager

如果 Apply 的发起者是有意覆盖值,比如是像 controller 这样的自动化过程,则申请者应该将 “force query” 设置为 true 并且再次发出请求。这将强制操作成功,更改字段的值,并且将该字段从 ManagedFields 中所有其他管理器的条目中删除。

情况二:不覆盖这个值,放弃这次改动

当一个 Apply 的发起者不再管理某个字段的时候,它可以在请求中将这个字段删除,这将导致这个字段的值不发生改变并且从 ManagedFields 的中删除这个发起者。

情况三:不覆盖值,成为共同 Manager

如果 Apply 的发起者仍然关心字段的值,但是又不想覆盖它,则可以在配置中更改字段的值来匹配到这个字段,这会导致这次改动并不生效,但是会把这个字段的 Manager 共享给这个 Apply 的发起者。

这样一来,每一次改动其实都会让所有的 Manager 来共同决定这个对象的变化

原始数据,加上 managedFields,加是 status,构成了完整的 k8s 资源对象数据 (可以说整个 k8s 资源对象你算是完全看懂了,至少字段大概是什么意思你知道了)

参考:https://www.shangyexinzhi.com/article/1585443.html

externalTrafficPolicy

在 NodePort 和 LoadBalance 两种模式下,service 的 externalTrafficPolicy 配置才生效

  • Cluster: 默认模式
  • Local: 使用此模式,可以保留源ip

Local 模式,不是本节点的请求会被丢弃

参考:

https://kubernetes.io/zh/docs/tutorials/services/source-ip/ https://www.cnblogs.com/yxh168/p/12245450.html

coreDNS 修改host

kubectl edit configmap -n kube-system coredns

比如在 prometheus 上面,直接 hosts {} 写入 ip + host

Etcd took too long

出现这个问题,很有可能是磁盘 IO 太慢。在整个服务器环境重启升级的情景下,磁盘 IO 负载太高导致。不能满足 etcd 的一致性需求

参考:

https://blog.csdn.net/didi_cloud/article/details/112006152

https://www.xtplayer.cn/kubernetes/controller-manager-and-scheduler-unavailable/

Etcd master 选举失败问题

这个问题大致是这样的:服务器环境重启之后,etcd 处于只读状态,master 选举一直失败。k8s 这边反映的效果就算,api-service 时好时坏。整个集群不可用

通过默认 kubeadm 安装,发现 3 节点宕机只剩 1 节点,etcd 也不会把剩下的选举成 master(猜测是配置,或者是因为服务器重启在磁盘负载特别高的情况下发生了不可恢复的 BUG)

解决办法就是手动删除节点,只保留 1 个

建议的一个解决思路:

3 节点,控制平面未做负载均衡,保留该 master1,其它 2 个 master 节点删除掉,执行 kubeadm reset

查看 kubectl edit configmaps -n kube-system kubeadm-config,在 ClusterStatus 中有记录 api 的信息,如果重置节点后,列表还有数据,删了另外 2 个

通过 docker shell 进入 etcd bash

alias etcdctl='etcdctl --endpoints=https://master的ip:2379 --cacert=/etc/kubernetes/pki/etcd/ca.crt --cert=/etc/kubernetes/pki/etcd/server.crt --key=/etc/kubernetes/pki/etcd/server.key'

/ # etcdctl member list
ceb6b1f4369e9ecc, started, cn-hongkong.i-j6caps6av1mtyxyofmrx, xxx, xxx
d4322ce19cc3f8da, started, cn-hongkong.i-j6caps6av1mtyxyofmrw, xxx, xxx
d598f7eabefcc101, started, cn-hongkong.i-j6caps6av1mtyxyofmry, xxx, xxx
 
#删除不存在的节点
/ # etcdctl member remove d4322ce19cc3f8da

然后把 2 个 master join

如果其它节点存在问题,reset 之后 join

另外发现在如果一开始不 reset,直接 kubectl delete node master2,之后 master2 会自己加进来,节点状态 running,但是还是存在一些问题

参考:

https://cloud.tencent.com/developer/article/1788389

https://www.cnblogs.com/Wshile/p/13195525.html

访问k8s集群出现Unable to connect to the server: x509: certificate is valid for xxx, not xxx问题解决

这个问题主要是集群校验访问 IP,在使用 VPN,或者一些公有云网络下,如果对 IP 做了源地址转换等操作,此时 api-service 的证书验证失败。 比如在腾讯云的轻量服务器中,服务器网卡是内外 IP,但是提供了公网 IP 转发请求到内网服务器,这是腾讯云自己的策略,安装 k8s 的时候,由于 eth0 网卡就是内网 IP,在不做特殊处理的情况下,使用内网 IP 配置部署 k8s,生成的证书就是只校验该内网 IP 的

在 cd /etc/kubernetes/pki 目录下,执行以下命令,查看证书配置的 IP

openssl x509 -noout -text -in apiserver.crt|grep DNS

发现只有我们内网服务器的 IP 地址

删除证书 (最好先备份一下)

rm apiserver.*

执行这个命令重新生成证书,--apiserver-advertise-address 后面跟需要允许访问的 IP,这里除了集群自身的 2 个,我们加上公网的 IP

kubeadm init phase certs apiserver --apiserver-advertise-address 13.20.0.1 --apiserver-cert-extra-sans 10.0.4.4 --apiserver-cert-extra-sans 124.221.xx.xx

没有报错,并且生产新的证书了,docker ps|grep apiserver,把相关的 2 个容器重启一下,问题解决(多控制面的自行处理)

Hello Halo 2023-08-02
APISIX Ingress Controller K8s 部署实践 2024-04-05

评论区