上一节:第一章、容器化实现原理

一、架构总览

k8s-架构总览

1.1 Master

Apiserver
资源操作的唯一入口,并提供认证、授权、访问控制、API 注册和发现等机制。

Controller Manager
资源对象自动化控制中心,如自动扩缩容、故障检测、滚动更新。

Scheduler
负责资源的调度,决定了一个服务需要运行在哪个节点机器。

Etcd
kubernetes 数据存储的地方,除此之外还提供了资源的 watch 机制,参与集群的 leader 精选(也是通过 watch 进行心跳检测)

1.2 Node

Kubelete
与 master 协同合作,控制节点上面容器的生命周期(容器什么时候创建、启停、销毁),同时也负责 存储(CSI)和 网络(CNI)的管理。

Kube-proxy
负责为 service 提供集群内部的服务发现和负载均衡。一个 pod 服务可能对应了多个实例,具体要访问哪个实例 service 会帮我们做负载均衡,service 的具体实现依赖的就是每个节点的 kube-proxy。

Docker
这边目前只要是实现了容器运行时规范(CRI)的容器引擎都可以,最常见的就是 docker 了,kata 也挺流行。相比 docker 基于 ns、cgroup 的隔离,kata 直接采用基于硬件的隔离方案,能够更加的轻量、安全、高效,毫无疑问这是未来容器云的发展方向。

二、服务运行的步骤流程

2.1 服务创建(k8s 侧流程)

  1. 客户端(kubectl)像 apiserver 创建 pod 的请求,该请求被解析后写入 etcd 中。
  2. controller manager 通过监听 etcd(也是通过 apiserver,不是直接监听 etcd)资源变化,watch 到这个事件。于是根据 pod 的模板定义生成了 pod 对象,并通过 apiserver 写入 etcd。
  3. 同样 scheduler 组件也监视着 etcd(利用watch etcd 的机制各个组件也相对解耦),在感知到有新的 pod 需要创建,通过一系列策略计算找到一个相对合适可供部署的节点。同样的把该节点信息通过 apiserver 写入 etcd 中。
  4. 目标节点的 kubelet 组件通过 apiserver 感知到有 pod 需要部署到自己的节点,于是调用节点上面容器运行时的 grpc 接口(简单说就是触发本地的容器引擎创建容器服务)跟节点的容器引擎交互。

cri

2.2 服务创建(容器侧流程)

这部分内容属于比较底层的实现细节,k8s 初学者可以跳过。
  1. kubelet 通过 CRI(Container Runtime Interface) 接口(gRPC) 调用 docker shim, 请求创建一个容器, 这一步中, Kubelet 可以视作一个简单的 CRI Client, 而 docker shim 就是接收请求的 Server
  2. docker shim 收到请求后, 通过适配的方式,适配成 Docker Daemon 的请求格式, 发到 Docker Daemon 上请求创建一个容器。(在docker 1.12后版本中,docker daemon被拆分成dockerd和containerd,containerd负责操作容器)
  3. dockerd收到请求后, 调用containerd进程去创建一个容器
  4. containerd 收到请求后, 并不会自己直接去操作容器, 而是创建一个叫做 containerd-shim 的进程, 让 containerd-shim 去操作容器
  5. containerd-shim 在这一步需要调用 runC 这个命令行工具, 来启动容器,runC是OCI(Open Container Initiative, 开放容器标准) 的一个参考实现。主要用来设置 namespaces 和 cgroups, 挂载 root filesystem等操作
  6. runC启动完容器后本身会直接退出, containerd-shim 则会成为容器进程的父进程, 负责收集容器进程的状态, 上报给 containerd, 并在容器中 pid 为 1 的进程退出后接管容器中的子进程进行清理, 确保不会出现僵尸进程(关闭进程描述符等)

runc

PS:k8s 在 1.20 版本之后去除 docker-shim,优化缩短了调用链,去除了 docker-shim,不需要借助 dockerd 直接调用 containerd。详细可以参看:https://www.kubeclub.cn/kubernetes/175.html

上面一个个步骤描述的是 kubelet 如何通过容器引擎的一层层服务把命令下发给宿主机才管理容器的生命周期,但是在 k8s 的角度 pod 是他的最小单位,所以他跟容器不是一对一的关系。

sandbox

