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进行扩容
- 程序自定义的度量指标,这里自定义指标可以通过
Prometheus
和Prometheus-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