云原生二十篇|Kubernetes基础知识

Kubernetes简称k8s,由于篇幅的原因,所以将k8s拆分为4篇文章:

  • Kubernetes基础知识
  • Kubernetes核心原理
  • Kubernetes实践
  • Kubernetes源码解析

先上一个k8s的总体架构图:

Kubernetes架构

1、为什么需要Kubernetes?

在了解为什么需要Kubernetes之前,先说说Kubernetes是什么?Kubernetes简称k8s,可以认为是Google的Borg开源版本,是大规模集群管理系统,基于容器技术,目的是实现资源管理的自动化,以及跨多个数据中心的资源利用率最大化。

为什么需要k8s?我总结大概有几个原因:

(1)当前云原生时代k8s能火的一个原因:这是一门看似美好的新技术(这个并非贬义,我这里是中性表达,毕竟现在很多人也吐槽k8s架构复杂,并未考虑到老的系统升级),不过对一些大型公司而已,通过k8s的确统一了很多云上的资源,即使没有k8s,很多中大型公司都有自己的资源管理平台,不过k8s将这一技术架构统一了,虽然未能解决定制化问题,但是技术架构的确新,而且为后续扩展提供了多种可能。

(2)从运维和开发角度来说,使用k8s我们不再需要费心于负载均衡的选择和实施部署问题,不必再考虑引入复杂的服务治理框架,不需要再头疼于服务监控和故障处理模块的开发,总之是方便让开发者更加关注业务本身。

2、简单的样例

在开始样例之前,我们可以找一台centos机器执行如下命令:

bash 复制代码
# 关闭防火墙
systemctl disable firewalld
systemctl stop firewalld

# 安装etcd和kubernetes
yum install -y etcd kubernetes

# 启动服务
systemctl start etcd
systemctl start docker
systemctl start kube-apiserver
systemctl start kube-controller-manager
systemctl start kube-scheduler
systemctl start kubelet
systemctl start kube-proxy

# 查看k8s运行情况
kubectl version
# 输出结果
Client Version: version.Info{Major:"1", Minor:"5", GitVersion:"v1.5.2", GitCommit:"269f928217957e7126dc87e6adfa82242bfe5b1e", GitTreeState:"clean", BuildDate:"2017-07-03T15:31:10Z", GoVersion:"go1.7.4", Compiler:"gc", Platform:"linux/amd64"}
Server Version: version.Info{Major:"1", Minor:"5", GitVersion:"v1.5.2", GitCommit:"269f928217957e7126dc87e6adfa82242bfe5b1e", GitTreeState:"clean", BuildDate:"2017-07-03T15:31:10Z", GoVersion:"go1.7.4", Compiler:"gc", Platform:"linux/amd64"}

# 解决k8s创建pod报错No API token found for service account "default", retry after the token is automatically
openssl genrsa -out /etc/kubernetes/serviceaccount.key 2048
# vim /etc/kubernetes/apiserver,添加如下内容:KUBE_API_ARGS="--service_account_key_file=/etc/kubernetes/serviceaccount.key"
# vim /etc/kubernetes/controller-manager,添加如下内容:KUBE_CONTROLLER_MANAGER_ARGS="--service_account_private_key_file=/etc/kubernetes/serviceaccount.key"
# 重启k8s
systemctl restart etcd kube-apiserver kube-controller-manager kube-scheduler

(1) 启动一个nginx的pod

创建文件nginx-pod.yaml文件如下:

bash 复制代码
apiVersion: v1 #必选 版本号
kind: ReplicationController #必选
metadata:   # 必选 元数据
    name: nginx  # 必选 Pod名称
    labels:   # 自定义标签
        app: nginx  # 自定义标签名称
spec:    # 必选 Pod中容器的详细定义
    containers:  # 必选 Pod中容器列表,一个pod里会有多个容器
        - name: nginx  # 必选 容器名称
        image: nginx  # 必选 容器的镜像名称
        imagePullPolicy: IfNotPresent # [Always | Never | IfNotPresent] 获取镜像的策略 Alawys表示下载镜像 IfnotPresent表示优先使用本地镜像,否则下载镜像,Nerver表示仅使用本地镜像 
       ports: # 需要暴露的端口库号列表
       - containerPort: 80 # 容器需要监听的端口号
restartPolicy: Always # [Always | Never | OnFailure] # Pod的重启策略,Always表示一旦不管以何种方式终止运行,kubelet都将重启,OnFailure表示只有Pod以非0退出码退出才重启,Nerver表示不再重启该Pod 

执行:

lua 复制代码
kubectl create -f nginx-pod.yaml

查看容器状态:

csharp 复制代码
[root@VM-0-11-centos ~]# kubectl get pods
NAME      READY     STATUS              RESTARTS   AGE
nginx     0/1       ContainerCreating   0          2m

