K8S服务发现与Ingress Controller

k8s中内置了dns和服务发现,但这些服务都是pod内部的机制,明显不能与外界通讯,为了与外界通讯,可以通过nodeport或其他方式把容器的端口映射到主机上。

内置服务service的举例

以deployment为例,创建一个deployment的容器集合。

apiVersion: apps/v1
kind: Deployment
metadata:
  name: hostnames
spec:
  selector:
    matchLabels:
      app: hostnames
  replicas: 3
  template:
    metadata:
      labels:
        app: hostnames
    spec:
      containers:
      - name: hostnames
        image: k8s.gcr.io/serve_hostname
        ports:
        - containerPort: 9376
          protocol: TCP

这个deployment表示,使用 k8s.gcr.io/serve_hostname这个image创建3个replica的pods,每个pod中的9376端口负责服务的发布,其实这个应用就是获得本机的hostname。
此时系统产生了3个pods,如下:
根据deployment定义的app=hostnames这个label,可以查看到这个deployment创建的pod如下:

root@k8s-master:~/services# kubectl get pods -l app=hostnames -o wide
NAME                         READY   STATUS    RESTARTS   AGE     IP          NODE           NOMINATED NODE   READINESS GATES
hostnames-5f9c957df4-8lgbh   1/1     Running   0          4d16h   10.10.2.3   k8s-worker02   <none>           <none>
hostnames-5f9c957df4-pxdjn   1/1     Running   0          4d16h   10.10.1.4   k8s-worker01   <none>           <none>
hostnames-5f9c957df4-vdp87   1/1     Running   0          4d16h   10.10.1.3   k8s-worker01   <none>           <none>

三个pod已经创建完毕,可以通过curl ip:9376端口访问到pod中运行的服务。

root@k8s-master:~/services# curl 10.10.1.3:9376
hostnames-5f9c957df4-vdp87

此时说明这个deployment已经顺利部署。
但是这三个pod的服务还是分离的,如何实现集中,此时就要用到service这个对象。
service负责以cluster ip或者headless的方式实现服务的集中注册与对外呈现,这就避免了以pod ip为服务端口的劣势,一旦pod发生变化,如重启或重建,pod ip都会发生变化,而service的方式则在pod之上抽象出一层服务对象。
基于上述三个pod,分别运行了hostnames的服务,用一个service可以把他们串联起来。

apiVersion: v1
kind: Service
metadata:
  name: hostnames
spec:
  selector:
    app: hostnames
  ports:
  - name: default
    protocol: TCP
    port: 80
    targetPort: 9376

这是一个services,它定义了selector app=hostnames,这就创建了一个独立于pod的service,用80端口对外提供服务,而pods们的9376端口都被代理到这个服务上了。

root@k8s-master:~/services# kubectl get svc
NAME                TYPE        CLUSTER-IP      EXTERNAL-IP   PORT(S)        AGE
demo                ClusterIP   10.20.87.120    <none>        80/TCP         15h
hostnames           ClusterIP   10.20.214.128   <none>        80/TCP         4d16h
kube-node-service   NodePort    10.20.156.215   <none>        80:31777/TCP   3d5h
kubernetes          ClusterIP   10.20.0.1       <none>        443/TCP        5d16h
root@k8s-master:~/services# kubectl get svc hostnames -o wide
NAME        TYPE        CLUSTER-IP      EXTERNAL-IP   PORT(S)   AGE     SELECTOR
hostnames   ClusterIP   10.20.214.128   <none>        80/TCP    4d16h   app=hostnames

此时可以明确看到,hostnames的service,他的cluster ip也就是VIP是10.20.214.128,他对pod之间的网络开放80端口访问。根据这个cluster ip进行访问,三次返回不同的hostname,说明cluster ip以vip的方式实现了负载的均衡。

root@k8s-master:~/services# curl 10.20.214.128:80
hostnames-5f9c957df4-pxdjn
root@k8s-master:~/services# curl 10.20.214.128:80
hostnames-5f9c957df4-vdp87
root@k8s-master:~/services# curl 10.20.214.128:80
hostnames-5f9c957df4-8lgbh

而其他pod可以连接这个service实现对负载均衡的三个服务的访问。这就是service这个内置服务的完整过程。

外部服务访问

上述的服务发现仅仅局限于pod内部和pod之间,这些地址对外边的hosts不可见,需要一层串透才能被发现。
通过deployment创建一个app=demo的一个pod,这个pod中运行http 80端口的服务。

