Kubernetes

Kubernetes(常简称为K8s)是用于自动部署、扩展和管理容器化(containerized)应用程序的开源系统

屏幕截图 2020-09-07 145646

架构

stateDiagram-v2  state control-plane(master) {    API服务器 --> etcd    Scheduler --> API服务器    ControllerManager --> API服务器  }  kubelet --> API服务器  kubeproxy --> API服务器  state worker1 {    kubelet --> 容器运行时    kubeproxy  }  worker2  --> API服务器
stateDiagram-v2  Ingress --> Service: I want to expose my Services  Service --> Deployment: I want to proxy my Pod replicas  Pod --> Deployment: I have many Pod replicas  Container --> Pod: I need co-scheduling  Pod --> CronJob: I run periodically  HorizontalPodAutoscaler --> Pod: I need auto-scaling  Pod --> Job: I only run for once  ConfigMap --> Pod: I read configure file  Secret --> Pod: I need confidential data  Pod --> StatefulSet: I am stateful  Pod --> DaemonSet: I run as daemon

master:用于控制集群

工作节点:运行用户部署应用的节点

分布式

etcd

只有API服务器才能直接与etcd通信

数据在etcd中存储的是一个层次级目录结构 末端节点存储的json数据

集群一致性保证:raft算法

API 服务器

屏幕截图 2020-09-15 143004

屏幕截图 2020-09-15 143259

安全防护

屏幕截图 2020-09-16 135815

kubectl get sa # 获取服务账户kubectl create serviceaccount foo # 创建

屏幕截图 2020-09-16 140540

spec:  serviceAccountName: foo

RBAC控制:使用插件

调度器

利用 API 服务器的监听机制等待新创建的 pod, 然后给每个新的、 没有节点集的 pod 分配节点

屏幕截图 2020-09-15 143719

调度过程是很复杂的:

调度的过程是一个生产者消费者模型,由 informer 监听 service、pod 等对象的变化,加入优先级队列,再由调度器消费队列中的 pod 信息,结合 cache 中的节点信息,过滤并选择节点,将 pod 绑定到指定节点上

控制管理器

确保系统真实状态朝 API 服务器定义的期望的状态收敛

for {  实际状态 := 获取集群中对象X的实际状态(Actual State)  期望状态 := 获取集群中对象X的期望状态(Desired State)  if 实际状态 == 期望状态{    什么都不做  } else {    执行编排动作,将实际状态调整为期望状态  }}

Kubelet

屏幕截图 2020-09-15 150145

kube-proxy

确保用户可以访问后端的pod

两种模式:

屏幕截图 2020-09-15 150639屏幕截图 2020-09-15 150657

控制器协作

屏幕截图 2020-09-15 151627

pod 到底是什么

屏幕截图 2020-09-15 152859

网络

屏幕截图 2020-09-15 153101

相同节点的pod通信:

屏幕截图 2020-09-15 153836

不同节点的pod通信:

屏幕截图 2020-09-15 153900

只有当所有节点连接到相同网关的时候 上述方案才有效

服务的实现

服务暴露的外部ip与端口通过每个节点上的kube-proxy实现

暴露的这个ip是虚拟的 主要是用来做映射用的 当kube-proxy接收到这个ip的请求 就会查找映射 转发请求

屏幕截图 2020-09-15 154607

高可用集群

应用高可用:

master高可用:

屏幕截图 2020-09-15 154955

优点

在K8S中运行应用

根据描述信息生成对应的pod 在pod中运行容器

K8S会保证集群中的容器数量实例 在容器死亡时 会启动新容器替补

K8S 在运行时可根据需求动态调整副本数量

通过kube-proxy能进行服务连接动态切换

本地运行K8S

minikube start \--image-mirror-country=cn \--registry-mirror='https://t9ab0rkd.mirror.aliyuncs.com' \--image-repository='registry.cn-hangzhou.aliyuncs.com/google_containers'

kubeadm

# 初始化masterkubeadm init# 运行必要的组件kubectl create -f https://github.com/weaveworks/weave/releases/download/v2.8.1/weave-daemonset-k8s.yaml# 污染master 使得其能运行用户的podkubectl taint nodes --all node-role.kubernetes.io/control-plane-

部署第一个应用

