38

达观数据:kubernetes简介和实战

 5 years ago
source link: https://www.jiqizhixin.com/articles/2018-12-04-25?amp%3Butm_medium=referral
Go to the source link to view the article. You can view the picture content, updated content and better typesetting reading experience. If the link is broken, please click the button below to view the snapshot at that time.

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

7nequm2.png!web

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而不是直接管理容器。

<strong>实战:创建一个nginx容器</strong>
<strong>apiVersion: v1
kind: Pod
metadata:</strong>
  name:nginx-test
  labels:
    app: web
<strong>spec:</strong>
  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将这些容器的存储资源作为一个实体来管理。

<strong>实战:在一个pod里放置两个容器:nginx与redis</strong>
<strong>apiVersion: v1
kind: Pod
metadata:</strong>
  name: rss-site
  labels:
    app: web
<strong>spec:</strong>
  containers:
  - name:front-end
    image:nginx:1.7.9
    ports:
    -containerPort: 80
  - name:rss-reader
    image: redis
    ports:
    -containerPort: 88<strong>
</strong>
创建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

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应用:

<strong>apiVersion:extensions/v1beta1
kind: Deployment
metadata:</strong>
    name:nginx-test
    namespace:test
<strong>spec:</strong>
    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的对象(比如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

实战命名空间

<strong>apiVersion: v1
kind: Namespace
metadata:</strong>
  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的创建问题。

<strong>apiVersion:rbac.authorization.k8s.io/v1beta1
kind: ClusterRoleBinding
metadata:</strong>
      name:kubernetes-dashboard
      labels:
         k8s-app: kubernetes-dashboard
<strong>roleRef:</strong>
      apiGroup:rbac.authorization.k8s.io
      kind:ClusterRole
      name:cluster-admin
<strong>subjects:</strong>
    - 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 <strong>
</strong>
创建secret.yaml
cat >> secrets.yml << EOF
<strong>apiVersion: v1
kind: Secret
metadata:</strong>
    name:mysecret
<strong>type: </strong><strong>Opaque
data:</strong>
    password:MWYyZDFlMmU2N2Rm
    username:YWRtaW4=  <strong>
</strong>
创建secret
kubectl create -f secrets.yml  
查看secret运行状态
kubectl get secret --all-namespaces  

Secret使用方式

  • 以Volume方式

  • 以环境变量方式

实战Secret使用Volume方式

<strong>apiVersion: v1
kind: Pod
metadata:</strong>
  name: mypod
  labels:
    name: wtf
<strong>spec:</strong>
  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使用环境变量方式

<strong>apiVersion:extensions/v1beta1
kind: Deployment
metadata:</strong>
  name:wordpress-deployment
<strong>spec:</strong>
  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<strong>
</strong>
查看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
<strong>root@wordpress-deployment-694f4c79b4-cpsxw:/var/www/html#env</strong>
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

<strong>kind: ConfigMap
apiVersion: v1
metadata:</strong>
 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文件需要的临时目录。

<strong>apiVersion: v1
kind: Pod
metadata:</strong>
  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

<strong>apiVersion: v1
kind: Pod
metadata:</strong>
  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内的文件共享

<strong>apiVersion: v1
kind: Pod
metadata:</strong>
  name:nginx-test
  labels:
    app: nginx
<strong>spec:</strong>
  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
<strong>apiVersion: v1
kind: PersistentVolume
metadata:</strong>
  name: nfs-pv
<strong>spec:</strong>
  capacity:
    storage: 4Gi
  accessModes:
    -ReadWriteMany
  nfs:
    server:192.168.246.168  ##NFS服务器的ip地址
    path:"/data"  ##NFS服务器上的共享目录
#创建PVC
<strong>apiVersion: v1
kind: PersistentVolumeClaim
metadata:</strong>
  name: nfs-pvc
<strong>spec:</strong>
  accessModes:
    - ReadWriteMany
 storageClassName: ""
  resources:
    requests:
      storage:3Gi  
#创建Deployment
<strong>apiVersion: apps/v1
kind: Deployment
metadata:</strong>
  name:nginx-deployment
  labels:
    app: nginx
<strong>spec:</strong>
  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"标签。

<strong>kind: Service
apiVersion: v1
metadata:</strong>
  name:my-service
<strong>spec:</strong>
  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就不会产生歧义,例如:

<strong>kind: Service
apiVersion: v1
metadata:</strong>
  name:my-service
<strong>spec:</strong>
    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,如下:

<strong>kind: Service
apiVersion: v1
metadata:</strong>
  name:my-service
<strong>spec:</strong>
  ports:
    - protocol:TCP
      port: 80
     targetPort: 9376  

由于这个Service没有selector,就不会创建相关的Endpoints对象。可以手动将Service映射到指定的Endpoints:

<strong>kind: Endpoints
apiVersion: v1
metadata:</strong>
  name:my-service
<strong>subsets:</strong>
  - 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文件

<strong>apiVersion: v1
kind: PersistentVolume
metadata:</strong>
  name: nfs-pv
<strong>spec:</strong>
  capacity:
    storage: 4Gi
  accessModes:
    -ReadWriteMany
  nfs:
    server:192.168.246.168  ##NFS服务器的ip地址
    path:"/data"  ##NFS服务器上的共享目录

PVC的yaml文件

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

Service的yaml文件

<strong>apiVersion: v1
kind: Service
metadata:</strong>
  name: edusoho
  labels:
    app: edusoho
<strong>spec:</strong>
  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相关领域有浓厚兴趣。


About Joyk


Aggregate valuable and interesting links.
Joyk means Joy of geeK