k8s 对外暴露服务的方法
向 k8s 集群外部暴露服务的方式有三种: nodePort,LoadBalancer 和本文要介绍的 Ingress。每种方式都有各自的优缺点,nodePort 方式在服务变多的情况下会导致节点要开的端口越来越多,不好管理。而 LoadBalancer 更适合结合云提供商的 LB 来使用,但是在 LB 越来越多的情况下对成本的花费也是不可小觑。Ingress 是 k8s 官方提供的用于对外暴露服务的方式,也是在生产环境用的比较多的方式,一般在云环境下是 LB + Ingress Ctroller 方式对外提供服务,这样就可以在一个 LB 的情况下根据域名路由到对应后端的 Service,有点类似于 Nginx 反向代理,只不过在 k8s 集群中,这个反向代理是集群外部流量的统一入口。
NodePort对外暴露服务的缺点
- 在每个节点上都会起到端口,在访问时候通过任何节点,通过节点ip+暴露端口号实现访问
- 意味着每个端口只能使用一次,一个端口对应一个应用
- 每个节点的端口都需要对外开放
-
ingress和pod关系
pod和 Ingress通过 service关联的
- Ingress作为统一入口,由service关联一组pod
Ingress 及 Ingress Controller 简介
Ingress 是 k8s 资源对象,用于对外暴露服务,该资源对象定义了不同主机名(域名)及 URL 和对应后端 Service(k8s Service)的绑定,根据不同的路径路由 http 和 https 流量。而 Ingress Contoller 是一个 pod 服务,封装了一个 web 前端负载均衡器,同时在其基础上实现了动态感知 Ingress 并根据 Ingress 的定义动态生成 前端 web 负载均衡器的配置文件,比如 Nginx Ingress Controller 本质上就是一个 Nginx,只不过它能根据 Ingress 资源的定义动态生成 Nginx 的配置文件,然后动态 Reload。个人觉得 Ingress Controller 的重大作用是将前端负载均衡器和 Kubernetes 完美地结合了起来,一方面在云、容器平台下方便配置的管理,另一方面实现了集群统一的流量入口,而不是像 nodePort 那样给集群打多个孔。
所以,总的来说要使用 Ingress,得先部署 Ingress Controller 实体(相当于前端 Nginx),然后再创建 Ingress (相当于 Nginx 配置的 k8s 资源体现),Ingress Controller 部署好后会动态检测 Ingress 的创建情况生成相应配置。Ingress Controller 的实现有很多种:有基于 Nginx 的,也有基于 HAProxy的,还有基于 OpenResty 的 Kong Ingress Controller 等,更多 Controller 见:https://kubernetes.io/docs/concepts/services-networking/ingress-controllers/,本文使用基于 Nginx 的 Ingress Controller:ingress-nginx。
Ingress架构
helm3 安装Nginx Ingress Controller
基于 Nginx 的 Ingress Controller 有两种,一种是 k8s 社区提供的 ingress-nginx,另一种是 Nginx 社区提供的 kubernetes-igress,关于两者的区别见 这里。
在这里我们部署 k8s 社区提供的 ingress-nginx,Ingress Controller 对外暴露方式采用 hostNetwork,在裸机环境下更多其他暴露方式见:https://kubernetes.github.io/ingress-nginx/deploy/baremetal/
使用 Helm 官方提供的 Chart stable/nginx-ingress,修改 values 文件:
- 使用 DaemonSet 控制器,默认是 Deployment:controller.kind 设为 DaemonSet;
- pod 使用主机网络:controller.hostNetwork 设为 true;
- 在hostNetwork 下 pod 使用集群提供 dns 服务:controller.dnsPolicy 设为 ClusterFirstWithHostNet;
- Service 类型设为 ClusterIP,默认是 LoadBalancer:controller.service.type 设为 ClusterIP;
nginx提供的ingress controller,官方文档:https://docs.nginx.com/nginx-ingress-controller/overview/
下载ingress-nginx
github地址:https://github.com/kubernetes/ingress-nginx
helm:https://github.com/kubernetes/ingress-nginx/releases/tag/helm-chart-3.23.0
helm-chart-3.23.0对应ingress-nginx版本为v0.44.0
解压
tar -xf ingress-nginx-3.23.0.tgz
ingress的部署模式
ingress的部署,需要考虑两个方面:
- ingress-controller是作为pod来运行的,以什么方式部署比较好
- ingress解决了把如何请求路由到集群内部,那它自己怎么暴露给外部比较好
下面列举一些目前常见的部署和暴露方式,具体使用哪种方式还是得根据实际需求来考虑决定。
Deployment+LoadBalancer模式的Service
如果要把ingress部署在公有云,那用这种方式比较合适。用Deployment部署ingress-controller,创建一个type为LoadBalancer的service关联这组pod。大部分公有云,都会为LoadBalancer的service自动创建一个负载均衡器,通常还绑定了公网地址。只要把域名解析指向该地址,就实现了集群服务的对外暴露。
Deployment+NodePort模式的Service
同样用deployment模式部署ingress-controller,并创建对应的服务,但是type为NodePort。这样,ingress就会暴露在集群节点ip的特定端口上。由于nodeport暴露的端口是随机端口,一般会在前面再搭建一套负载均衡器来转发请求。该方式一般用于宿主机是相对固定的环境ip地址不变的场景。NodePort方式暴露ingress虽然简单方便,但是NodePort多了一层NAT,在请求量级很大时可能对性能会有一定影响。
DaemonSet+HostNetwork+nodeSelector(推荐)
用DaemonSet结合nodeselector来部署ingress-controller到特定的node上,然后使用HostNetwork直接把该pod与宿主机node的网络打通,直接使用宿主机的80/433端口就能访问服务。这时,ingress-controller所在的node机器就很类似传统架构的边缘节点,比如机房入口的nginx服务器。该方式整个请求链路最简单,性能相对NodePort模式更好。缺点是由于直接利用宿主机节点的网络和端口,一个node只能部署一个ingress-controller pod。比较适合大并发的生产环境使用。
修改
开始部署
本文采用DaemonSet+HostNetwork+nodeSelector模型进行部署
这里用到了一个参数controller.hostNetwork,设置成true,允许nginx-ingress的pod使用宿主机的网络命名空间(network namespace)
需要将 k8s.gcr.io/ingress-nginx/controller 镜像地址为 willdockerhub/ingress-nginx-controller,因为中国无法访问 k8s.gcr.io,因此如果不修改镜像地址会导致拉取镜像失败,需要修改的地方如下
- controller.image.repository
- controller.image.tag
- controller.image.digest
去dockerhub官网查看willdockerhub/ingress-nginx-controller版本
随便找一台主机执行如下命令拉取镜像获取digest
[root@k8s-master01 ingress-nginx]# docker pull willdockerhub/ingress-nginx-controller:v0.44.0
v0.44.0: Pulling from willdockerhub/ingress-nginx-controller
596ba82af5aa: Pull complete
b198ba082145: Pull complete
c7166c196d5e: Pull complete
addc1fbd9812: Pull complete
f9949b841da7: Pull complete
97335954f799: Pull complete
826bbedb287b: Pull complete
063c436f15fe: Pull complete
490a05ff8dc9: Pull complete
a0b67e4fe5ea: Pull complete
0dd043ba2c85: Pull complete
442ea23ce506: Pull complete
086124362b40: Pull complete
08dbade34891: Pull complete
Digest: sha256:3dd0fac48073beaca2d67a78c746c7593f9c575168a17139a9955a82c63c4b9a
Status: Downloaded newer image for willdockerhub/ingress-nginx-controller:v0.44.0
docker.io/willdockerhub/ingress-nginx-controller:v0.44.0
执行install
[root@k8s-master01 ingress]# pwd
/root/ingress
[root@k8s-master01 ingress]# ll
总用量 32
drwxr-xr-x 4 root root 140 5月 5 09:52 ingress-nginx
-rw-r--r-- 1 root root 23784 5月 5 09:43 ingress-nginx-3.23.0.tgz
-rw-r--r-- 1 root root 329 5月 5 08:50 nginx-ingress.yaml
-rw-r--r-- 1 root root 505 5月 5 06:39 nginx.yaml
# 执行如下命令进行install
helm install ingress-nginx \
--set controller.image.repository=willdockerhub/ingress-nginx-controller \
--set controller.image.tag=v0.44.0 \
--set controller.image.digest=sha256:3dd0fac48073beaca2d67a78c746c7593f9c575168a17139a9955a82c63c4b9a \
--set controller.hostNetwork=true \
--set controller.kind=DaemonSet \
--set controller.dnsPolicy=ClusterFirstWithHostNet \
--set controller.service.type=ClusterIP ingress-nginx/
# 输出结果,且已经给出示例了
NAME: ingress-nginx
LAST DEPLOYED: Wed May 5 09:56:28 2021
NAMESPACE: default
STATUS: deployed
REVISION: 1
TEST SUITE: None
NOTES:
The ingress-nginx controller has been installed.
Get the application URL by running these commands:
export POD_NAME=$(kubectl --namespace default get pods -o jsonpath="{.items[0].metadata.name}" -l "app=ingress-nginx,component=controller,release=ingress-nginx")
kubectl --namespace default port-forward $POD_NAME 8080:80
echo "Visit http://127.0.0.1:8080 to access your application."
An example Ingress that makes use of the controller:
apiVersion: networking.k8s.io/v1beta1
kind: Ingress
metadata:
annotations:
kubernetes.io/ingress.class: nginx
name: example
namespace: foo
spec:
rules:
- host: www.example.com
http:
paths:
- backend:
serviceName: exampleService
servicePort: 80
path: /
# This section is only required if TLS is to be enabled for the Ingress
tls:
- hosts:
- www.example.com
secretName: example-tls
If TLS is enabled for the Ingress, a Secret containing the certificate and key must also be provided:
apiVersion: v1
kind: Secret
metadata:
name: example-tls
namespace: foo
data:
tls.crt: <base64 encoded cert>
tls.key: <base64 encoded key>
type: kubernetes.io/tls
上面输出的例子已经告诉你apiVersion: networking.k8s.io/v1beta1,因此我们在接下来定义ingress时候apiVersion的内容如下
apiVersion: networking.k8s.io/v1beta1
验证部署是否成功
[root@k8s-master01 ingress]# kubectl get pod,svc | grep nginx
pod/ingress-nginx-controller-mmhk2 1/1 Running 0 2m59s
pod/ingress-nginx-controller-z46vb 1/1 Running 0 2m59s
service/ingress-nginx-controller ClusterIP 10.20.179.243 <none> 80/TCP,443/TCP 2m59s
service/ingress-nginx-controller-admission ClusterIP 10.20.221.155 <none> 443/TCP 2m59s
浏览器访问节点 ip 出现:default backend - 404页面,部署成功。 :::warning 是节点ip(node),不是master的ip,你访问master的ip是不能访问的 :::
至此 Nginx Ingress Controller 已部署完成,接下来讲解如何通过 Ingress 结合 Ingress Controller 实现集群服务对外访问。
使用 Ingress 对外暴露服务
Deployment + Service
为了快速体验 Ingress,下面部署一个 nginx 服务,然后通过 Ingress 对外暴露 nginx service 进行访问。首先部署 nginx 服务:Deployment + Service:nginx.yml
apiVersion: apps/v1
kind: Deployment
metadata:
name: nginx-rc
labels:
app: nginx
spec:
selector:
matchLabels:
app: nginx
replicas: 2
template:
metadata:
labels:
app: nginx
spec:
containers:
- name: nginx
image: nginx
ports:
- containerPort: 80
---
apiVersion: v1
kind: Service
metadata:
name: nginx-svc
spec:
ports:
- port: 80
targetPort: 80
protocol: TCP
name: http
selector:
app: nginx
执行
[root@k8s-master01 ingress]# kubectl apply -f nginx.yaml
deployment.apps/nginx-deployment created
service/nginx-svc created
查看deploy和pod以及svc
[root@k8s-master01 ingress]# kubectl get pod,svc | grep nginx
pod/nginx-rc-85ff79dd56-5796t 1/1 Running 0 41s
pod/nginx-rc-85ff79dd56-q42tv 1/1 Running 0 41s
service/nginx-svc ClusterIP 10.20.137.127 <none> 80/TCP 41s
ingress.yml
接下来创建 Ingress 对外暴露 nginx service 80 端口:ingress.yml:
apiVersion: networking.k8s.io/v1beta1
kind: Ingress
metadata:
name: ingress-nginx
annotations:
# use the shared ingress-nginx
kubernetes.io/ingress.class: "nginx"
spec:
rules:
- host: nginx.kube.com
http:
paths:
- path: /
backend:
serviceName: nginx-svc
servicePort: 80
- kubernetes.io/ingress.class: “nginx”:是用来指定要使用的ingress-controller。nginx-ingress-controller默认的ingress class 是nginx。
- host: nginx.kube.com:对外访问的域名;
- serviceName: nginx-svc:对外暴露的 Service 名称;
- servicePort: 80:nginx service 监听的端口;
:::warning 创建的 Ingress 必须要和对外暴露的 Service 在同一命名空间下! :::
[root@k8s-master01 ingress]# kubectl apply -f nginx-ingress.yaml
ingress.extensions/ingress-nginx created
[root@k8s-master01 ingress]# kubectl get ingress | grep kube.com
ingress-nginx nginx.kube.com 10.20.179.243 80 74m
查看nginx-ingress的pod
这里有一个地方可以注意一下,我们查看一下nginx-ingress的pod
[root@k8s-master01 ingress]# kubectl get pod | grep nginx-ingress
nginx-ingress-nginx-ingress-5f87cfb7b-7c56g 1/1 Running 0 7m51s
[root@k8s-master01 ingress]# kubectl exec -it nginx-ingress-nginx-ingress-5f87cfb7b-7c56g /bin/bash
nginx@k8s-node01:/$ cat /etc/nginx/conf.d/default-ingress-nginx.conf
# configuration for default/ingress-nginx
upstream default-ingress-nginx-nginx.kube.com-nginx-80 {
zone default-ingress-nginx-nginx.kube.com-nginx-80 256k;
random two least_conn;
server 127.0.0.1:8181 max_fails=1 fail_timeout=10s max_conns=0;
}
server {
listen 80;
server_tokens on;
server_name nginx.kube.com;
set $resource_type "ingress";
set $resource_name "ingress-nginx";
set $resource_namespace "default";
location / {
set $service "nginx";
proxy_http_version 1.1;
proxy_connect_timeout 60s;
proxy_read_timeout 60s;
proxy_send_timeout 60s;
client_max_body_size 1m;
proxy_set_header Host $host;
proxy_set_header X-Real-IP $remote_addr;
proxy_set_header X-Forwarded-For $proxy_add_x_forwarded_for;
proxy_set_header X-Forwarded-Host $host;
proxy_set_header X-Forwarded-Port $server_port;
proxy_set_header X-Forwarded-Proto $scheme;
proxy_buffering on;
proxy_pass http://default-ingress-nginx-nginx.kube.com-nginx-80;
}
}
进入容器,可以看到多了一个文件default-ingress-nginx.conf ,里面可以看到访问到nginx.kube.com域名的都代理到default-ingress-nginx-nginx.kube.com-nginx-80这个服务上了,而这个正式我们上面定义的nginx-ingress。
查看效果
将域名nginx.kube.com绑定到 k8s 任意节点 ip 即可访问:http://nginx.kube.com :::warning nginx.kube.com绑定到 k8s 任意节点 ip不包含master,只能是node节点中的任意一个节点 :::
修改host配置文件:
使用浏览器访问
上面的示例不支持 https 访问,下面举一个支持 https 的 Ingress 例子:通过 Ingress 访问 kubernetes dashboard 服务。