kubectl  run  kubia  --image=luksa/kubia  --port=8080  # 创建容器运行kubectl get pods # 获取podkubectl get rckubectl port-forward kubia 8080:8080 # 开启端口转发kubectl get pods -o wide # 查看应用在哪个节点kubectl scale rc kubia --replicas=3 # 水平扩容

屏幕截图 2020-09-08 140428

逻辑架构:

屏幕截图 2020-09-08 142015

pod

一组紧密相关的容器 独立的逻辑机器

stateDiagram-v2  state 工作节点1 {    state pod1(10.1.0.1) {      容器1    }    state pod2(10.1.0.2) {      容器3      容器4    }    state pod3(10.1.0.3) {      容器5      容器6    }  }  state 工作节点2 {    state pod4(10.1.1.1) {      容器7      容器8    }    state pod5(10.1.1.2) {      容器9      容器10    }  }

一 个 pod 中的所有容器都在相同的 network 和 UTS 命名空间下运行

每个 pod 都有自己的 IP 地址, 并且可以通过这个专门的网络实现 pod。为了实现这个效果,在 k8s 中,其会通过先启动一个 infra 容器,由这个容器创建出一个 namespace,用户定义的容器再加入该 namespace 中,之间同个 pod 的容器就能互相访问

pod 扮演的是机器这个角色,容器扮演的机器中的进程。所以,关于 pod 对象的设计,凡是调度、网络、存储,以及安全相关的属性,基本上是 pod 级别的,跟容器 namespace 相关的,也是 pod 级别的。

pod的使用:

使用yml创建pod

apiVersion: v1kind: Podmetadata:  name: kubia-manual  labels:    env: test # 指定一个标签spec:  nodeSelector: # 选择特定标签的节点    super: "true"  containers:  - image: luksa/kubia    name: kubia    ports:    - containerPort: 8080      protocol: TCP
kubectl create -f kubia-manual.yamlkubectl logs kubia-manual # 查看日志

标签

kubectl get po --show-labelskubectl label po kubia-manual createtion_method=manual # 修改标签kubectl label node minikube super=truekubectl get po -l createtion_method=manual # 根据标签筛选

注解

注解也是键值对

kubectl annotate pod kubia-manual wang.ismy/name="cxk"

命名空间

命名空间简单为对象划分了一个作用域

kubectl get nskubectl get po -n kube-system # 获取命名空间下的podkubectl create namespace custom-namespace # 创建命名空间kubectl create -f kubia-manual.yaml -n custom-namespace # 指定命名空间

停止与移除

kubectl delete po kubia-manual # 根据名字删除

pod 的生命周期

pod 的状态:

  1. 应用必须意识到会被杀死或者重新调度
    • ip与主机名会发生变化
    • 使用卷解决数据写入问题
  2. 不断重启的pod不会被重新调度
  3. 固定顺序启动pod
    • 使用init容器
    • 应用要处理好其他依赖没有准备好的情况
  4. 生命周期钩子
    • postStart
    • preStop
  5. pod的关闭

屏幕截图 2020-09-19 145717

副本机制

k8s 会保证 pod 以及 容器的健康运行

存活探针

当存活探针探测失败 容器就会重启

apiVersion: v1kind: Podmetadata:  name: kubia-livenessspec:  containers:  - image: luksa/kubia-unhealthy    name: kubia    livenessProbe: # 存活探针      httpGet: # 返回2xx 或者 3xx就代表活着        path: /        port: 8080

ReplicaSet

ReplicaSet 取代了 ReplicationController

rs 的pod 选择器的表达能力更强

apiVersion: apps/v1kind: ReplicaSetmetadata:  name: kubiaspec:  replicas: 3  selector:    matchLabels:      app: kubia  template:    metadata:      labels:        app: kubia    spec:      containers:      - name: kubia        image: luksa/kubia

DaemonSet

由DaemonSet 创建的 pod 会绕过调度程序 会在所有集群节点上运行(或者也可以通过指定nodeSelector在其他节点运行), 当集群有新的节点加入或离开,pod 会被自动添加及删除

屏幕截图 2020-09-09 191240

apiVersion: apps/v1kind: DaemonSetmetadata:  name: ssd-monitorspec:  selector:    matchLabels:      app: ssd-monitor  template:    metadata:      labels:        app: ssd-monitor    spec:      nodeSelector:        disk: ssd      containers:      - name: main        image: luksa/ssd-monitor

