0%

k8s学习

k8s

什么是k8s

  • 下边罗列对于k8s的各种描述:
    • Kubernetes 是一款基础设施工具,可对多种不同计算资源(例如虚拟 / 物理机)进行分组,使其呈现为统一的巨量计算资源,从而供应用程序使用或与其他人共享
    • Kubernetes 是一个开源容器编排引擎,用于自动化容器化应用程序的部署、扩展和管理。Pod 是 kubernetes 应用程序的基本构建模块。Kubernetes 管理着 Pod,Pod 封装着容器。一个 Pod 可能包含一个或多个容器、存储、IP 地址,并控制着容器运行。
    • k8s的特点:
      • 可移植: 支持公有云、私有云、混合云、多重云(multi-cloud)。
      • 可扩展: 模块化,、插件化、可挂载、可组合。
      • 自动化: 自动部署、自动重启、自动复制、自动伸缩/扩展。
  • k8s中各个组件的概念

    • Master(主节点): 控制 Kubernetes 节点的机器,也是创建作业任务的地方。

      • 规划、监控 Pod
      • 向集群加入新的Node
      • 在master节点,必须有以下4个进程运行:
        • api server,kubectl、dashboard等k8s客户端通过api server操作k8s集群,获取集群状态信息等
          • 提供了资源操作的唯一入口,并提供认证、授权、访问控制、API 注册和发现等机制;
            • kubelet进程需要在api server中注册
          • 提供其他模块之间的数据交互和通信的枢纽(其他模块通过 API Server 查询或修改数据,只有 API Server 才直接操作 etcd)
        • Scheduler 调度器,调度Pod的部署位置,实际上客户端对api server的创建Pod的请求会交付给调度器执行调度
          • 负责资源的调度,按照预定的调度策略将 Pod 调度到相应的机器上;
          • **mention:调度器只是进行安排调度,真正执行Pod创建的是Node节点的Kubelet进程 **
          • kube-scheduler 负责分配调度 Pod 到集群内的节点上,它监听 kube-apiserver,查询还未分配 Node 的 Pod,然后根据调度策略为这些 Pod 分配节点(更新 Pod的 NodeName 字段)。
        • Controller Manager 用于进行Pod的状态监测
          • 负责维护集群的状态,比如故障检测、自动扩展、滚动更新等
            • 检测集群的状态变化,比如有Pod 崩掉了,Controller Manager请求Scheduler重新调度,随后Scheduler请求对应Node的Kubelet执行Pod的创建动作
          • Controller Manager由kube-controller-manager和cloud-controller-manager组成,是Kubernetes的大脑,它通过apiserver监控整个集群的状态,并确保集群处于预期的工作状态。
        • etcd
          • 基于Go语言实现的键值对类型的数据库,用来存储集群状态信息
          • Scheduler与Controller Manager都依赖于etcd中存储的集群状态信息
          • Etcd是CoreOS基于Raft开发的分布式key-value存储,可用于服务发现、共享配置以及一致性保障(如数据库选主、分布式锁等)
            • 基本的key-value存储
            • 监听机制
            • key的过期及续约机制,用于监控和服务发现
            • 原子CAS和CAD,用于分布式锁和leader选举
          • etcd的特点如下:
            • 简单:支持 REST 风格的 HTTP+JSON API,既然etcd自己有开放的API,为什么系统其他组件还需要听过api server才能与etcd联系呢
            • 安全:支持 HTTPS 方式的访问
            • 快速:支持并发 1k/s 的写操作
            • 可靠:支持分布式结构,基于 Raft 的一致性算法,Raft 是一套通过选举主节点来实现分布式系统一致性的算法
          • 补充etcd的可能使用场景(未必是在k8s中的使用场景,可以是etcd在任何一个分布式系统中的的使用场景)
            • 服务发现(Service Discovery):服务发现主要解决在同一个分布式集群中的进程或服务,要如何才能找到对方并建立连接。本质上来说,服务发现就是想要了解集群中是否有进程在监听udp或tcp端口,并且通过名字就可以查找和连接,也可以大概的理解为是一个查询etcd数据库的过程
            • 消息发布与订阅:在分布式系统中,最适用的一种组件间通信方式就是消息发布与订阅。即构建一个配置共享中心,数据提供者在这个配置中心发布消息,而消息使用者则订阅他们关心的主题,一旦主题有消息发布,就会实时通知订阅者。通过这种方式可以做到分布式系统配置的集中式管理与动态更新。应用中用到的一些配置信息放到etcd上进行集中管理(k8s中使用的是ConfigMap组件)
            • 负载均衡:在分布式系统中,为了保证服务的高可用以及数据的一致性,通常都会把数据和服务部署多份,以此达到对等服务,即使其中的某一个服务失效了,也不影响使用。etcd本身分布式架构存储的信息访问支持负载均衡。etcd集群化以后,每个etcd的核心节点都可以处理用户的请求。所以,把数据量小但是访问频繁的消息数据直接存储到etcd中也可以实现负载均衡的效果
            • 分布式通知与协调:****与消息发布和订阅类似,都用到了etcd中的Watcher机制,通过注册与异步通知机制,实现分布式环境下不同系统之间的通知与协调,从而对数据变更做到实时处理。
            • 分布式锁:因为etcd使用Raft算法保持了数据的强一致性,某次操作存储到集群中的值必然是全局一致的,所以很容易实现分布式锁。锁服务有两种使用方式,一是保持独占,二是控制时序。
            • 集群监控与Leader竞选:通过etcd来进行监控实现起来非常简单并且实时性强。
        • Master节点是整个集群的大脑,因此一般会有多个Master节点,此时api server同样具有负载均衡的作用,并且etcd也是在多个Master节点分布式存储的形式
        • Kubernetes Master接受使用YAML定义的配置文件,根据配置文件中相关信息将容器分配到其中一个Node上
    • Node(节点): 这些机器在 Kubernetes 主节点的控制下执行被分配的任务。可以是实体服务器或者是虚拟机

      • 作为真正干活的,Node节点所在的系统应该有如下三个进程运行
        • 容器运行时环境,可以是Docker或者其他容器环境,比如containerd
        • Kubelet,默认监听 10250 端口,接收并执行 master 发来的指令,管理Pod及 Pod 中的容器, 这个守护进程运行在各个工作节点上,负责获取容器列表,保证被声明的容器已经启动并且正常运行。
          • 负责维护容器的生命周期,同时也负责 Volume(CVI)和网络(CNI)的管理,是一个后台的守护进程,相当于MySQLd;
          • 每个 kubelet 进程会在 API Server 上注册节点自身信息,定期向 master 节点汇报节点的资源使用情况,并通过 cAdvisor 监控节点和容器的资源。
        • kube proxy 用于智能转发集群中的流量
          • 负责为 Service 提供 cluster 内部的服务发现和负载均衡
          • 每台机器上都运行一个 kube-proxy 服务,它监听 API server 中 service 和 endpoint 的变化情况,并通过 iptables 等来为服务配置负载均衡(仅支持 TCP 和 UDP)。
          • kube-proxy 可以直接运行在物理机上,也可以以 static pod 或者 daemonset 的方式运行。
          • Kube-proxy通过管理iptables等方式使得pod到pod之间,和pod到node之间网络能够互通。实质上在跨主机的pod之间网络也能够互通
          • kube-proxy支持以下三种代理模式:
            • Userspace
            • iptables
            • IPVS
    • Pod: 由一个或多个容器构成的集合(对于容器的抽象封装),作为一个整体被部署到一个单一节点。同一个 pod 中的容器共享 IP 地址、进程间通讯(IPC)、主机名以及其它资源。Pod 将底层容器的网络和存储抽象出来,使得集群内的容器迁移更为便捷。

      • 因为Pod的引入也使得k8s替换docker后,可以较为方便的迁移到其他容器技术栈比如containerd
      • 一个Pod内包含一个或多个完成相同功能的容器,比如数据库应用,后端处理应用等等
      • Pod重新创建时其IP会改变,因此使用Pod IP进行沟通是不合适的,故引入Service组件
      • 如果多个容器运行在一个POD中,就相当于这些容器运行在同一台主机中,需要注意端口占用问题。
      • 在与 Docker 结合使用时,一个 pod 中可以包含一个或多个 Docker 容器。但除了有紧密耦合的情况下,通常一个 pod 中只有一个容器,这样方便不同的服务各自独立地扩展
    • Replication controller(复制控制器): 控制一个 pod 在集群上运行的实例数量

    • Service(服务): 将服务内容与具体的 pod 分离。Kubernetes 服务代理负责自动将服务请求分发到正确的 Pod 处,不管 pod 移动到集群中的什么位置,甚至可以被替换掉

      • service具有固定IP,因此各个Pod之间的联系,可以通过Service层架进行联系,不必担心Pod的重建导致IP变更
      • 当Pod有多份时,Service可以充当Pod之间的load balancer
      • Endpoint是Service下的一个组件,当Service部署时,会自动创建一个与之同名的Endpoint组件,k8s使用此组件跟踪对应的Service,记录哪一个Pod是Service对应的成员(endpoints)
    • Ingress 做请求转发到特定的Service,相当于是一个网关或者是前端路由,管理进出集群中的路由,可以对外提供基于特定服务域名的安全协议服务。

    • ConfigMap 将服务配置抽离于服务本身,之前的服务配置往往写在服务本身所在容器之中,但是一旦对应的服务的服务名称有改动,那么使用此服务的所有其他服务的容器都需要更新,需要经历镜像更新-》容器更新一系列的复杂过程,因此需要将服务配置抽离出来,可以进行灵活配置,比如数据库的url,数据库用户名等等

    • Secret:前边的ConfigMap可以用来存储应用配置,但是不应将秘钥,用户密码等信息存在那里,专门用来存储秘钥的组件为Secret

      • 存储的内容必须实现经过base64编码
      • 内容加密并不是默认配置开启的,需要使用环境变量或者配置文件手手动配置开启
    • Volume 与Docker中的volume一样,用来执行Pod的数据持久化

      • 可以是Pod所在的宿主机的磁盘,也可以是远程存储服务
      • mention:k8s并不管理数据持久化的工作,需要自己维护数据持久化的正常工作
    • Deployment:

      • replicaset:k8s中的Pod 可以进行复制,提高了扩展性,同时也避免Pod down掉或者更新时带来的服务不可用状态,提高可用性,replicateset用来管理Deployment下的Pod的replica个数

        • replica有多多少份是通过Deployment配置的,相当于是Pod分布的blueprint,通过创建对应的Deployment来实现扩容与缩容
        • 与Pod一样,replicateset不需要单独设置,直接通过Deployment配置即可
      • 综上Deployment是基于Pod之上的抽象层,直接操作Deployment而不是Pod

      • 对于无状态的Pod可以任意复制,因为只是提供无状态的服务,但是对于有数据持久化需求或者说是有状态的Pod而言不能使用Deployment去执行复制,比如一个简单的数据库Pod,如果执行了replicate,两个Pod同时访问一块存储,可能会导致数据冲突的问题

        • 解决有状态Pod的复制问题的组件叫做StatefulSet
      • 总的来说Deployment是客户端与k8s打交道的抽象层,Deployment之下的所有抽象均由k8s管理

    • StatefulSet 有状态的Pod的创建,扩容,缩容使用StatefulSet,不过,最佳实践是把这种数据库应用放到k8s之外,只在k8s中部署无状态的应用

  • k8s支持插件机制,以下的几个是常用的插件

    • kube-dns:负责为整个集群提供 DNS 服务
    • Ingress Controller:为服务提供外网入口
    • Heapster:提供资源监控
    • Dashboard:提供 GUI
    • Federation:提供跨可用区的集群
    • Fluentd-elasticsearch:提供集群日志采集、存储与查询

