K8s Service原理介绍

Service的工作方式有三种:
 第一种: 是Userspace方式
  如下图描述, Client Pod要访问Server Pod时,它先将请求发给本机内核空间中的service规则,由它再将请求,
  转给监听在指定套接字上的kube-proxy,kube-proxy处理完请求,并分发请求到指定Server Pod后,再将请求
  递交给内核空间中的service,由service将请求转给指定的Server Pod。
  由于其需要来回在用户空间和内核空间交互通信,因此效率很差,接着就有了第二种方式.

  

 第二种: iptables模型
  此工作方式是直接由内核中的iptables规则,接受Client Pod的请求,并处理完成后,直接转发给指定ServerPod.

  

  第三种: ipvs模型
  它是直接有内核中的ipvs规则来接受Client Pod请求,并处理该请求,再有内核封包后,直接发给指定的Server Pod。

  

  注:
  以上不论哪种,kube-proxy都通过watch的方式监控着kube-APIServer写入etcd中关于Pod的最新状态信息,
  它一旦检查到一个Pod资源被删除了 或 新建,它将立即将这些变化,反应再iptables 或 ipvs规则中,以便
  iptables和ipvs在调度Clinet Pod请求到Server Pod时,不会出现Server Pod不存在的情况。

  自k8s1.1以后,service默认使用ipvs规则,若ipvs没有被激活,则降级使用iptables规则. 但在1.1以前,service
 使用的模式默认为userspace.


查看k8s集群中API Server面向集群内部的service地址:
 #其中第一个kubernets,类型为ClusterIP,暴露端口为443/tcp的即为APIServer的向集群内部提供服务的Service.
  kubectl get svc 

创建Service的清单文件:
  kubectl explain svc
  kubectl explain svc.spec
  type:
   service的类型有四种:
  1. ExternalName: 用于将集群外部的服务引入到集群内部,在集群内部可直接访问来获取服务。
      它的值必须是 FQDN, 此FQDN为集群内部的FQDN, 即: ServiceName.Namespace.Domain.LTD.
      然后CoreDNS接受到该FQDN后,能解析出一个CNAME记录, 该别名记录为真正互联网上的域名.
      如: www.test.com, 接着CoreDNS在向互联网上的根域DNS解析该域名,获得其真实互联网IP.
  2. ClusterIP: 用于为集群内Pod访问时,提供的固定访问地址,默认是自动分配地址,可使用ClusterIP关键字指定固定IP.

  3. NodePort: 用于为集群外部访问Service后面Pod提供访问接入端口.
    这种类型的service工作流程为:
      Client----->NodeIP:NodePort----->ClusterIP:ServicePort----->PodIP:ContainerPort
  4. LoadBalancer: 用于当K8s运行在一个云环境内时,若该云环境支持LBaaS,则此类型可自动触发创建
        一个软件负载均衡器用于对Service做负载均衡调度.
    因为外部所有Client都访问一个NodeIP,该节点的压力将会很大, 而LoadBalancer则可解决这个问题。
    而且它还直接动态监测后端Node是否被移除或新增了,然后动态更新调度的节点数。

#Service清单文件创建示例:
vim  redis-svc.yaml         #定义一个redis的服务.
apiVersion: v1
kind: Service
metadata:
  name: redis
  namespace: default
spec:
 selector:                  #指定标签选择器选择的标签范围.
   app: redis
   role: logstor
 clusterIP: 10.97.97.97
 type: ClusterIP
 ports:
 -  name: redis
    port: 6379           #设定Serivce对外提供服务的端口.
    targetPort: 6379     #设定容器(Pod)的端口,即Pod网络的端口。
    nodePort:            #它仅在type为NodePort时才需要指定.

  接着创建服务:
  kubectl apply -f redis-svc.yaml
  kubectl get svc
  kubectl describe svc redis           #可看到service redis它的详细配置信息.
    Endpoints: 10.224.1.3x:6379    #这个就是Pod的地址,Serice和Pod实际上并非直接联系,中间
                   #还有一个Endpoint作为转发。所以这里显示的是Endpoint而非Pod.
