Headless Service

   在某些应用场景中,开发人员希望自己控制负载均衡的策略,不使用Service提供的默认负载均衡的功能,或者应用程序希望知道属于同组服务的其他实例。k8s提供了Headless Service来实现这种功能,即不为Service设置ClusterIP(入口IP地址),仅通过Label Selector将后端的Pod列表返回给调用的客户端。

apiVersion: v1
kind: Service
metadata:
  name: nginx
  lables:
    app: nginx
spec:
  ports:
  - port: 80
  clusterIP: None
  selector:
    app: nginx

   这样,Service就不再具有一个特定的ClusterIP地址,对其进行访问将获得包含Label “app=nginx” 的全部Pod列表,然后客户端程序自行决定如何处理这个Pod列表。例如,StatefulSet就是使用Headless Service为客户端返回多个服地址的。

  对于“去中心化”类的应用集群,Headless Service 将非常有用。下面以搭建Cassandra集群为例,看看如何通过对Headless Servie的巧妙使用,自动实现应用集群的创建。

    Apache Cassandra是一套开源分布式NoSQL数据库系统,主要特点为它不是单个数据库,而是由一组数据库节点共同构成的一个分布式的集群数据库。由于Cassandra使用的是“去中心化”模式,所以在集群里的一个节点启动之后,需要一个途径获知集群中新节点的加入。Cassandra使用了Seed(种子)来完成集群中节点之间的相互查找和通信。

   通过对Headless Service的使用,实现了Cassandra各节点之间的相互查找和集群的自动搭建。主要步骤包含:自定义SeedProvider;通过Headless Service自动查找后端Pod;自动添加新Cassandra节点。

通过Service动态查找Pod

      在KubernetesSeedProovider类中,通过查询环境变量CASSANDRA_SERVICE的值来获得服务的名称。这样就要求Service在Pod之前创建。如果我们已经创建好DNS服务,那么也可以直接使用服务的名称为无须使用环境变量。

      回顾一下Service的概念。Service通常作为一个负载均衡器,提供k8s集群中其他应用(Pod)对属于该Service的一组Pod进行访问。由于Pod的创建和销毁都会实现更新Service的Endpoint数据,所以可以动态地对Service的后端Pod进行查询。Cassandra的去中心化设计使得Cassandra集群中的一个Cassandra实例只需要要查询到其他节点,即可以自动组成一个集群,正好可以使用Service的这个特性查询到新增节点。

需要先创建一个单节点的pod。

cassandra-pod.yaml

apiVersion: v1
kind: Pod
metadata:
  labels:
    name: cassandra
  name: cassandra
spec:
  containers:
  - args:
    - /run.sh
    resources:
      limits:
        cpu: "0.5"
    image: docker.io/shenshouer/cassandra:v5
    imagePullPolicy: IfNotPresent
    name: cassandra
    ports:
    - name: cql
      containerPort: 9042
    - name: thrift
      containerPort: 9160
    volumeMounts:
    - name: data
      mountPath: /cassandra_data
    env:
    - name: MAX_HEAP_SIZE
      value: 512M
    - name: HEAP_NEWSIZE
      value: 100M
    - name: POD_NAMESPACE
      valueFrom:
        fieldRef:
          fieldPath: metadata.namespace
  volumes:
    - name: data

 

 

     在k8s系统中,首先需要为Cassandra集群定一个Service:

cassandra-service.yaml

apiVersion: v1
kind: Service
metadata:
  labels:
    name: cassandra
  name: cassandra
spec:
  ports:
  - port: 9042
  selector: 
    name: cassandra

cassandra-rc.yaml

apiVersion: v1
kind: ReplicationController
metadata:
  labels:
    name: cassandra
  name: cassandra