Job

允许运行一种 pod, 该 pod 在内部进程成功结束时, 不重启容器。

apiVersion: batch/v1kind: Jobmetadata:  name: batch-jobspec:  completions: 5 # 运行pod数  parallelism: 2 # 并行运行数  template:    metadata:      labels:        app: batch-job    spec:      restartPolicy: OnFailure      containers:      - name: main        image: luksa/batch-job

CronJob

apiVersion: batch/v1beta1kind: CronJobmetadata:  name: cron-jobspec:  schedule: "0,15,30,45 * * * *"  jobTemplate:    spec:      template:        metadata:          labels:            app: batch-job        spec:          restartPolicy: OnFailure          containers:          - name: main            image: luksa/batch-job

服务

是一种为一组功能相同的 pod 提供单一不变的接入点的资源

屏幕截图 2020-09-10 190129

apiVersion: v1kind: Servicemetadata:  name: kubiaspec:  ports:  - port: 80    targetPort: 8080  selector:    app: kubia

Service 是由 kube-proxy 组件,加上 iptables 来共同实现的。当一个 Service 对象被提交给 k8s,kube-proxy 感知到,就会创建一条 iptables 规则

当 iptables 规则过多时,会占据较多的资源。

所以引入了 IPVS 模式,使用哈希表作为基础数据结构,并且在内核空间中工作。 这意味着,与 iptables 模式下的 kube-proxy 相比,IPVS 模式下的 kube-proxy 重定向通信的延迟要短,并且在同步代理规则时具有更好的性能

服务间的发现

kubectl exec kubia-9knkg -- env

域名:kubia.default.svc.cluster.local

如果在同一命名空间下 直接使用 kubia即可

Endpoint

暴露一个服务的 IP 地址和端口的列表

kubectl get endpoints kubia

暴露服务给外部

Service

NodePort:每个集群节点都会在节点上打开一个端口 将在该端口上接收到的流量重定向到基础服务

apiVersion: v1kind: Servicemetadata:  name: kubia-nodeportspec:  type: NodePort  ports:  - port: 80    targetPort: 8080    nodePort: 30123  selector:    app: kubia

通过nodeip:30123 访问

其也是通过添加 iptables 规则来实现的

LoadBalancer

apiVersion: v1kind: Servicemetadata:  name: kubia-loadbalancerspec:  type: LoadBalancer  ports:  - port: 80    targetPort: 8080  selector:    app: kubia

通过externalip:一个随机端口访问

Ingress

Ingress 对象,其实就是 Kubernetes 项目对“反向代理”的一种抽象

安装 ingress-nginx

kubectl apply -f https://raw.githubusercontent.com/kubernetes/ingress-nginx/controller-v1.3.1/deploy/static/provider/cloud/deploy.yaml

就绪探针

# kubia-rc.yaml    spec:      containers:      - name: kubia        image: luksa/kubia        readinessProbe:          exec:            command:            - ls            - /var/ready # 该文件存在 容器才被认为就绪

服务故障排除

卷是 pod 的 一 个组成部分, 因此像容器 一 样在 pod 的规范中定义

屏幕截图 2020-09-12 112125屏幕截图 2020-09-12 112142

在容器之间共享数据

emptyDir:pod被删除时 卷的内容就会丢失

apiVersion: v1kind: Podmetadata:  name: fortunespec:  containers:  - image: luksa/fortune    name: html-genrator    volumeMounts:    - name: html      mountPath: /var/htdocs  - image: nginx:alpine    name: web-server    volumeMounts:    - name: html # 使用html卷      mountPath: /usr/share/nginx/html # 挂载到容器的位置      readOnly: true    ports:    - containerPort: 80      protocol: TCP  volumes: # 创建一个卷  - name: html    emptyDir: {}

gitRepo:以git仓库文件填充目录文件

apiVersion: v1kind: Podmetadata:  name: gitrepo-volume-podspec:  containers:  - image: nginx:alpine    name: web-server    volumeMounts:    - name: html      mountPath: /usr/share/nginx/html      readOnly: true    ports:    - containerPort: 80      protocol: TCP  volumes:  - name: html    gitRepo:      repository: https://github.com/luksa/kubia-website-example.git      revision: master      directory: .