K8s中资源的全局FQDN格式:
  Service_NAME.NameSpace_NAME.Domain.LTD.
  Domain.LTD.=svc.cluster.local.     #这是默认k8s集群的域名。

 

创建一个nodePort类型的service,让节点外主机可以访问到服务.
vim  myapp-svc.yaml
apiVersion: v1
kind: Service
metadata:
  name: myapp
  namespace: default
spec:
  selector:
    app: myapp
    release: canary
  clusterIP: 10.99.99.99  #此地址设定为固定很容易冲突, 建议最好不写,让其自动分配.
  type: NodePort
  ports:
  - port: 80          #设置Service对外提供服务的端口
    targetPort: 80    # 设置Pod对外提供服务的端口.
    nodePort: 30080   #此宿主机端口也可不指定,系统将自动从3万~65535分配,若必须设为80,也可以,
                      #但必须保证所以宿主机上的80端口没有被占用,若有被占用,则该节点上的服务将无法被访问.

Service的externalName类型原理介绍:
 Pod---->SVC[externalName]------[SNAT]----->宿主机的物理网卡------>物理网关----->Internat上提供服务的服务器.
   注意: Service是externelName类型时, externalName必须是域名,而且此域名必须能被CoreDNS或CoreDNS能通过
     互联网上的根DNS解析出A记录.

Service在对外提供服务时,还支持会话粘性:
  sessionAffinity : 它支持ClientIP 和 None两种方式.
  None: 即不做会话粘性,进行随机调度.
  ClientIP: 根据客户端IP来调度,同一个客户端IP都调度到同一个后端主机。

通过打补丁的方式来测试会话粘性:
  #对上面创建的myapp service打补丁,让其支持基于ClientIP的会话粘性.
  kubectl patch svc myapp -p ‘{"spec":{"sessionAffinity":"ClientIP"}}’

  kubectl describe svc myapp    #可以查看到多了一个Session Affinity的字段.


headless service(无头service):
  所谓headless service指: 没有ClusterIP的service, 它仅有一个service name.这个服务名解析得到的不是
  service的集群IP,而是Pod的IP,当其它人访问该service时,将直接获得Pod的IP,进行直接访问。

示例:
vim  myapp-svc-headless.yaml
apiVersion: v1
kind: Service
metadata: 
  name: myapp-headless
  namespace: default
spec:
  selector:
     app: myapp
     release: canary
  clusterIP: None
  ports:
  - port: 80
    targetPort: 80

  #创建headless service:
  kubectl apply -f myapp-svc-headless.yaml
  kubectl get svc    #将可以看到ClusterIP项为None.

  #这里将显示service name解析为Pod的IP了.
  dig -t A myapp-headless.default.svc.cluster.local. @CoreDNS_IP    

  #CoreDNS_IP的查看方式:
  kubectl get svc -n kube-system

  #查看Pod的IP:
  kubectl get pods -o wide -l app=myapp



Ingress Controller
  下图即为Ingress Controller这种独特的控制器资源的工作流程图.
  需要注意: Ingress Controller 和 Ingress是两个不同的资源。
  Ingress它是通过headless service的集群内FQDN获取到它后端所有的Pod资源的IP,因为headless Service没有
    ClusterIP,它的域名对应的IP为PodIP。Ingress获取PodIP后,在立刻将其写入到ingress-nginx的配置文件中,
    并触发nginx重读配置文件。实现动态更新upstream。
    另外,外部LB可以直接跳过Service,直接访问到nginx,这需要将nginx这个Pod作为共享宿主机的网络名称空间
    才能实现,这时就需要借助于daemonSet控制器来控制着nginx这种七层反代Pod仅允许在指定节点上,并且
    每个节点上仅运行一个nginx Pod。

  

 #注:
  DaemonSet,RepliceSet,Deployment,StatuefulSet 等它们都是Master上的ControllerManager
 的子组件,而Ingress Controller则是独立运行的一个或一组Pod资源,它通常就是一个拥有七层
   调度能力的应用程序,在K8s上这种应用程序有四种:
  Nginx:一般默认使用Nginx作为这种应用程序。
  Traefik: 它原先设计也是为微服务而生的,就是为了能实现动态生成配置文件。
  Envoy: 在服务网格 或 微服务 中通常会比较喜欢用它.
  Traefik和Envoy: 它们都可以实现动态监控配置文件发生变化,若发生变化,
          则会即时自动重载配置,而无需手动参与。
  HAProxy: 它是最不受欢迎的一种解决方案。

 

