为什么需要Service?
因为直接通过pod的IP+port获取服务存在如下两个问题:
1)调用地址不稳定,pod可能出现故障,那么新的pod产生的IP和port都会发生变化
2)集群(多实例)场景无法自动实现负载均衡

service怎么解决的问题?
service本身有固定的Ip和port,且内部有负载均衡的实现,所以解决了上述问题

service相关指令

service选中的pod称为其endpoints
kubectl get endpoints hostnames

#获取hostnames对应的service
kubectl get svc hostnames
只有处于 Running 状态,且 readinessProbe 检查通过的 Pod,才会出现在 Service 的 Endpoints 列表里。并且,当某一个 Pod 出现问题时,Kubernetes 会自动把它从 Service 里摘除掉。

service 获取service和对应的endpoints
kubectl get endpoints

获取服务详情 
kubectl get svc hostnames

service的实例和创建
一个提供Web服务的RC:webapp-rc.yaml
apiVersion: v1
kind: ReplicationController
metadata:
 name: webapp
spec:
 replicas: 2
 template:
  metadata:
   name: webapp
   labels:
    app: webapp
  spec:
   containers:
   - name: webapp
    image: tomcat
    ports:
    - containerPort: 8080
创建该RC kubectl create -f webapp-rc.yaml
获取Pod的Ip: kubectl get pods -l app=webapp -o yaml | grep podIP
通过Pod 的Ip和端口号直接访问服务 curl ip:8080

通过创建service,让客户端能够访问到两个Tomcat Pod的实例
方式1: kubectl expose rc webapp #通过kubectl expose实现
方式2: 定义并创建service:webapp-svc.yaml
apiVersion: v1
kind: Service
metadata:
 name: webapp
spec:
 ports:
 - port: 8081 #指定service的虚拟端口号
  targetPort: 8080 #对应到Pod容器的8080端口
 selector: 
  app: webapp #对应到拥有label(app=webapp)的Pod
kubectl create -f webapp-svc.yaml
kubectl get svc #对应端口 ip
访问service: surl ip:8081

service提供服务的实现原理:kube-proxy 组件,加上 iptables 来共同实现的。
一旦service被提交给 Kubernetes,那么 kube-proxy 就可以通过 Service 的 Informer 感知到这样一个 Service 对象的添加。而作为对这个事件的响应,它就会在宿主机上创建一条 iptables 规则。
经过该iptables 处理之后,访问 Service VIP 的 IP 包就已经变成了访问具体某一个后端 Pod 的 IP 包了。不难理解,这些 Endpoints 对应的 iptables 规则,正是 kube-proxy 通过监听 Pod 的变化事件,在宿主机上生成并维护的。
不难想到,当你的宿主机上有大量 Pod 的时候,成百上千条 iptables 规则不断地被刷新,会大量占用该宿主机的 CPU 资源,甚至会让宿主机“卡”在这个过程中。所以说,一直以来,基于 iptables 的 Service 实现,都是制约 Kubernetes 项目承载更多量级的 Pod 的主要障碍。
IPVS模式可以解决上述问题。
核心:把对这些“规则”的处理放到了内核态,从而极大地降低了维护这些规则的代价。
 

k8s提供的负载均衡策略:
1)轮询模式,默认模式
2)会话模式:基于客户端IP地址进行的花花保持的模式,service.spec.sessionAffinity=ClientIP启用。

多端口service
使用不同协议的两个端口号service
apiVersion: v1
kind: Service
metadata:
 name: webapp
spec:
 ports:
 - port: 8080 #service对外提供的端口
   targetPort: 8080 #对应到pod的8080端口
   name: web #对应的服务名称
   protocol: TCP #网络类型
 - port: 8005
   targetPort: 8080
   name: web1
   protocol: TCP


外部服务service
如系统需要将一个外部数据库作为后端服务进行连接。
1) 创建一个无label selector的service
apiVersion: v1
kind: Service
metadata:
 name: my-service
spec:
 ports:
 - protocol: TCP
  port: 80
  targetPort: 80