k8s的部署

  • 常见的Kubernetes部署方式有:
    • kubeadm:也是推荐的一种部署方式;
    • 二进制
    • minikube:在本地轻松运行一个单节点 Kubernetes 群集的工具。

Minikube与Kubectl

Kubectl

  • k8s的命令行配置工具
  • 相当于是一个k8s的客户端,直接向master节点的api server请求服务

Minikube

  • 相当于在一个节点中同时运行Master与Worker Node的进程,并且是通过Virtual Box或者其他虚拟化技术创建的虚拟机运行在物理机之上
  • 通常用于测试或者是快速验证,而不是用作生产
  • 总的来说就是一个单节点的k8s集群

在Mac上安装Minikube

  1. brew update
  2. brew install hyperkit 安装运行Minikube虚拟化引擎,不使用Virtual Box 而是hyperkit,因为其更轻量且性能更好
  3. brew install minikube,kubectl是其依赖项,所以不需要单独安装kubuectl了

创建并启动一个Minikube 单节点集群

  1. minikube start --vm-driver=hyperkit --registry-mirror ${阿里云的镜像加速器地址}指定虚拟化引擎与Docker 镜像地址即可

    1. 看起来就是使用Docker Machine创建了一个虚拟机,虚拟机中预装了 Docker Daemon

    2. 至此集群已经启动起来,可以使用kubectl发起请求了,Minikube 启动时会自动配置 kubectl,把它指向 Minikube 提供的 Kubernetes API 服务,可以使用下述命令确定

      1. kubectl config current-context
    3. 如果你在第一次启动 Minikube 时遇到错误或被中断,后面重试仍然失败时,可以尝试运行 minikube delete 把集群删除,重新来过。

      1. 经过实测,把电脑的DNS改为Google的DNS 8.8.8.8即可,参考此issue
    4. Minikube的作用就是启动或者删除一个k8s集群,具体的集群配置全部使用kubectl来完成

    5. 集群启动后,会自动创建一个Service,此Service配置文件如下

      1
      2
      3
      4
      5
      6
      7
      8
      9
      10
      11
      12
      13
      14
      15
      16
      17
      18
      19
      20
      21
      22
      23
      24
      apiVersion: v1
      kind: Service
      metadata:
      creationTimestamp: "2020-12-20T10:20:18Z"
      labels:
      component: apiserver
      provider: kubernetes
      name: kubernetes
      namespace: default
      resourceVersion: "202"
      uid: 96540f7d-43cc-43f7-92e2-4be508285bbd
      spec:
      clusterIP: 10.96.0.1
      clusterIPs:
      - 10.96.0.1
      ports:
      - name: https
      port: 443
      protocol: TCP
      targetPort: 8443
      sessionAffinity: None
      type: ClusterIP
      status:
      loadBalancer: {}
      • 推测此Service是服务于apiServer的Service,因为apiServer就默认监听在8443端口

