服务Service和负载均衡
服务Service和负载均衡
Kubernetes中为了实现服务实例间的负载均衡和不同服务间的服务发现,创造了Serivce对象,同时又为从集群外部访问集群创建了Ingress对象。
一. Service
一种将在一组pod上运行的应用程序公开为网络服务的抽象方法。
使用Kubernetes,您不需要修改应用程序,就可以使用不熟悉的服务发现机制。Kubernetes为Pod提供了自己的IP地址和一组Pod的单个DNS名称,并且可以在它们之间实现负载平衡。
1.1 动机(Motivation)
Kubernetes Pods 是有生命周期的。他们可以被创建,而且销毁不会再启动。 如果您使用 Deployment 来运行您的应用程序,则它可以动态创建和销毁 Pod。
每个 Pod 都有自己的 IP 地址,但是在 Deployment 中,在同一时刻运行的 Pod 集合可能与稍后运行该应用程序的 Pod 集合不同。
这导致了一个问题: 如果一组 Pod(称为“后端”)为群集内的其他 Pod(称为“前端”)提供功能,那么前端如何找出并跟踪要连接的 IP 地址,以便前端可以使用工作量的后端部分?
答案就是使用Service
1.2 Service 资源
Kubernetes Service 定义了这样一种抽象:逻辑上的一组 Pod,一种可以访问它们的策略,通常称为微服务。 这一组 Pod 能够被 Service 访问到,通常是通过 selector (也可能需要没有 selector 的 Service)实现的。
举个例子,考虑一个图片处理 backend,它运行了3个副本。这些副本是可互换的。frontend 不需要关心它们调用了哪个 backend 副本。 然而组成这一组 backend 程序的 Pod 实际上可能会发生变化,frontend 客户端不应该也没必要知道,而且也不需要跟踪这一组 backend 的状态。 Service 定义的抽象能够解耦这种关联。
云原生服务发现
如果您想要在应用程序中使用 Kubernetes 接口进行服务发现,则可以查询 API server 的 endpoint 资源,只要服务中的Pod集合发生更改,端点就会更新。
对于非本机应用程序,Kubernetes提供了在应用程序和后端Pod之间放置网络端口或负载均衡器的方法。
1.2 定义 Service
一个 Service 在 Kubernetes 中是一个 REST 对象,和 Pod 类似。 像所有的 REST 对象一样, Service 定义可以基于 POST 方式,请求 API server 创建新的实例。
例如,假定有一组 Pod,它们对外暴露了 9376 端口,同时还被打上 app=MyApp 标签。
apiVersion: v1
kind: Service
metadata:
name: my-service
spec:
selector:
app: MyApp
ports:
- protocol: TCP
port: 80
targetPort: 9376
上述配置创建一个名称为 “my-service” 的 Service 对象,它会将请求代理到使用 TCP 端口 9376,并且具有标签 “app=MyApp” 的 Pod 上。 Kubernetes 为该服务分配一个 IP 地址(有时称为 “集群IP” ),该 IP 地址由服务代理使用。服务选择器的控制器不断扫描与其选择器匹配的 Pod,然后将所有更新发布到也称为 “my-service” 的Endpoint对象。
注意:需要注意的是,Service能够将一个接收port映射到任意的targetPort。 默认情况下,targetPort将被设置为与port字段相同的值。
Pods中的端口定义有名称,您可以在服务的targetPort属性中引用这些名称。即使服务中混合了使用单个配置名称的pod,并且通过不同的端口号提供相同的网络协议,这种方法仍然有效。这为部署和改进服务提供了很大的灵活性。例如,您可以更改Pods在下一个版本的后端软件中公开的端口号,而不破坏客户端。
服务的默认协议是TCP。 还可以使用任何其他受支持的协议。
由于许多服务需要公开多个端口,因此 Kubernetes 在服务对象上支持多个端口定义。 每个端口定义可以具有相同的 protocol,也可以具有不同的协议。
1.3 没有selector的Service
服务最常见的是抽象化对Kubernetes Pod的访问,但是它们也可以抽象化其他种类的后端。
- 希望在生产环境中使用外部的数据库集群,但测试环境使用自己的数据库。
- 希望服务指向另一个命名空间中或其它集群中的服务。
- 您正在将工作负载迁移到Kubernetes。在评估该方法时,您仅在Kubernetes中运行一部分后端。
在任何这些场景中,都能够定义没有selector的Service。实例:
apiVersion: v1
kind: Service
metadata:
name: my-service
spec:
ports:
- protocol: TCP
port: 80
targetPort: 9376
由于此服务没有选择器,因此不会自动创建相应的Endpoint对象。您可以通过手动添加Endpoint对象,将服务手动映射到运行该服务的网络地址和端口:
apiVersion: v1
kind: Endpoints
metadata:
name: my-service
subsets:
- addresses:
- ip: 192.0.2.42
ports:
- port: 9376
注意:端点IP地址不能是其他Kubernetes服务的群集IP,因为kube-proxy不支持将虚拟IP作为目标。
访问没有selector的Service,与有selector的Service的原理相同。请求将被路由到用户定义的Endpoint,YAML中为: 192.0.2.42:9376 (TCP)。
1.4 VIP 和 Service代理
在Kubernetes集群中,每个Node运行一个kube-proxy进程。kube-proxy负责为 Service实现了一种VIP(虚拟 IP)的形式。
User space 代理模式 (User space proxy mode)
这种模式,kube-proxy 会监视 Kubernetes master对Service对象和Endpoints对象的添加和移除。 对每个Service,它会在本地Node上打开一个端口(随机选择)。 任何连接到“代理端口”的请求,都会被代理到Service的backend Pods 中的某个上面(如 Endpoints 所报告的一样)。使用哪个backend Pod,是 kube-proxy基于SessionAffinity来确定的。
最后,它安装iptables规则,捕获到达该Service的clusterIP(是虚拟 IP)和Port的请求,并重定向到代理端口,代理端口再代理请求到backend Pod。
默认情况下,用户空间模式下的kube-proxy通过循环算法选择后端。
默认的策略是,通过round-robin 算法来选择backend Pod。
iptables 代理模式(iptables proxy mode)
这种模式,kube-proxy会监视Kubernetes控制节点对Service对象和Endpoints对象的添加和移除。对每个Service,它会安装iptables规则,从而捕获到达该Service的clusterIP和端口的请求,进而将请求重定向到Service的一组 backend中的某个上面。对于每个Endpoints对象,它也会安装iptables规则,这个规则会选择一个backend组合。
默认的策略是,kube-proxy在iptables模式下随机选择一个backend。
使用iptables处理流量具有较低的系统开销,因为流量由 Linux netfilter 处理,而无需在用户空间和内核空间之间切换。 这种方法也可能更可靠。
如果kube-proxy在iptables模式下运行,并且所选的第一个Pod没有响应,则连接失败。 这与用户空间模式不同:在这种情况下,kube-proxy将检测到与第一个Pod的连接已失败,并会自动使用其他后端Pod重试。
您可以使用Pod readiness探测器验证后端Pod可以正常工作,以便iptables 模式下的kube-proxy仅看到测试正常的后端。这样做意味着您避免将流量通过kube-proxy发送到已知已失败的Pod。
IPVS 代理模式(IPVS proxy mode)
在ipvs模式下,kube-proxy监视Kubernetes服务和端点,调用netlink接口相应地创建IPVS规则,并定期将IPVS规则与Kubernetes服务和端点同步。该控制循环可确保IPVS状态与所需状态匹配。访问服务时,IPVS将流量定向到后端Pod之一。
IPVS代理模式基于类似于iptables模式的netfilter挂钩函数,但是使用哈希表作为基础数据结构,并且在内核空间中工作。 这意味着,与iptables模式下的kube-proxy相比,IPVS模式下的kube-proxy重定向通信的延迟要短,并且在同步代理规则时具有更好的性能。与其他代理模式相比,IPVS模式还支持更高的网络流量吞吐量。
IPVS提供了更多选项来平衡后端Pod的流量。 这些是:
- rr: round-robin
- lc: least connection (smallest number of open connections)
- dh: destination hashing
- sh: source hashing
- sed: shortest expected delay
- nq: never queue
注意:
- 要在IPVS模式下运行kube-proxy,必须在启动kube-proxy之前使IPVS Linux在节点上可用。
- 当kube-proxy以IPVS代理模式启动时,它将验证IPVS内核模块是否可用。 如果未检测到IPVS内核模块,则kube-proxy将退回到以iptables代理模式运行。
在这些代理模型中,绑定到服务IP的流量:在客户端不了解Kubernetes或服务或Pod的任何信息的情况下,将Port代理到适当的后端。如果要确保每次都将来自特定客户端的连接传递到同一Pod,则可以通过将 service.spec.sessionAffinity
设置为 “ClientIP” (默认值是 “None”),来基于客户端的IP地址选择会话关联。
您还可以通过适当设置 service.spec.sessionAffinityConfig.clientIP.timeoutSeconds
来设置最大会话停留时间。(默认值为10800秒,即3小时)。
1.5 多端口 Service
对于某些服务,您需要公开多个端口。Kubernetes允许您在Service对象上配置多个端口定义。为服务使用多个端口时,必须提供所有端口名称,以使它们无歧义。例如:
apiVersion: v1
kind: Service
metadata:
name: my-service
spec:
selector:
app: MyApp
ports:
- name: http
protocol: TCP
port: 80
targetPort: 9376
- name: https
protocol: TCP
port: 443
targetPort: 9377
注意:与一般的Kubernetes名称一样,端口名称只能包含小写字母数字字符 和“-”。端口名称还必须以字母数字字符开头和结尾。
例如,名称123-abc和web有效,但是123_abc和-web无效。
1.6 选择自己的IP地址
在 Service 创建的请求中,可以通过设置spec.clusterIP
字段来指定自己的集群IP地址。比如,希望替换一个已经已存在的DNS条目,或者遗留系统已经配置了一个固定的IP且很难重新配置。
用户选择的IP地址必须合法,并且这个IP地址在service-cluster-ip-range CIDR 范围内,这对API Server来说是通过一个标识来指定的。如果IP地址不合法,API Server 会返回HTTP状态码422,表示值不合法。
1.7 服务发现(Discovering services)
Kubernetes支持2种基本的服务发现模式:环境变量和DNS。
环境变量
当Pod运行在Node上,kubelet会为每个活跃的Service添加一组环境变量。 它同时支持Docker links兼容变量(查看 makeLinkVariables)、简单的 {SVCNAME}_SERVICE_HOST和{SVCNAME}_SERVICE_PORT变量,这里Service的名称需大写,横线被转换成下划线。
举个例子,一个名称为"redis-master"的Service暴露了TCP端口6379,同时给它分配了Cluster IP地址10.0.0.11,这个Service生成了如下环境变量:
REDIS_MASTER_SERVICE_HOST=10.0.0.11
REDIS_MASTER_SERVICE_PORT=6379
REDIS_MASTER_PORT=tcp://10.0.0.11:6379
REDIS_MASTER_PORT_6379_TCP=tcp://10.0.0.11:6379
REDIS_MASTER_PORT_6379_TCP_PROTO=tcp
REDIS_MASTER_PORT_6379_TCP_PORT=6379
REDIS_MASTER_PORT_6379_TCP_ADDR=10.0.0.11
注意:当您具有需要访问服务的Pod时,并且您正在使用环境变量方法将端口和群集IP发布到客户端Pod时,必须在客户端Pod出现之前创建服务。否则,这些客户端Pod将不会设定其环境变量。
如果仅使用DNS查找服务的群集IP,则无需担心此设定问题。
DNS
可以(几乎总是应该)使用附加组件为Kubernetes集群设置DNS服务。
支持群集的DNS服务器(例如CoreDNS)监视Kubernetes API中的新服务,并为每个服务创建一组DNS记录。如果在整个群集中都启用了DNS,则所有Pod都应该能够通过其DNS名称自动解析服务。
例如,如果您在Kubernetes命名空间"my-ns"中有一个名为"my-service" 的服务,则控制平面和DNS服务共同为"my-service.my-ns"创建DNS记录。“my-ns"命名空间中的Pod应该能够通过简单地对my-service进行名称查找来找到它( “my-service.my-ns” 也可以工作)。
其他命名空间中的Pod必须将名称限定为my-service.my-ns。这些名称将解析为为服务分配的群集IP。
Kubernetes还支持命名端口的DNS SRV(服务)记录。如果"my-service.my-ns” 服务具有名为"http"的端口,且协议设置为TCP,则可以对_http._tcp.my-service.my-ns执行DNS SRV查询查询以发现该"http"的端口号以及IP地址。
Kubernetes DNS服务器是唯一的一种能够访问ExternalName类型的Service 的方式。
1.8 Headless Services
有时不需要或不想要负载均衡,以及单独的Service IP。 遇到这种情况,可以通过指定Cluster IP(spec.clusterIP)的值为"None"来创建Headless Service。
您可以使用headless Service与其他服务发现机制进行接口,而不必与Kubernetes的实现捆绑在一起。
headless Service并不会分配Cluster IP,kube-proxy不会处理它们,而且平台也不会为它们进行负载均衡和路由。 DNS如何实现自动配置,依赖于Service是否定义了selector。
配置Selector
对定义了selector的Headless Service,Endpoint控制器在API中创建了Endpoints记录,并且修改DNS配置返回A记录(地址),通过这个地址直接到达Service的后端Pod上。
不配置Selector
对没有定义selector的Headless Service,Endpoint控制器不会创建Endpoints记录。
1.9 发布服务(Publishing Services)
如何将服务暴露到集群外部,被集群外部的地址访问到呢?
Kubernetes ServiceTypes
允许指定一个你需要的服务类型来完成以上需求,默认是ClusterIP类型。
- ClusterIP:通过集群的内部IP暴露服务,选择该值,服务只能够在集群内部可以访问,这也是默认的 ServiceType。
- NodePort:通过每个Node上的IP和静态端口(NodePort)暴露服务。NodePort服务会路由到ClusterIP服务,这个ClusterIP服务会自动创建。通过请求 :,可以从集群的外部访问一个 NodePort 服务。
- LoadBalancer:使用云提供商的负载局衡器,可以向外部暴露服务。外部的负载均衡器可以路由到NodePort服务和ClusterIP 服务。
- ExternalName:通过返回CNAME和它的值,可以将服务映射到externalName字段的内容(例如,foo.bar.example.com )。没有任何类型代理被创建。
注意: 您需要CoreDNS 1.7或更高版本才能使用ExternalName类型。
也可以使用Ingress来暴露自己的服务。Ingress不是服务类型,但它充当集群的入口点。它可以将路由规则整合到一个资源中,因为它可以在同一IP地址下公开多个服务。
NodePort 类型
如果将type字段设置为NodePort,则Kubernetes控制平面将在--service-node-port-range
标志指定的范围内分配端口(默认值:30000-32767)。 每个节点将那个端口(每个节点上的相同端口号)代理到您的服务中。 您的服务在其.spec.ports[*].nodePort
字段中要求分配的端口。
从Kubernetes v1.10开始,可以指定特定的IP代理端口,可以将kube-proxy 中的--nodeport-addresses
标志设置为特定的IP块。该标志采用逗号分隔的IP块列表(例如10.0.0.0/8、192.0.2.0/25)来指定kube-proxy认为是此节点本地的IP地址范围。
如果需要特定的端口号,则可以在nodePort
字段中指定一个值。需要注意端口冲突,还必须使用有效的端口号,该端口号在配置用于NodePort的范围内。
LoadBalancer 类型
使用支持外部负载均衡器的云提供商的服务,设置type的值为"LoadBalancer",将为Service 提供负载均衡器。负载均衡器是异步创建的,关于被提供的负载均衡器的信息将会通过Service的status.loadBalancer
字段被发布出去。实例:
apiVersion: v1
kind: Service
metadata:
name: my-service
spec:
selector:
app: MyApp
ports:
- protocol: TCP
port: 80
targetPort: 9376
clusterIP: 10.0.171.239
loadBalancerIP: 78.11.24.19
type: LoadBalancer
status:
loadBalancer:
ingress:
- ip: 146.148.47.155
来自外部负载均衡器的流量将直接打到backend Pod上,不过实际它们是如何工作的,这要依赖于云提供商。
在这些情况下,将根据用户设置的loadBalancerIP来创建负载均衡器。某些云提供商允许设置loadBalancerIP。如果没有设置loadBalancerIP,将会给负载均衡器指派一个临时IP。如果设置了loadBalancerIP,但云提供商并不支持这种特性,那么设置的loadBalancerIP值将会被忽略掉。
二. Pod 与 Service 的DNS
Kubernetes DNS在群集上调度DNS Pod和服务,并配置kubelet以告知各个容器使用DNS服务的IP来解析DNS名称。
2.1 怎样获取DNS名字?
在集群中定义的每个Service(包括DNS服务器自身)都会被指派一个DNS名称。默认,一个客户端Pod的DNS搜索列表将包含该Pod自己的Namespace和集群默认域。
假设在Kubernetes集群的Namespace bar中,定义了一个Service foo。 运行在Namespace bar中的一个Pod,可以简单地通过DNS查询foo来找到该Service。 运行在Namespace quux中的一个Pod可以通过DNS查询foo.bar找到该 Service。
2.2 Services
A 记录
“正常” Service(除了 Headless Service)会以 my-svc.my-namespace.svc.cluster-domain.example
这种名字的形式被指派一个DNS A记录。这会解析成该Service的Cluster IP。
“Headless” Service(没有Cluster IP)也会以my-svc.my-namespace.svc.cluster-domain.example
这种名字的形式被指派一个DNS A记录。不像正常Service,它会解析成该Service选择的一组Pod 的IP。希望客户端能够使用这一组IP,否则就使用标准的round-robin策略从这一组IP中进行选择。
SRV 记录
命名端口需要创建SRV记录,这些端口是正常Service或Headless Services的一部分。对每个命名端口,SRV记录具有_my-port-name._my-port-protocol.my-svc.my-namespace.svc.cluster-domain.example
这种形式。对普通Service,这会被解析成端口号和CNAME:my-svc.my-namespace.svc.cluster-domain.example。 对Headless Service,这会被解析成多个结果,Service对应的每个backend Pod各一个,包含auto-generated-name.my-svc.my-namespace.svc.cluster-domain.example
这种形式Pod的端口号和CNAME。
Pods
Pod的hostname和subdomain字段
当前,创建Pod后,它的主机名是该Pod的metadata.name值。
PodSpec有一个可选的hostname字段,可以用来指定Pod的主机名。当这个字段被设置时,它将优先于Pod的名字成为该Pod的主机名。举个例子,给定一个hostname设置为 “my-host”的Pod,该Pod的主机名将被设置为“my-host“。
PodSpec还有一个可选subdomain字段,可以用来指定Pod的子域名。举个例子,一个Pod的hostname设置为“foo”,subdomain设置为“bar”,在namespace “my-namespace” 中对应的完全限定域名(FQDN)为“foo.bar.my-namespace.svc.cluster-domain.example”。
apiVersion: v1
kind: Service
metadata:
name: default-subdomain
spec:
selector:
name: busybox
clusterIP: None
ports:
- name: foo # Actually, no port is needed.
port: 1234
targetPort: 1234
---
apiVersion: v1
kind: Pod
metadata:
name: busybox1
labels:
name: busybox
spec:
hostname: busybox-1
subdomain: default-subdomain
containers:
- image: busybox:1.28
command:
- sleep
- "3600"
name: busybox
---
apiVersion: v1
kind: Pod
metadata:
name: busybox2
labels:
name: busybox
spec:
hostname: busybox-2
subdomain: default-subdomain
containers:
- image: busybox:1.28
command:
- sleep
- "3600"
name: busybox
如果Headless Service与Pod在同一个Namespace中,它们具有相同的子域名,集群的KubeDNS服务器也会为该Pod的完整合法主机名返回A记录。例如,在同一个Namespace中,给定一个主机名为“busybox-1”的Pod,子域名设置为“default-subdomain”,名称为“default-subdomain”的Headless Service,Pod 将看到自己的FQDN为“busybox-1.default-subdomain.my-namespace.svc.cluster.local”。DNS会为那个名字提供一个A记录,指向该Pod的IP。“busybox1”和“busybox2” 这两个Pod分别具有它们自己的A记录。
端点对象可以为任何端点地址及其IP指定hostname。
Pod 的 DNS 策略
DNS策略可以基于每个pod设置。目前,Kubernetes支持以下特定于pod的DNS策略。这些策略在Pod规范的dnsPolicy
字段中指定。
- Default:Pod从Pod运行所在的节点继承名称解析配置。
- ClusterFirst:任何与配置的群集域后缀不匹配的DNS查询(例如“ www.kubernetes.io”)都将转发到从节点继承的上游名称服务器。
- ClusterFirstWithHostNet:对于与hostNetwork一起运行的Pod,您应明确设置其DNS策略为
ClusterFirstWithHostNet
。 - None:它允许Pod忽略Kubernetes环境中的DNS设置。应该使用Pod Spec中的
dnsConfig
字段提供所有DNS设置。
注意:“Default” 不是默认的DNS策略。如果未明确指定dnsPolicy
,则使用“ClusterFirst”。
下面的示例显示了一个Pod,其DNS策略设置为ClusterFirstWithHostNet
,因为它已将hostNetwork
设置为true
。
apiVersion: v1
kind: Pod
metadata:
name: busybox
namespace: default
spec:
containers:
- image: busybox:1.28
command:
- sleep
- "3600"
imagePullPolicy: IfNotPresent
name: busybox
restartPolicy: Always
hostNetwork: true
dnsPolicy: ClusterFirstWithHostNet
Pod 的 DNS 配置
Pod的DNS配置可让用户对Pod的DNS设置进行更多控制。
dnsConfig
字段是可选的,它可以与任何dnsPolicy
设置一起使用。 但是,当Pod的dnsPolicy
设置为None
时,必须指定dnsConfig
字段。
用户可以在dnsConfig
字段中指定以下属性:
- nameservers: 用于指定Pod的DNS服务器的IP地址列表。最多可以指定3个IP地址。当Pod的
dnsPolicy
设置为None
时,列表必须至少包含一个IP地址,否则此属性是可选的。列出的服务器将合并到从指定的DNS策略生成的基本名称服务器,并删除重复的地址。 - searches: 用于在Pod中查找主机名的DNS搜索域的列表。此属性是可选的。指定后,提供的列表将合并到根据所选DNS策略生成的基本搜索域名中。重复的域名将被删除。Kubernetes最多允许6个搜索域。
- options: 对象的可选列表,其中每个对象可能具有name属性(必需)和value属性(可选)。此属性中的内容将合并到从指定的DNS策略生成的选项。重复的条目将被删除。
以下是具有自定义DNS设置的Pod示例:
##service/networking/custom-dns.yaml
apiVersion: v1
kind: Pod
metadata:
namespace: default
name: dns-example
spec:
containers:
- name: test
image: nginx
dnsPolicy: "None"
dnsConfig:
nameservers:
- 1.2.3.4
searches:
- ns1.svc.cluster-domain.example
- my.dns.search.suffix
options:
- name: ndots
value: "2"
- name: edns0
创建上面的Pod后,容器test会在其/etc/resolv.conf文件中获取以下内容:
nameserver 1.2.3.4
search ns1.svc.cluster-domain.example my.dns.search.suffix
options ndots:2 edns0
三. Ingress
Ingress是对集群中服务的外部访问进行管理的API对象,典型的访问方式是HTTP。
Ingress可以提供负载均衡、SSL终结和基于名称的虚拟主机。
3.1 术语(Terminology)
- 节点(Node):Kubernetes集群中其中一台工作机器,是集群的一部分。
- 集群(Cluster):运行由Kubernetes管理的容器化应用程序的一组节点。在大多数常见的Kubernetes部署中,集群中的节点不是公共internet的一部分。
- 边缘路由器(Edge router):在集群中强制性执行防火墙策略的路由器(router)。可以是由云提供商管理的网关,也可以是物理硬件。
- 集群网络(Cluster network):一组逻辑或物理的链接,根据Kubernetes 网络模型在集群内实现通信。
- 服务(Service):Kubernetes Service使用标签选择器(selectors)标识的一组Pod。除非另有说明,否则假定服务只具有在集群网络中可路由的虚拟IP。
3.2 Ingress 是什么?
Ingress 公开了从集群外部到集群内services的HTTP和HTTPS路由。流量路由由Ingress资源上定义的规则控制。
internet
|
[ Ingress ]
--|-----|--
[ Services ]
可以将Ingress配置为服务提供外部可访问的URL,负载均衡流量,终止SSL/TLS,并提供基于名称的虚拟主机。Ingress控制器通常负责通过负载平衡器来实现入口,尽管它也可以配置边缘路由器或其他前端以帮助处理流量。
Ingress不会公开任意端口或协议。将HTTP和HTTPS以外的服务公开给Internet时,通常使用以下类型的服务Service.Type=NodePort或者Service.Type=LoadBalancer。
3.3 Ingress 控制器(Ingress Controllers)
为了让Ingress资源工作,集群必须有一个正在运行的Ingress控制器。
与作为kube-controller-manager可执行文件的一部分运行的其他类型的控制器不同,Ingress控制器不是随集群自动启动的。您可以选择最适合您的集群的ingress控制器实现。
Kubernetes作为一个项目,目前支持和维护GCE和nginx控制器。
下载ingress
[root@k8s-master ~]# wget -c https://github.com/kubernetes/ingress-nginx/archive/master.zip
[root@k8s-master ~]# unzip ingress-nginx-master.zip
安装
首先要更改镜像仓库位置,国外的不容易拉取
[root@k8s-master ~]# cd ingress-nginx-master/deploy/static/
[root@k8s-master static]# vim mandatory.yaml
apiVersion: apps/v1
kind: Deployment
metadata:
name: nginx-ingress-controller
namespace: ingress-nginx
<-省略部分内容->
spec:
# wait up to five minutes for the drain of connections
terminationGracePeriodSeconds: 300
serviceAccountName: nginx-ingress-serviceaccount
nodeSelector:
kubernetes.io/os: linux
containers:
- name: nginx-ingress-controller
##修改这里镜像的位置,改成阿里的仓库位置
image: registry.aliyuncs.com/google_containers/nginx-ingress-controller:0.30.0
args:
进行安装
[root@k8s-master ~]# kubectl apply -f ingress-nginx-master/deploy/static/mandatory.yaml
namespace/ingress-nginx created
configmap/nginx-configuration created
configmap/tcp-services created
configmap/udp-services created
serviceaccount/nginx-ingress-serviceaccount created
clusterrole.rbac.authorization.k8s.io/nginx-ingress-clusterrole created
role.rbac.authorization.k8s.io/nginx-ingress-role created
rolebinding.rbac.authorization.k8s.io/nginx-ingress-role-nisa-binding created
clusterrolebinding.rbac.authorization.k8s.io/nginx-ingress-clusterrole-nisa-binding created
deployment.apps/nginx-ingress-controller created
limitrange/ingress-nginx created
##可以监控其安装过程
[root@k8s-master ~]# kubectl get pods --all-namespaces -l app.kubernetes.io/name=ingress-nginx --watch
NAMESPACE NAME READY STATUS RESTARTS AGE
ingress-nginx nginx-ingress-controller-7b86f6f9fc-tk5tt 0/1 ImagePullBackOff 0 2m14s
##安装成功后
[root@k8s-master ~]# kubectl get pods -n ingress-nginx
NAME READY STATUS RESTARTS AGE
nginx-ingress-controller-7b86f6f9fc-tk5tt 1/1 Running 0 8m21s
3.4 Ingress 资源(The Ingress Resource)
一个最小的Ingress资源示例:
apiVersion: networking.k8s.io/v1beta1
kind: Ingress
metadata:
name: test-ingress
annotations:
nginx.ingress.kubernetes.io/rewrite-target: /
spec:
rules:
- http:
paths:
- path: /testpath
pathType: Prefix
backend:
serviceName: test
servicePort: 80
与所有其他Kubernetes资源一样,Ingress需要使用apiVersion、kind和metadata字段。Ingress经常使用注解(annotations)来配置一些选项,具体取决于Ingress控制器,例如rewrite-target annotation。不同的Ingress控制器支持不同的注解(annotations)。
Ingress规范具有配置负载均衡器或者代理服务器所需的所有信息。最重要的是,它包含与所有传入请求匹配的规则列表。Ingress资源仅支持用于定向HTTP流量的规则。
Ingress 规则(Ingress rules)
每个 HTTP 规则都包含以下信息:
- 可选host:在此示例中,未指定host,因此该规则适用于通过指定IP地址的所有入站HTTP通信。如果提供了主机(例如foo.bar.com),则规则适用于该主机。
- 路径列表(paths):(例如,/testpath),每个路径都有一个由
serviceName
和servicePort
定义的关联后端。在负载均衡器将流量定向到引用的服务之前,主机和路径都必须匹配传入请求的内容。 - 后端(backend):是服务文档中所述的服务和端口名称的组合。与规则的主机和路径匹配的对Ingress的HTTP(和HTTPS)请求将发送到列出的后。
通常在Ingress控制器中配置默认后端,以服务任何不符合规范中路径的请求。
默认后端(Default Backend)
没有规则的Ingress将所有流量发送到单个默认后端。默认后端通常是在Ingress控制器里的配置选项,而不在Ingress资源中指定。
如果没有主机或路径与Ingress对象中的HTTP请求匹配,则流量将路由到您的默认后端。
3.5 Ingress 类型(Types of Ingress)
单服务 Ingress(Single Service Ingress)
现有的Kubernetes允许您暴露单个Service,您也可以通过指定无规则的默认后端来对Ingress进行此操作。
##service/networking/ingress.yaml
apiVersion: networking.k8s.io/v1beta1
kind: Ingress
metadata:
name: test-ingress
spec:
backend:
serviceName: testsvc
servicePort: 80
如果使用kubectl apply -f
创建它,则应该能够查看刚刚添加的Ingress的状态:
kubectl get ingress test-ingress
##类似输出
NAME HOSTS ADDRESS PORTS AGE
test-ingress * 107.178.254.228 80 59s
##其中107.178.254.228是由Ingress控制器分配以满足该Ingress的IP。
##Ingress 控制器和负载均衡器可能需要一两分钟才能分配IP地址。
##在此之前,您通常会看到地址为 <pending>
简单扇形分叉(Simple fanout)
基于所请求的HTTP URI将流量从单个IP地址路由到多个服务。入口允许您将负载平衡器的数量保持在最小值。例如:
foo.bar.com -> 178.91.123.132 -> / foo service1:4200
/ bar service2:8080
需要一个Ingress,例如:
apiVersion: networking.k8s.io/v1beta1
kind: Ingress
metadata:
name: simple-fanout-example
annotations:
nginx.ingress.kubernetes.io/rewrite-target: /
spec:
rules:
- host: foo.bar.com
http:
paths:
- path: /foo
backend:
serviceName: service1
servicePort: 4200
- path: /bar
backend:
serviceName: service2
servicePort: 8080
使用kubectl apply -f
创建后:
kubectl describe ingress simple-fanout-example
##类似输出:
Name: simple-fanout-example
Namespace: default
Address: 178.91.123.132
Default backend: default-http-backend:80 (10.8.2.3:8080)
Rules:
Host Path Backends
---- ---- --------
foo.bar.com
/foo service1:4200 (10.8.0.90:4200)
/bar service2:8080 (10.8.0.91:8080)
Annotations:
nginx.ingress.kubernetes.io/rewrite-target: /
Events:
Type Reason Age From Message
---- ------ ---- ---- -------
Normal ADD 22s loadbalancer-controller default/test
Ingress控制器将提供实现特定的负载均衡器来满足Ingress,只要Service(s1,s2) 存在。成功创建后,会在地址栏看到负载均衡器的地址。
基于名称的虚拟主机(Name based virtual hosting)
基于名称的虚拟主机支持将HTTP流量路由到同一IP地址上的多个主机名。
foo.bar.com --| |-> foo.bar.com service1:80
| 178.91.123.132 |
bar.foo.com --| |-> bar.foo.com service2:80
以下Ingress让后台负载均衡器基于主机header路由请求。
apiVersion: networking.k8s.io/v1beta1
kind: Ingress
metadata:
name: name-virtual-host-ingress
spec:
rules:
- host: foo.bar.com
http:
paths:
- backend:
serviceName: service1
servicePort: 80
- host: bar.foo.com
http:
paths:
- backend:
serviceName: service2
servicePort: 80
如果您创建的Ingress资源没有规则中定义的任何主机名,则可以匹配到您Ingress控制器IP地址的任何网络流量,而无需基于名称的虚拟主机。
例如,以下Ingress资源会将first.bar.com
请求的流量路由到service1,将second.foo.com
请求的流量路由到service2,而没有在请求中定义主机名的IP地址的流量路由到service3。
apiVersion: networking.k8s.io/v1beta1
kind: Ingress
metadata:
name: name-virtual-host-ingress
spec:
rules:
- host: first.bar.com
http:
paths:
- backend:
serviceName: service1
servicePort: 80
- host: second.foo.com
http:
paths:
- backend:
serviceName: service2
servicePort: 80
- http:
paths:
- backend:
serviceName: service3
servicePort: 80
TLS
可以通过指定包含TLS私钥和证书的Secret
来加密Ingress。目前,Ingress 只支持单个TLS端口443,并假设TLS终结于此。
如果Ingress中的TLS配置部分指定了不同的主机,那么它们将根据通过SNI TLS扩展指定的主机名(如果Ingress控制器支持SNI)在同一端口上进行复用。 TLS Secret必须包含名为tls.crt和tls.key的密钥,这些密钥包含用于TLS的证书和私钥,例如:
apiVersion: v1
kind: Secret
metadata:
name: testsecret-tls
namespace: default
data:
tls.crt: base64 encoded cert
tls.key: base64 encoded key
type: kubernetes.io/tls
在Ingress
中引用此Secret
将会告诉Ingress 控制器使用TLS加密从客户端到负载均衡器的通道。您需要确保创建的TLS secret包含域名为sslexample.foo.com
的CN证书。
apiVersion: networking.k8s.io/v1beta1
kind: Ingress
metadata:
name: tls-example-ingress
spec:
tls:
- hosts:
- sslexample.foo.com
secretName: testsecret-tls
rules:
- host: sslexample.foo.com
http:
paths:
- path: /
backend:
serviceName: service1
servicePort: 80
注意:各种Ingress控制器所支持的TLS功能之间存在差异。
3.6 更新 Ingress
要更新现有的Ingress
以添加新的Host
,可以通过编辑资源来对其进行更新:
# kubectl describe ingress test
##类似如下输出:
Name: test
Namespace: default
Address: 178.91.123.132
Default backend: default-http-backend:80 (10.8.2.3:8080)
Rules:
Host Path Backends
---- ---- --------
foo.bar.com
/foo service1:80 (10.8.0.90:80)
Annotations:
nginx.ingress.kubernetes.io/rewrite-target: /
Events:
Type Reason Age From Message
---- ------ ---- ---- -------
Normal ADD 35s loadbalancer-controller default/test
执行以下命令:
#kubectl edit ingress test
##弹出具有YAML格式的现有配置的编辑器,修改它来增加新的主机:
spec:
rules:
- host: foo.bar.com
http:
paths:
- backend:
serviceName: service1
servicePort: 80
path: /foo
- host: bar.baz.com
http:
paths:
- backend:
serviceName: service2
servicePort: 80
path: /foo
..
保存更改后,kubectl将更新API服务器中的资源,该资源将告诉Ingress控制器重新配置负载均衡器。
#kubectl describe ingress test
##得到类似如下输出:
Name: test
Namespace: default
Address: 178.91.123.132
Default backend: default-http-backend:80 (10.8.2.3:8080)
Rules:
Host Path Backends
---- ---- --------
foo.bar.com
/foo service1:80 (10.8.0.90:80)
bar.baz.com
/foo service2:80 (10.8.0.91:80)
Annotations:
nginx.ingress.kubernetes.io/rewrite-target: /
Events:
Type Reason Age From Message
---- ------ ---- ---- -------
Normal ADD 45s loadbalancer-controller default/test
也可以通过kubectl replace -f
命令调用修改后的Ingress yaml文件来获得同样的结果。
四. Ingress案例
4.1 安装Ingress控制器 ingress-nginx
步骤略:前面已经安装过,确保其已经运行
[root@k8s-master ~]# kubectl get pods -n ingress-nginx -o wide
NAME READY STATUS RESTARTS AGE IP NODE NOMINATED NODE READINESS GATES
nginx-ingress-controller-7b86f6f9fc-tk5tt 1/1 Running 1 22h 10.244.2.19 k8s-node2 <none> <none>
4.2 创建匹配nginx-ingress Pod的Service
ingress-nginx控制器实际上就是一个Nginx Pod,里面就是一个nginx反向代理服务器,来做为负载平衡器的功能。因为其只是一个Pod,所以在集群外面访问不方便,需要创建一个service,以便可以方便访问。
[root@k8s-master ~]# cd ingress-nginx-master/deploy/static/provider/baremetal/
[root@k8s-master baremetal]# ls
service-nodeport.yaml
修改service-nodeport.yaml文件,固定其Nodeport。
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
nodePort: 30080 ##添加的固定端口号
protocol: TCP
- name: https
port: 443
targetPort: 443
nodePort: 30443 ##添加的固定端口号
protocol: TCP
selector:
app.kubernetes.io/name: ingress-nginx
app.kubernetes.io/part-of: ingress-nginx
创建这个service
[root@k8s-master k8s-yaml]# kubectl apply -f service-nodeport.yaml
service/ingress-nginx created
[root@k8s-master k8s-yaml]# kubectl get svc -n ingress-nginx
NAME TYPE CLUSTER-IP EXTERNAL-IP PORT(S) AGE
ingress-nginx NodePort 10.1.154.111 <none> 80:30080/TCP,443:30443/TCP 26s
现在如果访问这个service的话,因为还没有后端服务,所以会报404错误
[root@k8s-master k8s-yaml]# curl http://192.168.154.220:30080
<html>
<head><title>404 Not Found</title></head>
<body>
<center><h1>404 Not Found</h1></center>
<hr><center>nginx/1.17.8</center>
</body>
</html>
[root@k8s-master k8s-yaml]# curl -k https://192.168.154.220:30443
<html>
<head><title>404 Not Found</title></head>
<body>
<center><h1>404 Not Found</h1></center>
<hr><center>nginx/1.17.8</center>
</body>
</html>
4.3 创建一个Nginx后端服务
apiVersion: v1
kind: Service
metadata:
name: my-service
labels:
app: my-service
spec:
selector:
app: my-service
ports:
- name: http
port: 80
targetPort: 80
---
apiVersion: apps/v1
kind: DaemonSet
metadata:
name: my-service
labels:
addonmanager.kubernetes.io/mode: Reconcile
spec:
selector:
matchLabels:
app: my-service
template:
metadata:
labels:
app: my-service
spec:
containers:
- name: my-nginx
image: nginx:1.7.9
ports:
- containerPort: 80
执行这个yaml文件
[root@k8s-master ingress]# kubectl apply -f my-service.yaml
service/my-service created
daemonset.apps/my-service created
[root@k8s-master ingress]# kubectl get svc
NAME TYPE CLUSTER-IP EXTERNAL-IP PORT(S) AGE
kubernetes ClusterIP 10.1.0.1 <none> 443/TCP 20d
my-service ClusterIP 10.1.251.21 <none> 80/TCP 14s
4.4 创建ingress资源
apiVersion: networking.k8s.io/v1beta1
kind: Ingress
metadata:
name: ingress-test
annotations:
nginx.ingress.kubernetes.io/rewrite-target: /
spec:
rules:
- host: www.shengzhe.com ##访问的域名
http:
paths:
- path: /app1
backend:
serviceName: my-service
servicePort: 80
执行yaml文件
[root@k8s-master ingress]# kubectl apply -f ingress-test.yaml
ingress.networking.k8s.io/ingress-test created
[root@k8s-master ingress]# kubectl get ingress
NAME HOSTS ADDRESS PORTS AGE
ingress-test www.shengzhe.com 10.1.154.111 80 19s
4.5 访问服务
[root@k8s-master ~]# curl -H "Host: www.shengzhe.com" http://192.168.154.220:30080/app1
<!DOCTYPE html>
<html>
<head>
<title>Welcome to nginx!</title>
<style>
body {
width: 35em;
margin: 0 auto;
font-family: Tahoma, Verdana, Arial, sans-serif;
}
</style>
</head>
<body>
<h1>Welcome to nginx!</h1>
<p>If you see this page, the nginx web server is successfully installed and
working. Further configuration is required.</p>
<p>For online documentation and support please refer to
<a href="http://nginx.org/">nginx.org</a>.<br/>
Commercial support is available at
<a href="http://nginx.com/">nginx.com</a>.</p>
<p><em>Thank you for using nginx.</em></p>
</body>
</html>
客户端使用浏览器访问:
- 原文作者:老鱼干🦈
- 原文链接://www.tinyfish.top:80/post/kubernets/1.-service%E5%8F%8A%E8%B4%9F%E8%BD%BD%E5%9D%87%E8%A1%A1/
- 版权声明:本作品采用知识共享署名-非商业性使用-禁止演绎 4.0 国际许可协议. 进行许可,非商业转载请注明出处(作者,原文链接),商业转载请联系作者获得授权。