因为定义的service没有labelSelector,所以不会定义到任何Pod,需要手动创建一个和该service同名的Endpoint,用于指向实际的后端访问地址,
2) endPoint的配置文件内容如下:
kind: Endpoints
apiVersion: v1
metadat: 
 name: my-service #和上面service名称完全一致
subjects:
- address:
 - IP: 1.2.3.4
 ports:
 - port: 80
说明:上面没有标签选择器的Service建会被路由到用户手动定义的后端Endpoint上。

2.4.3 Headless Service
应用场景:
1)开发人员希望自己控制负载均衡的策略,不使用Service提供的默认负载均衡的功能。
2)应用程序希望知道属于同组服务的其他实例
原理:不为service设置clusterIp,仅通过Label selector将后端的Pod列表返回给调用的客户端。
下面是一个Hedaless Service定义实例:
apiVersion: v1
kind: Service
metadata:
 name: nginx
 labels:
  app: nginx
spec:
 ports:
 - port: 80
 clusterIP: None
 selector:
  app: nginx
说明:该service不再具有特定的clusterIp地址,对其进行访问会返回包含Label“app=nginx”的全部Pod列表。

2.4.4 集群外部访问Pod 或 service
由于Pod和service的IP和port都是k8s集群范围内的虚拟概念,所以集群外的客户端无法访问。
解决方案:
将Pod和Service的端口号映射到宿主机,使得客户端应用能够通过物理机访问容器应用。
Pod的两种实现方案:
1)设置容器级别的hostPort: 将容器应用的端口映射到物理机上
apiVersion: v1
kind: Pod
metadata:
 name: webapp
 labels:
  app: webapp
spec:
 containers:
 - name: webapp
  image: tomcat
  ports:
  - containerPort: 8080 #容器的端口
   hostPort: 8081 #物理机端口,通过物理机IP和该port可以访问
2)设置Pod级别的hostNetwork=true,将该Pod中所有容器的端口号都直接映射到物理机上。且默认hostPort等于containerPort
apiVersion: v1
kind: Pod
metadata:
 name: webapp
 labels:
  app: webapp
spec:
 hostNetwork: true 
 containers:
 - name: webapp
  image: tomcat
  ports:
  - containerPort: 8080 #容器的端口,自动映射到物理机8080的端口

Service的实现方案
1)通过nodePort映射到物理机,同时设置service的类型为NodePort
通过node的物理Ip和暴露的nodePort暴露服务,但是没有办法进行负载均衡;所以此时一般会搭配别的slb服务
apiVersion: v1
kind: Service
metadata:
  name: my-nginx
  labels:
    run: my-nginx
spec:
  type: NodePort
  ports:
  - nodePort: 8080 #物理机Ip+端口可以访问服务
    targetPort: 80
    protocol: TCP
    name: http
  - nodePort: 443
    protocol: TCP
    name: https
  selector:
    run: my-nginx

2 1.7版本之后支持的一个新特性,叫作 ExternalName和externalIPs。
kind: Service
apiVersion: v1
metadata:
  name: my-service
spec:
  type: ExternalName
  externalName: my.database.example.com

externalIPs:

kind: Service
apiVersion: v1
metadata:
  name: my-service
spec:
  selector:
    app: MyApp
  ports:
  - name: http
    protocol: TCP
    port: 80
    targetPort: 9376
  externalIPs:
  - 80.11.12.10
在这里 Kubernetes 要求 externalIPs 必须是至少能够路由到一个 Kubernetes 的节点。

3 公有云上的 Kubernetes 服务。这时候,你可以指定一个 LoadBalancer 类型的 Service
如:
---
kind: Service
apiVersion: v1
metadata:
  name: example-service
spec:
  ports:
  - port: 8765
    targetPort: 9376
  selector:
    app: example
  type: LoadBalancer