Kubelet 会通过 Remote Procedure Command(RPC) 协议调用 RunPodSandbox。sandbox 用于描述一组容器,例如在 Kubernetes 中它表示的是 Pod。sandbox 是一个很宽泛的概念,所以对于其他没有使用容器的运行时仍然是有意义的(比如在一个基于 hypervisor 的运行时中,sandbox 可能指的就是虚拟机)。
我们的例子中使用的容器运行时是 Docker,创建 sandbox 时首先创建的是 pause 容器。pause 容器作为同一个 Pod 中所有其他容器的父容器,它为 Pod 中的每个业务容器提供了大量的 Pod 级别资源,这些资源就是 Linux Namespace, 例如 IPC NS、Net NS、PID NS。
pause 容器提供了一种方法来管理所有这些命名空间并允许业务容器共享它们,在同一个网络命名空间中的好处是:同一个 Pod 中的容器可以使用 localhost 来相互通信。pause 容器的第二个功能与 PID 命名空间的工作方式相关,在 PID 命名空间中,进程之间形成一个树状结构,一旦某个子进程由于父进程的错误而变成了“孤儿进程”,其便会被 init 进程进行收养并最终回收资源。

2.3 服务访问

pod 创建完之后一般无法直接访问,而且 pod 一般有多个实例,具体要访问哪个,这个问题需要借助 service 来处理。
  1. 通过 kubectl 提交 一个映射到上面 pod 的 service 创建请求。
  2. Controller Manager会通过Label标签查询到相关联的Pod实例,然后生成 Service 的 Endpoints(关联了后端 pod 访问入口) 信息,并通过 API Server写入到etcd中。
  3. 所有节点运行的 proxy 进程通过 apiserver 查询并监听 service 和 endpoints 信息,利用 iptable 跟 nat 机制,在本地节点创建了一系列复杂的访问转发策略。

当然,service 侧重于解决 pod 的负载均衡问题,解决 pod 集群内通信、集群外通信又有一系列知识需要引入,这边就不过多展开。网络的解决方案有很多,kubernetes 提供了 CNI 网络插件接口来实现。

三、Deployment

deployment 是对 pod 的一种纳管,可以不用 deployemnt 单独创建 pod,但是创建 deployment 的过程落到实处就是创建 pod。有了 deployment 我们能方便的对 pod 设置副本、更新策略、回滚、重启等。在使用 kubernetes 过程中,最频繁用到的也就是 deployment 了,很有必要对他的各种参数进行了解。

元数据

  • metadata: depoyment 级别的元数据
  • spec.template.metadata : pod 模板对应实例的元数据

资源清单

  • spec: deployment 资源清单

      replicas:  #副本数量
      revisionHistoryLimit: #保留历史版本,默认是10
      paused: #暂停部署,默认是false
      progressDeadlineSeconds: #部署超时时间(s),默认是600
      strategy: #策略
        type: RollingUpdates  #滚动更新策略
        rollingUpdate:  #滚动更新
          maxSurge: #最⼤额外可以存在的副本数,可以为百分⽐,也可以为整数
          maxUnavaliable: #最⼤不可⽤状态的pod的最⼤值,可以为百分⽐,也可以为整数
      selector:  #选择器,通过它指定该控制器管理哪些pod
        matchLabels:   #Labels匹配规则
           app: hotwheel-admin
        matchExpressions:   #Expression匹配规则
  • spec.template.spec: pod 资源清单

        dnsPolicy: ClusterFirst
        terminationGracePeriodSeconds: 15
        automountServiceAccountToken: false  # 将该服务的账号证书挂载到 pod 里面 /var/run/secrets/kubernetes.io/serviceaccount
        dnsConfig:    # 手动修改容器里面的 dns 服务器信息
          nameservers:
    options:
      - name: single-request-reopen
  imagePullSecrets:  # 镜像拉取权限设置
    - name: default-secret
  volumes:
    - emptyDir: {}
      name: vol-log
    - emptyDir: {}
      name: framework-log
  containers:
    - lifecycle:
        preStop: # pod结束前调用的 hook
          exec:
            command:
              - /bin/sh
              - '-c'
              - >-
                java -jar /home/appuser/java_pre_stop-1.0.jar
                '/home/appuser/gc.hprof' >> upload_obs.log
      image: >-
        xxxxxxx-tag-v4.3.2:123993
      imagePullPolicy: Always
      livenessProbe:  # 健康检测-存活探针
        failureThreshold: 20
        periodSeconds: 10
        timeoutSeconds: 2
        successThreshold: 1
        initialDelaySeconds: 15
        httpGet:
          path: /health
          scheme: HTTP
          port: 8080
      terminationMessagePolicy: File
      terminationMessagePath: /dev/termination-log
      name: hotwheel-admin
      readinessProbe:  # 健康检测-就绪探针
        failureThreshold: 5
        periodSeconds: 30
        timeoutSeconds: 2
        successThreshold: 1
        initialDelaySeconds: 15
        httpGet:
          path: /health
          scheme: HTTP
          port: 8080
      resources:
        requests:
          memory: 1Gi
          cpu: 100m
        limits:
          memory: 8Gi
          cpu: '2'
      securityContext:
        capabilities:
          add:
            - NET_BIND_SERVICE
          drop:
            - KILL
        allowPrivilegeEscalation: false
      env:  # 环境变量
        - name: SPRING_PROFILES_ACTIVE
          value: prod
        - name: EGG_SERVER_ENV
          value: prod
        - name: SERVER_SOURCE
          value: CCE
        - name: CCE_POD_NAME
          valueFrom:
            fieldRef:
              apiVersion: v1
              fieldPath: metadata.name
        - name: CCE_NAMESPACE
          valueFrom:
            fieldRef:
              apiVersion: v1
              fieldPath: metadata.namespace
        - name: CCE_POD_IP
          valueFrom:
            fieldRef:
              apiVersion: v1
              fieldPath: status.podIP
        - name: CCE_NODE_NAME
          valueFrom:
            fieldRef:
              apiVersion: v1
              fieldPath: spec.nodeName
      volumeMounts:
        - mountPath: /logs/
          name: vol-log
        - mountPath: /usr/local/logs/
          name: framework-log
  hostAliases: # 对应 etc/hosts 配置
    - ip: 10.88.128.19
      hostnames:
        - trinotest01.joymo.tech
  securityContext: # 安全设置
    runAsUser: 1000
    fsGroup: 1000
    runAsGroup: 1000
    runAsNonRoot: true
  restartPolicy: Always
  schedulerName: default-scheduler
  affinity:    # 亲和性
    nodeAffinity:
      requiredDuringSchedulingIgnoredDuringExecution:
        nodeSelectorTerms:
          - matchExpressions:
              - values:
                  - gitlab-runner
                key: nodename
                operator: NotIn
    podAntiAffinity:
      preferredDuringSchedulingIgnoredDuringExecution:
        - podAffinityTerm:
            labelSelector:
              matchExpressions:
                - values:
                    - hotwheel-admin
                  key: app
                  operator: In
            topologyKey: kubernetes.io/hostname
          weight: 1

