1. 外部无法访问 Pod
最近,我们在阿里云上买了 K8S 集群,项目好不容易部署上去,Depolyment 和 Service 发布完后;接着又去买了域名,添加域名解析,又去配置了 SLB(负载均衡)。
这时候问题来了,外部无法访问我的项目,然后我该怎么办呢?
apiVersion: v1
kind: Service
metadata:
name: $CI_PROJECT_NAME
namespace: $NAMESPACE
spec:
type: ClusterIP
ports:
- port: $PORT
targetPort: $PORT
name: $CI_PROJECT_NAME
selector:
app: $CI_PROJECT_NAME
好嘛,原来有个东西叫 Ingress,我们压根就没配置,能访问才怪呢!
当我们使用 K8S 集群部署好应用的 Service 时,默认的 Service 类型是 ClusterIP,这种类型只有 Cluster 内的节点和 Pod 可以访问。
K8s 集群内访问服务
服务发现是 K8s 中的一个重要机制,其基本功能为:在集群内通过服务名对服务进行访问,即需要完成从服务名到 ClusterIP(服务的虚拟 IP) 的解析。
K8s 主要有两种服务发现机制:环境变量和 DNS。没有 DNS 服务的时候,K8s 会采用环境变量的形式,但一旦有多个服务,环境变量会变复杂,为解决该问题,我们使用 DNS 服务。
如下图,我们可以这样访问我们的服务:
- 如果在同一个namespace内,http://hsh-product-service:80 或 http://172.21.1.41:80
- 如果在不同的namespece外,hsh-product-service.pre-service:80
2. NodePort 和 LoadBalancer 外部访问
K8s 集群的外网访问方式有3种:
NodePort,LoadBanlancer 和 Ingress
NodePort 和 LoadBanlancer 是 k8s 中 service 的类型。上面讲到的集群内访问,ClusterIP 也是 service 的一种类型。
Ingress 是 k8s 的一个抽象层,有很多的 Ingress Controller 和服务可以来实现这个 Ingress 服务,然后由这个 Ingress 服务把外网的请求转发到集群内的服务。
NodePort
NodePort 服务是让外部流量直接访问服务的最原始方式。NodePort,顾名思义,在所有的节点(虚拟机)上开放指定的端口,所有发送到这个端口的流量都会直接转发到服务。
创建 NodePort 类型的 Service 时,用户可以指定范围为 30000-32767 的端口,对该端口的访问就能通过 kube-proxy 代理到 Service 后端的 Pod 中。使用 NodeIP:PodPort 访问 Pod。
这种服务暴露方式,无法让你指定自己想要的应用常用端口,不过可以在集群上再部署一个反向代理作为流量入口。
apiVersion: v1
kind: Service
metadata:
name: $CI_PROJECT_NAME
namespace: $NAMESPACE
spec:
type: NodePort
ports:
- port: $PORT
targetPort: $PORT
nodePort: 30001
protocol: TCP
name: $CI_PROJECT_NAME
selector:
app: $CI_PROJECT_NAME
从本质上来看,NodePort 服务有两个地方不同于一般的 “ClusterIP” 服务。首先,它的类型是 “NodePort”。还有一个叫做 “nodePort” 的端口,能在节点上指定开放哪个端口。如果没有指定端口,它会选择一个随机端口。大多数时候应该让 Kubernetes 选择这个端口。
NodePort 方式有一些不足:
- 一个端口只能供一个服务使用;
- 只能使用30000–32767的端口;
- 如果节点 / 虚拟机的IP地址发生变化,需要进行处理。
因此,我不推荐在生产环境使用这种方式来直接发布服务。如果不要求运行的服务实时可用,或者在意成本,这种方式适合你。例如用于演示的应用或是临时运行就正好用这种方法。
LoadBalancer
LoadBalancer 服务是发布服务到互联网的标准方式。在GKE中,它会启动一个 Network Load Balancer,分配一个单独的IP地址,将所有流量转发到服务中。
LoadBanlancer 类型需要各个云厂商自己来实现的 CloudControllerManager,所以,采用不同的云厂商,它们的 LoadBanlancer 也就会有一些区别,它们的功能以及使用方法也就不一样了。
LoadBalancer 针对于 Service,且只能在云服务平台上使用(AWS、Azure、阿里云等),使用任一节点的 IP 访问,如 LoadBalancerIP:LoadBalancerPort。
如果你想直接发布服务,这是默认方式。指定端口的所有流量都会转发到服务中,没有过滤,也没有路由。这意味着你几乎可以发送任意类型的流量到服务中,比如 HTTP、TCP、UDP、Websockets、gRPC 等等。
这里我们可以购买阿里云的 SLB(负载均衡):
LoadBalancer 方式有一些不足:
- 使用LoadBalancer发布的每个服务都会有一个自己的IP地址,你需要支付每个服务的LoadBalancer 费用,这是一笔不小的开支。
3. Ingress 外部访问(智能路由和网关)
Ingress Controller 以 Docker 容器的方式部署在 K8s 的顶部,简单来说就是 K8s 所使用的负载均衡器,类型有:Nginx、Envoy、HAProxy 等。
一般使用 K8s 官方的 Ingress-Nginx Controller(注意,不是 Nginx 公司的 Nginx Ingress Controller) 作为外网访问服务的方式。
Ingress 实际上不是一种服务。相反,它在多个服务前面充当“智能路由”的角色,或者是集群的入口。
使用 Ingress 可以做很多事情,不同类型的 Ingress 控制器有不同的功能。
默认的 GKE ingress 控制器会启动一个 HTTP(S) Load Balancer,可以通过基于路径或者是基于子域名的方式路由到后端服务。例如,可以通过 foo.domain.com 发送任何东西到 foo 服务,或者是发送domain.com/bar/ 路径下的任何东西到bar服务。
这里我们来举一个比较具体的例子,我们部署一个 Redis 服务:
apiVersion: apps/v1
kind: Deployment
metadata:
name: redis-deployment
namespace: dev
spec:
replicas: 3
selector:
matchLabels:
app: redis-pod
template:
metadata:
labels:
app: redis-pod
spec:
containers:
- name: redis
image: redis:6.3
ports:
- containerPort: 6379
apiVersion: v1
kind: Service
metadata:
name: redis-service
namespace: dev
spec:
selector:
app: redis-pod
clusterIP: None
type: ClusterIP
ports:
- port: 6379
targetPort: 6379
此时我们根据上边章节的内容,集群内部已经可以访问: redis-service.dev:6379
我们期望 外部通过 http 可以访问这个服务:
http://redis.domain.com --> redis-service.dev:6379
http://foo.domain.com/redis --> redis-service.dev:6379
apiVersion: networking.k8s.io/v1
kind: Ingress
metadata:
name: ingress-http
namespace: dev
annotations:
kubernetes.io/ingress.class: "nginx"
spec:
rules:
- host: redis.domain.com
http:
paths:
- path: /
pathType: Prefix
backend:
serviceName: redis-service
servicePort: 6379
- host: foo.domain.com
http:
paths:
- path: /redis
pathType: Prefix
backend:
serviceName: redis-service
servicePort: 6379
这里为了方便大家理解,相当于 Nginx 原生 .conf 文件的写法:
server {
listen 80;
server_name redis.domain.com;
location ~ / {
proxy_pass http://redis-service:6379;
}
}
server {
listen 80;
server_name foo.domain.com;
location ~ /redis {
proxy_pass http://redis-service:6379;
}
}
这里我们升级一下,期望 外部通过 https 可以访问这个服务:
https://redis.domain.com --> redis-service.dev:6379
https://foo.domain.com/redis --> redis-service.dev:6379
# 生成自签名证书(365天有效期)
# Key: tls.key 证书: tls.crt
$ openssl req -x509 -sha256 -nodes \
-days 365 -newkey rsa:2048 \
-keyout tls.key -out tls.crt -subj "/CN=nginxsvc/O=nginxsvc"
Generating a 2048 bit RSA private key
................+++
................+++
writing new private key to 'tls.key'
# 在K8S中创建一个名称为tls-secret的secret格式的证书信息
$ kubectl create secret tls tls-secret --key tls.key --cert tls.crt
secret "tls-secret" created
Deployment/Service/Ingress
apiVersion: networking.k8s.io/v1
kind: Ingress
metadata:
name: ingress-https
namespace: dev
annotations:
kubernetes.io/ingress.class: "nginx"
spec:
tls:
- hosts:
- redis.domain.com
- foo.domain.com
secretName: tls-secret
rules:
- host: redis.domain.com
http:
paths:
- path: /
pathType: Prefix
backend:
serviceName: redis-service
servicePort: 6379
- host: foo.domain.com
http:
paths:
- path: /redis
pathType: Prefix
backend:
serviceName: redis-service
servicePort: 6379
Ingress 和 网关的区别
Ingress 是 Kubernetes 中的一个抽象层,用于将多个服务公开到同一 IP 地址和端口,并根据请求路径或主机名进行路由,而网关则是提供对应用程序的访问控制、身份验证、安全性、流量管理和监视等功能的组件,它可以与 Kubernetes 集群一起使用,但也可以独立于 Kubernetes 集群之外部署。
Ingress 是 Kubernetes 中的一个抽象层,它允许您公开多个服务到同一 IP 地址和端口,并根据请求路径或主机名进行路由。 Ingress 通常用于 HTTP/HTTPS 流量,并且可以支持 TLS endpoint、基于路径的路由和负载均衡等功能。可以使用 Kubernetes Ingress Controller 实现 Ingress 功能,例如 Nginx Ingress Controller、Traefik Ingress Controller 和 Istio Ingress Gateway。
网关(Gateway)通常是一个独立的组件,用于提供对应用程序的访问控制、身份验证、安全性、流量管理和监视等功能。 网关通常可以支持多个协议和传输层,并且可以部署在 Kubernetes 集群之外,例如 API网关、网络应用防火墙(WAF)和服务网格(Service Mesh)等。流量可以通过不同的方式路由到网关,例如 DNS名、IP地址、负载均衡器和 Ingress 等。