spec:
  replicas: 1
  selector:
    name: cassandra
  template:
    metadata:
      labels:
        name: cassandra
    spec:
      containers:
      - command:
        - /run.sh
        resources:
          limits:
            cpu: 0.5
        env:
        - name: MAX_HEAP_SIZE
          value: 512M
        - name: HEAP_NEWSIZE
          value: 100M
        - name: POD_NAMESPACE
          valueFrom:
            fieldRef:
              fieldPath: metadata.namespace
        image: docker.io/shenshouer/cassandra:v5
        name: cassandra
        ports:
        - containerPort: 9042
          name: cql
        - containerPort: 9160
          name: thrift
        volumeMounts:
        - mountPath: /cassandra_data
          name: data
      volumes:
       - name: data
         emptyDir: {}

y依次执行

kubectl create -f cassandra-pod.yaml
kubectl create -f cassandra-service.yaml
kubectl create -f cassandra-rc.yaml

 

现在,一个Cassandra Pod运行起来了,但还没有组成Cassandra集群。

Cassandra集群中新节点的自动添加

现在,我们使用k8s提供的Scale(扩容和缩容)机制对Cassandra进行扩容

   使用cassandra提供的nodetool工具对任一cassandra实例(pod)进行访问来验证cassandra集群的状态。下面的命令将访问名为cassandra的Pod:

kubectl exec -it cassandra -- nodetool status

可以看看到对应节点的状态。

本例描述了一种通过API查询Service来完成动态Pod发现的应用场景。对于类似于Cassandra的去中心化集群类应用,都可以使用Headless Service查询后端Endpoint这种巧妙的方法来实现对应用集群(属于同一Service)中新加入节点的查找。

从集群外部访问Pod或Service

    由于Pod和Service都是k8s集群范围内的虚拟概念,所以集群外的客户端系统无法通过Pod的IP地址或者Service的虚拟IP地址和虚拟端口号访问它们。为了让外部客户可以访问这些服务,可以将Pod或Service的端口号映射到宿主机,以使客户端应用能够通过物理机访问容器应用。

将容器应用的端口号映射到物理机

(1)通过设置容器级别的hostPort,将容器应用的端口号映射到物理机上:

apiVersion: v1
kind: Pod
metadata:
  name: webapp
  labels:
    app: webapp
spec:
  containers:
  - name: webapp
    image: tomcat
    ports:
    - containerPort: 8080
      hostPort: 8081

(2) 通过设置Pod级别的hostNetwork=true,该Pod中所有容器的端口号都将被直接映射到物理机上。在设置hostNetwork=true时需要注意,在容器ports定义部分如果不指定hostPort,则默认hostPort等于containerPort,如果指定了hostPort,则hostPort必须等于containerPort的值:

apiVersion: v1
kind: Pod
metadata:
  name: webapp1
  labels:
    app: webapp1
spec:
  hostNetwork: true
  containers:
  - name: webapp
    image : tomcat
    ports:
    - containerPort: 18080

   同样,对该Service的访问也将被负载分发到后端的多个Pod上。

(2) 通过设置LoadBlancer映射到云服务商提供的LoadBalancer地址。这种用法仅用于在公有云服务提供商的云平台上设置Service的场景。

     在下面的例子中,status.loadBalancer.ingress.ip设置的146.148.47.155为云服务商提供的负载均衡器的IP地址。对该Service的访问请求将会通过LoadBalancer转发到后端Pod上,负载分发的实现方式则依赖于云服务商提供的LoadBalancer的实现机制:

kind: Service
apiVersion: v1
metadata:
  name: my-server
spec:
  selector:
    app: MyApp
  ports:
  - protocol: TCP
    port: 80
    targetPort: 9376
    nodePort: 30061
  clusterIP: 10.0.171.239
  loadBalancerIP: 78.11.24.19
  type: LoadBalancer
status:
  loadBalancer:
    ingress:
    - ip: 146.148.47.155 

 

 

 

小结:

        今天的内容到此结束,近几天的内容相对来说比较简单。

          希望大家可以认真看哦~

           多多关注,谢谢。

 

 

 

 

Logo

K8S/Kubernetes社区为您提供最前沿的新闻资讯和知识内容

更多推荐