访问工作节点文件

hostPath 卷指向节点文件系统上的特定文件或目录

持久化存储

持久卷

屏幕截图 2020-09-12 140458屏幕截图 2020-09-12 144057

apiVersion: v1kind: PersistentVolumemetadata:  name: mongodb-pvspec:  capacity:    storage: 1Gi  accessModes:    - ReadWriteOnce    - ReadOnlyMany  persistentVolumeReclaimPolicy: Retain  hostPath:    path: /tmp/mongodb
apiVersion: v1kind: PersistentVolumeClaimmetadata:  name: mongodb-pvcspec:  resources:    requests:      storage: 1Gi  accessModes:  - ReadWriteOnce  storageClassName: "" # 动态持久卷
# ...  volumes:  - name: mongodb-data    persistentVolumeClaim:      claimName: mongodb-pvc

动态持久卷

apiVersion: storage.k8s.io/v1kind: StorageClassmetadata:  name: fastprovisioner: k8s.io/minikube-hostpathparameters:  type: pd-ssd

声明是通过名称引用它的 方便之处主要是在不同集群之间移植

屏幕截图 2020-09-12 150052

参数配置

通过定义传递参数:

  - image: luksa/fortune:args    args: ["2"]

使用环境变量:

- image: luksa/fortune:env  env:  - name: INTERVAL    value: "30"

ConfigMap

类似于配置中心:

屏幕截图 2020-09-13 142528

kubectl create configmap fortunes-config --from-literal=sleep-interval=25
  - image: luksa/fortune:env    env:    - name: INTERVAL      valueFrom:        configMapKeyRef:          name: fortunes-config          key: sleep-interval
  - image: luksa/fortune:env    env:    envFrom:    - prefix: CONFIG_    configMapRef:      name: fortunes-config    args: ["${CONFIG_xxx}"] # 传递到命令行
volumes:- name: config  configMap:    name: configmap
kubectl edit configmap xxx

Secret

存储与分发敏感信息

 kubectl create secret generic fortune-https --from-file=https.key
- image: xxx  volumeMounts:  - name: keys    mountPath: /etc/nginx/keys/volumes:- name: keys  secret:    secretName: fortune-https
env:- name: FOO_SECRET  valueFrom:    secretKeyRef:      name: fortune-https      key: name

pod 元数据访问

Downward API

屏幕截图 2020-09-13 152325

通过环境变量:

env:- name: POD IP  valueFrom:    fieldRef:      fieldPath: status.podIP- name: CONTAINER CPU REQUEST MILLICORES  valueFrom:    resourceFieldRef:      resource: requests.cpu      divisor: lm

通过卷:

volumes:- name: downward  downwardAPI:    items:    - path: "podName"      fieldRef:        fieldPath: metadata.name

屏幕截图 2020-09-13 153906

使用 K8S API 服务器

REST API:

curl http://localhost:8001/apis/batch/v1/jobs

在 pod 内部使用

客户端API

Deployment

实际上是一个两层控制器。首先,它通过 ReplicaSet 的个数来描述应用的版本;然后,它再通过 ReplicaSet 的属性(比如 replicas 的值),来保证 Pod 的副本数量

stateDiagram-v2  Deployment --> ReplicaSet(V1)  Deployment --> ReplicaSet(V2)  ReplicaSet(V1) --> pod1  ReplicaSet(V1) --> pod2  ReplicaSet(V2) --> pod3

更新应用:

屏幕截图 2020-09-14 135712

使用rc进行滚动升级

书上通过rolling-update的方法已经过时

使用 Deployment 声明式升级

apiVersion: apps/v1kind: Deploymentmetadata:  name: kubiaspec:  replicas: 3  selector:    matchLabels:      app: kubia  template:    metadata:      name: kubia      labels:        app: kubia    spec:      containers:      - image: luksa/kubia:v1        name: nodejs
kubectl create -f kubia-dep-v1.yaml --record # 加上该参数会记录历史版本号
kubectl set image deployment kubia nodejs=luksa/kubia:v2
kubectl rollout undo deployment kubia

使用 - -to-revision=xxx 回滚到特定版本

rollingUpdate :  maxSurge: 1 # 最多允许超过的副本数  maxunavailable: 0 # 最多允许多少百分比pod不可用

