在本文中,我们从技术细节上对kubernetes进行简单运用介绍,利用一些yaml脚本层面上实例告诉大家kubernetes基本概念。Kubernetes以及它呈现出的编程范式值得你去使用和整合到自己的技术栈中。
Kubernetes最初认为是谷歌开源的容器集群管理系统,是Google多年大规模容器管理技术Borg或Omega的开源版本。准确来说的话,kubernetes更是一个全新的平台,一个全新的平台管理工具,它是专门为job和service设计。完全开放,2014年6月开始接受公开的commit,任何人都可以提供意见。由于kubernetes简化了开发、运维和管理负荷,越来越多的企业开始在生产环境使用,因此kubernetes得到了迅速的发展。
基于容器的应用部署、维护和滚动升级
负载均衡和服务发现
跨机器和跨地区的集群调度
自动伸缩
无状态服务和有状态服务
广泛的Volume支持
插件机制保证扩展性
Kubernetes主要由以下几个核心组件组成:
etcd保存了整个集群的状态;
apiserver提供了资源操作的唯一入口,并提供认证、授权、访问控制、API注册和发现等机制;
controller manager负责维护集群的状态,比如故障检测、自动扩展、滚动更新等;
scheduler负责资源的调度,按照预定的调度策略将Pod调度到相应的机器上;
kubelet负责维护容器的生命周期,同时也负责Volume(CVI)和网络(CNI)的管理;
Container runtime负责镜像管理以及Pod和容器的真正运行(CRI);
kube-proxy负责为Service提供cluster内部的服务发现和负载均衡
如果只是为了了解kubernetes,可以使用minikube的方式进行单机安装,minikube实际就是本地创建了一个虚拟机,里面运行了kubernetes的一些必要的环境,相当于k8s的服务环境,创建pod,service,deployment等都是在里面进行创建和管理。在本文中,我使用kubeadm方式安装kubernetes 1.10.0,具体kubernetes部署步骤:
使用kubeadm方式安装kubernetes 1.10.0
Kubernetes集群添加/删除Node
Kubernetes Dashboard1.8.3部署
k8s原生的集群监控方案(Heapster+InfluxDB+Grafana)
请注意:上述环境只是测试环境,生产环境部署大同小异。
Container(容器)是一种便携式、轻量级的操作系统级虚拟化技术。它使用namespace隔离不同的软件运行环境,并通过镜像自包含软件的运行环境,从而使得容器可以很方便的在任何地方运行。由于容器体积小且启动快,因此可以在每个容器镜像中打包一个应用程序。这种一对一的应用镜像关系拥有很多好处。
使用容器,不需要与外部的基础架构环境绑定,因为每一个应用程序都不需要外部依赖,更不需要与外部的基础架构环境依赖,完美解决了从开发到生产环境的一致性问题。容器同样比虚拟机更加透明,这有助于监测和管理。尤其是容器进程的生命周期由基础设施管理,而不是由容器内的进程对外隐藏时更是如此。最后,每个应用程序用容器封装,管理容器部署就等同于管理应用程序部署。在Kubernetes必须要使用Pod来管理容器,每个Pod可以包含一个或多个容器。
Pod是kubernetes中你可以创建和部署的最小也是最简的单位。一个Pod代表着集群中运行的一个进程;
在Kubrenetes集群中Pod的使用方式;
Pod中如何管理多个容器
上面已经说了“Pod是kubernetes中你可以创建和部署的最小也是最简的单位。一个Pod代表着集群中运行的一个进程。”Pod中封装着应用的容器(有的情况下是好几个容器),存储、独立的网络IP,管理容器如何运行的策略选项。
Pod代表着部署的一个单位:kubernetes中应用的一个实例,可能由一个或者多个容器组合在一起共享资源。
请注意:Docker是kubernetes中最常用的容器运行时,但是Pod也支持其他容器运行时。
(1)一个Pod中运行一个容器“每个Pod中一个容器”的模式是最常见的用法;在这种使用方式中,你可以把Pod想象成是单个容器的封装,kuberentes管理的是Pod而不是直接管理容器。
实战:创建一个nginx容器
apiVersion: v1
kind: Pod
metadata:
name:nginx-test
labels:
app: web
spec:
containers:
- name:front-end
image:nginx:1.7.9
ports:
-containerPort: 80
创建Pod:
kubectl create -f ./pod1-deployment\
查看Pod:
kubectl get po
查看Pod详细情况:
kubectl describe po nginx-test
进入到Pod(容器)内部:
kubectl exec -it nginx-test /bin/bash
(2)在一个Pod中同时运行多个容器
说明:在一个Pod中同时运行多个容器是一种比较高级的用法。只有当你的容器需要紧密配合协作的时候才考虑用这种模式。
一个Pod中也可以同时封装几个需要紧密耦合互相协作的容器,它们之间共享资源。这些在同一个Pod中的容器可以互相协作成为一个service单位——一个容器共享文件,另一个“sidecar”容器来更新这些文件。Pod将这些容器的存储资源作为一个实体来管理。
实战:在一个pod里放置两个容器:nginx与redis
apiVersion: v1
kind: Pod
metadata:
name: rss-site
labels:
app: web
spec:
containers:
- name:front-end
image:nginx:1.7.9
ports:
-containerPort: 80
- name:rss-reader
image: redis
ports:
-containerPort: 88
创建Pod:
kubectl create -f ./test-deployment
查看pod
kubectl get po
查看Pod详细情况
kubectl describe po rss-site
进入front-end内部:
kubectl exec -it rss-site -c front-end /bin/bash
进入rss-reade内部:
kubectl exec -it rss-site -c rss-reader /bin/bash
以上是关于Pod的简单介绍。
Node是Pod真正运行的主机,可以物理机,也可以是虚拟机。为了管理Pod,每个Node节点上至少要运行container runtime(比如docker或者rkt)、kubelet和kube-proxy服务。
Namespace是对一组资源和对象的抽象集合,比如可以用来将系统内部的对象划分为不同的项目组或用户组。常见的pods, services, replication controllers和deployments等都是属于某一个namespace的(默认是default),而node, persistentVolumes等则不属于任何namespace。
我们既然有Pod了,为什么还要使用Deployment呢?这是因为实际工作中,我们很少会直接在kubernetes中创建单个Pod。因为Pod的生命周期是短暂的,用后即焚的实体。Deployment为Pod和ReplicaSet提供了一个声明式定义(declarative)方法,用来替代以前的ReplicationController来方便的管理应用。你只需要在Deployment中描述想要的目标状态是什么,Deploymentcontroller就会帮你将Pod和ReplicaSet的实际状态改变到你的目标状态。你可以定义一个全新的Deployment来创建ReplicaSet或者删除已有的Deployment并创建一个新的来替换。
什么是复制控制器(ReplicationController,RC)
RC是K8s集群中最早的保证Pod高可用的API对象。通过监控运行中的Pod来保证集群中运行指定数目的Pod副本。指定的数目可以是多个也可以是1个;少于指定数目,RC就会启动运行新的Pod副本;多于指定数目,RC就会杀死多余的Pod副本。即使在指定数目为1的情况下,通过RC运行Pod也比直接运行Pod更明智,因为RC也可以发挥它高可用的能力,保证永远有1个Pod在运行。RC是K8s较早期的技术概念,只适用于长期伺服型的业务类型,比如控制小机器人提供高可用的Web服务。
什么是副本集(Replica Set,RS)
RS是新一代RC,提供同样的高可用能力,区别主要在于RS后来居上,能支持更多种类的匹配模式。副本集对象一般不单独使用,而是作为Deployment的理想状态参数使用。
Deployment典型的应用场景
定义Deployment来创建Pod和ReplicaSet
滚动升级和回滚应用;如果当前状态不稳定,回滚到之前的Deployment revision。每次回滚都会更新Deployment的revision
扩容和缩容,扩容Deployment以满足更高的负载
暂停和继续Deployment,暂停Deployment来应用PodTemplateSpec的多个修复,然后恢复上线
比如,我们这里定义一个简单的nginx应用:
apiVersion:extensions/v1beta1
kind: Deployment
metadata:
name:nginx-test
namespace:test
spec:
replicas: 3
template:
metadata:
labels:
app: nginx
spec:
containers:
-name: nginx
image: nginx:1.7.9
ports:
- containerPort: 80
创建deploy
kubectl create -f ./nginx-deployment
查看deploy
kubectl get deploy --namespace=test
查看rs(副本集)
kubectl get rs --namespace=test
查看pods(容器组)
kubectl get po --namespace=test
关于Deployment的应用还有很多,如:扩容、缩容、滚动升级、回滚应用等,这里由于篇幅的问题不再一一介绍。
Label是识别Kubernetes对象的标签,以key/value的方式附加到对象上(key最长不能超过63字节,value可以为空,也可以是不超过253字节的字符串)。
Label不提供唯一性,并且实际上经常是很多对象(如Pods)都使用相同的label来标志具体的应用。Label定义好后其他对象可以使用Label Selector来选择一组相同label的对象(比如ReplicaSet和Service用label来选择一组Pod)。Label Selector支持以下几种方式:
等式,如app=nginx和env!=production
集合,如env in(production, qa)
多个label(它们之间是AND关系),如app=nginx,env=test
Service account作用
Service account是为了方便Pod里面的进程调用Kubernetes API或其他外部服务。
Serviceaccount使用场景
运行在pod里的进程需要调用Kubernetes API以及非Kubernetes API的其它服务。Service Account它并不是给kubernetes集群的用户使用的,而是给pod里面的进程使用的,它为pod提供必要的身份认证。
与User account区别
User account是为人设计的,而service account则是为了Pod中的进程
User account是跨namespace的,而service account则是仅局限它所在的namespace
实战命名空间
apiVersion: v1
kind: Namespace
metadata:
name:datagrand
labels:
name: test
创建namespace:test
kubectl create -f ./test.yaml
查看命名空间test的sa
kubectl get sa -n test
查看命名空间test生成的default
kubectl get sa default -o yaml -n test
我们可以创建Deployment时,使用这个test命名空间了,如上例Deployment实战。
Service Account鉴权
Service Account为服务提供了一种方便的认知机制,但它不关心授权的问题。可以配合RBAC来为Service Account鉴权:
配置--authorization-mode=RBAC和--runtime-config=rbac.authorization.k8s.io/v1alpha1
配置--authorization-rbac-super-user=admin
定义Role、ClusterRole、RoleBinding或ClusterRoleBinding
实战鉴权
我们在Kubernetes Dashboard1.8.3部署中,碰到首次登入出现访问权限报错的问题,原因就是ServiceAccount的创建问题。
apiVersion:rbac.authorization.k8s.io/v1beta1
kind: ClusterRoleBinding
metadata:
name:kubernetes-dashboard
labels:
k8s-app: kubernetes-dashboard
roleRef:
apiGroup:rbac.authorization.k8s.io
kind:ClusterRole
name:cluster-admin
subjects:
- kind:ServiceAccount
name:kubernetes-dashboard
namespace:kube-system
Secret介绍
Secret解决了密码、token、密钥等敏感数据的配置问题,而不需要把这些敏感数据暴露到镜像或者Pod Spec中。Secret可以以Volume或者环境变量的方式使用。
Secret类型
Opaque(default):任意字符串,base64编码格式的Secret,用来存储密码、密钥等
kubernetes.io/service-account-token:作用于ServiceAccount,就是kubernetes的Service Account中所说的。即用来访问Kubernetes API,由Kubernetes自动创建,并且会自动挂载到Pod的/run/secrets/kubernetes.io/serviceaccount目录中
kubernetes.io/dockercfg:作用于Docker registry,用户下载docker镜像认证使用。用来存储私有docker registry的认证信息
实战Opaque Secret类型
Opaque类型的数据是一个map类型,要求value是base64编码格式:
创建admin账户
echo -n "admin" | base64
YWRtaW4=
echo -n "1f2d1e2e67df" | base64
MWYyZDFlMmU2N2Rm
创建secret.yaml
cat >> secrets.yml << EOF
apiVersion: v1
kind: Secret
metadata:
name:mysecret
type: Opaque
data:
password:MWYyZDFlMmU2N2Rm
username:YWRtaW4=
创建secret
kubectl create -f secrets.yml
查看secret运行状态
kubectl get secret --all-namespaces
以Volume方式
以环境变量方式
实战Secret使用Volume方式
apiVersion: v1
kind: Pod
metadata:
name: mypod
labels:
name: wtf
spec:
volumes:
- name:secrets
secret:
secretName: mysecret
containers:
- image:nginx:1.7.9
name: nginx
volumeMounts:
- name:secrets
mountPath:"/etc/secrets"
readOnly:true
ports:
- name: cp
containerPort: 5432
hostPort:5432
说明:这样就可以通过文件的方式挂载到容器内,在/etc/secrets目录下回生成这个文件。
实战Secret使用环境变量方式
apiVersion:extensions/v1beta1
kind: Deployment
metadata:
name:wordpress-deployment
spec:
replicas: 2
template:
metadata:
labels:
app:wordpress
spec:
containers:
- name:"wordpress"
image:"wordpress:latest"
ports:
-containerPort: 80
env:
- name:WORDPRESS_DB_USER
valueFrom:
secretKeyRef:
name: mysecret
key: username
- name:WORDPRESS_DB_PASSWORD
valueFrom:
secretKeyRef:
name: mysecret
key: password
查看Pod运行状态
kubectl get po
NAME READY STATUS RESTARTS AGE
wordpress-deployment-6b569fbb7d-8qcpg 1/1 Running 0 2m
wordpress-deployment-6b569fbb7d-xwwkg 1/1 Running 0 2m
进入容器内部查看环境变量
kubectl exec -itwordpress-deployment-694f4c79b4-cpsxw /bin/bash
root@wordpress-deployment-694f4c79b4-cpsxw:/var/www/html#env
WORDPRESS_DB_USER=admin
WORDPRESS_DB_PASSWORD=1f2d1e2e67df
Configure说明
ConfigMaps允许你将配置文件、命令行参数或环境变量中读取的配置信息与docker image分离,以保持集装箱化应用程序的便携性。即ConfigMapAPI给我们提供了向容器中注入配置信息的机制。
理解ConfigMaps和Pods
ConfigMap API资源用来保存key-value pair配置数据,这个数据可以在pods里使用,或者被用来为像controller一样的系统组件存储配置数据。虽然ConfigMap跟Secrets类似,但是ConfigMap更方便的处理不含敏感信息的字符串。
注意:ConfigMaps不是属性配置文件的替代品。ConfigMaps只是作为多个properties文件的引用。你可以把它理解为Linux系统中的/etc目录,专门用来存储配置文件的目录。
实战创建ConfigMap
kind: ConfigMap
apiVersion: v1
metadata:
creationTimestamp: 2016-02-18T19:14:38Z
name:example-config
namespace:default
data:
example.property.1: hello
example.property.2: world
example.property.file: |-
property.1=value-1
property.2=value-2
property.3=value-3
data一栏包括了配置数据,ConfigMap可以被用来保存单个属性,也可以用来保存一个配置文件。配置数据可以通过很多种方式在Pods里被使用。ConfigMaps可以被用来:
设置环境变量的值
在容器里设置命令行参数
在数据卷里面创建config文件
为什么需要Volume
容器磁盘上文件的生命周期是短暂的,这就使得在容器中运行重要应用时出现一些问题。比如,当容器崩溃时,kubelet会重启它,但是容器中的文件将丢失--容器以干净的状态(镜像最初的状态)重新启动。
其次,在Pod中同时运行多个容器时,这些容器之间通常需要共享文件。Kubernetes中的Volume抽象就很好的解决了这些问题。
Volume背景
Docker中也有一个volume的概念,尽管它稍微宽松一些,管理也很少。在Docker中,卷就像是磁盘或是另一个容器中的一个目录。它的生命周期不受管理,直到最近才有了local-disk-backed卷。Docker现在提供了卷驱动程序,但是功能还非常有限(例如Docker1.7只允许每个容器使用一个卷驱动,并且无法给卷传递参数)。
Kubernetes中的卷有明确的寿命——与封装它的Pod相同。所以,卷的生命比Pod中的所有容器都长,当这个容器重启时数据仍然得以保存。当然,当Pod不再存在时,卷也将不复存在。也许更重要的是,Kubernetes支持多种类型的卷,Pod可以同时使用任意数量的卷。要使用卷,需要为pod指定为卷(spec.volumes字段)以及将它挂载到容器的位置(spec.containers.volumeMounts字段)。
Volume类型
Kubernetes支持以下类型的卷:
awsElasticBlockStore、azureDisk、azureFile、cephfs、csi、downwardAPI、emptyDir、fc (fibre channel)、flocker、gcePersistentDisk、gitRepo、glusterfs、hostPath、iscsi、local、nfs、persistentVolumeClaim、projected、portworxVolume、quobyte、rbd、scaleIO、secret、storageos、vsphereVolume等
K8S的存储系统分类
K8S的存储系统从基础到高级大致分为三个层次:普通Volume,Persistent Volume和动态存储供应。
普通Volume
最简单的普通Volume是单节点Volume。它和Docker的存储卷类似,使用的是Pod所在K8S节点的本地目录。
persistent volume
它和普通Volume的区别是什么呢?
普通Volume和使用它的Pod之间是一种静态绑定关系,在定义Pod的文件里,同时定义了它使用的Volume。Volume是Pod的附属品,我们无法单独创建一个Volume,因为它不是一个独立的K8S资源对象。而Persistent Volume简称PV是一个K8S资源对象,所以我们可以单独创建一个PV。它不和Pod直接发生关系,而是通过Persistent Volume Claim,简称PVC来实现动态绑定。Pod定义里指定的是PVC,然后PVC会根据Pod的要求去自动绑定合适的PV给Pod使用。
PV的访问模式有三种:
ReadWriteOnce:是最基本的方式,可读可写,但只支持被单个Pod挂载
ReadOnlyMany:可以以只读的方式被多个Pod挂载
ReadWriteMany:这种存储可以以读写的方式被多个Pod共享。比较常用的是NFS
一般来说,PV和PVC的生命周期分为5个阶段:
Provisioning,即PV的创建,可以直接创建PV(静态方式),也可以使用StorageClass动态创建
Binding,将PV分配给PVC
Using,Pod通过PVC使用该Volume
Releasing,Pod释放Volume并删除PVC
Reclaiming,回收PV,可以保留PV以便下次使用,也可以直接从云存储中删除
根据这5个阶段,Volume的状态有以下4种:
Available:可用
Bound:已经分配给PVC
Released:PVC解绑但还未执行回收策略
Failed:发生错误
变成Released的PV会根据定义的回收策略做相应的回收工作。有三种回收策略:
Retain就是保留现场,K8S什么也不做,等待用户手动去处理PV里的数据,处理完后,再手动删除PV
Delete K8S会自动删除该PV及里面的数据
Recycle K8S会将PV里的数据删除,然后把PV的状态变成Available,又可以被新的PVC绑定使用
在实际使用场景里,PV的创建和使用通常不是同一个人。这里有一个典型的应用场景:管理员创建一个PV池,开发人员创建Pod和PVC,PVC里定义了Pod所需存储的大小和访问模式,然后PVC会到PV池里自动匹配最合适的PV给Pod使用。
前面在介绍PV的生命周期时,提到PV的供给有两种方式,静态和动态。其中动态方式是通过StorageClass来完成的,这是一种新的存储供应方式。
使用StorageClass有什么好处呢?除了由存储系统动态创建,节省了管理员的时间,还有一个好处是可以封装不同类型的存储供PVC选用。在StorageClass出现以前,PVC绑定一个PV只能根据两个条件,一个是存储的大小,另一个是访问模式。在StorageClass出现后,等于增加了一个绑定维度。
比如这里就有两个StorageClass,它们都是用谷歌的存储系统,但是一个使用的是普通磁盘,我们把这个StorageClass命名为slow。另一个使用的是SSD,我们把它命名为fast。在PVC里除了常规的大小、访问模式的要求外,还通过annotation指定了Storage Class的名字为fast,这样这个PVC就会绑定一个SSD,而不会绑定一个普通的磁盘。限于篇幅问题,这里简单说一下emptyDir、nfs、PV和PVC。
实战--emptyDir
emptyDir说明
EmptyDir类型的volume创建于pod被调度到某个宿主机上的时候,而同一个pod内的容器都能读写EmptyDir中的同一个文件。一旦这个pod离开了这个宿主机,EmptyDir中的数据就会被永久删除。所以目前EmptyDir类型的volume主要用作临时空间,比如Web服务器写日志或者tmp文件需要的临时目录。
apiVersion: v1
kind: Pod
metadata:
labels:
name:test-emptypath
role: master
name: test-emptypath
spec:
containers:
- name:test-emptypath
image:nginx:1.7.9
volumeMounts:
- name:log-storage
mountPath:/tmp/
volumes:
- name:log-storage
emptyDir: {}
实战使用共享卷的标准多容器Pod
apiVersion: v1
kind: Pod
metadata:
name:datagrand
spec:
containers:
- name: test1
image:nginx:1.7.9
volumeMounts:
- name:log-storage
mountPath:/usr/share/nginx/html
- name: test2
image:centos
volumeMounts:
- name:log-storage
mountPath:/html
command:["/bin/sh","-c"]
args:
- whiletrue;do
data>> /html/index.html;
sleep 1;
done
volumes:
- name:log-storage
emptyDir:{}
简单解释下上面的内容:在这个例子中,我们定义了一个名为HTML的卷。它的类型是emptyDir,这意味着当一个Pod被分配到一个节点时,卷先被创建,并只要Pod在节点上运行时,这个卷仍存在。正如名字所说,它最初是空的。
第一容器运行nginx的服务器并将共享卷挂载到目录/ usr /share/ nginx /html。第二容器使用centos的镜像,并将共享卷挂载到目录/HTML。每一秒,第二容器添加当前日期和时间到index.html文件中,它位于共享卷。当用户发出一个HTTP请求到Pod,nginx的服务器读取该文件并将其传递给响应请求的用户。
实战--NFS卷
NFS卷说明
nfs卷允许将现有的NFS(网络文件系统)共享挂载到你的容器中。不像emptyDir,当删除Pod时,nfs卷的内容被保留,卷仅仅是被卸载。这意味着NFS卷可以预填充数据,并且可以在pod之间“切换”数据。NFS可以被多个写入者同时挂载。
NFS卷使用注意
请先部署好自己的NFS服务
在使用共享之前,必须运行自己的NFS服务器并运行共享
实战pod内的文件共享
apiVersion: v1
kind: Pod
metadata:
name:nginx-test
labels:
app: nginx
spec:
containers:
- name: nginx
image:nginx:1.7.9
ports:
-containerPort: 80
volumeMounts:
#Mount thepath to the container
- mountPath:"/tmp/"
name:pv0003
volumes:
- name: pv0003
nfs:
#fixed:This ip is the addressof the nfs server
server:192.168.246.169
#fixed:This path is sharedexternally by the nfs server
path:"/data"
实战PV和PVC
nfs作为k8s的网络存储驱动,可以满足持久存储业务的需求,支持多节点读写。下面是两个Pod同时使用一个持久性volume实例。
#创建PV
apiVersion: v1
kind: PersistentVolume
metadata:
name: nfs-pv
spec:
capacity:
storage: 4Gi
accessModes:
-ReadWriteMany
nfs:
server:192.168.246.168 ##NFS服务器的ip地址
path:"/data" ##NFS服务器上的共享目录
#创建PVC
apiVersion: v1
kind: PersistentVolumeClaim
metadata:
name: nfs-pvc
spec:
accessModes:
- ReadWriteMany
storageClassName: ""
resources:
requests:
storage:3Gi
#创建Deployment
apiVersion: apps/v1
kind: Deployment
metadata:
name:nginx-deployment
labels:
app: nginx
spec:
replicas: 2
selector:
matchLabels:
app: nginx
template:
metadata:
labels:
app:nginx
spec:
containers:
- name:nginx
image:nginx:1.7.9
volumeMounts:
-mountPath: "/wtf"
name: datadir
volumes:
- name:datadir
persistentVolumeClaim:
claimName: nfs-pvc -containerPort: 80
Kubernetes Pod是有生命周期的,它们可以被创建,也可以被销毁,然而一旦被销毁生命就永远结束。通过ReplicaSets能够动态地创建和销毁Pod(例如,需要进行扩缩容,或者执行滚动升级)。每个Pod都会获取它自己的IP地址,即使这些IP地址不总是稳定可依赖的。这会导致一个问题:在Kubernetes集群中,如果一组Pod(称为backend)为其它Pod(称为frontend)提供服务,那么那些frontend该如何发现,并连接到这组Pod中的哪些backend呢?
什么是Service
Kubernetes Service定义了这样一种抽象:一个Pod的逻辑分组,一种可以访问它们的策略——通常称为微服务。这一组Pod能够被Service访问到,通常是通过Label Selector(查看下面了解,为什么可能需要没有selector的Service)实现的。
举个例子,考虑一个图片处理backend,它运行了3个副本。这些副本是可互换的—— frontend不需要关心它们调用了哪个backend副本。然而组成这一组backend程序的Pod实际上可能会发生变化,frontend客户端不应该也没必要知道,而且也不需要跟踪这一组backend的状态。Service定义的抽象能够解耦这种关联。
对Kubernetes集群中的应用,Kubernetes提供了简单的Endpoints API,只要Service中的一组Pod发生变更,应用程序就会被更新。对非Kubernetes集群中的应用,Kubernetes提供了基于VIP的网桥的方式访问Service,再由Service重定向到backend Pod。
定义Service
有selector的单端口Service
一个Service在Kubernetes中是一个REST对象,和Pod类似。像所有的REST对象一样,Service定义可以基于POST方式,请求apiserver创建新的实例。例如,假定有一组Pod,它们对外暴露了9376端口,同时还被打上"app=MyApp"标签。
kind: Service
apiVersion: v1
metadata:
name:my-service
spec:
selector:
app: MyApp
ports:
- protocol:TCP
port: 80
targetPort: 9376
上述配置将创建一个名称为“my-service”的Service对象,它会将请求代理到使用TCP端口9376,并且具有标签"app=MyApp"的Pod上。这个Service将被指派一个IP地址(通常称为“Cluster IP”),它会被服务的代理使用(见下面)。该Service的selector将会持续评估,处理结果将被POST到一个名称为“my-service”的Endpoints对象上。
需要注意的是,Service能够将一个接收端口映射到任意的targetPort。默认情况下,targetPort将被设置为与port字段相同的值。可能更有趣的是,targetPort可以是一个字符串,引用了backend Pod的一个端口的名称。但是,实际指派给该端口名称的端口号,在每个backend Pod中可能并不相同。对于部署和设计Service,这种方式会提供更大的灵活性。例如,可以在backend软件下一个版本中,修改Pod暴露的端口,并不会中断客户端的调用。
Kubernetes Service能够支持TCP和UDP协议,默认TCP协议。
有selector的多端口Service
很多Service需要暴露多个端口。对于这种情况,Kubernetes支持在Service对象中定义多个端口。当使用多个端口时,必须给出所有的端口的名称,这样Endpoint就不会产生歧义,例如:
kind: Service
apiVersion: v1
metadata:
name:my-service
spec:
selector:
app: MyApp
ports:
- name:http
protocol: TCP
port: 80
targetPort: 9376
- name:https
protocol: TCP
port:443
targetPort: 9377
没有selector的Service
Service抽象了该如何访问Kubernetes Pod,但也能够抽象其它类型的backend,例如:
希望在生产环境中使用外部的数据库集群,但测试环境使用自己的数据库。
希望服务指向另一个Namespace中或其它集群中的服务。
正在将工作负载转移到Kubernetes集群,和运行在Kubernetes集群之外的backend。
根据以上的应用场景,我们都能够定义没有selector的Service,如下:
kind: Service
apiVersion: v1
metadata:
name:my-service
spec:
ports:
- protocol:TCP
port: 80
targetPort: 9376
由于这个Service没有selector,就不会创建相关的Endpoints对象。可以手动将Service映射到指定的Endpoints:
kind: Endpoints
apiVersion: v1
metadata:
name:my-service
subsets:
- addresses:
- ip:10.0.0.3 ##Endpoint IP = PodIP +ContainerPort
ports:
- port:9376
注意:Endpoint IP地址不能是loopback(127.0.0.0/8)、link-local(169.254.0.0/16)、或者link-local多播(224.0.0.0/24)。访问没有selector的Service,与有selector的Service的原理相同。请求将被路由到用户定义的Endpoint(该示例中为10.0.0.3:9376)。
发布服务——服务类型
对一些应用(如Frontend)的某些部分,可能希望通过外部(Kubernetes集群外部)IP地址暴露Service。Kubernetes ServiceTypes允许指定一个需要的类型的Service,默认是ClusterIP类型。Type的取值以及行为如下:
ClusterIP通过集群的内部IP暴露服务,选择该值,服务只能够在集群内部可以访问,这也是默认的ServiceType。
NodePort通过每个Node上的IP和静态端口(NodePort)暴露服务。NodePort服务会路由到ClusterIP服务,这个ClusterIP服务会自动创建。通过请求:,可以从集群的外部访问一个NodePort服务。
LoadBalancer使用云提供商的负载均衡器,可以向外部暴露服务。外部的负载均衡器可以路由到NodePort服务和ClusterIP服务。
ExternalName通过返回CNAME和它的值,可以将服务映射到externalName字段的内容(例如,foo.bar.example.com)。没有任何类型代理被创建,这只有Kubernetes 1.7或更高版本的kube-dns才支持。
NodePort类型
如果设置type的值为"NodePort",Kubernetes master将从给定的配置范围内(默认:30000-32767)分配端口,每个Node将从该端口(每个Node上的同一端口)代理到Service。该端口将通过Service的spec.ports[*].nodePort字段被指定。如果需要指定的端口号,可以配置nodePort的值,系统将分配这个端口,否则调用API将会失败(比如,需要关心端口冲突的可能性)。
# pwd
/data
# tree -L 3
.
├── mysql
│ ├── conf
│ │ └── my.cnf
│ └── data
│ ├── auto.cnf
│ ├── edusoho
│ ├── ibdata1
│ ├── ib_logfile0
│ ├── ib_logfile1
│ ├── mysql
│ └── performance_schema
├── nginx
│ ├── conf
│ │ └── nginx.conf
│ ├── edusoho
│ │ ├── api
│ │ ├── app
│ │ ├── bootstrap
│ │ ├── plugins
│ │ ├── src
│ │ ├── vendor
│ │ ├── vendor_user
│ │ └── web
│ └── log
│ └── error.log
├── php
│ ├── log
│ │ └── php-fpm.log
│ ├── php-fpm.conf
│ ├── php.ini
│ └── www.conf
apiVersion: v1
kind: Pod
metadata:
name:lamp-edusoho
labels:
app:lamp-edusoho
restartPolicy: Always
spec:
containers:
- name: nginx
abels:
app:lamp-nginx
image:dockerhub.datagrand.com/global/nginx:v1
ports:
-containerPort: 80
volumeMounts:
- name:datadir
mountPath: "/var/log/nginx/error.log"
subPath:./nginx/log/error.log
- name:datadir
mountPath: "/etc/nginx/nginx.conf"
subPath:./nginx/conf/nginx.conf
- name:datadir
mountPath: "/usr/share/nginx/html"
subPath:./nginx/edusoho
- name: php
image:dockerhub.datagrand.com/global/php:v1
ports:
-containerPort: 9000
volumeMounts:
-mountPath: /usr/local/php/etc/php-fpm.conf
name:datadir
subPath:./php/php-fpm.conf
-mountPath: /usr/local/php/etc/php-fpm.d/www.conf
name:datadir
subPath:./php/www.conf
-mountPath: /usr/local/php/etc/php.ini
name:datadir
subPath:./php/php.ini
-mountPath: /usr/local/php/var/log/php-fpm.log
name:datadir
subPath:./php/log/php-fpm.log
-mountPath: /usr/share/nginx/html
name:datadir
subPath:./nginx/edusoho
- name: mysql
image:dockerhub.datagrand.com/global/mysql:5.6
ports:
-containerPort: 3306
env:
- name:MYSQL_ROOT_PASSWORD
value:"123456"
- name:MYSQL_DATABASE
value:"edusoho"
- name:MYSQL_USER
value:"edusoho"
- name:MYSQL_PASSWORD
value:"edusoho"
args:['--character-set-server=utf8']
volumeMounts:
- name:datadir
mountPath: "/var/lib/mysql"
subPath:./mysql/data
- name:datadir
mountPath: "/etc/my.cnf"
subPath:./mysql/conf/my.cnf
volumes:
- name:datadir
persistentVolumeClaim:
claimName:nfs-pvc
apiVersion: v1
kind: PersistentVolume
metadata:
name: nfs-pv
spec:
capacity:
storage: 4Gi
accessModes:
-ReadWriteMany
nfs:
server:192.168.246.168 ##NFS服务器的ip地址
path:"/data" ##NFS服务器上的共享目录
4 PVC的yaml文件
apiVersion: v1
kind: PersistentVolumeClaim
metadata:
name: nfs-pvc
spec:
accessModes:
-ReadWriteMany
storageClassName: ""
resources:
requests:
storage:3Gi
apiVersion: v1
kind: Service
metadata:
name: edusoho
labels:
app: edusoho
spec:
type: NodePort
ports:
- port: 80
nodePort:32756
selector:
app:lamp-edusoho
查看Pod
kubectl get po -o wide
查看Service
kubectl get svc
进入容器内部某个应用,如这里的nginx
kubectl exec -it lamp-edusoho -c nginx /bin/bash
http://192.168.246.168:32756/install/start-install.php
说明:这里的192.168.246.168是kubernetes的node节点IP,32756是Service中定义的nodePort。
参考文档
kubernetes概念--Service:https://kubernetes.io/docs/concepts/services-networking/service/
kubernetes官网教程:https://kubernetes.io/docs/tutorials/
Kubernetes中的Persistent Volume解析:https://jimmysong.io/posts/kubernetes-persistent-volume/
关于作者
吴腾飞:达观数据运维工程师 ,负责达观数据系统平台和应用业务的快速部署、监控、优化及维护,设计并研发自动化运维工具和平台,数据库的日常维护。
对自动化运维,Docker容器、虚拟化技术和容器编排kubernetes相关领域有浓厚兴趣。