威九国际

      威九国际动态

      威九国际愿与业内同行分享 助力各企业在大数据浪潮来临之际一起破浪前行

      威九国际数据:kubernetes简介和实战

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

      kubernetes简单介绍
      1

      kubernetes起源

       

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

      2

      kubernetes功能

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

      • 负载均衡和服务发现

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

      • 自动伸缩

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

      • 广泛的Volume支持

      • 插件机制保证扩展性

      3

      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基本概念

      1

      Container

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

       

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

       

      2

      Pod

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

       

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

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

      • Pod中如何管理多个容器

       

      理解Pod:

       

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

       

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

       

      请注意:Dockerkubernetes中最常用的容器运行时,但是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里放置两个容器:nginxredis

       

       
      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的简单介绍。

       

      3
      Node

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

      4

      Namespace

       

       

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

       

      5

      Deployment

       

      我们既然有Pod了,为什么还要使用Deployment呢?这是因为实际工作中,我们很少会直接在kubernetes中创建单个Pod。因为Pod的生命周期是短暂的,用后即焚的实体。DeploymentPodReplicaSet给予了一个声明式定义(declarative)方法,用来替代以前的ReplicationController来方便的管理应用。你只需要在Deployment中描述想要的目标状态是什么,Deploymentcontroller就会帮你将PodReplicaSet的实际状态改变到你的目标状态。你可以定义一个全新的Deployment来创建ReplicaSet或者删除已有的Deployment并创建一个新的来替换。

       

      什么是复制控制器(ReplicationController,RC

       

      RCK8s集群中最早的保证Pod高可用的API对象。顺利获得监控运行中的Pod来保证集群中运行指定数目的Pod副本。指定的数目可以是多个也可以是1个;少于指定数目,RC就会启动运行新的Pod副本;多于指定数目,RC就会杀死多余的Pod副本。即使在指定数目为1的情况下,顺利获得RC运行Pod也比直接运行Pod更明智,因为RC也可以发挥它高可用的能力,保证永远有1Pod在运行。RCK8s较早期的技术概念,只适用于长期伺服型的业务类型,比如控制小机器人给予高可用的Web服务。

       

      什么是副本集(Replica Set,RS

       

      RS是新一代RC,给予同样的高可用能力,区别主要在于RS后来居上,能支持更多种类的匹配模式。副本集对象一般不单独使用,而是作为Deployment的理想状态参数使用。

       

      Deployment典型的应用场景

       

      • 定义Deployment来创建PodReplicaSet

      • 滚动升级和回滚应用;如果当前状态不稳定,回滚到之前的Deployment revision。每次回滚都会更新Deploymentrevision

      • 扩容和缩容,扩容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的应用还有很多,如:扩容、缩容、滚动升级、回滚应用等,这里由于篇幅的问题不再一一介绍。

       5 

      Label

       

      Label是识别Kubernetes对象的标签,以key/value的方式附加到对象上(key最长不能超过63字节,value可以为空,也可以是不超过253字节的字符串)。

       

      Label不给予唯一性,并且实际上经常是很多对象(如Pods)都使用相同的label来标志具体的应用。Label定义好后其他对象可以使用Label Selector来选择一组相同label的对象(比如ReplicaSetServicelabel来选择一组Pod)。Label Selector支持以下几种方式:

      • 等式,如app=nginxenv!=production

      • 集合,如env in(production, qa)

      • 多个label(它们之间是AND关系),如app=nginx,env=test

       

      6

      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 
      查看命名空间testsa
      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、RoleBindingClusterRoleBinding

       

      实战鉴权

       

      我们在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  
      7

      Secret

       

      Secret介绍

       

      Secret解决了密码、token、密钥等敏感数据的配置问题,而不需要把这些敏感数据暴露到镜像或者Pod Spec中。Secret可以以Volume或者环境变量的方式使用。

       

      Secret类型

      • Opaque(default):任意字符串,base64编码格式的Secret,用来存储密码、密钥等

      • kubernetes.io/service-account-token:作用于ServiceAccount,就是kubernetesService Account中所说的。即用拜访问Kubernetes API,由Kubernetes自动创建,并且会自动挂载到Pod/run/secrets/kubernetes.io/serviceaccount目录中

      • kubernetes.io/dockercfg:作用于Docker registry,用户下载docker镜像认证使用。用来存储私有docker registry的认证信息

       

      实战Opaque Secret类型

       
      Opaque类型的数据是一个map类型,要求valuebase64编码格式:
      创建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   
      8

      ConfigMap

      Configure说明

       

      ConfigMaps允许你将配置文件、命令行参数或环境变量中读取的配置信息与docker image分离,以保持集装箱化应用程序的便携性。即ConfigMapAPI给我们给予了向容器中注入配置信息的机制。

       

      理解ConfigMapsPods 

       

      ConfigMap API资源用来保存key-value pair配置数据,这个数据可以在pods里使用,或者被用来为像controller一样的系统组件存储配置数据。虽然ConfigMapSecrets类似,但是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文件

      9

      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的文件里,同时定义了它使用的VolumeVolumePod的附属品,我们无法单独创建一个Volume,因为它不是一个独立的K8S资源对象。而Persistent Volume简称PV是一个K8S资源对象,所以我们可以单独创建一个PV它不和Pod直接发生关系,而是顺利获得Persistent Volume Claim,简称PVC来实现动态绑定。Pod定义里指定的是PVC,然后PVC会根据Pod的要求去自动绑定合适的PVPod使用。

       

      PV的访问模式有三种:

      • ReadWriteOnce:是最基本的方式,可读可写,但只支持被单个Pod挂载

      • ReadOnlyMany:可以以只读的方式被多个Pod挂载

      • ReadWriteMany:这种存储可以以读写的方式被多个Pod共享。比较常用的是NFS

       

      一般来说,PVPVC的生命周期分为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:发生错误

       

      变成ReleasedPV会根据定义的回收策略做相应的回收工作。有三种回收策略:

      • Retain就是保留现场,K8S什么也不做,等待用户手动去处理PV里的数据,处理完后,再手动删除PV

      • Delete K8S会自动删除该PV及里面的数据

      • Recycle K8S会将PV里的数据删除,然后把PV的状态变成Available,又可以被新的PVC绑定使用

      在实际使用场景里,PV的创建和使用通常不是同一个人。这里有一个典型的应用场景:管理员创建一个PV池,开发人员创建PodPVC,PVC里定义了Pod所需存储的大小和访问模式,然后PVC会到PV池里自动匹配最合适的PVPod使用。

       

      前面在介绍PV的生命周期时,提到PV的供给有两种方式,静态和动态。其中动态方式是顺利获得StorageClass来完成的,这是一种新的存储供应方式。

       

      使用StorageClass有什么好处呢?除了由存储系统动态创建,节省了管理员的时间,还有一个好处是可以封装不同类型的存储供PVC选用。在StorageClass出现以前,PVC绑定一个PV只能根据两个条件,一个是存储的大小,另一个是访问模式。在StorageClass出现后,等于增加了一个绑定维度。

       

      比如这里就有两个StorageClass,它们都是用谷歌的存储系统,但是一个使用的是普通磁盘,我们把这个StorageClass命名为slow。另一个使用的是SSD,我们把它命名为fast。在PVC里除了常规的大小、访问模式的要求外,还顺利获得annotation指定了Storage Class的名字为fast,这样这个PVC就会绑定一个SSD,而不会绑定一个普通的磁盘。限于篇幅问题,这里简单说一下emptyDir、nfs、PVPVC

       

      实战–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”   

      实战PVPVC

       

      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(查看下面分析,为什么可能需要没有selectorService)实现的。

       

      举个例子,考虑一个图片处理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

       

      一个ServiceKubernetes中是一个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”),它会被服务的代理使用(见下面)。该Serviceselector将会持续评估,处理结果将被POST到一个名称为“my-service”Endpoints对象上。

       

      需要注意的是,Service能够将一个接收端口映射到任意的targetPort默认情况下,targetPort将被设置为与port字段相同的值。可能更有趣的是,targetPort可以是一个字符串,引用了backend Pod的一个端口的名称。但是,实际指派给该端口名称的端口号,在每个backend Pod中可能并不相同。对于部署和设计Service,这种方式会给予更大的灵活性。例如,可以在backend软件下一个版本中,修改Pod暴露的端口,并不会中断客户端的调用。

       

      Kubernetes Service能够支持TCPUDP协议,默认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:http
             protocol: TCP
              port:443
             targetPort: 9377  
      没有selectorService

       

      Service抽象了该如何访问Kubernetes Pod,但也能够抽象其它类型的backend,例如:

       

      • 希望在生产环境中使用外部的数据库集群,但测试环境使用自己的数据库。

      • 希望服务指向另一个Namespace中或其它集群中的服务。

      • 正在将工作负载转移到Kubernetes集群,和运行在Kubernetes集群之外的backend

       

      根据以上的应用场景,我们都能够定义没有selectorService,如下:

       
      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)。访问没有selectorService,与有selectorService的原理相同。请求将被路由到用户定义的Endpoint(该示例中为10.0.0.3:9376)。

       

      发布服务——服务类型

       

      对一些应用(如Frontend)的某些部分,可能希望顺利获得外部(Kubernetes集群外部)IP地址暴露ServiceKubernetes 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。该端口将顺利获得Servicespec.ports[*].nodePort字段被指定。如果需要指定的端口号,可以配置nodePort的值,系统将分配这个端口,否则调用API将会失败(比如,需要关心端口冲突的可能性)。

       

       

      kubernetes实战–edusoho平台创建

      1

      文件目录结构

       
      # 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
      2

      Podyaml文件

       

      apiVersion: v1
      kind: Pod
      metadata:
        name:lamp-edusoho
        labels:
          app:lamp-edusoho
      restartPolicy: Always
      spec:
        containers:
        – name: nginx
          abels:
            app:lamp-nginx
          image:dockerhub.cqzixu.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.cqzixu.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.cqzixu.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
      3

      PVyaml文件

       

      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

       

      PVCyaml文件

       
      apiVersion: v1
      kind: PersistentVolumeClaim
      metadata:

        name: nfs-pvc
      spec:
        accessModes:
          -ReadWriteMany
       storageClassName: “”
        resources:
          requests:
            storage:3Gi
      5

      Serviceyaml文件

       

      apiVersion: v1
      kind: Service
      metadata:

        name: edusoho
        labels:
          app: edusoho
      spec:
        type: NodePort
        ports:
        – port: 80
          nodePort:32756
        selector:
          app:lamp-edusoho
      6

      命令汇总

       

      查看Pod
      kubectl get po -o wide
      查看Service
      kubectl get svc
      进入容器内部某个应用,如这里的nginx
      kubectl exec -it lamp-edusoho -c nginx /bin/bash
      7

       

      访问安装Edusoho平台

      http://192.168.246.168:32756/install/start-install.php


      说明:这里的192.168.246.168kubernetesnode节点IP,32756Service中定义的nodePort

       

      参考文档
      • kubernetes概念–Service:http://kubernetes.io/docs/concepts/services-networking/service/

      • kubernetes官网教程:http://kubernetes.io/docs/tutorials/

      • Kubernetes中的Persistent Volume解析:http://jimmysong.io/posts/kubernetes-persistent-volume/

      A

      BOUT

      关于作者

      吴腾飞:威九国际数据运维工程师 ,负责威九国际数据系统平台和应用业务的快速部署、监控、优化及维护,设计并研发自动化运维工具和平台,数据库的日常维护。

      对自动化运维,Docker容器、虚拟化技术和容器编排kubernetes相关领域有浓厚兴趣。