如果 一 个新的pod 运行出错, 并且在minReadySeconds时间内它的就绪探针出现了失败, 那么新版本的滚动升级将被阻止

StatefulSet

如何复制有状态的pod?

StatefulSet 把应用的状态抽象为了:

  1. 拓扑状态:应用的多个实例之间不是完全对等的关系。这些应用实例,必须按照某些顺序启动,再次被创建出来时也必须严格按照相同的顺序,k8是是通过 pod 的名字+编号固定拓扑状态的
  2. 存储状态:Statefulset 保证了pod在重新调度后保留它们的标识和状态

StatefulSet 的控制器直接管理的是 Pod,每个 Pod 的 hostname、名字等都是不同的、携带了编号的。k8s 通过 Headless Service,为这些有编号的 Pod,在 DNS 服务器中生成带有同样编号的 DNS 记录。StatefulSet 会为每个 Pod 分配一个同样编号的 pvc ,这样 k8s 通过持久化卷机制为 pvc 绑定 pv,每个pod都有专属于它的持久卷

使用

apiVersion: v1kind: Servicemetadata:  name: kubiaspec:  clusterIP: None  selector:    app: kubia  ports:  - name: http    port: 80
apiVersion: apps/v1kind: StatefulSetmetadata:  name: kubiaspec:  serviceName: kubia # 在执行控制循环(Control Loop)的时候,使用这个 Headless Service 来保证 Pod 的“可解析身份”  replicas: 2  selector:    matchLabels:      app: kubia # has to match .spec.template.metadata.labels  template:    metadata:      labels:        app: kubia    spec:      containers:      - name: kubia        image: luksa/kubia-pet        ports:        - name: http          containerPort: 8080        volumeMounts:        - name: data          mountPath: /var/data  volumeClaimTemplates:  - metadata:      name: data    spec:      resources:        requests:          storage: 1Mi      accessModes:      - ReadWriteOnce
apiVersion: v1kind: Servicemetadata:  name: kubia-publicspec:  selector:    app: kubia  ports:  -  port: 80     targetPort: 8080

发现伙伴节点

安全

pod 使用宿主节点的Linux命名空间

spec:  hostNetwork: true

屏幕截图 2020-09-16 142921

如果使用hostport 一个节点只能有一个相同的pod

spec:  hostPID: true  hostIPC: true

开启后 相同节点的pod的进程之间就是可见的 可通信的

安全上下文

spec:  securityContext:    # ... pod 级别的  containers:    securityContext:      runAsUser: 405 # 以指定用户运行      runAsNonRoot: true # 禁止以root运行      privileged: true # 在特权模式下允许      capabilities:        add:        - SYS_TIME # 开放硬件时间修改权限        drop:        - CHOWN # 禁用文件所有者修改权限      readOnlyRootFilesystem: true # 禁止在根目录写文件

pod 网络隔离

apiVersion: networking.k8s.io/v1kind: NetworkPolicymetadata:  name: postgres-netpolicyspec:  podSelector:    matchLabels:      app: database # 对该标签的pod生效  ingress: # 只允许来自匹配下面标签的pod请求  - from:    - podSelector:        matchLabels:          app: webserver    ports:    - port: 5432

计算资源管理

申请资源

spec:  containers:  - image: busybox    command: ["dd", "if=/dev/zero", "of=/dev/null"]    name: main    resources:     requests:       cpu: 200m # 申请200毫核 也就说20%CPU       memory: 10Mi # 申请10M内存

添加了requests对调度的影响:

通过设置资源requests我们指定了pod对资源需求的最小值。

调度器不关心资源的实际使用了 而是关心各pod所定义的requests资源量

屏幕截图 2020-09-17 134722

如果将 Pod 的 CPU 资源的 requests 和 limits 设置为同一个相等的整数值,那么该 Pod 就会被绑定在 2 个独占的 CPU 核上

限制资源

resources:  limits:    cpu: 1 # 允许最大使用1核    memory: 20Mi # 内存允许最大 20M

超过limits的情况:

QoS 等级

通过定义优先级决定资源不足时杀谁

屏幕截图 2020-09-17 142031

限制命名空间中的pod

监控 pod

屏幕截图 2020-09-17 143016

自动伸缩与集群

kubectl autoscale deployment kubia --cpu-percent=30 --min=1 --max=5

自动修改CPU与内存大小