四、service

由于pod ip可变,对于访问者来讲需要通过service这种固定的方式访问pod。Service有四种类型的实现:ClusterIP、NodePort、LoadBalancer、ExternalName

ClusterIP
clusterip
这种类型的service 只能在集群内访问。

NodePort
NodePort
此时我们可以通过http://4.4.4.1:30080或http://4.4.4.2:30080对pod-python访问。该端口有一定的范围,比如默认Kubernetes控制平面将在--service-node-port-range标志指定的范围内分配端口(默认值:30000-32767)。

LoadBalancer
LoadBalancer
对于使用nodeport类型的service来讲,如果k8s主机有增减,外部调用者需要感知到ip的变化,相对比较麻烦。云厂商在nodeport基础上通过LoadBalancer解决了这一问题,LoadBalancer可以动态感知node节点变化,华为云上就是这么用的。需要说明的是开源版本k8s不支持这种类型的service。

ExternalName

使用ExternalName将外部服务映射到内部服务
ExternalName
name: service-python
spec:
  ports:
  - port: 3000
    protocol: TCP
    targetPort: 443
  type: ExternalName
  externalName: remote.server.url.com

五、Ingress

k8s外部访问k8s内部,通常有几种做法:

  1. ingress
  2. nodeport
  3. elb(云厂商独有)
  4. 路由指向

ingress是k8s标准的外内网访问方法,有多种具体实现,如nginx、traffic等等。早期ingress只支持7层协议,现在基本上都支持4层协议了,但还是推荐使用7层协议。

nodeport方式在每一台k8s主机上新打开一个端口,通过主机ip:port方式访问到主机,然后内部映射到service cluster ip:port,再由service转发到pod上。但这种方式也有不变之处,每个主机ip:port都可以访问到service,如果增减主机外部调用者也需要同步进行主机ip变更,如果有一个负载均衡器统一管理这些主机ip就比较理想了,于是云厂商在nodeport基础上结合elb实现了基于elb的访问k8s内部服务方法。至于一个elb绑定几个service,这个要具体结合业务系统具体情况来考虑。

路由指向是最传统的一种方法,在路由器上配置路由规则,直接打通外部网络与k8s server网络,这种方式多用于vm、k8s混合使用的场景,尤其是应用逐渐从vm迁移到k8s的过程中使用。

点赞(2) 打赏

评论列表 共有 0 条评论

暂无评论
立即
投稿
发表
评论
返回
顶部