使用kubectl发起请求

查看集群组件状态
  • kubectl get nodes 获取nodes的状态,或者使用minikube status查看集群状态
  • kubectl version查看k8s版本
  • kubectl get pods查看Pod状态
  • kubectl get services查看Service组件状态
  • kubectl get deployments 查看Deployment的状态
  • kubectl get namespace 查看命名空间状态
  • kubectl get all 查看集群中的所有组件的状态信息
  • get指令可以设置-o wide参数可以显示更多信息,比如Pod的IP
  • get指令可以设置-o yaml参数可以获得更新后的yaml配置文件
  • get指令可以设置--watch参数可以动态更新状态
  • get指令可以设置-n参数可以获取指定NS下的组件的状态,默认只获取default NS 下的资源
    • 当某团队只被限制使用本团队的NS时,执行kubectl命令都得加上一个-n ${NS_name}比较麻烦,应当能设置当前环境下的默认NS,但是k8s没有提供支持,解决办法是:
      • 下载kubens工具brew install kubectx
      • kubens命令打印NS列表,并高亮当前NS
      • kubens ${NS_name}切换到指定NS,此后执行kubectl不需要指定-n参数了
创建集群组件
  • 使用kubectl create ...相关的命令即可
  • 首先是创建k8s的最基本单元–Pod,但是不要忘了Deployment是Pod之上的抽象封装,因此是创建Deployment而不是Pod
  • kubectl create deployment ${deployment_name} --image=${image_name}
    • 在这里通过kubectl指定了创建Pod的blueprint,最基本的信息就是Depoyment的名字(Deployment对应的Pod的名字就是${deployment_name}-${replicateset_hash}-${pod_hash})和镜像名
    • 其余配置都是默认的,当然也可以通过命令行配置,不过更好的方式是使用yaml配置文件进行声明
    • kubectl get replicasets 查看Deployment下的replicate配置
      • replicaset的名字是${deployment_name}-${replicateset_hash}
  • 使用kubectl直接编辑Deployment配置,会自动生成一份yaml格式的配置文件供编辑
    • kubectl edit deployment ${deployment_name}
    • 编辑保存之后,会自动执行更新
Debugging Pod
  • 即获取Pod日志(实际上是获取Pod内的容器的日志)

    • kubectl logs ${pod_name}
  • 如果要查看Pod内容器的状态

    • kubectl describe pod ${pod_name}

  • 与原生Docker Command类似,使用kubectl也可以进入到Pod内部的容器命令行

    • kubectl exec -it ${pod_name} -- /bin/bash
    • 区别在于在command参数前这里多了--
  • 下文介绍的配置文件中的statue节点的更新,也可以作为debug信息

删除Deployment与使用yaml配置文件声明Deployment
  • 删除Deployment

    • kubectl delete deployment ${deployment_name}
    • 这里仅记录一个现象,Service并不依附于Pod存在,即便我删除了Deployment,Service并没有消除,实际上也可以说Service并不是Deployment之下的一个抽象层,而是一个单独的k8s组件
  • 使用yaml配置文件

    • kubectl apply -f ${yaml_file_path}
    • 如果需要改动,直接更改配置文件,再执行相同的命令即可
    • 也可以通过制定配置文件来删除Deployment
      • kubectl delete -f ${yaml_file_path}
    • Deployment yaml配置文件案例
    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    12
    13
    14
    15
    16
    17
    18
    19
    20
    21
    22
    23
    24
    25
    apiVersion: apps/v1
    # 要部署的组件类型
    kind: Deployment
    metadata:
    labels:
    app: nginx
    name: nginx-depl
    # deployment blueprint
    spec:
    replicas: 1
    selector:
    matchLabels:
    app: nginx-depl
    # pod blueprint
    template:
    metadata:
    labels:
    app: nginx-depl
    spec:
    containers:
    - image: nginx
    name: nginx
    ports:
    - containerPort: 8080
    restartPolicy: Always
    • Service yaml配置文件案例

      1
      2
      3
      4
      5
      6
      7
      8
      9
      10
      11
      12
      13
      apiVersion: v1
      kind: Service
      metadata:
      name: nginx-service
      spec:
      selector:
      app: nginx
      ports:
      - protoccol: TCP
      # Service对其他Service暴露的端口是80
      port: 80
      # Service把请求转发到对应的Pod的port,对应的就是pod 配置中的containerPort
      targetPort: 8080
      • 创建Service后,可以使用kubectl describe service nginx-service查看Endpoints项目,是否有匹配的Pod,Pod IP 信息可用kubectl get pod -o wide查看