集群节点扩容

新节点启动后,其上运行的Kubelet会联系API服务器,创建 一 个Node资源以注册该节点

当一 个节点被选中下线,它首先会被标记为不可调度, 随后运行其上的pod 将被疏散至其他节点

高级调度

污点和容忍度

限制哪些pod可以被调度到某 一 个节点

kubectl describe node minikube | grep Taints # 查看节点污点

屏幕截图 2020-09-19 134744

kubectl taint node minikube node-type=production:NoSchedule
spec:  replicas: 5  template:    spec:      ...      tolerations:      - key: node-type        operator: Equal        value: production        effect: NoSchedule

节点亲缘性

这种机制允许你通知 Kubemetes将 pod 只调度到某个几点子集上面

spec:  affinity:    nodeAffinity:      requiredDuringSchedulingIgnoredDuringExecution:        nodeSelectorTerms:        - matchExpressions:          - key: gpu            operator: In            values:            - "true"

屏幕截图 2020-09-19 142905

最佳实践

屏幕截图 2020-09-19 143453

客户端请求处理

  1. pod启动时避免客户端连接断开
    • 使用一个就绪探针来探测pod是否准备好接受请求了
  2. pod关闭时避免请求断开
    • 停止接受新连接
    • 等待所有请求完成
    • 关闭应用

让应用方便运行与管理

  1. 可管理的容器镜像
    • 镜像太大难以传输 镜像太小会缺失很多工具
  2. 合理给镜像打标签
    • 不要使用latest 使用具体版本号
  3. 使用多维度的标签
  4. 使用注解描述额外信息
  5. 使用/dev/termination-log 写入失败信息
  6. 日志
    • 将日志打印到标准输出方便查看
    • 集中式日志系统

应用扩展

CRD对象

apiVersion: apiextensions.k8s.io/v1beta1kind: CustomResourceDefinitionmetadata:  name: websites.extensions.example.comspec:  scope: Namespaced  group: extensions.example.com  version: v1  names:    kind: Website    singular: website    plural: websites
apiVersion: extensions.example.com/v1kind: Websitemetadata:  name: kubiaspec: gitRepo: https://github.com/luksa/kubia-website-example.git

服务目录

服务目录就是列出所有服务的目录。 用户可以浏览目录并自行设置目录中列出的服务实例

网络

CNI

CNI 的设计思想是 Kubernetes 在启动 Infra 容器之后,就可以直接调用 CNI 网络插件,为这个 Infra 容器的 Network Namespace,配置符合预期的网络栈

容器运行时

kubelet 会监听 pod 对象的变化,根据变化并调用 CRI 来创建或者更新容器。CRI 由不同的容器技术实现,对 kubelet 暴露出 gRPC 服务

CRI 的接口分为两组:

  1. 第一组,是 RuntimeService。它提供的接口,主要是跟容器相关的操作
  2. 第二组,则是 ImageService。它提供的接口,主要是容器镜像相关的操作

监控

  1. 宿主机的监控数据通过以 DaemonSet 的方式运行在宿主机的 Node Exporter 提供
  2. API Server 也会在 /metrics API 里,暴露出各个 Controller 的工作队列(Work Queue)的长度、请求的 QPS 和延迟数据等等
  3. Kubernetes 相关的监控数据。容器相关的 Metrics 主要来自于 kubelet 内置的 cAdvisor 服务。其他数据则是由 Metrics Server 提供
stateDiagram-v2  aggregator --> apiserver  aggregator --> metricsserver  aggregator --> 其他server

日志

第一种方式:logging agent 以 DaemonSet 的方式运行在节点上,然后将宿主机上的容器日志目录挂载进去,最后由 logging-agent 把日志转发到后端存储,这种方式要求应用输出的日志,都必须是直接输出到容器的 stdout 和 stderr 里

第二种方式:当容器的日志只能输出到某些文件里的时候,可以通过一个 sidecar 容器把这些日志文件重新输出到 sidecar 的 stdout 和 stderr 上,这样就能够继续使用第一种方案了。但这会存两份日志文件,占用磁盘。

第三种方式:通过一个 sidecar 容器,直接把应用的日志文件发送到远程存储里面去。这个 sidecar 会对日志文件进行解析,这可能需要消耗较多资源,从而导致拖垮应用容器。