(2) 启动一个nginx的service

创建文件nginx-svc.yaml文件如下:

yaml 复制代码
apiVersion: v1   # 必选 版本号
kind: Service       # 必选
metadata:   # 必选 元数据
    name: service-hello  # 必选 Pod名称
    labels:   # 自定义标签
        name: service-hello # 自定义标签名称
spec:    # 必选 Pod中容器的详细定义
    type: NodePort # 这里代表是NodePort类型的,另外还有ingress,LoadBalancer 
    ports: 
    - port: 80 # 这里的端口和clusterIP(kubectl describe service service-hello中的IP的port)对应,即在集群中所有机器上curl clusterIP:80可访问发布的应用服务
     targetPort: 8080 # 端口一定要和contrainer暴露出来的端口对应,nodejs暴露出来的端口是8080
     protocol: TCP
     nodePort: 31111 # 所有的节点都会开发此端口30000~32767,此端口供外部调用
    selector:
     run: hello  # 这里选择器一定要选择容器的标签

执行:

lua 复制代码
kubectl create -f nginx-svc.yaml

查看service状态:

scss 复制代码
[root@VM-0-11-centos ~]# kubectl get service
NAME            CLUSTER-IP       EXTERNAL-IP   PORT(S)        AGE
kubernetes      10.254.0.1       <none>        443/TCP        37m
service-hello   10.254.251.205   <nodes>       80:31111/TCP   30s

(3) 访问服务

可以通过curl -i 'http://{服务器IP}:31111'访问对应的服务。

3、资源对象

k8s的资源对象包括Node,Pod,Controller,Service等,下面来介绍一下这些相关的资源对象。

(1) Master

Master在k8s中是集群控制节点,基本上所有的控制命令都会发给master节点,所以也需要考虑高可用,通常是多台Master组成一个高可用集群。

Master节点上运行一些服务,如下:

  • API Server:提供HTTP接口,负责操作集群的增删改查
  • Controller Manager:自动化控制中心,通过循环监听事件,统一处理各个资源对象
  • Scheduler:负责Pod的调度
  • etcd:相当于k8s的数据库,保存所有的资源信息

(2) Node

Node节点可以认为是工作节点,一般是物理主机或者虚拟机的形式存在,统一由k8s的Master节点管理,当某个Node不可用或者退出当前集群时,master都会感知其状态,从而重新调度Node,Master是如何感知这些Node呢?需要借助Node上的几个服务:

  • kublet:负责Pod的生命周期,同时将各个信息上报给Master
  • kube-proxy:实现k8s中的service代理的角色,主要是提供服务负载均衡

可以通过命令查看node状态等信息:

csharp 复制代码
kubectl get nodes # 查看信息
kubectl describe node # 查看详情

(3) Pod

Pod对于使用过k8s的开发应该不陌生,这个是最基础的调度单元,为什么需要有Pod存在?

首选在前面容器的文章中介绍过容器,理论上容器应该是最小调度单元才对吧?不对,容器应该是单个进程部署(当然也有开发将容器当虚拟机用,部署多个进程),那么一个业务只有一个进程么,显然不一定,在微服务架构中的一个服务可能是包含多个进程组件,这些进程组件理应打包为一个大的服务单元,所以以Pod的方式更好管理;

其次如果一个服务中的某个组件挂了,认为这个服务是可用还是不可用呢?最保守的做法是不可用,所以将这些服务放在一个Pod单元中,如果某个服务挂了,那么可以认为这个Pod不可用;

最后以Pod为单位的所有容器服务,共享网络,Volume等,更简化了架构设计;

说回Pod,Pod相关的概念有哪些呢?

Pod组成

  • Pause:初始化容器,主要为Pod提供共享网络,Volume等,同时pause容器的生命周期就是Pod的生命周期
  • containerPort:暴露Pod对集群内的端口,其中Pod是按照每个Pod都会有一个IP,这个IP只能是在相同的k8s集群内才能使用
  • Requests:Pod作为容器调度的最小单元,当然就需要有对应的资源限定,其中Requests就是一个Pod最小运行的在Node上分配的资源,比如0.5核512M等
  • Limits:Pod有最小运行单元,当然也就有最大运行限制,其中Limits就是限制CPU和内存的使用,这里要注意的是这个是一个预期值,当Node资源够的情况下可以超过限制,但是当Node节点资源调度不过来的时候,超过Limits的情况下,Pod有可能会被kill调重新调度
  • Volume:挂载的存储卷,能够被多个容器访问的共享目录,Volume生命周期Volume与Pod的生命周期相同,但与容器的生命周期不相关,当容器终止或者重启时,Volume中的数据也不会丢失

(4) Label

先来一个例子:

yaml 复制代码
spec:
    name: myweb
    labels:
        app: myweb
  • Label主要方便kube-scheduler定向调度到指定标签上的pod
  • kube-proxy通过Service的Label选择对应的Pod,实现对应的路由转发

(5) Deployment

为了更好地解决服务编排的问题,k8s在V1.2版本开始,引入了deployment控制器,值得一提的是,这种控制器并不直接管理pod,而是通过管理replicaset来间接管理pod,即:deployment管理replicaset,replicaset管理pod,所以deployment比replicaset的功能更强大,其关系如下:

Deployment

deployment的主要功能有下面几个:

  • 支持replicaset的所有功能
  • 支持发布的停止、继续
  • 支持版本的滚动更新和版本回退

查看deployments命令:

arduino 复制代码
kubectl get deployments

(6) HPA

为了实现集群自动化,扩缩容在k8s肯定是需要得到支持,其中Horizontal Pod Autoscaler(HPA)就是通过跟踪Pod的负载变化,动态调整目标Pod的副本数(当高于阈值就扩容,低于阈值就缩容),当前Pod的度量指标有两种方式:

  • CPU利用率的平均值,这里计算的所有Pod的平均值,如果负载不均出现某个Pod很高,但是平均值不高,也不出触发HPA进行扩容
  • 程序自定义的度量指标,这里自定义指标可以通过PrometheusPrometheus-Adapter来实现的,Prometheus用于监控应用的负载和集群本身的各种指标,Prometheus Adapter可以使用Prometheus收集的指标并使用它们来制定扩展策略,这些指标都是通过APIServer暴露

自定义扩容样例,比如nginx_http_requests的值到达10的情况,执行扩容:

vbnet 复制代码
apiVersion: autoscaling/v2beta1
kind: HorizontalPodAutoscaler
metadata:
  name: nginx-custom-hpa
spec:
  scaleTargetRef:
    apiVersion: apps/v1
    kind: Deployment
    name: nginx-exporter
  minReplicas: 2
  maxReplicas: 5
  metrics:
  - type: Pods
    pods:
      metricName: nginx_http_requests
      targetAverageValue: 10

(7) StatefulSet

我们从上面的介绍中知道每个Pod都会有对应IP和Volume,这些生命周期都会和Pod保持一致,所以服务看上去都是无状态的,但是面对有状态的服务在k8s是怎么处理的呢?StatefulSet就是为了解决这个问题,有如下特性:

  • StatefulSet的Pod都是固定的标志,比如nginx-0,nginx-1,nginx-2...
  • StatefulSet的Pod的启动是有顺序的,一般按照固定标识的0~n的顺序启动
  • StatefulSet的Pod的存储卷通过PV/PVC实现,删除Pod默认不会删除与StatefulSet相关的存储卷
  • 这里注意的是在有的云厂商提供IP固定的StatefulSet,但是这个在官方StatefulSet是没有保证IP不变的功能

(8) Service

Service是k8s经常被使用的功能,为服务提供了入口,其在k8s中的角色如下图:

Service

前端应用通过Service访问部署在Pod中的业务逻辑,其中Label Selector是选择对应的Pod,那么其中有几个组件:

  • kube-proxy:每个Node上都有一个kube-proxy,主要是负责均衡,负责Service到Pod之间的转发
  • Cluster IP:Service的全局唯一虚拟IP,在Service生命周期内,Cluster IP是不变的,但是这个IP并非一个实体,在k8s集群外是没法访问,只能在k8s的集群内访问
  • Node IP:Node节点的IP地址,一般是物理IP
  • Pod IP:Pod的IP地址,生命周期与Pod相同
  • NodePort:前面提到了Service的Cluster IP能在k8s集群内的访问,但是无法被k8s集群外的其他服务访问,如何解决?通过在每个Node上开一个NodePort端口,然后与Service的port组成映射关系,那么每个Node IP+NodePort就能进入Service,从而进入k8s集群内访问

获取Cluster IP如下:

scss 复制代码
[root@VM-0-11-centos ~]# kubectl get svc service-hello
NAME            CLUSTER-IP       EXTERNAL-IP   PORT(S)        AGE
service-hello   10.254.251.205   <nodes>       80:31111/TCP   1d

(9) Volume

Volume是能被Pod中多个容器访问的共享目录,其样例如下:

bash 复制代码
apiVersion: v1
kind: Pod   # 资源类型是pod
metadata:
  name: vol1
spec:
  containers:  # 创建两个容器
  - image: busyboxplus
    name: vm1
    command: ["sleep", "300"]
    volumeMounts:
    - mountPath: /cache  # 容器vm1挂载的目录
      name: cache-volume
  - name: vm2
    image: nginx
    volumeMounts:
    - mountPath: /usr/share/nginx/html  # 容器vm2挂载的目录
      name: cache-volume
  volumes:
  - name: cache-volume
    emptyDir:   # 创建empty卷
      medium: Memory # 基于内存
      sizeLimit: 100Mi # 限制大小100M6

