服务 kubernetes服务是为一组功能相同的pod提供单一不变的接入点资源。当一个服务存在时,它的IP和端口不会改变。客户端通过IP地址和端口号建立连接。连接会被路由到提供该服务的任意一个pod。
通过这种方式,客户端不需要知道提供服务pod的ip和端口信息,这样这些pod可以在集群中随时被创建和删除。
假如有前端web服务器和后端数据库服务器,有很多pod提供前端服务,而中有一个pod提供后台数据库服务。需要解决两个问题才能发挥系统作用
外部客户无须关心服务器数量而连接到前端Pod上。
前端的pod需要连接后端的数据库。由于数据库运行在pod上,他可能会在集群中随时重启而移动,导致IP地址随时变动。当后端数据库被移动时,不必为前端pod重新配置。
通过为前端pod创建服务,并且将其配置成可以在集群外部访问。可以暴露一个单一不变的IP提供给外部客户端访问,同样的,可以在后端创建一个服务,分配一个固定的ip地址。尽管pod的IP地址会改变,但是服务的IP地址固定不变,并始终指向提供前端服务的pod。通过创建服务,能够让前端的pod通过环境变量或DNS以及服务名来访问后端服务。
创建一个服务 通过标签选择器决定哪些pod属于服务 服务的后端可以有不止一个pod。服务的连接对所有的后端pod是负载均衡的。那么,要如何准确定义哪些pod是属于这个服务的呢
通过kubectl expose 创建服务 创建服务最简单的方法是通过kubectl expose,像创建Replication时使用的pod选择器那样,利用expose命令和pod选择器来创建服务资源,从而通过单个并固定的ip来访问所有的pod。
通过YAML描述文件来创建服务 1 2 3 4 5 6 7 8 9 10 11 apiVersion: v1 kind: Service metadata: name: static-blog spec: selector: webtype: staticblog #具有webtype=staticblog标签的pod都属于该服务 ports: - protocol: TCP port: 80 #该服务的可用端口 targetPort: 80 #服务将连接转发到这个容器端口
使用kubectl create -f [filename]发布文件来创建服务。
创建了一个名叫static-blog的服务,它将在端口80接收请求并将连接路由到具有webtype=staticblog标签的pod80端口上。
查看新的服务 1 2 3 4 kubectl get svc NAME TYPE CLUSTER-IP EXTERNAL-IP PORT(S) AGE kubernetes ClusterIP 10.96.0.1 443/TCP 32d static-blog ClusterIP 10.105.172.229 80/TCP 30d
从内部集群测试服务 可以通过以下几种方法向服务发送请求:
创建一个Pod,将请求发送到服务的集群IP并记录响应。通过查看Pod日志检查服务的响应。
ssh登录到kubernetes集群的一台子节点上,通过curl命令查看。
通过Kubectl exec登录到一个已存在的pod中执行curl命令。
在运行的容器中执行命令 1 kubectl exec nginx-hexo-blog-798d89bfd4-ntqkj -- curl -s http://10.105.172.229
双横杠(–)代表着kubectl命令的结束。在两个横杠之后的内容是指在pod内部要执行的命令,如果需要执行的命令没有以横杠开始的参数,那么双横杠也不用必须添加。
** 如果这里不使用双横杠,curl命令的-s选项会被解析成kubectl exec选项,导致异常**
1 2 kubectl exec nginx-hexo-blog-798d89bfd4-ntqkj curl -s http://10.105.172.229 Unable to connect to the server: dial tcp 10.105.172.229:443: i/o timeout
服务会拒绝连接。这是因为kubectl并不能连接到位于10.105.172.229的api服务器(-s选项kubectl需要连接一个不同的api服务器而不是默认的)。
在一个pod容器上执行curl命令。curl命令会向一个后端有三个Pod服务的IP发送了HTTP请求,Kubernetes服务获取该连接,将请求转发给三个Pod中任意一个Pod。nginx应用在Pod中获取该请求,并返回该Pod nginx的HTTP响应。curl命令打印返回值,该返回值被Kubectl截取并打印到宿主机。
配置服务上的会话亲和性 在 kubernetes 集群中有两种负载均衡分发策略,也可以称之为服务的会话亲和性:
RoundRobin
:轮询模式,这是默认配置。即定义 YAML 文件时不设置spec.sessionAffinity
或设置spec.sessionAffinity=None
。它表示的是将请求分发到后端各个 Pod 上,没有固定某个 Pod 对请求进行响应。
SessionAffinity
:会话保持模式,需要在定义 YAML 文件时设置spec.sessionAffinity=ClientIP
。当某个客户端第一次请求转发到后端的某个 Pod 上,那么之后这个客户端的请求也一直由相同的 Pod 进行响应。
暴露多个端口 服务可以暴露一个端口,也可以暴露多个端口。提供nginx服务的Pod有http和https端口,可以将服务暴露两个端口转发至pod的http和https端口,这样就不必创建两个服务了。
当在一个服务中创建多个端口时,需要给每个端口指定名字
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 apiVersion: v1 kind: Service metadata: name: static-blog spec: sessionAffinity: ClientIP selector: webtype: staticblog ports: - name: http port: 80 targetPort: 80 # pod的80端口 - name: https port: 8080 targetPort: 443 #pod的443端口
当在多个端口的服务中设置SessionAffinity时,客户端第一次请求的端口转发到某个pod上时,之后这个客户端的请求也一直由相同的pod进行响应,当有多个端口时,每一个独立端口都会有会话保持模式。
使用命名的端口创建服务 在pod的容器定义中,可以使用一个名字来指定相应的容器端口,这样服务中就可以将targetPort端口指定为pod的命名端口,当spec pod中的端口发生改变时,服务的spec就不需要更改了。
1 2 3 4 5 6 7 8 9 10 ...... containers: - name: hexo-blog image: nginx:1.20.1 ports: - name: http containerPort: 80 - name: https containerPort: 443 ......
service中的设置
1 2 3 4 5 6 7 8 9 10 11 12 13 14 apiVersion: v1 kind: Service metadata: name: static-blog spec: selector: webtype: staticblog ports: - name: http port: 80 targetPort: http - name: https port: 8080 targetPort: https
服务发现 通过环境变量发现服务 1 2 3 4 5 6 7 8 [root@VM-20-9-centos nginx]# kubectl exec -it nginx-hexo-blog-7f478ccb98-t86vj -- env |grep -i service KUBERNETES_SERVICE_HOST=10.96.0.1 KUBERNETES_SERVICE_PORT=443 STATIC_BLOG_SERVICE_PORT=80 STATIC_BLOG_SERVICE_PORT_HTTPS=8080 KUBERNETES_SERVICE_PORT_HTTPS=443 STATIC_BLOG_SERVICE_HOST=10.105.227.251 STATIC_BLOG_SERVICE_PORT_HTTP=80
当创建一个Pod的时候,kubelet会在该Pod中放入集群内所有Service的相关环境变量。需要注意的是,要想一个Pod中放入某个Service的环境变量,则Service要先比Pod创建。
通过DNS发现服务 kube-dns运行着k8s内部的dns服务,每个服务从内部DNS获得一个DNS条目,pod可以用全限定域名FQDN来访问服务
1 static-blog.default.svc.cluster.local
static-blog对应着服务名称
default为Service所在的命名空间
svc.cluster.local是在所有集群本地服务名称中使用的可配置集群后缀
在Kubernetes中,ClusterIP/NodePort类型的Service的IP地址是虚拟的,并不分配给任何实体【没有实际的mac地址】,因此无法响应ICMP请求,也就是说,它们不能被ping通。这是因为Kubernetes的Service仅仅是一个IP地址和端口的组合,用于负载均衡和服务发现,而不是一个实际的网络接口。
endpoint 服务并不是与pod直接相连的,还有一个资源介于两者之间,这就是endpoint
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 [root@VM-20-9-centos ~]# kubectl describe svc static-blog Name: static-blog Namespace: default Labels: Annotations: Selector: webtype=staticblog #用于创建服务时选择pod Type: ClusterIP IP: 10.96.100.84 Port: http 80/TCP TargetPort: http/TCP Endpoints: 10.244.1.16:80,10.244.2.23:80 Port: https 8080/TCP TargetPort: https/TCP Endpoints: 10.244.1.16:443,10.244.2.23:443 #用于endpoint的pod ip和端口 Session Affinity: None Events:
endpint同其他kubernetes资源一样,可以用kubectl info来获取信息
1 2 3 [root@VM-20-9-centos ~]# kubectl get endpoints static-blog NAME ENDPOINTS AGE static-blog 10.244.1.16:80,10.244.2.23:80,10.244.1.16:443 + 1 more... 11d
1 2 3 4 5 6 7 8 9 10 11 12 13 [root@VM-20-9-centos nginx]# kubectl describe endpoints static-blog Name: static-blog Namespace: default Labels: Annotations: Subsets: Addresses: 10.244.1.16,10.244.2.23 NotReadyAddresses: Ports: Name Port Protocol ---- ---- -------- http 80 TCP https 443 TCP
Events: <none>
手动配置endpoint 创建一个服务,并没有定义selector标签
1 2 3 4 5 6 7 8 [root@VM-20-9-centos nginx]# cat enternal-server.yaml apiVersion: v1 kind: Service metadata: name: enternal-service spec: ports: - port: 80
为service手动创建一个endpoint。
[root@VM-20-9-centos nginx]# cat enternal-server-endpoints.yaml
apiVersion: v1
kind: Endpoints
metadata:
name: enternal-service #endpoint的名称必须和服务的名称相匹配
subsets:
- addresses:
- ip 10.10.10.10 #服务将连接重定向到endpoint的IP地址
- ip 10.10.10.11
ports:
- port: 80 # endpoint的目标端口
如上文所说,在此服务之前创建的环境变量中不会包含该服务的环境变量。
1 2 3 4 5 6 7 8 [root@VM-20-9-centos nginx]# kubectl exec -it nginx-hexo-blog-7f478ccb98-t86vj -- env|grep -i service KUBERNETES_SERVICE_HOST=10.96.0.1 KUBERNETES_SERVICE_PORT=443 STATIC_BLOG_SERVICE_PORT=80 STATIC_BLOG_SERVICE_PORT_HTTPS=8080 KUBERNETES_SERVICE_PORT_HTTPS=443 STATIC_BLOG_SERVICE_HOST=10.105.227.251 STATIC_BLOG_SERVICE_PORT_HTTP=80
当把此pod删除让其自动创建后,在环境变量中就可以看到此服务的信息。
1 2 3 4 5 [root@VM-20-9-centos nginx]# kubectl exec -it nginx-hexo-blog-7f478ccb98-6dck2 -- env|grep -i service EXTERNAL_SERVICE_SERVICE_HOST=10.111.125.202 EXTERNAL_SERVICE_PORT_80_TCP=tcp://10.111.125.202:80 EXTERNAL_SERVICE_PORT_80_TCP_ADDR=10.111.125.202 ...
Endpoint对象需要与服务相同的名称,并要在endpoint对象中定义service的目标IP和端口
创建用于访问外部服务的service类型 如果在k8s集群外部有需要访问的服务,可以将service的type设置为ExternalName。
假如外部有一个nginx需要提供给内部的pod访问
1 2 3 4 5 6 7 8 9 10 cat external-service.yaml apiVersion: v1 kind: Service metadata: name: external-service spec: type: ExternalName externalName: www.mountainmist.work ports: - port: 443
ExternalName服务会创建一个CNAME DNS记录,pod可以使用external-service.default.svc.cluster.local连接到此外部服务,
服务暴露至外部 使用nodeport类型的服务 1 2 3 4 5 6 7 8 9 10 11 12 13 cat nginx-nodeport.yml apiVersion: v1 kind: Service metadata: name: test-nodeport spec: type: NodePort # 设置服务类型为NodePort ports: - port: 80 targetPort: 80 # pod的目标端口 nodePort: 30012 #通过k8s集群节点的该端口可以访问 selector: webtype: staticblog
指定端口并不是必须的,如果没有指定,kubernetes将选择一个随机端口。
使用jsonpath可以获取节点所有ip
1 kubectl get nodes -o jsonpath='{.items[*].status.addresses[?(@.type=="InternalIP")].address}'
使用Ingress 在Kubernetes中,为了使外部的应用访问集群内的service,可以使用NodePort和LoadBalancer两种类型的service。
NodePort方式会占用很多集群机器的端口,而LoadBanlancer类型要求Kubernetes运行在支持的云上。当同时存在多个LoadBanlancer类型的Service时,就会占用大量公网ip地址,而Ingress只需要一个公网IP就能为许多服务提供访问,当客户端发起请求时,Ingress会根据请求的主机名和路径将请求转发到哪个服务。
Ingress在应用层操作,可以提供服务不能实现的功能,如基于cookie的会话亲和性。
关于Ingress控制器 Ingress控制器类似于nginx,Ingress类似于nginx的配置文件。如果只安装nginx,而没有nginx的配置文件,则此nginx将毫无意义;反过来,若只有nginx的配置文件,但没安装nginx,则配置文件无运行载体,所以,只有ingress 控制器在kubernetes集群中正常运行,ingress资源才能正常工作,不同的k8s环境可以选择不同的ingress控制器来实现
创建一个ingress 控制器 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 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 70 71 72 73 74 75 76 77 78 79 80 81 82 83 84 85 86 87 88 89 90 91 92 93 [root@VM-20-9-centos ingress]# cat deploy_nginx.yaml ... apiVersion: apps/v1 kind: Deployment metadata: labels: app.kubernetes.io/component: controller app.kubernetes.io/instance: ingress-nginx app.kubernetes.io/name: ingress-nginx app.kubernetes.io/part-of: ingress-nginx app.kubernetes.io/version: 1.10.1 name: ingress-nginx-controller namespace: ingress-nginx spec: minReadySeconds: 0 revisionHistoryLimit: 10 selector: matchLabels: app.kubernetes.io/component: controller app.kubernetes.io/instance: ingress-nginx app.kubernetes.io/name: ingress-nginx strategy: rollingUpdate: maxUnavailable: 1 type : RollingUpdate template: metadata: labels: app.kubernetes.io/component: controller app.kubernetes.io/instance: ingress-nginx app.kubernetes.io/name: ingress-nginx app.kubernetes.io/part-of: ingress-nginx app.kubernetes.io/version: 1.10.1 spec: hostNetwork: true containers: - args: - /nginx-ingress-controller - --publish-service=$(POD_NAMESPACE)/ingress-nginx-controller - --election-id=ingress-nginx-leader - --controller-class=k8s.io/ingress-nginx - --ingress-class=nginx - --configmap=$(POD_NAMESPACE)/ingress-nginx-controller - --validating-webhook=:8443 - --validating-webhook-certificate=/usr/local/certificates/cert - --validating-webhook-key=/usr/local/certificates/key - --enable-metrics=false env : - name: POD_NAME valueFrom: fieldRef: fieldPath: metadata.name - name: POD_NAMESPACE valueFrom: fieldRef: fieldPath: metadata.namespace - name: LD_PRELOAD value: /usr/local/lib/libmimalloc.so image: registry.cn-hangzhou.aliyuncs.com/google_containers/nginx-ingress-controller:v1.10.1 imagePullPolicy: IfNotPresent lifecycle: preStop: exec : command : - /wait-shutdown livenessProbe: failureThreshold: 5 httpGet: path: /healthz port: 10254 scheme: HTTP initialDelaySeconds: 10 periodSeconds: 10 successThreshold: 1 timeoutSeconds: 1 name: controller ports: - containerPort: 80 name: http protocol: TCP - containerPort: 443 name: https protocol: TCP - containerPort: 8443 name: webhook protocol: TCP ... nodeSelector: ingress: "true" ...
创建一个ingress资源 1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 [root@VM-20-9-centos ingress]# cat ingress.yaml apiVersion: networking.k8s.io/v1 kind: Ingress metadata: name: ingress-blog spec: ingressClassName: nginx rules: - host: test.example.com http: paths: - path: / pathType: Prefix backend: service: name: test-nodeport port: number: 80
通过ingress访问服务 要确保通过ingress访问服务,需要确保将域名解析成为ingress的ip
获取ingress的ip
1 2 3 kubectl get ingress test-ingress NAME CLASS HOSTS ADDRESS PORTS AGE test-ingress external-lb * 203.0.113.123 80 59s
address不显示时,因为设置控制器Pod使用宿主机的端口,所以ingres控制器的ip就是宿主机的ip,知道ip以后,通过配置DNS服务器将test.example.com解析为此ip,或者在/etc/hosts文件中(windows系统为C:\Windows\System32\drivers\etc\hosts)添加以下内容:
1 203.0.113.123 test.example.com
通过ingress访问pod 1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 [root@VM-20-9-centos ingress]# curl test.example.com <!DOCTYPE html> <html lang="zh-CN" > <head > <meta charset="UTF-8" > <meta name="viewport" content="width=device-width" > <meta name="theme-color" content="#222" ><meta name="generator" content="Hexo 7.2.0" > <link rel="apple-touch-icon" sizes="180x180" href="/images/apple-touch-icon-next.png" > <link rel="icon" type ="image/png" sizes="32x32" href="/images/favicon-32x32-next.png" > <link rel="icon" type ="image/png" sizes="16x16" href="/images/favicon-16x16-next.png" > <link rel="mask-icon" href="/images/logo.svg" color="#222" > <link rel="stylesheet" href="/css/main.css" > ...
查看ingress控制器的日志可以看到,请求转发到了test-nodeport的80端口,并把它转发到pod 10.244.9.110
1 2 3 [root@VM-20-9-centos ingress]# kubectl logs -f --tail 20 ingress-nginx-controller-9c6994c4d-72vgh -n ingress-nginx 82.156.79.118 - - [10/Oct/2024:03:56:26 +0000] "GET / HTTP/1.1" 200 33547 "-" "curl/7.29.0" 80 0.001 [default-test-nodeport-80] [] 10.244.9.110:80 33547 0.000 200 00210085880e233324f53a9c58a12e23 82.156.79.118 - - [10/Oct/2024:03:56:38 +0000] "GET / HTTP/1.1" 200 33547 "-" "curl/7.29.0" 80 0.001 [default-test-nodeport-80] [] 10.244.9.110:80 33547 0.000 200 7b31d9f8362676a68c6c06962645a667
了解ingress的工作原理 客户端首先会对test.example.com执行DNS查找,DNS或本地系统返回了ingress控制器的ip。然后客户端向ingress控制器发送HTTP请求,并在Host头中指定test.example.com。控制器从头部确定客户端尝试访问哪个服务,通过与该服务关联的Endpoint对象查看pod ip,并将客户端的请求转发给其中一个Pod
ingress控制器不会将请求转发给服务,只用它来选择一个pod
通过ingress暴露多个服务 将不同的服务映射到相同域名的不同路径 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 [root@VM-20-9-centos ingress]# cat ingress_1.yaml apiVersion: networking.k8s.io/v1 kind: Ingress metadata: name: ingress-blog-test spec: ingressClassName: nginx rules: - host: test.example.com http: paths: - path: /bar pathType: Exact backend: service: name: test-nodeport port: number: 80 - path: /foo pathType: Exact backend: service: name: static-blog port: number: 80
在这种情况下,根据请求URL的路径,请求将发送到两个不同的服务。客户端可以通过一个Ingress IP访问两个不同的服务
1 2 3 4 [root@VM-20-9-centos ingress]# kubectl logs -f --tail 20 ingress-nginx-controller-9c6994c4d-72vgh -n ingress-nginx ... 82.156.79.118 - - [11/Oct/2024:07:43:23 +0000] "GET /foo HTTP/1.1" 200 13 "-" "curl/7.29.0" 83 0.015 [default-static-blog-80] [] 10.244.13.41:80 13 0.015 200 66c489c83d00400e7c7aef7cbcbc9fd9 82.156.79.118 - - [11/Oct/2024:07:43:25 +0000] "GET /bar HTTP/1.1" 200 13 "-" "curl/7.29.0" 83 0.001 [default-test-nodeport-80] [] 10.244.9.110:80 13 0.001 200 dbaa49564be02fdcee05b168f9ebd4c2
可以在ingress控制器的日志中看到,请求发送到了两个不同的服务
将不同的服务映射到不同的域名 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 apiVersion: networking.k8s.io/v1 kind: Ingress metadata: name: ingress-blog spec: ingressClassName: nginx rules: - host: test.example.com http: paths: - path: / pathType: Prefix backend: service: name: test-nodeport port: number: 80 - host: test1.example.com http: paths: - path: / pathType: Prefix backend: service: name: static-blog port: number: 80
根据请求中的host头,控制器收到的请求将转发到这两个不同的服务,DNS需要将这两个域名都指向ingress控制器的ip。
1 2 [11/Oct/2024:07:24:33 +0000] "GET / HTTP/1.1" 200 33547 "-" "curl/7.29.0" 81 0.000 [default-static-blog-80] [] 10.244.9.110:80 33547 0.001 200 699c9be1dc65a057f0ab775413b1cd3d [11/Oct/2024:07:24:36 +0000] "GET / HTTP/1.1" 200 33547 "-" "curl/7.29.0" 80 0.001 [default-test-nodeport-80] [] 10.244.9.110:80 33547 0.001 200 57e3e285368edb0fe449a34163d7a04f