yaml配置文件详解
  • 事实上配置文件分为以下3部分:
    • metadata
      • 要配置的组件(Deployment、Service etc)的元数据
        • name:为此组件设置的名字
        • lables:此节点下可以是任意键值对,仅是用来匹配的,Deployment的lables用来与Service的Selector进行匹配
          • 需要注意的是:尽管Service与Deployment定义在不同的配置文件中,但是这个lable是全局的,意味着可以在其他配置文件中与此label进行匹配
          • Service中的selector与对应的Pod匹配
    • spec
      • 创建不同类型的组件对应的spec显然是不一样的
        • 对于常用的Deployment:
          • replicas:replica的个数
          • selector:与lables一样用来联系组件,此处就是用来匹配Pod与Deployment
            • matchLabels:
              • 此节点下放置一个任意键值对,表示此Deployment匹配所有以这个键值对为lable的Pod
              • 需要注意的是如果此节点配置变更了,不能通过apply跟新配置,否则会有以下错误:The Deployment "nginx-depl" is invalid: spec.selector: Invalid value: v1.LabelSelector{MatchLabels:map[string]string{"app":"nginx"}, MatchExpressions:[]v1.LabelSelectorRequirement(nil)}: field is immutable,应该删除后,重新创建才行
          • template:pod blueprint,用来具体设置Pod
            • template的子结构与整个配置文件的结构类似
              • metadata
                • lables
                  • 此节点下可以是任意键值对,仅是用来匹配的,上边案例中的是app: nginx-depl
              • spec
                • containers 列表结构
                  • image
                  • name
                  • ports 列表结构,与selector、lables一样用来联系组件
                    • containerPort
    • 头部信息
      • apiVersion
        • 不同的组件对应的apiVersion不一样,具体如何选择参考此文章
      • kind 要创建的组件的类型,比如:Service、Deployment、CronJob etc
    • status
      • 此部分是不需要手动配置的,而是有k8s维护的
      • k8s会对spec部分声明的配置与实际的运行状况进行对比,并尝试使实际运行状况与生命的配置保持一致,这个也是k8s的self-healing特性的一部分,实际运行状况会被k8s持续维护在此status节点信息中
        • 当然这种维护并不是直接写到我们自己的配置文件中,可以使用edit指令查看,但是不推荐,应该使用kubectl get deployment ${deployment_name} -o yaml来查看,如果要导出到文件中使用管道即可

Demo

  • mongodb

  • mongo-express

  • 展示典型的web应用的构建

  • 基本的请求流程如下:

    • mogodbURL存储在configMap中
    • mogodb的用户名密码存储在Secret中
  1. mongoDB相关

    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    12
    13
    14
    15
    16
    17
    18
    19
    20
    21
    22
    23
    24
    25
    26
    27
    28
    29
    30
    31
    32
    33
    34
    35
    36
    37
    38
    39
    40
    41
    42
    43
    44
    45
    46
    47
    48
    49
    apiVersion: apps/v1
    kind: Deployment
    metadata:
    labels:
    app: mongodb
    name: mongodb-deployment
    spec:
    replicas: 1
    selector:
    matchLabels:
    app: mongodb
    template:
    metadata:
    labels:
    app: mongodb
    spec:
    containers:
    - image: mongo
    name: mongodb
    ports:
    - containerPort: 27017
    env:
    - name: MONGO_INITDB_ROOT_USERNAME
    valueFrom:
    secretKeyRef:
    name: mongodb-secret
    key: mongo-root-username
    - name: MONGO_INITDB_ROOT_PASSWORD
    valueFrom:
    secretKeyRef:
    name: mongodb-secret
    key: mongo-root-password
    restartPolicy: Always

    # 使用yaml中的document隔离
    ---

    apiVersion: v1
    kind: Service
    metadata:
    name: mongo-service
    spec:
    selector:
    app: mongodb
    ports:
    - protocol: TCP
    # 因为这里设置的同样是mongodb的默认端口,所以mongo-express中不用单独配置端口了
    port: 27017
    targetPort: 27017
    1
    2
    3
    4
    5
    6
    7
    8
    9
    apiVersion: v1
    kind: Secret
    metadata:
    name: mongodb-secret
    type: Opaque
    data:
    # echo -n 'username' | base64
    mongo-root-username: dXNlcm5hbWU=
    mongo-root-password: cGFzc3dvcmQ=
    1. MongoDB Deployment
    2. MongoDB Service
    3. Secret
    4. 启动顺序很重要:因为Deployment中要引用Secret中的值,所以必须首先启动Secret,这种顺序要求,应该也能写在配置文件中,就像docker compose中一样
  2. Mongo-express相关

    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    12
    13
    14
    15
    16
    17
    18
    19
    20
    21
    22
    23
    24
    25
    26
    27
    28
    29
    30
    31
    32
    33
    34
    35
    36
    37
    38
    39
    40
    41
    42
    43
    44
    45
    46
    47
    48
    49
    50
    51
    52
    53
    54
    55
    56
    57
    58
    59
    apiVersion: apps/v1
    kind: Deployment
    metadata:
    labels:
    app: mongo-express
    name: mongo-express-deployment
    spec:
    replicas: 1
    selector:
    matchLabels:
    app: mongo-express
    template:
    metadata:
    labels:
    app: mongo-express
    spec:
    containers:
    - image: mongo-express
    name: mongo-express
    ports:
    - containerPort: 8081
    env:
    - name: ME_CONFIG_MONGODB_SERVER
    valueFrom:
    configMapKeyRef:
    name: mongodb-configmap
    key: database_url
    - name: ME_CONFIG_MONGODB_ADMINUSERNAME
    valueFrom:
    secretKeyRef:
    name: mongodb-secret
    key: mongo-root-username
    - name: ME_CONFIG_MONGODB_ADMINPASSWORD
    valueFrom:
    secretKeyRef:
    name: mongodb-secret
    key: mongo-root-password
    restartPolicy: Always

    # 使用yaml中的document隔离
    ---

    apiVersion: v1
    kind: Service
    metadata:
    name: mongo-express-service
    spec:
    selector:
    app: mongo-express
    # 名字稍有歧义;通过给这个service一个external ip来让这个service可以接受集群外部请求
    # 默认的Service类型是CLuster IP 也就是Internal Service,可以不用指定
    type: LoadBalancer
    ports:
    - protocol: TCP
    port: 8081
    targetPort: 8081
    # extrnal ip的端口 必须在30000-32767之间
    # 本质上使用的是NodePort类型的Service
    nodePort: 30000
    1
    2
    3
    4
    5
    6
    7
    apiVersion: v1
    kind: ConfigMap
    metadata:
    name: mongodb-configmap
    data:
    # mogodb 的service name
    database_url: mongo-service
    1. Mongo-express Deployment
    2. Mongo-express Service
    3. configMap
    4. 启动顺序很重要:同理必须首先启动ConfigMap
    5. 在minikube中无法自动为service分配external ip,执行如下命令:minikube service ${Service_name}
      1. 执行此命令为Service分配一个external ip,并且自动打开浏览器访问对应Service external IP
      2. 在实际的k8s集群中,此external ip应当是集群中该service运行所在节点的节点IP与节点的端口
        1. 这仅用于测试与快速部署,但是不能用于生产,生产环境下应该使用HTTS安全协议与域名,可以使用Ingress实现需求

