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颁发的证书,这是怎么回事?
#目前我还没有找到答案,希望路过的大牛们,多多指点,万分感谢....