root@k8s-master:~# kubectl create deployment demo --image=httpd --port=80 
root@k8s-master:~# kubectl get pods -l app=demo -o wide
NAME                    READY   STATUS    RESTARTS   AGE   IP           NODE           NOMINATED NODE   READINESS GATES
demo-654c477f6d-spvvk   1/1     Running   0          16h   10.10.2.11   k8s-worker02   <none>           <none>

此时这个pod正常访问80端口。
此时把服务expose出去,就产生了cluster ip的服务

root@k8s-master:~# kubectl expose deployment demo 
root@k8s-master:~# kubectl get svc -l app=demo -o wide
NAME   TYPE        CLUSTER-IP     EXTERNAL-IP   PORT(S)   AGE   SELECTOR
demo   ClusterIP   10.20.87.120   <none>        80/TCP    16h   app=demo

以上全是内部的。

如果想让这个pod的80端口被其他外部主机访问,这个简单,需要创建一个ingress service,使用node port的方式把80端口的内部服务,映射到host主机所在的80端口,这个服务定义如下:

apiVersion: v1
kind: Service
metadata:
  name: kube-node-service
  labels:
    name: kube-node-service
spec:
  type: NodePort
  ports:
  - port: 80
    targetPort: 80
    protocol: TCP
  selector:
    app: demo

这个服务定义产生了内置的service以nodeport的方式对外服务,通过nodeselector,作用于app=demo的pod的应用。

root@k8s-master:~# kubectl get svc -A
NAMESPACE       NAME                                 TYPE        CLUSTER-IP      EXTERNAL-IP   PORT(S)                      AGE
default         demo                                 ClusterIP   10.20.87.120    <none>        80/TCP                       16h
default         hostnames                            ClusterIP   10.20.214.128   <none>        80/TCP                       4d17h
default         kube-node-service                    NodePort    10.20.156.215   <none>        80:31777/TCP                 3d5h 

root@k8s-master:~# kubectl get ep -o wide
NAME                ENDPOINTS                                      AGE
demo                10.10.2.11:80                                  16h
hostnames           10.10.1.3:9376,10.10.1.4:9376,10.10.2.3:9376   4d17h
kube-node-service   10.10.2.11:80                                  3d5h
kubernetes          192.168.1.11:6443                              5d16h

此时表示这个service通过31777的端口对外服务了。
访问worker01、worker02的主机执行测试,发现worker节点上的31777端口定向到了容器中,实现了容器服务对外发布。

root@k8s-master:~# curl 192.168.1.12:31777
<html><body><h1>It works!</h1></body></html>
root@k8s-master:~# curl 192.168.1.13:31777
<html><body><h1>It works!</h1></body></html>

为什么要用ingress

上述nodeport的方式固然不错,但是集群复杂,服务越来越多,通过维护端口的方式反而不利于服务的统一管理。
每个lb都需要一个公网ip,如果每个service都需要一个lb对应,会需要很多的公网ip,而ingress相当于lb -> ingress – 多个service。此时就出现了ingress的概念,ingress对外服务的端口集合,就是“服务”的“服务”。
这种全局的、为了代理不同后端 Service 而设置的负载均衡服务,就是 Kubernetes 里的 Ingress 服务。

所以,Ingress 的功能其实很容易理解:所谓 Ingress,就是 Service 的“Service”。举个例子,假如我现在有这样一个站点:https://cafe.example.com。其中,https://cafe.example.com/coffee,对应的是“咖啡点餐系统”。而,https://cafe.example.com/tea,对应的则是“茶水点餐系统”。这两个系统,分别由名叫 coffee 和 tea 这样两个 Deployment 来提供服务。那么现在,我如何能使用 Kubernetes 的 Ingress 来创建一个统一的负载均衡器,从而实现当用户访问不同的域名时,能够访问到不同的 Deployment 呢?

上述功能,在 Kubernetes 里就需要通过 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