namespace 命名空间

  • namespace是一个用来组织k8s资源的逻辑分区,可以将其看作为k8s集群内部的小集群

    image-20201221083105953
  • 当k8s集群创建后,默认创建4个namespace

    • default 用户自己创建的资源会被放置到此namespace中
    • kube-node-lease 用来存储每一个node的心跳数据,这些心跳数据决定了这个node的可用性(是否存活)
    • kube-public 内部是可公共访问的资源,具体来说就是ConfigMap,其中存储了一些集群信息,不需要验证就可以访问其中的数据,可以使用kubectl cluster-info来访问到
    • kube-system 不要在此namespace中修改会添加资源,这个namespace中管理的是Master节点的一些进程和kubectl进程等等
    • kubernetes-dashboard 此namespace是由minikube创建,标准的k8s集群中不会默认创建
  • 除了这些默认的namespace之外,还可以创建自定义的命名空间,可以使用命令行kubectl create namespace ${namespace_name}

    • 只需要在其他资源,比如ConfigMap中的metadata节点中设置namespace: ${namespace_name}子节点,就可以将资源规划到对应namespace中或者是创建资源的使用指定NS:kubectl apply -f configmap.yaml --namespace=my-namespace,当然还是推荐第一种方法

    • 这里有一个小细节需要注意:当已经创建好一个default中的ConfigMap后,更改其配置文件将其配置到自定义的NS中,再执行apply后,之前的default中的ConfigMap不会被删除

  • namspace的意义,使用场景

    • 实际上就是为了从逻辑上组织k8s资源

      • 如果仅使用一个默认的namespace(或者说没有namespace这个概念)的话,当复杂系统创建的组件很多时,就变得难以管理

        image-20201221083452567
      • 使用多个namespace进行业务逻辑划分,就变得更加容易管理

        image-20201221083618956
      • 除了进行业务层面的划分,还可以进行人员组织上的逻辑划分,避免多个团队在集群中创建资源时发生冲突

        • 进一步的还可以进行namespace层面的访问控制,这意味着团队只能在自已的namespace中管理资源,而不能管理其他namespace的资源
        • 再进一步,还可以设置namespace层面的资源配额管理
        image-20201221083902926

    image-20201221083932782image-20201221085147422

    image-20201221085147422

    • 可以在资源共享时使用namespace,比如要共享集群中的通用资源时,可以创建一个namespace来使用共享资源,而不用再创建另外一个集群去重复搭建相同的业务

      image-20201221084212305
    • 与上边的资源共享的例子类似的是,在集群中可以设置服务版本划分,比如当前服务版本与未来待发布版本,这些版本的服务都需要使用相同的共享服务,因此部署在一个集群中,用namespace划分即可

      image-20201221084816575
  • 使用namespace中的一些注意事项

    • 当多个ns(A、B)想要使用共享的ns资源(比如数据库),A、B两个ns必须有自己独立的ConfigMap与Secret,不能一个ns(A)配置了这两个组件,另一个ns中(B)的Deployment从A中引用配置

      image-20201221090042209
      • 前边说ConfigMap是不能跨NS引用的,事实上大部分NS的资源不能跨NS访问,但是Service组件是可以跨NS使用的,如下图所示,只要在引用时,指定Service所在的ns即可:${service_name}.${namespace_name}

        image-20201221090746389
  • 并不是k8s中的所有资源都能被分配到NS中进行划分隔离,以下这些组件在k8s集群中是全局可访问的

    • Volume
    • Node
    • etc
    • 可以使用kubectl api-resources --namespaced=false进行查看