查看ingress controller的定义:
  kubectl explain ingress
  kubectl explain ingress.spec

 

创建名称空间的方式:
  1. 使用命令:
    kubectl create namespace test
    kubectl get ns
    kubectl delete ns/test              #删除一个test名称空间, 需要注意: 删除一个名称空间,则会删除其内所有的资源.
  2. 使用清单创建名称空间:
    apiVersion: v1
    kind: Namespace
    metadata:
      name: test

实现Ingress-ngnix的示例:
  下面这个项目是kubernetes中ingress-nginx项目安装说明
  https://github.com/kubernetes/ingress-nginx/blob/master/docs/deploy/index.md

#由于我是直接用VMware上的VM安装的K8s,因此使用裸机(Bare-metal)的方式来安装.
#首先,下载这个主配置文件,它里面帮我们写好了创建,Ingress-nginx所必须namespace,configMap,ServiceAccount,RBAC,Role,和ingress-nginx等.
# 在使用下面这清单文件时,建议先把ingress-nginx的镜像先下载下来,避免下载镜像失败导致创建ingress-nginx失败.
  wget https://raw.githubusercontent.com/kubernetes/ingress-nginx/master/deploy/static/mandatory.yaml

#接着, 下载裸机所对应的创建Ingress-nginx的Service配置清单.
  https://raw.githubusercontent.com/kubernetes/ingress-nginx/master/deploy/static/provider/baremetal/service-nodeport.yaml

#在继续之前先看下,当前的拓扑图

  

    上面mandatory.yaml 帮我们创建了Nginx-Ingres-Controller,另外还有一些资源没有画出来.
   service-nodeport.yaml 帮我们创建了Service/Ingress-nginx

  

#接着我们需要自己创建Ingress 和 Service/myapp 以及三个myapp Pod
vim   deploy-demo.yaml 
apiVersion: v1
kind: Service       #这部分创建service/myapp
metadata:
  name: myapp
  namespace: default
spec:
  selector:         #通过下面两个标签(label)来选择Pod
    app: myapp
    release: canary
  ports:
  - name: http
     targetPort: 80
     port: 80

---
apiVersion: apps/v1
kind: Deployment     #这部分来创建Myapp Pod的deployment.apps控制器
metadata:
  name: myapp-ingress
  namespace: default
spec:
  replicas: 3        #设置其副本数量控制器ReplicaSet,监控Pod至少保证有3个
  selector:
    matchLabels:     #它筛选自己管理的Pod时,使用的label是下面两个.
      app: myapp
      release: canary
  template:          #replicaSet发现Pod不足时,使用此模板定义的规则,创建Pod.
    metadata:
      labels:        #每次创建的Pod都打上下面两个label
        app: myapp
        release: canary
    spec:
      containers:
      - name: myapp
         image: harbor.zcf.com/k8s/myapp:v1
         ports:
         - name: http
            containerPort: 80
#接着创建ingress
vim   ingress-myapp.yaml 
apiVersion: extensions/v1beta1
kind: Ingress
metadata:
  name: ingress-myapp
  namespace: default      #注意ingress要和后端提供Web服务的Pod处于同一名称空间中.
  annotations:
        #kubernetes.io为前缀,ingress.class:为键名,通过这种定义来告诉ingress,
        #这里使用的ingress-controller为nginx,你在生成配置时,生成nginx相关的配置。
      kubernetes.io/ingress.class: "nginx"   
