前言 首先聊一下Deployment,一个应用的所有Pod是完全一样的。它们相互之间没有顺序,也无所谓运行在哪台宿主机上,需要的时候就创建,不需要的时候就可以中止任意一个Pod。
有些分布式应用,在多个实例之间存有依赖关系,比如主从、主备关系。还有些数据库存储类应用,在本地磁盘会存放一些数据,而实例一旦被杀掉,即使重建出来,实例和数据之间的对应关系都已经丢失。
所以实例之间有不对等关系,以及实例对外部数据有依赖关系的应用,被称为有状态应用(Stateful Appication)。
Statefulset StatefulSet 将应用状态抽象为两种情况:
拓扑状态。意味着应用之间的多个实例之间不是对等关系。这些应用必须按照某些顺序启动,并且新创建的Pod,必须和原来的网络标识一样,这样原来的访问者才能使用同样的方法,访问到这个新的Pod。 存储状态。这种情况意味着,应用的多个实例分别绑定了不同的存储数据。典型例子就是一个数据库应用的多个存储实例。 StatefulSet 的核心功能就是通过某种方式记录这些状态,然后在Pod被重新创建时,能够为新Pod恢复这些状态。
StatefulSet中每个Pod的DNS格式为:
1 statefulSetName-{0..N-1}.serviceName.namespace.svc.cluster.local
serviceName 为Headless Service name 0…N-1 为Pod的序号,从0开始 statefulSetName 为StatefulSet name namespace 为服务所在的namespace cluster.local为Cluster Domain 图解
扩容
收缩
持久卷的创建和删除
增加副本时,会创建对应的pvc; 减少副本时,从高索引值的Pod名开始删除Pod,但是PVC不会被删除,需要手动释放; 当先收缩再扩容时,重建后的Pod实例会绑定到对应序号的PVC上。 Headless Service 首先聊一下Service是如何被访问的呢?
以service的VIP模式。比如访问10.0.23.1 这个service的IP地址时,10.0.23.1就是一个VIP,会把请求转发到该service所代理的某一个Pod上。 以service的DNS方式。比如访问“my-svc.my-namspace.svc.cluster.local”这条DNS记录,就可以访问到名为my-svc的service所搭理的某一个Pod。 针对于第二种方式,具体还可以分为两种处理方法:
Normal Service。访问“my-svc.my-namspace.svc.cluster.local” 解析到的,正是my-svc这个service的VIP,后面的流程和VIP的方式一致 Headless Service。访问“my-svc.my-namspace.svc.cluster.local”解析到的,直接就是my-svc代理的某一个Pod的IP地址。这里的区别在于Headless Service 不需要分配一个VIP,而是直接以DNS记录的方式解析出被代理POD的IP地址。 比如eureka-0.register-server.test.svc.cluster.local
。
标签规则:
spec.containers.name 容器的名字,如何配置基本无影响 template.metadata.labels 设定的label,注意和selector保持一致。并且该标签需要和headless 中的selector,以及nodePort中的selector保持一致。 示例 创建一个service
1 2 3 4 5 6 7 8 9 10 11 12 13 apiVersion: v1 kind: Service metadata: name: nginx labels: app: nginx spec: ports: - port: 80 name: web clusterIP: None selector: app: nginx
再创建一个StatefulSet
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 apiVersion: apps/v1 kind: StatefulSet metadata: name: web spec: serviceName: "nginx" replicas: 2 selector: matchLabels: app: nginx template: metadata: labels: app: nginx spec: containers: - name: nginx image: nginx:1.14.2 ports: - containerPort: 80 name: web
部署eureka集群 eureka集群部署的主要难点在于如何指定 eureka.client.service-url.defaultZone
的值?
对于上述问题可以通过headless机制,不给cluster分配IP,而是通过域名访问服务来解决。在application.yaml文件中使用环境变量,传值给eueka。
每个eureka会注册到另外的eureka上,也就是eureka.client.serviceUrl.dafaultZone; 通过StatefulSet,可以知道每个eureka的name; 通过Headless,可以访问到每个eureka; 所以eureka.client.serviceUrl.defaultZone的值就是http://eureka-0.eureka:8000/eureka/,http://eureka-1.eureka:8000/eureka/,http://eureka-2.eureka:8000/eureka/
由于三个pod在同一个命名空间内,因此可以省略.namespace.svc.cluster.local。
配置文件 application.yaml
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 spring: application: name: DiscoveryServer info: version: $project .version$ server: port: 8761 eureka: instance: hostname: ${POD_HOST_NAME} prefer-ip-address: false client: register-with-eureka: true fetch-registry: true service-url: defaultZone: ${EUREKA_INSTANCE_LIST} joy: logback: path: logs/EUREKA_INSTANCE_HOSTNAME
需要注意的是hostname要使用pod的主机名,否则会出现unavailable-replicas。此外还有其他可能:
eureka.instance.preferIpAddress = false defaultZone 后面的eureka注册中心的地址要写成域名; eureka.client.register-with-eureka的值要写为true eureka.client.fetch-registry的值要写为true eureka集群中多个eureka服务的spring.application.name的值要一致 eureka.instance.prefer-ip-address的值必须设置为false service.yaml
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 apiVersion: v1 kind: Service metadata: name: eureka labels: service: eureka spec: clusterIP: None type : ClusterIP ports: - port: 8761 targetPort: 8761 name: eureka selector: app: eureka
StatefulSet.yaml
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 apiVersion: apps/v1 kind: StatefulSet metadata: name: eureka spec: serviceName: "eureka" selector: matchLabels: app: eureka-pod replicas: 3 template: metadata: labels: app: eureka-pod spec: containers: - env : name: eureka image: 192.168.1.232:5000/k8s/eureka:statefull imagePullPolicy: Always env : - name: APP_NAME value: "eureka" - name: POD_NAME valueFrom: fieldRef: fieldPath: metadata.name - name: POD_HOST_NAME value: "$(POD_NAME) .$(APP_NAME) .default.svc.cluster.local" - name: EUREKA_INSTANCE_LIST value: "http://eureka-0.$(APP_NAME) .default.svc.cluster.local:8761/eureka/,http://eureka-1.$(APP_NAME) .default.svc.cluster.local:8761/eureka/,http://eureka-2.$(APP_NAME) .default.svc.cluster.local:8761/eureka/" ports: - containerPort: 8761 livenessProbe: failureThreshold: 10 httpGet: path: /health port: 8761 scheme: HTTP initialDelaySeconds: 30 periodSeconds: 60 successThreshold: 1 timeoutSeconds: 2 readinessProbe: failureThreshold: 1 httpGet: path: /health port: 8761 scheme: HTTP initialDelaySeconds: 10 periodSeconds: 3 successThreshold: 1 timeoutSeconds: 2
传递三个变量:
APP_NAME 取值于metadata.name POD_NAME 取值于metadata.name POD_HOST_NAME 拼接单个Pod的主机名 EUREKA_INSTANCE_LIST 由Pod的域名拼接组成 创建完成后会出现三个带序号的Pod和statfulSet,这个时候请求eureka.default.svc.cluster.local
就可以使用服务了。但是如果想在集群外访问,可以使用nodeport的方式暴露。
1 2 3 4 5 6 7 8 9 10 11 12 apiVersion: v1 kind: Service metadata: name: eureka-node-port spec: type : NodePort ports: - port: 31331 targetPort: 8761 nodePort: 31331 selector: app: eureka-pod
案例分析 headless service和普通service的区别 headless 不分配clusterIP,配置clusterIP: None
headless service可以通过解析service的DNS,返回所有Pod的地址和DNS 普通service,只能通过解析service的DNS返回service的CLusterIP statefulSet和Deployment的区别 statefulSet 的Pod有DNS地址,通过解析Pod的DNS可以返回Pod的IP Deployment下的Pod没有DNS 普通service解析 Service的ClusterIP原理: 一个service可以对应一组endpoints,client访问ClusterIP,通过iptables或ipvs转发到real server。
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 [root@uat-master ~] NAME TYPE CLUSTER-IP EXTERNAL-IP PORT(S) AGE eureka-node-port NodePort 10.105.146.146 <none> 31331:31331/TCP 8m7s [root@uat-master ~] Name: eureka-node-port Namespace: default Labels: <none> Annotations: <none> Selector: app=eureka Type: NodePort IP Families: <none> IP: 10.105.146.146 IPs: 10.105.146.146 Port: <unset > 31331/TCP TargetPort: 8761/TCP NodePort: <unset > 31331/TCP Endpoints: 10.200.128.133:8761,10.200.128.134:8761,10.200.128.137:8761 Session Affinity: None External Traffic Policy: Cluster Events: <none> / Server: 10.96.0.10 Address 1: 10.96.0.10 kube-dns.kube-system.svc.cluster.local Name: eureka-node-port.default.svc.cluster.local Address 1: 10.105.146.146 eureka-node-port.default.svc.cluster.local
综上所述,DNS查询时只会返回service的clusterIP,具体client访问的是哪个real server,由iptabels或者ipvs决定。
headless service解析 1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 [root@uat-master ~] NAME TYPE CLUSTER-IP EXTERNAL-IP PORT(S) AGE eureka ClusterIP None <none> 8761/TCP 27m [root@uat-master ~] Name: eureka Namespace: default Labels: service=eureka Annotations: <none> Selector: app=eureka Type: ClusterIP IP Families: <none> IP: None IPs: None Port: eureka 8761/TCP TargetPort: 8761/TCP Endpoints: 10.200.128.133:8761,10.200.128.134:8761,10.200.128.137:8761 Session Affinity: None Events: <none> / Server: 10.96.0.10 Address 1: 10.96.0.10 kube-dns.kube-system.svc.cluster.local Name: eureka.default.svc.cluster.local Address 1: 10.200.128.134 eureka-1.eureka.default.svc.cluster.local Address 2: 10.200.128.133 eureka-0.eureka.default.svc.cluster.local Address 3: 10.200.128.137 eureka-2.eureka.default.svc.cluster.local / Server: 10.96.0.10 Address 1: 10.96.0.10 kube-dns.kube-system.svc.cluster.local Name: eureka-2.eureka.default.svc.cluster.local Address 1: 10.200.128.137 10-200-128-137.eureka-node-port.default.svc.cluster.local
综上所述,DNS查询会返回所有的endpoint,通过解析Pod的DNS记录,也能返回Pod的IP。
headless service使用场景 自助选择权,client可以自己决定使用哪个real server,可以通过DNS来获取real server信息。 headless service关联的每个pod,都会有对应的DNS域名,这样Pod直接可以互相访问。这样对于一些集训类型的应用就可以解决身份是吧的问题了。 为什么要用headless+statefulSet部署有状态应用 headless service会为关联的Pod分配一个域 <service name>.$<namespace name>.svc.cluster.local
statefulSet 会为关联的Pod保持一个不变的Pod Name $(statefulSet name)-$(pod序号)
statefulSet会为关联的Pod分配一个dnsName $<Pod Name>.$<service name>.$<namespace name>.svc.cluster.local