其中Volume有几个资源类型:

  • emptyDir:临时目录,生命周期是在Pod在当前Node被移除时候清理,其中存储类型由medium决定
  • hostPath:挂载在宿主机上的目录或者文件

除了Volume这种随着Pod生命周期变化的存储外,还有一种是PersistentVolume(PV),是对存储资源的抽象,将存储定义为一种容器应用可以使用的资源,PV由管理员创建和配置,它与存储提供商的具体实现直接相关,例如:GlusterFS、iSCSI、RBD或GCE和AWS公有云提供的共享存储,通过插件式的机制进行管理,供应用访问和使用。

PersistentVolumeClaim(PVC)是用户对存储资源的一个申请,就像Pod消耗Node的资源一样,PVC消耗PV的资源,PVC可以申请存储空间的大小(Size)和访问模式(例如ReadWriteOnce、ReadOnlyMany或ReadWriteMany)。

PV

(10) Namespace

Namespace是为多租户隔离提供逻辑分组,以便于不同的分组在同一个集群内共享资源同时能被分别管理,如果不加命名空间,其默认是default。

查看可以通过kubectl get namepsaces,如果需要定义并使用Namespace,可以参考如下yaml文件:

vbnet 复制代码
apiVersion: v1
kind: Namespace
metadata:
  name: dev

apiVersion: v1
kind: Pod
metadata:
  namespace: dev # 将当前部署的pod分组到dev
  ...

4、其他

(1) Pod健康检查

当一个Pod启动的时候,在提供对外服务之前,k8s是无法确定知道当前服务是由就绪或者进程还在,但是服务已经不可用了,所以提供了探针功能:

  • exec:执行一段命令
  • http:检测某个http请求
  • tcpSocket:使用此配置,kubelet将尝试在指定端口上打开容器的套接字,如果可以建立连接,容器被认为是健康的,如果不能就认为是失败的

探针也包含两种类型:

  • readiness probe:就绪探测,可以在Pod启动过程中使用
  • liveness probe:Pod生命周期内使用,如果服务探测不通,会被自动拉起来

(2) Job任务

k8s提供了Job任务功能,表示服务运行完成不需要再被调度,主要用于一些临时任务,可以通过kubectl get jobs查看任务,也可以通过kubectl logs {容器名}查看运行期间的日志。

当然任务往往有多类型,Job提供如下功能:

顺序运行

makefile 复制代码
apiVersion: batch/v1
kind: Job
metadata:
  creationTimestamp: null
  name: job2
spec:
  backoffLimit: 4
  completions: 6
  ...
  • backoffLimit:如果job失败,则重试几次
  • completions:需要成功运行的Pod个数,在没有其他配置的情况下,是每次运行一个,运行完成一个接着运行第二个,直到达到上线

并行运行

makefile 复制代码
apiVersion: batch/v1
kind: Job
metadata:
  creationTimestamp: null
  name: job2
spec:
  backoffLimit: 4
  completions: 6
  parallelism: 2
  ...
  • parallelism:每次同时运行的Pod个数,直到达到completions设置的值

CronJob

vbnet 复制代码
apiVersion: batch/v1
kind: CronJob
metadata:
  creationTimestamp: null
  name: job3
spec:        
  schedule: '*/1 * * * *'
...

CronJob和linux的crontab类似,提供 分 小时 日 月 周 指定的运行周期,配置方式也是相同的,这里就不介绍了,其中查看任务命令可以如下:

sql 复制代码
[root@master job]# kubectl get cj
NAME   SCHEDULE      SUSPEND   ACTIVE   LAST SCHEDULE   AGE
mycj   */1 * * * *   False     0        <none>          6s
相关推荐
m0_748256144 小时前
SpringBoot
java·spring boot·后端
多想和从前一样5 小时前
Django 创建表时 “__str__ ”方法的使用
后端·python·django
Godlovesea6 小时前
ubuntu安装docker 无法拉取问题
云原生·eureka
涛粒子7 小时前
Spring Bean 生命周期的执行流程
java·后端·spring
赵琳琅7 小时前
Java语言的云计算
开发语言·后端·golang
赵琳琅7 小时前
MDX语言的安全开发
开发语言·后端·golang
夏梓蕙9 小时前
Elixir语言的软件开发工具
开发语言·后端·golang
夏梓蕙9 小时前
R语言的Web开发
开发语言·后端·golang
绝无仅有9 小时前
Deepseek 万能提问公式:高效获取精准答案
后端·面试·架构
慕容秋瑶10 小时前
T-SQL语言的Web开发
开发语言·后端·golang