在上面这个名叫 cafe-ingress.yaml 文件中,最值得我们关注的,是 rules 字段。
在 Kubernetes 里,这个字段叫作:IngressRule。IngressRule 的 Key,就叫做:host。它必须是一个标准的域名格式(Fully Qualified Domain Name)的字符串,而不能是 IP 地址。备注:Fully Qualified Domain Name 的具体格式,可以参考RFC 3986标准。而 host 字段定义的值,就是这个 Ingress 的入口。
这也就意味着,当用户访问 cafe.example.com 的时候,实际上访问到的是这个 Ingress 对象。这样,Kubernetes 就能使用 IngressRule 来对你的请求进行下一步转发。而接下来 IngressRule 规则的定义,则依赖于 path 字段。你可以简单地理解为,这里的每一个 path 都对应一个后端 Service。
所以在我们的例子里,我定义了两个 path,它们分别对应 coffee 和 tea 这两个 Deployment 的 Service(即:coffee-svc 和 tea-svc)。
通过上面的讲解,不难看到,所谓 Ingress 对象,其实就是 Kubernetes 项目对“反向代理”的一种抽象。一个 Ingress 对象的主要内容,实际上就是一个“反向代理”服务(比如:Nginx)的配置文件的描述。而这个代理服务对应的转发规则,就是 IngressRule。这就是为什么在每条 IngressRule 里,需要有一个 host 字段来作为这条 IngressRule 的入口,然后还需要有一系列 path 字段来声明具体的转发策略。这其实跟 Nginx、HAproxy 等项目的配置文件的写法是一致的。而有了 Ingress 这样一个统一的抽象,Kubernetes 的用户就无需关心 Ingress 的具体细节了。在实际的使用中,你只需要从社区里选择一个具体的 Ingress Controller,把它部署在 Kubernetes 集群里即可。然后,这个 Ingress Controller 会根据你定义的 Ingress 对象,提供对应的代理能力。目前,业界常用的各种反向代理项目,比如 Nginx、HAProxy、Envoy、Traefik 等,都已经为 Kubernetes 专门维护了对应的 Ingress Controller。
所以重点在于,你需要在集群中部署ingress controller这个反向代理,这样内部的svc通过ingress就可以通向外部了。

我在我的3节点k8s集群上安装部署了ingress controller如下:

root@k8s-master:~# kubectl apply -f https://raw.githubusercontent.com/kubernetes/ingress-nginx/controller-v1.1.0/deploy/static/provider/baremetal/deploy.yaml  
root@k8s-master:~# POD_NAMESPACE=ingress-nginx  
root@k8s-master:~# POD_NAME=$(kubectl get pods -n $POD_NAMESPACE -l app.kubernetes.io/name=ingress-nginx --field-selector=status.phase=Running -o name)  
kubectl exec $POD_NAME -n $POD_NAMESPACE -- /nginx-ingress-controller --version
-------------------------------------------------------------------------------
NGINX Ingress controller
  Release:       v1.1.0
  Build:         cacbee86b6ccc45bde8ffc184521bed3022e7dee
  Repository:    https://github.com/kubernetes/ingress-nginx
  nginx version: nginx/1.19.9

查看ingress-nginx创建的pod如下:

root@k8s-master:~# kubectl get pods -n ingress-nginx -o wide
NAME                                        READY   STATUS      RESTARTS   AGE     IP             NODE           NOMINATED NODE   READINESS GATES
ingress-nginx-admission-create--1-xdwbd     0/1     Completed   0          2d17h   10.10.1.10     k8s-worker01   <none>           <none>
ingress-nginx-admission-patch--1-r4znz      0/1     Completed   2          2d17h   10.10.2.9      k8s-worker02   <none>           <none>
ingress-nginx-controller-7b8bdb996c-ptl42   1/1     Running     0          120m    192.168.1.12   k8s-worker01   <none>           <none>

此时说明服务正常启动了。
这是controller已经创建好,只需要按照应用创建ingress就可以了。

root@k8s-master:~# kubectl create ingress demo-localhost --class=nginx   --rule=demo.localdev.me/*=demo:80
root@k8s-master:~# kubectl get ingress -o wide
NAME             CLASS   HOSTS              ADDRESS        PORTS   AGE
demo-localhost   nginx   demo.localdev.me   192.168.1.12   80      16h

说明这个demo.localdev.me就对应主机192.168.1.12上的80端口,实现外部访问,这是k8s集群的worker01
把demo.localdev.me写入到所在k8s master服务器的host文件中,master可以解析这个主机名,对应192.168.1.12 这台host。

root@k8s-master:~# curl demo.localdev.me
<html><body><h1>It works!</h1></body></html>

看似简单,其实这个ingress是定义了demo.localdev.me和demo:80服务之间的对应关系

root@k8s-master:~# kubectl describe ing demo-localhost
Name:             demo-localhost
Namespace:        default
Address:          192.168.1.12
Default backend:  default-http-backend:80 (<error: endpoints "default-http-backend" not found>)
Rules:
  Host              Path  Backends
  ----              ----  --------
  demo.localdev.me
                    /   demo:80 (10.10.2.11:80)
Annotations:        <none>
Events:             <none>

