达观数据:kubernetes简介和实战

在本文中,我们从技术细节上对kubernetes进行简单运用介绍,利用一些yaml脚本层面上实例告诉大家kubernetes基本概念。Kubernetes以及它呈现出的编程范式值得你去使用和整合到自己的技术栈中。

达观数据:kubernetes简介和实战_第1张图片

kubernetes简单介绍

kubernetes起源

Kubernetes最初认为是谷歌开源的容器集群管理系统,是Google多年大规模容器管理技术Borg或Omega的开源版本。准确来说的话,kubernetes更是一个全新的平台,一个全新的平台管理工具,它是专门为job和service设计。完全开放,2014年6月开始接受公开的commit,任何人都可以提供意见。由于kubernetes简化了开发、运维和管理负荷,越来越多的企业开始在生产环境使用,因此kubernetes得到了迅速的发展。

kubernetes功能

  • 基于容器的应用部署、维护和滚动升级

  • 负载均衡和服务发现

  • 跨机器和跨地区的集群调度

  • 自动伸缩

  • 无状态服务和有状态服务

  • 广泛的Volume支持

  • 插件机制保证扩展性

kubernetes核心组件

Kubernetes主要由以下几个核心组件组成:

  • etcd保存了整个集群的状态;

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

  • controller manager负责维护集群的状态,比如故障检测、自动扩展、滚动更新等; 

  • scheduler负责资源的调度,按照预定的调度策略将Pod调度到相应的机器上; 

  • kubelet负责维护容器的生命周期,同时也负责Volume(CVI)和网络(CNI)的管理; 

  • Container runtime负责镜像管理以及Pod和容器的真正运行(CRI); 

  • kube-proxy负责为Service提供cluster内部的服务发现和负载均衡

kubernetes环境部署

如果只是为了了解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)

请注意:上述环境只是测试环境,生产环境部署大同小异。

kubernetes基本概念

Container

Container(容器)是一种便携式、轻量级的操作系统级虚拟化技术。它使用namespace隔离不同的软件运行环境,并通过镜像自包含软件的运行环境,从而使得容器可以很方便的在任何地方运行。由于容器体积小且启动快,因此可以在每个容器镜像中打包一个应用程序。这种一对一的应用镜像关系拥有很多好处。

使用容器,不需要与外部的基础架构环境绑定,因为每一个应用程序都不需要外部依赖,更不需要与外部的基础架构环境依赖,完美解决了从开发到生产环境的一致性问题。容器同样比虚拟机更加透明,这有助于监测和管理。尤其是容器进程的生命周期由基础设施管理,而不是由容器内的进程对外隐藏时更是如此。最后,每个应用程序用容器封装,管理容器部署就等同于管理应用程序部署。在Kubernetes必须要使用Pod来管理容器,每个Pod可以包含一个或多个容器。

Pod

关于Pod的概念主要有以下几点:

  • Pod是kubernetes中你可以创建和部署的最小也是最简的单位。一个Pod代表着集群中运行的一个进程;

  • 在Kubrenetes集群中Pod的使用方式;

  • Pod中如何管理多个容器

理解Pod:

上面已经说了“Pod是kubernetes中你可以创建和部署的最小也是最简的单位。一个Pod代表着集群中运行的一个进程。”Pod中封装着应用的容器(有的情况下是好几个容器),存储、独立的网络IP,管理容器如何运行的策略选项。

Pod代表着部署的一个单位:kubernetes中应用的一个实例,可能由一个或者多个容器组合在一起共享资源。

请注意:Docker是kubernetes中最常用的容器运行时,但是Pod也支持其他容器运行时。

Kubrenetes集群中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

Node是Pod真正运行的主机,可以物理机,也可以是虚拟机。为了管理Pod,每个Node节点上至少要运行container runtime(比如docker或者rkt)、kubelet和kube-proxy服务。

Namespace

Namespace是对一组资源和对象的抽象集合,比如可以用来将系统内部的对象划分为不同的项目组或用户组。常见的pods, services, replication controllers和deployments等都是属于某一个namespace的(默认是default),而node, persistentVolumes等则不属于任何namespace。

Deployment

我们既然有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的多个修复,然后恢复上线

实战Deployment

比如,我们这里定义一个简单的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

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作用

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介绍

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   

Secret使用方式

  • 以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   

ConfigMap

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文件

Volum

为什么需要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

10 Service

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将会失败(比如,需要关心端口冲突的可能性)。

kubernetes实战--edusoho平台创建

文件目录结构

# 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

Pod的yaml文件

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

PV的yaml文件

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的yaml文件

apiVersion: v1
kind: PersistentVolumeClaim
metadata:
  name: nfs-pvc
spec:
  accessModes:
    -ReadWriteMany
 storageClassName: ""
  resources:
    requests:
      storage:3Gi

Service的yaml文件

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

访问安装Edusoho平台

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相关领域有浓厚兴趣。

你可能感兴趣的:(文本智能处理)