在上述 LoadBalancer 类型的 Service 被提交后,Kubernetes 就会调用 CloudProvider 在公有云上为你创建一个负载均衡服务,并且把被代理的 Pod 的 IP 地址配置给负载均衡服务做后端。
kuebctl get svc :可以看到external-ip。我们就可以通过该ip+port来访问了。

由于每个 Service 都要有一个负载均衡服务,所以这个做法实际上浪费成本,更希望看到 Kubernetes 为我内置一个全局的负载均衡器。然后,通过我访问的 URL,把请求转发给不同的后端 Service。

对应的就是Kubernetes 里的 Ingress 服务。
ingress对象的定义如下:
apiVersion: extensions/v1beta1
kind: Ingress
metadata:
  name: cafe-ingress
spec:
  tls:
  - hosts:
    - cafe.example.com
    secretName: cafe-secret
  rules:
  - host: cafe.example.com
    http:
      paths:
      - path: /tea
        backend:
          serviceName: tea-svc
          servicePort: 80
      - path: /coffee
        backend:
          serviceName: coffee-svc
          servicePort: 80
重点关注rules字段。
host:该Ingress对象的入口,即当用户访问 cafe.example.com 的时候,实际上访问到的是这个Ingress对象。
对应的值必须是一个标准的域名格式的字符串,而不能是 IP 地址。

path:定义具体规则,一个 path 都对应一个后端 Service,并且指明了对应的服务名称和端口。

实际的使用中,需要从社区里选择一个具体的 Ingress Controller,把它部署在 Kubernetes 集群里。
目前, Nginx、HAProxy、Envoy、Traefik 等,都已经为 Kubernetes 专门维护了对应的 Ingress Controller。

大概以Nginx Ingress Controller 为例,看下实际使用过程。
部署 Nginx Ingress Controller:
1 创建pod:
kubectl apply -f https://raw.githubusercontent.com/kubernetes/ingress-nginx/master/deploy/mandatory.yaml
mandatory.yaml内容如下:

kind: ConfigMap
apiVersion: v1
metadata:
  name: nginx-configuration
  namespace: ingress-nginx
  labels:
    app.kubernetes.io/name: ingress-nginx
    app.kubernetes.io/part-of: ingress-nginx
---
apiVersion: extensions/v1beta1
kind: Deployment
metadata:
  name: nginx-ingress-controller
  namespace: ingress-nginx
  labels:
    app.kubernetes.io/name: ingress-nginx
    app.kubernetes.io/part-of: ingress-nginx
spec:
  replicas: 1
  selector:
    matchLabels:
      app.kubernetes.io/name: ingress-nginx
      app.kubernetes.io/part-of: ingress-nginx
  template:
    metadata:
      labels:
        app.kubernetes.io/name: ingress-nginx
        app.kubernetes.io/part-of: ingress-nginx
      annotations:
        ...
    spec:
      serviceAccountName: nginx-ingress-serviceaccount
      containers:
        - name: nginx-ingress-controller
          image: quay.io/kubernetes-ingress-controller/nginx-ingress-controller:0.20.0
          args:
            - /nginx-ingress-controller
            - --configmap=$(POD_NAMESPACE)/nginx-configuration
            - --publish-service=$(POD_NAMESPACE)/ingress-nginx
            - --annotations-prefix=nginx.ingress.kubernetes.io
          securityContext:
            capabilities:
              drop:
                - ALL
              add:
                - NET_BIND_SERVICE
            # www-data -> 33
            runAsUser: 33
          env:
            - name: POD_NAME
              valueFrom:
                fieldRef:
                  fieldPath: metadata.name
            - name: POD_NAMESPACE
            - name: http
              valueFrom:
                fieldRef:
                  fieldPath: metadata.namespace
          ports:
            - name: http
              containerPort: 80
            - name: https
              containerPort: 443
mandatory.yaml定义了nginx-ingress-controller 镜像的 Pod,就是一个监听 Ingress 对象以及它所代理的后端 Service 变化的控制器。即一个新的 Ingress 对象由用户创建后,nginx-ingress-controller 就会根据 Ingress 对象里定义的内容,生成一份对应的 Nginx 配置文件(/etc/nginx/nginx.conf),并使用这个配置文件启动一个 Nginx 服务。而一旦 Ingress 对象被更新,nginx-ingress-controller 就会更新这个配置文件。