spec:                                                                          
 rules:
 ##定义使用虚拟主机来代理后端Web应用.而识别该虚拟主机的域名定义为myapp.test.com
 -  host: myapp.test.com 
    http:
      paths:
      - path:
        backend:
          #这里要指明后端提供Web服务的前端Service的名称,该Service负责筛选出提供Web服务的Pod.
          serviceName: myapp
          servicePort: 80

#以上四步,都完成后,我们就有了下面这些文件:
  deploy-demo.yaml
  ingress-myapp.yaml
  mandatory.yaml
  service-nodeport.yaml

1.  kubectl   apply  -f  mandatory.yaml
#可验证以下信息: 其它信息的验证可参考扩展验证.
# kubectl get ns
    NAME            STATUS   AGE
    default         Active   3d14h
    ingress-nginx   Active   6h12m
    .....
# kubectl get deployments
# kubectl describe  deployments myapp-ingress
# kubectl get replicaset
# kubectl describe  replicaset myapp-ingress-5b8676cff7

2.  kubectl  apply  -f  service-nodeport.yaml
#验证:
# kubectl get service -n ingress-nginx ingress-nginx
NAME            TYPE       CLUSTER-IP       EXTERNAL-IP   PORT(S)                      AGE
ingress-nginx   NodePort   172.30.245.115   <none>        80:53712/TCP,443:46652/TCP   6h36m
3. kubectl  apply  -f  deploy-demo.yaml
4. kubectl  apply  -f  ingress-myapp.yaml
#验证:
# kubectl get pod -o wide
    NAME                             READY   STATUS    RESTARTS   AGE     IP            NODE             NOMINATED NODE   READINESS GATES
    client-f5cdb799f-2wsmr           1/1     Running   3          2d18h   10.10.97.37   192.168.111.80   <none>           <none>
    myapp-ingress-5b8676cff7-jxmg8   1/1     Running   0          3h51m   10.10.97.60   192.168.111.80   <none>           <none>
    myapp-ingress-5b8676cff7-s2nsf   1/1     Running   0          3h51m   10.10.171.5   192.168.111.81   <none>           <none>
    myapp-ingress-5b8676cff7-wx5q7   1/1     Running   0          3h51m   10.10.171.4   192.168.111.81   <none>           <none>

# kubectl get deployment
        NAME            READY   UP-TO-DATE   AVAILABLE   AGE
        client          1/1     1            1           2d18h
        myapp-ingress   3/3     3            3           3h52m

# kubectl describe deployment myapp-ingress 
        Name:                   myapp-ingress
        .......
        Replicas:               3 desired | 3 updated | 3 total | 3 available | 0 unavailable
        StrategyType:           RollingUpdate           #deployment控制器默认的为滚动更新
        MinReadySeconds:        0
        RollingUpdateStrategy:  25% max unavailable, 25% max surge
        ......
        NewReplicaSet:   myapp-ingress-5b8676cff7 (3/3 replicas created)
        ......

# kubectl get replicaset
        NAME                       DESIRED   CURRENT   READY   AGE
        client-f5cdb799f           1         1         1       2d18h
        myapp-ingress-5b8676cff7   3         3         3       3h52m

# kubectl describe replicasets.apps myapp-ingress-5b8676cff7 
        Name:           myapp-ingress-5b8676cff7
        Namespace:      default
        Selector:       app=myapp,pod-template-hash=5b8676cff7,release=canary
              ..............
        Annotations:    deployment.kubernetes.io/desired-replicas: 3
                                deployment.kubernetes.io/max-replicas: 4      #最多只允许多出一个副本,这说明,此ReplicaSet更新策略为:滚动更新,即先创建一个,然后在删除一个。
                                deployment.kubernetes.io/revision: 1
        Controlled By:  Deployment/myapp-ingress        #上游控制器是myapp-ingress,控制器类型: deployment
        Replicas:       3 current / 3 desired                           #副本数量, 当前3个,期望3个
        Pods Status:    3 Running / 0 Waiting / 0 Succeeded / 0 Failed

    5. 进入nginx-ingress-controller中,查看OpenResty的配置文件:
  kubectl exec -n ingress-nginx -it nginx-ingress-controller-.... -- /bin/sh
  $ cat nginx.conf
    #这里面,内容很多,可只看重点部分
    # 1. server_name myapp.test.com    #这个server段,可重点看。
    # 2. upstream upstream_balancer {}    #这段可参考,因为它可能是通过balancer_by_lua_block {} 实现获取Pod列表的.而该段调用的是ruby脚本,我暂时没看懂。

 6. 测试访问:
  再集群外部主机上访问: 必须能解析 myapp.test.com 这个域名.
    http://myapp.test.com:53712/
    <h1>WERCOME TO www.zcf.com WEB SITE | Sun Jul 21 02:13:51 UTC 2019 | myapp-ingress-5b8676cff7-s2nsf | 10.10.171.5 | -v1- | </h1>

 