Ingress

  • 前边demo所说,使用external service,可以向集群外部开放服务,生产环境下应该使用HTTS安全协议与域名,可以使用Ingress实现需求,external service可以退化为Internal service,由Ingress来接受外部请求与转发请求

  • Ingress配置文件实例

    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    12
    13
    14
    15
    16
    apiVersion: networking.k8s.io/v1beta1
    kind: Ingress
    metadata:
    name: mongo-express-ingress
    spec:
    # 定制路由规则
    rules:
    # 对于此host的请求会被转发到指定的backend中的serviceName:servicePort
    - host: mongo-express.com
    # 注意这里并不对应外界请求使用http或者https,而是对应ingress将请求转发到Internal Service用到的协议
    http:
    # 定制URLpath
    paths:
    - backend:
    serviceName: mongo-express-service
    servicePort: 8081
    • 注意host节点的域名应当是有效域名,并映射到集群中作为entrypoint的Node的IP(作为集群entrypoint的主机未必是集群中的Node也可以是集群外的某主机
Ingress配置与部署
  • 实际上Ingress的配置离不开另外一个组建的配合:Ingress Controller

    • Ingress本身就像是一个接口,我们在这个接口中定义了规则,但是真正对请求进行规则匹配与流量转发的是Ingress Controller这个接口实现,IC是整个集群的entrypoint
    • Ingress Controller其实就是一个或多个特殊的Pod罢了
    • 有许多Ingress Controller的第三方实现,可参考官方网站
  • 对于实际部署中不同的服务器环境,也有不同的架构

    • 云服务厂商提供的服务器环境下:

      image-20201221134430442
    • Bear metal:

      • image-20201221134549715
      • 需要自己提供entrypoint,并做好负载均衡,一般会使用一个集群外部的机器做代理服务器,可以软件代理服务器或者是硬件代理服务器

        image-20201221134808346
  • 在minikube中部署Ingress是比较简单的

    1. minikube addons enable ingress自动部署Ingress Controller 实现:Nginx Ingress Controller

      1. 可在kube-system这个NS中查看pod会找到部署好的Ingress Controller
      2. 因为是第三方实现,其镜像仓库一般比较慢,可能出现 Exiting due to MK_ENABLE: run callbacks: running callbacks: [waiting for app.kubernetes.io/name=ingress-nginx pods: timed out waiting for the condition]的异常,解决办法:
        1. 可以使用上边的debug方法,首先检查下Pod状态(Ingress Controller就是一个Pod)kubectl get pod -A,果然可以找到kube-system ingress-nginx-controller-558664778f-j6hs9 0/1 ImagePullBackOff,再执行kubectl describe pod ingress-nginx-controller-558664778f-j6hs9 --namespace=kube-system找到其拉取的镜像名称,执行minikube ssh进入到minikube虚拟机手动执行镜像拉取 docker pull ..
        2. 但是经过尝试可能image本身有错误,可以参考issue掘金文章来解决镜像不能下载的问题
    2. 初步尝试使用Ingress配置一个可以访问kubernetes-dashboard的路由(此dashborad服务是minikube自动配置的,你可以在这个同名的NS中找到其部署的Pod与Service,但是不能在集群外部访问,所以需要使用Ingress配置规则)

      1
      apiVersion: networking.k8s.io/v1beta1kind: Ingressmetadata:  name: dashboard-ingress  namespace: kubernetes-dashboardspec:    rules:    - host: dashboard.com      http:         paths:        - backend:            # 注意向后缩进           serviceName: kubernetes-dashboard           servicePort: 80
      • 使用apply进行部署后,会自动在集群内部为此域名分配对应解析的IP(因此可以推测出k8s内部应该有一个DNS系统维护),稍等片刻就能在ingress的info信息中出现,在本地将此解析加入(/etc/hosts)就能在浏览器中访问dashboard了
      • 补充一个异常信息:error converting YAML to JSON: yaml: line 11: did not find expected key这表示yaml文件的格式出现问题,最有可能出现的就是在map类型的节点没有向后添加空格,比如上述backend节点中的键值对是否向后缩进了
  • Default backend

    • 顾名思义,这个backend的意思就是对于任何不匹配rule的请求会被重定向到这个Service中,可以通过kubectl describe ingress dashboard-ingress -n kubernetes-dashboard查看

    • 可以使用这个Service来配置定制化的错误界面等等

    • 默认情况下,集群中没有此Service,需要自己创建,并创建对应的Pod处理请求即可

      image-20201221151117686
  • 更多使用案例:

    • Ingress在实际使用中肯定会指定更为复杂的rule,但是总的来说有以下两种

      • path 区分

        image-20201221151753679
      • subdomian区分

        image-20201221151831171
    • 配置HTTPS

      • 需要用到secret组件来存储证书

        image-20201221152022214
        • 需要注意的是,Secret中数据的key必须是tls.crttls.key,不能修改为其他名字
        • Secret与Ingress必须处于同一NS 否则Ingress无法访问到Secret

Helm

  • helm是k8s中的包管理器

    • 所谓的包就是可以将集群中的多个yaml文件进行打包,并可以发布到公开的或私有的仓库中
  • 使用场景,为什么在k8s中需要包管理器

    • 比方说要在自己的k8s集群中使用ES stack(ES、kibana、logstash),需要自己查询并手动创建相当多的组件才能运行起来,并且容易出错,因此可以将整个服务打包共享供他人使用,自己也可以方便的拿到打好的包部署运行
    • 开发环境集群中开发好的微服务应用要部署到测试环境或生产环境中,可以直接打包好,并一键部署
  • 打好的包就叫做Helm Charts

  • 使用helm search ${keyword}来查询可用的包,或者去helm hub搜索

  • Helm还有一个重要的特征就是其模板引擎的功能

    • 集群中的多个微服务的Yaml配置文件是十分相似的,只有在关键的一些配置上不同,为了减少配置的荣誉,可以使用Helm定义一个common blueprint,在关键的可替换节点处使用占位符标志,可以为占位符绑定动态的值(模板引擎)

      image-20201221161356116
      • 动态值的来源是外部的一个yaml文件
        • 模板文件中的.Values不是与values.yaml的文件名匹配的,固定如此,values文件也有自己固定的位置,并且可以被覆盖,下边有介绍
        • 除了从外部的yaml文件获取配置之外,还可以从--set参数中配置value
    • 模板引擎的功能在 CI CD中是很有用的,可以在流程中更改value即可

  • Helm chart打包的结构

    • ${Helm_Chart_Name}文件夹,chart的名字
      • Chart.yaml chart的元信息,包括name version dependencoes等等
      • values.yaml 前边所说的template文件中的占位符的填充值存储在此文件中,此文件是chart中的默认的value文件,可以使用多种方式对其覆盖
        • helm install --values=my-value.yaml ${Helm_Chart_Name}
        • `helm install –set version=2.0.0 ${Helm_Chart_Name}``
      • charts 文件夹,内部包含chart的依赖,也就是当前chart依赖的其他chart会被存储在此文件夹下
      • templates文件夹,内部包含模板文件
      • README
      • license
  • Helm中的Release Management

    • 主要是Helm的两个大版本的区分
      • 2
        • Client( helm cli)
        • Server (Tiller)(运行在某k8s 集群中)
          • 会记录每一次chart的执行部署,并自动生成版本以进行版本维护
            • 因此可以执行版本升级以及回滚
              • helm upgrade ${Helm_Chart_Name}
              • helm rollback ${Helm_Chart_Name}
          • 每一次新的执行部署都会直接在原有的部署上更改,而不是产生一个新的
          • image-20201221165911286
      • 3
        • 显然Tiller作为集群的一部分拥有者很高的权限,会带来安全问题,因此在版本3中,Tiller就没了
  • 使用Helm Chart部署服务

    • helm install ${Helm_Chart_Name}

Volumes组件

  • k8s默认并未开启对于Pod的数据持久化,也就是说Pod每次重新创建都会导致其中数据的丢失,持久化存储应该不依赖于Pod的生命周期

  • 持久化存储必须在所有节点都是可用的,因为Pod可能被部署在任意一个节点上

  • 持久化存储需要高可用,即便集群crash也可以恢复

  • mention: volume组件不属于任何NS,是集群全局可访问的

  • 在k8s中集群数据持久化使用此组件,存储形式有以下三种:

    • Persistent Volume

      • 可以将PV视为集群的资源,如果CPU RAM一样,因此在集群创建之处,在其他组件创建之前就应该已经创建好
        • 补充:
          • 在k8s中有两种用户角色:
            • k8s系统管理员:
              • 创建并维护集群
              • 保证集群资源足够可用(因此管理员负责获得可用的存储资源,并配置好volume)
            • k8s系统使用者
              • 在集群中创建部署应用
      • 同样使用Yaml创建部署
      • PV 支持存储层的一个抽象,真正的存储需要有实际的集群中的物理磁盘,或者集群外的nfs Server或者云存储服务(可以在配置文件中配置)
        • k8s只提供PV这样的抽象服务,但是真正用什么存储以及存储过程需要自己管理,包括维护存储的可用性等等
        • 可以把此Volume视为集群的一个插件
        • nfsServer:image-20201221183600917
        • Google Cloud: image-20201221183954543
        • localStorage: image-20201221184015654
          • 对于数据库的持久化应该使用remote storage因为localStorage是绑定在某个特定节点的,不能保证每个节点都能访问使用,并且在集群崩溃后不能保证数据可用
        • 除此之外,k8s还支持很多存储
      • 场景举例: Pod应用要访问的数据文件夹或配置文件夹
    • Persistent Volume Claim

      • 其作用于集群应用于PV之间,用来声明应用使用哪个PV

      • 同样使用yaml文件进行配置

      • image-20201221185904305
      • PVC声明了应用要使用何种类型的存储,并声明好访问方式,需求的容量等等,满足此声明要求的PV都可以成为这个应用实际上使用的PV

      • 既然PVC声明了应用的需求,那么在Pod或者Deployment的配置中也要声明自己使用的指定的PVC

      • image-20201221190606415
        • 在Pod的配置文件中,volumes节点声明合适的PV会挂载到本Pod中,volumeMounts节点声明合适的PV挂载到Pod中的所有的或者部分容器的某文件系统位置处(name节点匹配对应的Volume_name)
      • 换句话说Pod通过PVC来请求PV,由PVC帮助Pod寻找符合声明的PV,并使得Volume可以访问PV

        image-20201221190940657
      • mention: PVC必须和引用自己的Pod处于同一个NS中,否则不可跨NS引用

      • 之所以引入PVC这一层是有好处的:

        • 集群的使用者不用关心实际的存储是怎么提供的,只需要说明自己的需求,就足够了
    • Storage Class

      • developer通过提出PVC,请求PV,这要求在developer的应用部署之前,system admin就要进行大量的存储实现配置,PV配置等等,比较繁琐,因此引入第三方的一个组件–Storage Class,动态的根据需求创建PV

      • 使用yaml进行配置

      • 因为SC执行对于PV的自动创建,所以PV中对于实际存储后台的配置一般都要同样在SC中配置好

        image-20201221193546876
        • 通过指定provisioner节点来告诉k8s如何自动创建与指定的存储形式(nfs、Google Cloud etc)匹配的PV

          • 每一种存储形式都有其对应的provisioner节点的值,此值由k8s执行,以kubernetes.io开头
          • 当然也有第三方的provisioner
        • parameter节点声明了需要的PV的一些具体参数,比如文件系统类型等等,与PV 配置中的类似

        • SC相当于执行了PV的自动化构建,需求仍然是来自于PVC,因此还是在PVC中指定SC,使用storageClassName节点匹配即可

          image-20201221194449598
          • 此过程就是PVC指定一个SC创建出匹配PVC要求的PV供Pod使用(主动的,自动的创建,而不是等待系统管理员事先准备好)
    • 实际上与Volume类似的,ConfigMap与Secret都是存储相关的组件,但是这两个与Volume差别很大:

      • 使用本地存储
      • 不是通过PV、PVC创建
      • 由k8s管理,保证其可用性
      • 除了之前的案例中使用的ConfigMap、Secret的键值对的存储形式外,也可以将其挂载到Pod中
      • image-20201221192235053
  • Pod可以同时使用多种类型的Volume

    image-20201221192552950

    image-20201221192644059

k8s StatefulSet

  • 与Deployment对立,针对有状态应用的的抽象管理(Pod管理,replicaset等等)

  • 什么是有、无状态应用呢?

    • 有状态应用
      • 所有的数据库应用
      • 需要记录数据以得到应用状态的应用
    • 无状态应用
      • 不记录应用状态
      • 每一次提供服务都与之前的服务无关
  • Deployment与StatefulSet的区别

    • 使用Deployment部署stateless应用是很简单的,可以随意的创建删除、扩容缩容,因为这些Pod都是一模一样的

    • StateFulSet管理的stateful应用中的pod并不是完全一致的,因此并不能随意的创建与删除,实际上其Pod的都有自己特定的ID用以标识其身份,并且是不可更改的ID(**格式是:${statefulset name}-${从0开始的序号}**)(比如重新规划Pod之后,ID也不会更改)(Deployment中的Pod ID 就是任意的哈希值)

      • StateFulSet管理的Pod并不是平等的,这是因为对于比如数据库这样的有状态应用来说,当有多个Pod时,不会允许这些Pod同时读或者写数据库,因为可能造成数据冲突,只会允许特定ID的Pod执行写操作,这个特定的Pod即为Master,其余的就是slaves

      • 除了身份上的不同之外,这些Pod(以数据库应用为例)对应的存储层的PV也不会是同一个物理存储位置,也就是每一个Pod都有自己对应的数据存储位置(这里不仅存储实际上的业务数据,也存储着该Pod的身份信息等元信息,因此当一个Pod down掉重新创建后其ID不更改,对应的数据存储也能与之重新建立联系,同样的,建议使用remote Storage 而不是local storage,如果Pod重新创建在其他Node,将不能与之前的另外一个Node的数据重新建立联系),因此,需要不断地维持这些Pod对应的多个数据存储位置中的数据的一致性

        image-20201221203440229
        • 实际上只有master对应的存储允许写操作,会发生状态更改,因此slave节点需要维持持续的同步操作,以保证状态更改后的下一次请求获得的是最新的数据
      • 新加入的Pod会clone前一个Pod(注意不是任一个,而是按照ID的前一个)的数据,随后维持更改同步

      • 类似的在Pod创建时,后一个Pod的创建的前提是前一个Pod创建并运行成功,按照次序有条件的创建,序号为0的Pod一般是Master Pod

      • 类似的在Pod删除时,从序号的最后一个开始,后一个Pod完全停止运行后,才会轮到前一个

      • 在Service层级,Service可以作为Statefulset中多个Pod的loadbalancer,但是与Deployment不同的是,每一个Pod都有一个单独的Dns Service提供域名解析服务,域名解析服务的名字的格式是${pod name}.${loadbalancer service name}

        image-20201221205501875
        • 这意味着当Pod重启后,IP会改变但是其名字和endpoint(可以理解为域名)不会变
  • 以上的众多机制,都是为了维护Pod的状态

  • 但是机制是复杂的,k8s也没有提供足够完善的机制,比如数据同步等操作k8s是不负责的,所以有状态应用尽量变得无状态,实在不行还是部署在外部服务中,而不是k8s集群中

k8s中的Service

  • 固定的静态IP,代替Pod为外界提供稳定的服务

  • 对内,对多个Pod之间执行负载均衡操作

  • 有以下4种类型的Service

    • Cluster IP

      • 默认的Service类型

      • 使用场景分析:

        image-20201221213343427

        • 假设一个Pod有两个容器,部署在Node2

        • 每一个Node都会在集群内部被分配到一个范围内的IP用于对内分配到Pod

        • 在Service中注册的Pod称为Service的endpoint

        • 可以把此类型的Service,视为Internal Service

          image-20201221215502999

        • Service配置案例:

          1
          apiVersion: v1kind: Servicemetadata:  name: mongo-servicespec:  selector:      app: mongodb  ports:      - name: mongodb        protocol: TCP        port: 27017        targetPort: 27017      - name: mongodb-exporter        protocol: TCP        port: 9216        targetPort: 9216
          • mention: 当作为endpoint的Pod中有多个容器并且都对外监听端口时,以为着Service也要开启多个端口进行监听转发,当设置多个端口的Service时,配置文件中应为Port分配对应的name属性,如果只有单端口开放的话,不需要设置name
    • headless

      • Cluster IP会默认做负载均衡,但是如果想直接怼特定的Pod进行通信的话,就需要此类型的Service
      • 部署Stateful应用,比如数据库时,需要这样的Service,因为不同的Pod身份不一样,能执行的操作也不一样
      • 如果要访问特定的Pod,就必须知道要访问的Pod的IP,请求端获取IP的方法是使用DNS Lookup,对集群内部维护的DNS 服务发起对Service的lookup请求会返回Service的IP,但是如果把Service配置文件中的clusterIP节点设置为None后,返回的就是Service中注册的Pod的IP
      • mention:创建此类型的Service只需要把clusterIP设置为None即可,不用设置type属性,实际上也不能设置为headless
        • type属性只能设置为:
          • ClusterIP
          • NodePort
          • LoadBalancer
      • image-20201221220724685
        • 对于stateful应用来说,Cluster IP 与headless两种类型的Service一般是共存的
    • NodePort类型

      • image-20201221221225300
        • 集群外部的流量可以直接通过特定的Node的端口访问到Service的特定端口

        • 在创建NodePort类型的Service后会自动(隐式)创建一个ClusterIP 类型的Service(上图中的绿色部分

        • 当Pod分布在多个Node上时:

        • image-20201221222139529

          • 自动创建的绿色部分的ClusterIP类型的Service仍然发挥负载均衡的作用,并且只接受来自NodePort Service的流量
        • 显然此类型的Service不安全,更好的向集群外部提供服务的Service类型是LoadBalancer类型,在实际使用中只有在快速测试时会用到NodePort类型的Service,生产环境下有如下两个选择以暴露服务:

          image-20201221223301567

      • LoadBalancer类型

        • image-20201221222519829
          • 通过云厂商提供的负载均衡服务器来访问
          • 类似的LoadBalancer类型的Service(蓝色部分)创建后会自动创建(隐式)对应的NodePort(灰色边界未画出)、ClusterIP(绿色部分)类型的Service
            • 类似的 此时NodePort类型的Service只会接受来自LoadBalancer的流量

参考