系列目录html
前面咱们介绍了如何在windows单机以及如何基于docker部署consul集群,看起来也不是很复杂,然而若是想要把consul部署到kubernetes集群中并充分利用kubernetes集群的伸缩和调度功能并不是易事.前面咱们首先部署一个节点,部署完成之后获取它的ip,而后其它的ip都join到这个ip里组成集群.node
前面的部署方式存在如下问题:nginx
咱们知道,在kubernetes里,当节点发生故障或者资源不足时,会根据策略杀掉节点的一些pod转而将pod移到其它节点上.这时候咱们就须要从新获取主节点ip,而后将新的节点加入进去,以上作法不利于充分发挥kubernetes自身的伸缩功能.docker
不论是新节点加入或者失败后从新生成的节点从新加入集群,都须要知道主节点ip,这会产生和上面相同的问题,就是须要人工介入.bootstrap
理想的状态是,当集群主节点切换时,新节点仍然可以在无需人工介入的状况下自动加入集群.咱们解决这个问题的思路以下:使用kubernetes集群的dns功能,而不直接硬编码节点的ip.若是集群中有三个server,则这三个sever中必然有一个是主节点,咱们能够依次尝试经过dns来解析到具体的节点,依次尝试加入每个sever节点,尝试到真正的主节点时便可以加入集群.windows
咱们首先建立服务,定义服务的文件名为consul-service.ymlcentos
apiVersion: v1 kind: Service metadata: name: consul labels: name: consul spec: type: ClusterIP ports: - name: http port: 8500 targetPort: 8500 - name: https port: 8443 targetPort: 8443 - name: rpc port: 8400 targetPort: 8400 - name: serflan-tcp protocol: "TCP" port: 8301 targetPort: 8301 - name: serflan-udp protocol: "UDP" port: 8301 targetPort: 8301 - name: serfwan-tcp protocol: "TCP" port: 8302 targetPort: 8302 - name: serfwan-udp protocol: "UDP" port: 8302 targetPort: 8302 - name: server port: 8300 targetPort: 8300 - name: consuldns port: 8600 targetPort: 8600 selector: app: consul
经过以上服务咱们把pod的端口映射到集群中,经过名称咱们能够看到每个商品作什么类型通讯用的.这个服务会选择标签为app: consul
的pod.经过这个示例咱们也能够看到,对于一些复杂的服务,采用nodeport类型的服务是很不可取的.这里使用的是clusterip类型的服务.后面咱们会经过nginx ingress controller把http端口暴露到集群外,供外部调用.api
咱们经过kubectl create -f consul-service.yml
来建立这个服务浏览器
这里咱们采用的是statefulset的方式建立的server,这里之因此使用statefulset是由于statefulset包含的pod名称是固定的(普通pod以必定hash规则随机生成),若是命名规则固定,pod数量固定,则咱们能够预先知道它们的dns规则,以便在自动加入集群中时使用. statefulset建立文件以下(名为consul-statefulset.yml)bash
apiVersion: apps/v1beta1 kind: StatefulSet metadata: name: consul spec: serviceName: consul replicas: 3 template: metadata: labels: app: consul spec: terminationGracePeriodSeconds: 10 containers: - name: consul image: consul:latest args: - "agent" - "-server" - "-bootstrap-expect=3" - "-ui" - "-data-dir=/consul/data" - "-bind=0.0.0.0" - "-client=0.0.0.0" - "-advertise=$(PODIP)" - "-retry-join=consul-0.consul.$(NAMESPACE).svc.cluster.local" - "-retry-join=consul-1.consul.$(NAMESPACE).svc.cluster.local" - "-retry-join=consul-2.consul.$(NAMESPACE).svc.cluster.local" - "-domain=cluster.local" - "-disable-host-node-id" env: - name: PODIP valueFrom: fieldRef: fieldPath: status.podIP - name: NAMESPACE valueFrom: fieldRef: fieldPath: metadata.namespace ports: - containerPort: 8500 name: ui-port - containerPort: 8400 name: alt-port - containerPort: 53 name: udp-port - containerPort: 8443 name: https-port - containerPort: 8080 name: http-port - containerPort: 8301 name: serflan - containerPort: 8302 name: serfwan - containerPort: 8600 name: consuldns - containerPort: 8300 name: server
经过以上定义文件咱们能够看到,里面最核心的部分是--retry-join后面的dns规则,因为咱们指定了pod名称为consul,而且有三个实例,所以它们的名称为consul-0,consul-1,consul-2,即使有节点失败,起来之后名称仍然是固定的,这样无论新起的podIp是多少,经过dns都可以正确解析到它.
这里的data-dir之前没有提到过,很容易理解,就是consul持久化数据存储的位置.咱们前面说过server节点的数据都是要持久化存储的,这个data-dir即是持久化数据存储的位置.
咱们能够经过进入到pod内部来查看consul集群的成员信息
[centos@k8s-master ~]$ clear [centos@k8s-master ~]$ kubectl exec -it consul-0 /bin/sh / # consul members Node Address Status Type Build Protocol DC Segment consul-0 10.244.1.53:8301 alive server 1.4.4 2 dc1 <all> consul-1 10.244.1.54:8301 alive server 1.4.4 2 dc1 <all> consul-2 10.244.1.58:8301 alive server 1.4.4 2 dc1 <all> / #
经过以上你们能够看到,Ip 53和54是相邻的,可是58是跳跃的,这是由于我故意删除了其中的一个pod,因为咱们在建立statefulset的时候指定的副本集个数为3,所以kubernetes会从新建立一个pod,通过测试这个新建立的pod仍然可以正确加入集群,符合咱们的需求.
咱们在容器内执行curl localhost:8500
的时候,会出现如下结果
<a href="/ui/">Moved Permanently</a>.
不用担忧,以上结果是正确的,由于经过浏览器访问的话,localhost:8500会自动跳转到http://localhost:8500/ui/dc1/services
默认ui展现界面,因为curl没法模拟浏览器交互跳转行为,所以显示以上内容永久重定向.
以上节本完成了demo演示,可是仍然有两个问题
其它有状态服务也要把持久化数据目录挂载到宿主机上
下面贴出完整的配置
apiVersion: apps/v1beta1 kind: StatefulSet metadata: name: consul spec: serviceName: consul replicas: 3 template: metadata: labels: app: consul spec: affinity: podAntiAffinity: requiredDuringSchedulingIgnoredDuringExecution: - labelSelector: matchExpressions: - key: app operator: In values: - consul topologyKey: kubernetes.io/hostname terminationGracePeriodSeconds: 10 containers: - name: consul image: consul:latest args: - "agent" - "-server" - "-bootstrap-expect=3" - "-ui" - "-data-dir=/consul/data" - "-bind=0.0.0.0" - "-client=0.0.0.0" - "-advertise=$(PODIP)" - "-retry-join=consul-0.consul.$(NAMESPACE).svc.cluster.local" - "-retry-join=consul-1.consul.$(NAMESPACE).svc.cluster.local" - "-retry-join=consul-2.consul.$(NAMESPACE).svc.cluster.local" - "-domain=cluster.local" - "-disable-host-node-id" volumeMounts: - name: data mountPath: /consul/data env: - name: PODIP valueFrom: fieldRef: fieldPath: status.podIP - name: NAMESPACE valueFrom: fieldRef: fieldPath: metadata.namespace ports: - containerPort: 8500 name: ui-port - containerPort: 8400 name: alt-port - containerPort: 53 name: udp-port - containerPort: 8443 name: https-port - containerPort: 8080 name: http-port - containerPort: 8301 name: serflan - containerPort: 8302 name: serfwan - containerPort: 8600 name: consuldns - containerPort: 8300 name: server volumes: - name: data hostPath: path: /home/data
以上须要注意的是,要挂载到的宿主机的目录必须是预先存在的,kubernetes并不会在宿主机上建立须要的目录.
因为主机节不能容纳普通pod,所以要完成以上高可用部署,须要集群中至少有四个节点.(若是不使用完整配置,则只须要一主一从便可)
经过以上配置,咱们即可以在kubernetes集群内部访问consul集群了,可是若是想要在集群外部访问.还须要将服务暴露到集群外部,前面章节咱们也讲到过如何将服务暴露到集群外部.这里咱们使用 nginx-ingress 方式将服务暴露到外部.
apiVersion: extensions/v1beta1 kind: Ingress metadata: name: ingress-consul namespace: default annotations: kubernets.io/ingress.class: "nginx" spec: rules: - host: consul.my.com.local http: paths: - path: backend: serviceName: consul servicePort: 8500
因为我没有申请域名,所以我使用的hosts里添加映射的方式来访问的.我已经在hosts文件里添加了映射,这里直接打开浏览器访问
咱们点击Nodes
能够看到一共有三个节点
节点后面的绿色对号标识表示服务的状态是可用的
若是集群中有节点挂掉后,存活的节点仍然符合法定数量(构建一个consul集群至少须要两个存活节点进行仲裁),因为statefulset规定的副本集的数量是3,所以k8s会保证有3个数量的副本集在运行,当k8s集群发现运行的副本数量少于规定数量时,便会根据调度策略从新启动必定数量pod以保证运行副本集数量和规定数量相符.因为在编排consul部署时使用了retry-join
参数,所以有新增节点会自动尝试从新加入集群(传统方式是根据ip来加入集群),一样若是挂掉的是master节点也不用担忧,若是存活节点数量仍然符合法定数量(这里的法定数量并非指statefulset副本集数量,而是consul组成集群所须要最小节点数量),consul会依据必定策略从新选择master节点.
咱们先来看一下集群中pod状态
[root@k8s-master consul]# kubectl get pod NAME READY STATUS RESTARTS AGE consul-0 1/1 Running 0 9m20s consul-1 1/1 Running 0 9m19s consul-2 1/1 Running 0 9m17s easymock-dep-84767b6f75-57qm2 1/1 Running 1708 35d easymock-dep-84767b6f75-mnfzp 1/1 Running 1492 40d mvcpoc-dep-5856db545b-qndrr 1/1 Running 1 46d sagent-b4dd8b5b9-5m2jc 1/1 Running 3 54d sagent-b4dd8b5b9-brdn5 1/1 Running 1 40d sagent-b4dd8b5b9-hfmjx 1/1 Running 2 50d stock-dep-5766dfd785-gtlzr 1/1 Running 0 5d18h stodagent-6f47976ccb-8fzmv 1/1 Running 3 56d stodagent-6f47976ccb-cv8rg 1/1 Running 2 50d stodagent-6f47976ccb-vf7kx 1/1 Running 3 56d trackingapi-gateway-dep-79bb86bb57-x9xzp 1/1 Running 3 56d www 1/1 Running 1 48d
咱们看到有三个consul pod在运行
下面咱们手动杀掉一个pod,来模拟故障.
[root@k8s-master consul]# kubectl delete pod consul-0 pod "consul-0" deleted
因为consul-0
被干掉,所以consul集群中consul-0
变得不可用
过一段时间后,k8s检测到运行的pod数量少于stateful规定的数量,便会从新再启动一个pod(须要注意的是,这里的从新启动并非把原来pod从新启动,而是从新再调度一个全新的pod到集群中,这个pod与挂掉的pod无任何关系,固然ip也是不同的,若是仍然依赖ip,则会带来无限麻烦)
咱们能够看到,这时候consul-0服务的ip已经变成了86(前面是85)
部署nginx ingress可能并非一件很是容易的事,由其是对使用单节点docker on windows的朋友来讲,若是有的朋友不想部署nginx ingress,仅作为测试使用,也能够把服务的类型由
ClusterIP
更改成NodePort
类型,这样就能够像docker同样把服务的端口映射到宿主机端口,方便测试.
本节涉及到的内容比较多,也相对比较复杂(前面也屡次说过,部署有状态服务是难点),所以后面会专门再开一节来详细讲解本文中的一些细节,有问题的朋友也能够留言或者经过其它方式联系我,你们共同交流