将服务暴露给外部客户端 P136
有以下三种方式可以在外部访问服务:
- 将服务的类型设置成
NodePort
- 将服务的类型设置为
LoadBalance
- 创建一个
Ingress
资源
使用 NodePort
类型的服务 P137
通过创建一个 NodePort
服务,可以让 Kubernetes 在其所有节点上保留一个端口(所有节点上都使用相同端口号),并将传入的连接转发给作为服务部分的 pod 。 P137
创建 NodePort
类型的服务 P137
可以使用如下描述文件 kubia-svc-nodeport.yaml
创建一个 NodePort
类型的服务。
# 遵循 v1 版本的 Kubernetes API
apiVersion: v1
# 资源类型为 Service
kind: Service
metadata:
# Service 的名称
name: kubia-nodeport
spec:
# 指定服务类型为 NodePort
type: NodePort
# 该服务可用的端口
ports:
# 第一个可用端口的名字
- name: http
# 可用端口为 80
port: 80
# 服务将连接转发到容器的 8080 端口
targetPort: 8080
# 通过集群节点的 30000 端口可以访问该服务
nodePort: 30000
# 第二个可用端口的名字
- name: https
# 可用端口为 443
port: 443
# 服务将连接转发到容器的 8443 端口
targetPort: 8443
# 通过集群节点的 32767 端口可以访问该服务
nodePort: 32767
# 具有 app=kubia 标签的 pod 都属于该服务
selector:
app: kubia
nodePort
属性不是强制的,如果忽略就会随机选择一个端口。 P137
kubectl get services kubia-nodeport
: 查看该服务的基础信息
NAME TYPE CLUSTER-IP EXTERNAL-IP PORT(S) AGE
kubia-nodeport NodePort 10.111.59.156 80:30000/TCP,443:32767/TCP 2s
PORT(S)
列显示集群 IP 内部端口 (80, 443) 和节点端口 (30000, 32767) ,可通过 10.111.59.156:80
和
等访问服务。 P138
使用 JSONPath 输出需要的信息:通过指定 kubectl 的 JSONPath ,我们可以只输出需要的信息。例如: kubectl get nodes -o jsonpath='{.items[*].status.addresses[0].address}'
将输出所有节点的 IP 地址。
通过负载均衡器将服务暴露出来 P140
负载均衡器拥有自己独一无二的可公开访问的 IP 地址,并将所有连接重定向到服务。 P140
创建 LoadBalance
服务 P140
可以使用如下描述文件 kubia-svc-loadbalancer.yaml
创建一个 LoadBalancer
类型的服务。
# 遵循 v1 版本的 Kubernetes API
apiVersion: v1
# 资源类型为 Service
kind: Service
metadata:
# Service 的名称
name: kubia-loadbalancer
spec:
type: LoadBalancer
# 该服务可用的端口
ports:
# 第一个可用端口的名字
- name: http
# 可用端口为 80
port: 80
# 服务将连接转发到容器的 8080 端口
targetPort: 8080
# 第二个可用端口的名字
- name: https
# 可用端口为 443
port: 443
# 服务将连接转发到容器的 8443 端口
targetPort: 8443
# 具有 app=kubia 标签的 pod 都属于该服务
selector:
app: kubia
使用 minikube 会发现服务的 EXTERNAL-IP
一直为
,我们可以使用 minikube 自带的 minikube tunnel
命令可以完成暴露(02. 开始使用 Kubernetes 和 Docker 介绍过相关处理及踩过的坑)。
LoadBalancer
类型的服务是一个具有额外的基础设施提供的负载均衡器 NodePort
服务。使用 kubectl describe service kubia-loadbalancer
命令可以发现该服务选择了一个节点端口。 P141
了解外部连接的特性 P142
了解并防止不必要的网络跳数:当外部客户端通过节点端口连接到服务时,随机选择的 pod 并不一定在接收连接的同一节点上。可能需要额外的网络跳转才能到达 pod 。可配置 Service.spec.externalTrafficPolicy
的值为 Local
指定仅将外部通信重定向到接收连接的节点上运行的 pod 。
- 如果接收连接的节点上没有运行对应的 pod ,那么连接将挂起,所以需要确保负载均衡器将连接转发给至少具有一个 pod 的节点
- 假设有一个两节点三个 pod 的集群(节点 A 运行一个 pod ,节点 B 运行两个 pod),如果负载均衡器在两个节点间均匀分布连接,那么 pod 的负载分布不均衡
客户端 IP 是不记录的:当通过节点端口接收到连接时,由于对数据包执行了源网络地址转换 (SNAT) ,因此数据包对源 IP 将发生更改。如果配置 Service.spec.externalTrafficPolicy
的值为 Local
,那么将会保留客户端 IP ,因为在接收连接的节点和托管目标 pod 的节点之间没有额外的跳跃(不执行 SNAT )。 P143
通过 Ingress
暴露服务 P143
为什么需要 Ingress
P144
每个 LoadBalancer
服务都需要自己的负载均衡器,以及独有的公有 IP 地址,而 Ingress
只需要一个公网 IP 就能为许多服务提供访问。当客户端向 Ingress
发送 HTTP 请求时, Ingress
会根据请求的主机名和路径决定请求转发到的服务。 P144
Ingress
在网络栈 (HTTP) 的应用层操作,并且可以提供一些服务不能实现的功能,例如基于 cookie 的会话亲和性 (session affinity) 等功能。 P144
Ingress 控制器是必不可少的 P144
只有 Ingress
控制器在集群中运行, Ingress
资源才能正常工作。 P144
在 minikube 上启动 Ingress
的扩展功能 P145
minikube addons list
: 可以列出所有的插件及其启用状态
minikube addons enable ingress
: 启用 ingress
插件
kubectl get pods -n kube-system
: 查看 kube-system
命名空间下的 pod ,可以发现 Ingress
控制器 pod
创建 Ingress
资源 P145
可以使用如下描述文件 kubia-ingress.yaml
创建一个 Ingress
资源。
# 遵循 extensions/v1beta1 版本的 Kubernetes API
apiVersion: extensions/v1beta1
# 资源类型为 Ingress
kind: Ingress
metadata:
# Ingress 的名称
name: kubia
spec:
# Ingress 的规则列表
rules:
# 第一条规则匹配的域名为 kubia.example.com
- host: kubia.example.com
# 匹配 http
http:
# 匹配的路径列表
paths:
# 第一条路径为 /
- path: /
# 该路径将被转发到的后端服务
backend:
# 将被转发到 kubia-nodeport 服务
serviceName: kubia-nodeport
# 对应服务的端口为 80
servicePort: 80
minikube 下创建 Ingress
资源时报错了,提示超时。后来找到一种解决方案:使用 kubectl edit ValidatingWebhookConfiguration/ingress-nginx-admission
进行编辑,找到 failurePolicy: Fail
这行,并将 Fail
改为 Ignore
,然后就能成功创建 Ingress
资源了,等一段时间后就可以看见其分配了一个 IP 地址 (192.168.64.70
) 。
为了能将指定的域名 kubia.example.com
指向分配的 IP 地址 (192.168.64.70
),可以使用 SwitchHosts 这个软件进行快速切换。
此时我们在主机上就可以通过 curl kubia.example.com
访问 kubia-nodeport
服务了。
了解 Ingress
的工作原理 P147
- 客户端对
kubia.example.com
执行 DNS 查找,本地操作系统返回了Ingress
控制器的 IP - 客户端向
Ingress
控制器发送 HTTP 请求,并在 Host 头中指定kubia.example.com
- 控制器从头部确定客户端尝试访问哪个服务,通过与该服务关联的
Endpoint
对象查看 pod IP ,并将客户端的请求转发给其中一个 pod
Ingress
控制器不会将请求转发给服务,只用它来选择一个 pod 。大多数控制器都是这样工作的。 P147
通过相同的 Ingress
暴露多个服务 P147
Ingerss
的 rules
和 paths
都是数组,所以它们可以包含多个条目,因此一个 Ingress
可以将多个域名和路径映射到多个服务。 P147
配置 Ingress
处理 TLS 传输 P149
为 Ingress
创建 TLS 认证 P149
当客户端创建到 Ingress
控制器到 TLS 连接时,控制器将终止 TLS 连接。客户端和控制器之间到通信是加密的,而控制器和后端 pod 之间的通信则未加密。运行在 pod 上的应用程序不需要支持 TLS 。 P149
为了让 Ingress
控制器负责处理与 TLS 相关的所有内容,需要将证书和私钥附加到 Ingress
。这两个必须资源存储在称为 Secret
的 Kubernetes 资源中(将在第 7 章中详细介绍 Secret
),然后在 Ingress
的描述文件中引用它。 P149
openssl genrsa -out tls.key 2048
: 创建私钥
openssl req -new -x509 -key tls.key -out tls.cert -days 365 -subj /CN=kubia.example.com
: 创建证书
kubectl create secret tls tls-secret --cert=tls.cert --key=tls.key
: 创建 Secret
资源
然后我们就可以改写 kubia-ingress.yaml
得到 kubia-ingress-tls.yaml
描述文件:
...
spec:
# 配置 TLS
tls:
# 第一条配置的域名列表
- hosts:
- kubia.example.com
# 这些域名使用 tls-secret 获得私钥和证书
secretName: tls-secret
...
然后我们就可以使用 curl -k -v https://kubia.example.com
通过 HTTPS 访问服务了。( minikube 下未进行上述操作前也可以访问,不过可以发现是 Ingress
控制器使用了假证书) P150
pod 就绪后发出信号 P150
与存活探测器(04. 副本机制和其他控制器:部署托管的 pod 中介绍过)类似, Kubernetes 还允许为容器定义就绪探测器。就绪探测器会定期调用,并确保特定的 pod 是否接收客户端请求。当容器的就绪探测器返回成功时,表示容器已准备好接收请求。 P151
就绪探测器的类型: P151
-
Exec
探测器:在容器内执行任意命令,并检查命令的退出状态码。如果状态码是 0 ,则探测成功,认为容器已经就绪,所有其他状态码都被认为失败 -
HTTP GET
探测器:对容器的 IP 地址(指定的端口和路径)执行HTTP GET
请求。如果探测器收到响应,并且响应状态码不代表错误(状态码为 2xx 或 3xx ),则认为探测成功,认为容器已经就绪。如果服务器返回错误响应状态码或者没有响应,那么探测就被认为是失败的 -
TCP Socket
探测器:尝试与容器指定端口建立 TCP 连接。如果连接成功建立,则探测成功,认为容器已经就绪
了解就绪探测器的操作 P151
启动容器时,可以为 Kubernetes 配置一个等待时间,经过等待时间后才可以执行第一次准备就绪检查。之后,它会周期性地调用探测器,并根据就绪探测器的结果采取行动。如果某个 pod 报告它尚未准备就绪,那么就会从服务中删除该 pod ;如果这个 pod 再次准备就绪,那么就会将给 pod 重新添加到服务中。 P151
就绪探测器和存活探测器的区别 P151
存活探测器通过杀死异常的容器并用新的正常容器替代它们来保持 pod 正常工作,而就绪探测器确保只有准备好处理请求的 pod 才可以接收请求,并不会终止或重新启动容器。 P151
就绪探测器的重要性:确保客户端只与正常的 pod 交互,并且永远不会知道系统存在的问题。 P152
了解就绪探测器的实际作用 P154
务必定义就绪探测器 P155
应该始终定义一个就绪探测器,即使它只是向基准 URL 发送 HTTP 请求一样简单。如果没有将就绪探测器添加到 pod 中,那么它们启动后几乎立即成为服务端点。 P155
不要将停止 pod 的逻辑纳入到就绪探测器中 P155
当一个容器关闭时,运行在其中的应用程序通常会在收到终止信号后立即停止接收连接。但在启动关机程序后,没有必要让就绪探测器返回失败以达到从所有服务中移除 pod 目的,因为在该容器删除后, Kubernetes 就会自动从所有服务中移除该容器。 P155
使用 headless 服务来发现独立的 pod P155
要让客户端连接到所有 pod ,需要找出每个 pod 的 IP 。 Kubernetes 允许客户通过 DNS 查找发现 pod IP 。通常,当执行服务的 DNS 查找时, DNS 服务器会返回单个 IP —— 服务的集群 IP 。但是,如果告诉 Kubernetes ,不需要为服务提供集群 IP (通过在服务 spec
中将 clusterIP
字段设置为 None
来完成此操作),则 DNS 服务器将会返回 pod IP 而不是单个服务 IP 。 P155
DNS 服务器不会返回单个 DNS A 记录,而是会为该服务返回多个 A 记录,每个记录指向当时支持该服务的单个 pod 的 IP 。客户端因此可以做一个简单的 DNS A 记录查询并获取属于该服务的所有 pod 的 IP 。客户端可以使用该信息连接到其中的一个、多个或全部。 P155
创建 headless 服务 P156
可以使用如下描述文件 kubia-svc-headless.yaml
创建一个 headless 的 Service
资源。
# 遵循 v1 版本的 Kubernetes API
apiVersion: v1
# 资源类型为 Service
kind: Service
metadata:
# Service 的名称
name: kubia-headless
spec:
# 该服务的集群 IP 为 None ,使其变为 headless 的
clusterIP: None
# 该服务可用的端口
ports:
# 第一个可用端口的名字
- name: http
# 可用端口为 80
port: 80
# 服务将连接转发到容器的 8080 端口
targetPort: 8080
# 第二个可用端口的名字
- name: https
# 可用端口为 443
port: 443
# 服务将连接转发到容器的 8443 端口
targetPort: 8443
# 具有 app=kubia 标签的 pod 都属于该服务
selector:
app: kubia
通过 DNS 发现 pod P156
kubia 容器镜像不包含 nslookup
二进制文件,所以需要用一个新的容器镜像来执行相应的命令。 P156
kubectl run dnsutils --image=tutum/dnsutils --generator=run-pod/v1 --command -- sleep infinity
: 创建一个可以执行 nslookup
命令的 pod
kubectl exec dnsutils nslookup kubia-headless
: 在 dnsutils
pod 内执行 nslookup kubia-headless
命令,可以发现 DNS 服务器为 kubia-headless.default.svc.cluster.local
FQDN 返回了多个 IP ,且它们都是 pod 的 IP ,可以通过 kubectl get pods -o wide
进行确认
kubectl exec dnsutils nslookup kubia
: 在 dnsutils
pod 内执行 nslookup kubia
命令,可以发现 DNS 服务器为 kubia.default.svc.cluster.local
FQDN 返回了一个 IP ,该 IP 是服务的集群 IP
尽管 headless 服务看起来可能与常规服务不同,但是在客户的视角上它们并无不同。对于 headless 服务,由于 DNS 返回了 pod 的 IP ,客户端直接连接到该 pod ,而不是通过服务代理(注意这里是直接访问的 pod ,所以对应的端口要改成 pod 的端口)。 P157
注意: headless 服务仍然提供跨 pod 的负载均衡,但是通过 DNS 轮循机制不是通过服务代理 P157
发现所有的 pod —— 包括未就绪的 pod P157
可以通过在 Service.metadata.annotations
下面增加一条 service.alpha.kubernetes.io/tolerate-unready-endpoints: "true"
告诉 Kubernetes 无论 pod 的准备状态如何,希望将所有 pod 添加到服务中 P158
排除服务故障 P158
如果无法通过服务访问 pod ,应根据下面的列表进行排查: P158
- 确保从集群内连接到服务的集群 IP
- 不要通过
ping
服务 IP 来判断服务是否可访问(服务的集群 IP 是虚拟 IP ,是无法 ping 通的) - 如果已经定义了就绪探测器,请确保它返回成功;否则该 pod 不会成为服务的一部分
- 要确认某个容器是服务的一部分,请使用
kubectl get endpoints
来检查相应的端点对象 - 如果尝试通过 FQDN 或其中一部分来访问服务,但并不起作用,请查看是否可以使用其集群 IP 而不是 FQDN 来访问服务
- 检查是否连接到服务公开的端口,而不是目标端口
- 尝试直接连接到 pod IP 以确认 pod 正在接收正确端口上的连接
- 如果甚至无法通过 pod 的 IP 访问应用,请确保应用不是仅绑定到
localhost (127.0.0.1)