一个 Nginx Ingress Controller 为你提供的服务,其实是一个可以根据 Ingress 对象和被代理后端 Service 的变化,来自动进行更新的 Nginx 负载均衡器。

2 创建对应的service
为了让用户能够用到这个 Nginx,我们就需要创建一个 Service 来把 Nginx Ingress Controller 管理的 Nginx 服务暴露出去,如下所示:
$ kubectl apply -f https://raw.githubusercontent.com/kubernetes/ingress-nginx/master/deploy/provider/baremetal/service-nodeport.yaml
service-nodeport.yaml内容如下:
apiVersion: v1
kind: Service
metadata:
  name: ingress-nginx
  namespace: ingress-nginx
  labels:
    app.kubernetes.io/name: ingress-nginx
    app.kubernetes.io/part-of: ingress-nginx
spec:
  type: NodePort
  ports:
    - name: http
      port: 80
      targetPort: 80
      protocol: TCP
    - name: https
      port: 443
      targetPort: 443
      protocol: TCP
  selector:
    app.kubernetes.io/name: ingress-nginx
    app.kubernetes.io/part-of: ingress-nginx

Service 的唯一工作,就是将所有携带 ingress-nginx 标签的 Pod 的 80 和 433 端口暴露出去。


记录下这个 Service 的访问入口,即:宿主机的地址和 NodePort 的端口,如下所示:
$ kubectl get svc -n ingress-nginx
NAME            TYPE       CLUSTER-IP     EXTERNAL-IP   PORT(S)                      AGE
ingress-nginx   NodePort   10.105.72.96   <none>        80:30044/TCP,443:31453/TCP   3h
把上述访问入口设置为环境变量:
$ IC_IP=10.168.0.2 # 任意一台宿主机的地址
$ IC_HTTPS_PORT=31453 # NodePort端口

然后:就是部署应用的Pod、service、secret、ingress对象

$ kubectl create -f cafe.yaml
#创建 Ingress 所需的 SSL 证书(tls.crt)和密钥(tls.key)
$ kubectl create -f cafe-secret.yaml
$ kubectl create -f cafe-ingress.yaml

访问服务:
$ curl --resolve cafe.example.com:$IC_HTTPS_PORT:$IC_IP https://cafe.example.com:$IC_HTTPS_PORT/coffee --insecure
$ curl --resolve cafe.example.com:$IC_HTTPS_PORT:$IC_IP https://cafe.example.com:$IC_HTTPS_PORT/tea --insecure


容器port ---> pod的port
pod定义中containerPort: 80

pod的port ---> service的port
service中的定义:
spec:
 ports:
 - port: 8080 #service对外提供的端口
   targetPort: 8080 #对应到pod的8080端口
   name: web #对应的服务名称
   protocol: TCP #网络类型


service的port --> node的port
spec:
 type: NodePort
 ports: 
 - port: 8080
  targetPort: 8080
  nodePort: 8081 #物理机Ip+端口可以访问服务
此时可以通过node的ip+node的port访问单个节点的服务

node的port --> slb的port
slb配置多个node的ip和port进行负载均衡

2.4.5 DNS服务搭建指南
为了实现集群内能够通过服务名对服务进行访问。
k8s提供的虚拟DNS服务名为skydns,由四个组件组成:
1) etcd: DNS存储
2)kube2sky:将kubenetes Msater中的service注册到etcd
3)skyDNS:提供DNS域名解析服务
4)healthz: 提供对skydns服务的健康检查功能

2.4.6 自定义DNS和上游DNS服务器

问题:
线上的ssh的k8s集群怎么向外提供的服务?
怎么向外部提供服务的? 
jude的k8s集群中的slb的service 和 slb服务是怎样的关系?

附:service定义属性

Logo

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

更多推荐