云原生二十篇|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
相关推荐
xiaocaibao77738 分钟前
编程语言的软件工程
开发语言·后端·golang
0wioiw01 小时前
Flask-----SQLAlchemy教程
后端·python·flask
uhakadotcom1 小时前
代码人生-精选文章周刊
前端·后端·github
秀儿y2 小时前
单机服务和微服务
java·开发语言·微服务·云原生·架构
Lucifer三思而后行2 小时前
达梦 DM8 参数设置全攻略,建库前先看这篇!
后端
三天不学习2 小时前
C# 中的记录类型简介 【代码之美系列】
后端·c#·微软技术·record·记录类型
任小永的博客2 小时前
VUE3+django接口自动化部署平台部署说明文档(使用说明,需要私信)
后端·python·django
凡人的AI工具箱3 小时前
每天40分玩转Django:Django类视图
数据库·人工智能·后端·python·django·sqlite
凡人的AI工具箱3 小时前
每天40分玩转Django:实操图片分享社区
数据库·人工智能·后端·python·django
Q_19284999063 小时前
基于Spring Boot的个人健康管理系统
java·spring boot·后端