demo这个服务如下所示:

root@k8s-master:~# kubectl get svc -l app=demo -o wide
NAME   TYPE        CLUSTER-IP     EXTERNAL-IP   PORT(S)   AGE   SELECTOR
demo   ClusterIP   10.20.87.120   <none>        80/TCP    17h   app=demo

正是这个demo:80端口,被ingress-nginx-controller反向代理,发布到外部网络。

此处为什么这个ingress入口在192.168.1.12呢?这还要从ingress controller说起,这个controller运行在worker01上。

root@k8s-master:~# kubectl get pods -n ingress-nginx -o wide
NAME                                        READY   STATUS      RESTARTS   AGE     IP             NODE           NOMINATED NODE   READINESS GATES
ingress-nginx-admission-create--1-xdwbd     0/1     Completed   0          2d17h   10.10.1.10     k8s-worker01   <none>           <none>
ingress-nginx-admission-patch--1-r4znz      0/1     Completed   2          2d17h   10.10.2.9      k8s-worker02   <none>           <none>
ingress-nginx-controller-7b8bdb996c-ptl42   1/1     Running     0          136m    192.168.1.12   k8s-worker01   <none>           <none>

所以被这个ingress controller代理的服务,都要从192.168.1.12(worker01)上通过外部访问。

默认ingress controllert通过随机产生的端口,如图所示的30664对外提供访问。

root@k8s-master:~# kubectl get svc -A
NAMESPACE       NAME                                 TYPE        CLUSTER-IP      EXTERNAL-IP   PORT(S)                      AGE
default         demo                                 ClusterIP   10.20.87.120    <none>        80/TCP                       17h
default         hostnames                            ClusterIP   10.20.214.128   <none>        80/TCP                       4d18h
default         kube-node-service                    NodePort    10.20.156.215   <none>        80:31777/TCP                 3d6h
default         kubernetes                           ClusterIP   10.20.0.1       <none>        443/TCP                      5d17h
ingress-nginx   ingress-nginx-controller             NodePort    10.20.187.113   <none>        80:30664/TCP,443:32661/TCP   2d17h
ingress-nginx   ingress-nginx-controller-admission   ClusterIP   10.20.48.170    <none>        443/TCP                      2d17h
kube-system     kube-dns                             ClusterIP   10.20.0.10      <none>        53/UDP,53/TCP,9153/TCP       5d17h

但是如果想修改ingress controller的端口,定向到80端口怎么办呢?这就要修改ingress controller的pod配置文件。

root@k8s-master:~# kubectl edit deployment ingress-nginx-controller -n ingress-nginx 

加上这一句:hostNetwork: true,这样就使用host的网络了,也就是192.168.1.12的80端口。

至此ingress controller就配置完毕,也就可以理解,集群中必须先有ingress controller,然后才能创建ingress服务。
这个ingress服务创建的时候,写了什么东西给controller呢?

root@k8s-master:~# kubectl create ingress demo-localhost --class=nginx   --rule=demo.localdev.me/*=demo:80

其实这一句话,在ingress controller看来,就是在controller这个nginx的配置文件中写入了server和这个demo:80的service之间的对应关系,把这个代理关系写入到nginx的配置文件。

root@k8s-master:~# kubectl -n ingress-nginx exec ingress-nginx-controller-7b8bdb996c-ptl42 -- cat /etc/nginx/nginx.conf

这个文件中,在start server demo.localdev.me和end server demo.localdev.me
之间创建了一个nginx的server,代理了demo:80这个服务。如果创建多个ingress,则会在这个controller上创建多个server,这就实现了服务的统一管理。

 ## start server demo.localdev.me
        server {
                server_name demo.localdev.me ;

                listen 80  ;
                listen [::]:80  ;
                listen 443  ssl http2 ;
                listen [::]:443  ssl http2 ;

                set $proxy_upstream_name "-";

                ssl_certificate_by_lua_block {
                        certificate.call()
                }

                location / {

                        set $namespace      "default";
                        set $ingress_name   "demo-localhost";
                        set $service_name   "demo";
                        set $service_port   "80";
                        set $location_path  "/";
                        set $global_rate_limit_exceeding n;
XXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXX
      ## end server demo.localdev.me

以上部分内容参考文档:https://www.1024sou.com/article/217420.html

暂无评论

发表回复

您的邮箱地址不会被公开。 必填项已用 * 标注


虚拟化 | 云计算 | 机器学习 | 股市复盘
© 2024 涛哥,版权所有, 京ICP备20014492-2号