实验总结:
  当ingress创建完成后,就相当于它将ingress-controller 和 后端提供Web服务的Pod关联起来了。
  Pod的前端Service负责实时监控Pod的变化,并反应在自己的Endpoints中,而ingress通过定义backend为后端Service,从而与后端的Service取得联系,并获取Service的Endpoints列表,从而得到它所监控的Pod列表,这些Pod就是实际提供Web服务的后端容器。
  当Ingress获取到后端Pod列表后,它就可以联系前端的ingress-controller,并根据自己annotations中的定义知道前端ingress-controller所使用的反向代理为nginx,然后ingress就会生成nginx的配置信息,并自定写入ingress-controller-nginx中。当ingress-controller-nginx获得配置信息后,它就可以对外提供反向代理服务了。
  另外:
  ingress中定义rules,指明使用虚拟主机,虚拟主机的域名为myapp.test.com,若有多个虚拟主机,则可定义多个。那么它生成的nginx配置就是定义一个server,并指明其servername为myapp.test.com ,该虚拟主机的location / { proxy_pass http://upstream }可以简单这么理解,若有多个虚拟主机,就是定义多个server,每个server都要有独立的域名,比如论坛,商城,博客等。
  location中定义的proxy_pass 的upstream的后端服务器地址列表,则是由ingress中backend定义的serviceName所指定的myapp这个service所监控的后端Pod。
这样以来整个访问链条就构建完成了。

以上四步创建的结构如下图 【注: nginx-ingress-controller 就是我要说明的 ingress-controller-nginx,再书写时写错了】
  但需要注意,ingress仅是动态监控service所监控的后端Pod是否发生变化了,若发生变化,它会立即生成nginx的最新upstream配置,然后再次注入配置到ingress-controller-nginx中,这样ingress-controller-nginx就可以实时知道后端Pod的变化,并直接将前端Client的访问流量代理给后端Pod,图中看上去要先经过ingress,再转发给Pod,实际上是ingress-controller-nginx直接将请求转发给Pod,因为ingress已经将Pod的地址写入到nginx的upstream中了。

 

下面示例演示配置一个tomcat的七层代理应用:
vim  tomcat-deploy.yaml
apiVersion: v1
kind: Service
metadata:
   #这个名字可根据需要定义, 如: 是一个电商站点,则可取名为 eshop 等名字.
   name: tomcat           
   namespace: default
spec:
  selector:
     app: tomcat
     release: canary
  ports:
  -  name: http
     targetPort: 8080
     port: 8080
  -  name: ajp
     targetPort: 8009
     port: 8009
---
apiVersion: apps/v1
kind: Deployment
metadata:
  name: tomcat-deploy
  namespace: default
spec:
  replicas: 3
  selector:
    matchLabels:
      app: tomcat
      release: canary
  template:
    metadata:
      labels: 
        app: tomcat
        release: canary
    spec:
       containers:
       -  name: tomcat
          image: tomcat:8.5.32-jre8-alpine
          ports:
            - name: http
              containerPort: 8080
          ports:
            - name: ajp
              containerPort: 8009

  以上配置说明:
  它定义了一个Deployment的控制器,它下面有3个tomcat Pod,并且都暴露了8080和8009端口,然后还定义了一个service,此service将自己的8080端口映射到Pod的8080端口上,8009也一样。这个service并不作为访问这些tomcat Pod的统一入口,而是作为ingress获取这些tomcat Pod的IP而存在的。

下面测试创建一个TLS类型的secret,然后实现使用同一套证书为两个网站提供HTTPS

1. 使用Kubeasz部署的集群在制作证书时,必须使用cfssl工具来做,因为kubeasz是使用此工具来做证书的,它制作证书的字符编码与OpenSSL的字符编码不同,因此你若使用cfssl制作的ca证书给Openssl制作的证书签证,是不可以的,但若能修改Openssl默认证书中的字符编码,应该是可以的,但我没有研究过如何修改。

  cd  /etc/kubernetes/ssl      #这是kubeasz部署后,CA证书默认存放位置。

       cp  admin-csr.json   test.com-csr.json

vim  test.com-csr.json
{
  "CN": "test.com",
  "key": {
    "algo": "rsa",
    "size": 2048
  },
  "names": [
    {
      "C": "CN",
      "ST": "HangZhou",
      "L": "XS"
    }
  ]
}  

  # grep -C1 'profile' ca-config.json
  },
  "profiles": {
    "kubernetes": {

# cfssl gencert -ca=ca.pem -ca-key=ca-key.pem -config=ca-config.json -profile=kubernetes test.com-csr.json | cfssljson -bare test.com

 

#创建好的证书,不能直接放到nginx中使用,这个证书必须做出k8s中的资源对象,然后,让nginx来引用这个对象才行.
  kubectl create secret tls website-ingress-secret --cert=website.crt --key=website.key
    注:
           tls: 是要创建secret的类型。
      tomcat-ingress-secret:是这个secret的名字.
      --cert: 指明TLS类型的secret所使用的证书在哪里。
      --key: 指明证书的私钥的位置

  kubectl get secret                  #查看创建的secret对象
  kubectl describe secret website-ingress-secret      #查看tomcat-ingress-secret对象的详细信息。

有了secret对象后,就可以创建基于HTTPS的tomcat访问了.

vim  ingress-tomcat-tls.yaml
apiVersion: extensions/v1beta1
kind: Ingress
metadata:
   name: ingress-website-tls
   namespace: default
   annotations:
        kubernetes.io/ingress.class: “nginx”
spec:
  tls:
  - hosts:
     #这里的列表可定义多个主机, 意思是这个多个主机都将使用相同的证书来加密响应数据.这就是所谓的SNI
     -  tomcat.test.com  
     -  myapp.test.com
     secretName:  website-ingress-secret
  rules:         #rules可定义多个, 每一个就是一个虚拟主机 或 URL映射.
  -  host: tomcat.test.com
     http:
       paths:
         - path:
           backend:
             serviceName: tomcat
             servicePort: 8080
  -  host: myapp.test.com
     http:
       paths:
       - path:
         backend:
            serviceName:  myapp
            servicePort: 80

  以上做好以后,就可以创建支持TLS的ingress了.
  kubectl apply -f ingress-tomcat-tls.yaml
  kubectl get ingress
  kubectl describe ingress ingress-tomcat-tls

  # kubectl get svc -n ingress-nginx
    NAME TYPE CLUSTER-IP EXTERNAL-IP PORT(S) AGE
    ingress-nginx NodePort 172.30.245.115 <none> 80:53712/TCP,443:46652/TCP 9h


  kubectl exec -n ingress-nginx -it nginx-ingress-controller-x... -- /bin/sh
  #登陆后可查看nginx的配置文件,确认一下tomcat是否支持HTTPS.

最后,测试访问:
  https://tomcat.test.com:46652/
  #测试发现,Client打开网页后,查看证书,竟然是Kubernetes Ingress Controller颁发的证书,这是怎么回事?
  #目前我还没有找到答案,希望路过的大牛们,多多指点,万分感谢....

  

 

posted @ 2019-08-02 13:47  张朝锋  阅读(40146)  评论(0编辑  收藏  举报