9.1 Ingress为弥补NodePort不足而生
1 单独用service暴露服务的方式,在实际生产环境中不太合适 ClusterIP 只能在集群内部访问。 NodePort 方式的话,测试环境使用还行,当有几十上百的服务在集群中运行时,NodePort的端口管理是灾难。 LoadBalance 方式受限于云平台,且通常在云平台部署ELB还需要额外的费用。
2 ingress与ingress-controller
ingress对象: 指的是k8s中的一个api对象,一般用yaml配置。作用是定义请求如何转发到service的规则,可以理解为配置模板。
ingress-controller:具体实现反向代理及负载均衡的程序,对ingress定义的规则进行解析,根据配置的规则来实现请求转发。简单来说,ingress-controller才是负责具体转发的组件,通过各种方式将它暴露在集群入口,外部对集群的请求流量会先到ingress-controller,而ingress对象是用来告诉ingress-controller该如何转发请求,比如哪些域名哪些path要转发到哪些服务等等。Ingress Controller会动态感知集群中的Ingress的规则变化,然后读取,动态生成Nginx的配置文件,最后注入到运行nginx的pod的中,然后会自动reload,配置生效。
3 k8s真正提供服务的是pod,为了负载均衡,为了使用域名, service 诞生了,然后 ingress 诞生了,那么Ingress的作用如下
(1) 帮助位于集群中的Service能够有一个对外可达的url,即让集群外的客户端也可以访问到自己。(对于这一点,NodePort类型的Service可以实现,只支持4层负载均衡,一个端口只能一个服务使用,端口需提前规划
)
(2) 做专业的负载均衡,毕竟Service的负载均衡还是很初级的
4 Ingress工作流程
如下图所示,流量到达外部负载均衡器(externalLB
)后,首先转发至Service
资源Ingres-nginx
上,然后通过Ingress
控制器基于Ingress
资源定义的规则将客户端请求流量直接转发至与Service
对应的后端Pod
资源之上。这种转发机制会绕过Service
资源(app Service
;api Service
),从而省去了由kube-proxy
实现的端口代理开销。Ingress
规则需要由一个Service
资源对象辅助识别相关的所有Pod
资源。如下Ingress
通过app service
资源去匹配后端的pod1
和pod2
;这个app service
只是起到一个辅助识别功能。
user -> 域名 -> node ip:80/443 -> ingress controller -> 域名分流 –> pod
ingress controller pod -> 获取service(apiserver)关联的pod -> 应用到本地nginx(提供七层负载均衡)
因为是DaemoSet方式部署的ingress controller, 所以除master节点外每个从node节点都会部署一个ingress controller的pod并监听80和443端口。
9.2 Pod与Ingress的关系
-
通过Service相关联
-
通过Ingress Controller实现Pod的负载均衡
-
支持TCP/UDP 4层和HTTP 7层
-
9.3 Ingress Controller
为了使Ingress资源正常工作,集群必须运行一个Ingress Controller(负载均衡实现)。
所以要想通过ingress暴露你的应用,大致分为两步:
-
部署Ingress Controller
-
创建Ingress规则
整体流程如下:
Ingress Controller有很多实现,我们这里采用官方维护的Nginx控制器。
其他主流控制器:(1) Traefik: HTTP反向代理、负载均衡工具 (2) Istio:服务治理,控制入口流量
部署文档:https://github.com/kubernetes/ingress-nginx/blob/nginx-0.30.0/docs/deploy/index.md
# wget https://raw.githubusercontent.com/kubernetes/ingress-nginx/nginx-0.30.0/deploy/static/mandatory.yaml
注意事项:
- 镜像地址修改成国内的(国外的源无法连接):lizhenliang/nginx-ingress-controller:0.20.0
- 使用宿主机网络(提高网络转发性能):hostNetwork: true
# vim mandatory.yaml...... spec: hostNetwork: true serviceAccountName: nginx-ingress-serviceaccount containers: - name: nginx-ingress-controller image: lizhenliang/nginx-ingress-controller:0.20.0 args: - /nginx-ingress-controller - --configmap=$(POD_NAMESPACE)/nginx-configuration - --tcp-services-configmap=$(POD_NAMESPACE)/tcp-services - --udp-services-configmap=$(POD_NAMESPACE)/udp-services - --publish-service=$(POD_NAMESPACE)/ingress-nginx - --annotations-prefix=nginx.ingress.kubernetes.io ......
# kubectl apply -f mandatory.yaml
# kubectl get pods -n ingress-nginx -o wide
NAME READY STATUS RESTARTS AGE IP NODE
nginx-ingress-controller-9r25t 1/1 Running 0 5m16s 172.16.1.71 k8s-node1
nginx-ingress-controller-p8g66 1/1 Running 0 5m16s 172.16.1.72 k8s-node2
# 查看ingress-controller中的nginx配置文件
# kubectl exec -it nginx-ingress-controller-9r25t sh -n ingress-nginx
$ ls /etc/nginx/nginx.conf
/etc/nginx/nginx.conf
$
# 此时在任意Node上就可以看到该控制监听的80和443端口,80和443端口就是接收来自外部访问集群中应用流量,转发对应的Pod上
# netstat -natp |egrep ":80|:443"
tcp 0 0 0.0.0.0:80 0.0.0.0:* LISTEN 83841/nginx: master
tcp 0 0 0.0.0.0:443 0.0.0.0:* LISTEN 83841/nginx: master
9.4 Ingress
接下来,就可以创建ingress规则了。
在ingress里有三个必要字段:
-
host: 访问该应用的域名,也就是域名解析
-
serverName: 应用的service名称
-
serverPort: service端口
1、HTTP访问
# vim http.yamlapiVersion: networking.k8s.io/v1beta1 kind: Ingress metadata: name: example-lc-ingress spec: rules: - host: example.lc.com http: paths: - path: / backend: serviceName: nginx servicePort: 80
# kubectl apply -f http.yaml
# kubectl get ingress
NAME HOSTS ADDRESS PORTS AGE
example-lc-ingress example.lc.com 80 47s
# 添加windows本地hosts解析
C:\Windows\System32\drivers\etc\hosts
172.16.1.71 example.lc.com
# 使用域名访问
http://example.lc.com/
注意:
(1) 生产环境:example.lc.com 域名是在你购买域名的运营商上进行解析,A记录值为K8S Node的公网IP(该Node必须运行了Ingress controller)。
(2) 测试环境:可以绑定hosts模拟域名解析("C:\Windows\System32\drivers\etc\hosts"),对应IP是K8S Node的内网IP。例如:
172.16.1.71 example.lc.com
2、HTTPS访问
# vim sslhttp.yamlapiVersion: networking.k8s.io/v1beta1 kind: Ingress metadata: name: sslexample-lc-ingress spec: tls: - hosts: - sslexample.lc.com secretName: sslexample-lc-com rules: - host: sslexample.lc.com http: paths: - path: / backend: serviceName: nginx servicePort: 80
# kubectl apply -f sslhttp.yaml
# kubectl get ingress
NAME HOSTS ADDRESS PORTS AGE
sslexample-lc-ingress sslexample.lc.com 80, 443 8s
里面用到了secret名为example-lc-com,用于保存https证书。这里使用cfssl工具自签证书用于测试,先下载cfssl工具:
# cat cfssl.sh
wget https://pkg.cfssl.org/R1.2/cfssl_linux-amd64
wget https://pkg.cfssl.org/R1.2/cfssljson_linux-amd64
wget https://pkg.cfssl.org/R1.2/cfssl-certinfo_linux-amd64
chmod +x cfssl*
mv cfssl_linux-amd64 /usr/bin/cfssl
mv cfssljson_linux-amd64 /usr/bin/cfssljson
mv cfssl-certinfo_linux-amd64 /usr/bin/cfssl-certinfo
# cat certs.shcat > ca-config.json <<EOF { "signing": { "default": { "expiry": "87600h" }, "profiles": { "kubernetes": { "expiry": "87600h", "usages": [ "signing", "key encipherment", "server auth", "client auth" ] } } } } EOF cat > ca-csr.json <<EOF { "CN": "kubernetes", "key": { "algo": "rsa", "size": 2048 }, "names": [ { "C": "CN", "L": "Beijing", "ST": "Beijing" } ] } EOF cfssl gencert -initca ca-csr.json | cfssljson -bare ca - cat > sslexample.lc.com-csr.json <<EOF { "CN": "sslexample.lc.com", "hosts": [], "key": { "algo": "rsa", "size": 2048 }, "names": [ { "C": "CN", "L": "BeiJing", "ST": "BeiJing" } ] } EOF cfssl gencert -ca=ca.pem -ca-key=ca-key.pem -config=ca-config.json -profile=kubernetes sslexample.lc.com-csr.json | cfssljson -bare sslexample.lc.com
执行certs.sh脚本,生成证书:
# sh certs.sh
# ls *.pem
ca-key.pem ca.pem sslexample.lc.com-key.pem sslexample.lc.com.pem
将证书保存在secret里:
# kubectl create secret tls sslexample-lc-com --cert=sslexample.lc.com.pem --key=sslexample.lc.com-key.pem
# kubectl get secret
NAME TYPE DATA AGE
default-token-wc5fp kubernetes.io/service-account-token 3 13d
sslexample-lc-com kubernetes.io/tls 2 16s
# kubectl describe secrets sslexample-lc-com
这样,ingress就能通过secret名称拿到要用的证书了,然后绑定本地hosts,就可以https访问了:https://sslexample.lc.com/
3、根据URL路由到多个服务
# vim url.yamlapiVersion: networking.k8s.io/v1beta1 kind: Ingress metadata: name: url-ingress annotations: nginx.ingress.kubernetes.io/rewrite-target: / spec: rules: - host: foobar.lc.com http: paths: - path: /foo backend: serviceName: service1 servicePort: 80 - host: foobar.lc.com http: paths: - path: /bar backend: serviceName: service2 servicePort: 80
工作流程:
foobar.lc.com -> 172.16.1.71 -> /foo service1:80
/bar service2:80
4、基于名称的虚拟主机
# vim vhost.yamlapiVersion: networking.k8s.io/v1beta1 kind: Ingress metadata: name: name-virtual-host-ingress spec: rules: - host: foo.lc.com http: paths: - backend: serviceName: service1 servicePort: 80 - host: bar.lc.com http: paths: - backend: serviceName: service2 servicePort: 80
工作流程:
foo.lc.com --| |-> service1:80
| 172.16.1.71 |
bar.lc.com --| |-> service2:80
9.5 Annotations对Ingress个性化配置
HTTP:配置Nginx常用参数
# vim nginx_parameter.yamlapiVersion: networking.k8s.io/v1beta1 kind: Ingress metadata: name: example-ingress annotations: kubernetes.io/ingress.class: "nginx“ nginx.ingress.kubernetes.io/proxy-connect-timeout: "600" nginx.ingress.kubernetes.io/proxy-send-timeout: "600" nginx.ingress.kubernetes.io/proxy-read-timeout: "600" nginx.ingress.kubernetes.io/proxy-body-size: "10m" spec: rules: - host: example.lc.com http: paths: - path: / backend: serviceName: nginx servicePort: 80
HTTPS:禁止访问HTTP强制跳转到HTTPS(创建https后通过http访问默认会跳转到https)
# vim redirect_https.yamlapiVersion: networking.k8s.io/v1beta1 kind: Ingress metadata: name: tls-example-ingress annotations: kubernetes.io/ingress.class: "nginx“ nginx.ingress.kubernetes.io/ssl-redirect: 'true' spec: tls: - hosts: - sslexample.lc.com secretName: secret-tls rules: - host: sslexample.lc.com http: paths: - path: / backend: serviceName: nginx servicePort: 80
9.6 Ingress Controller高可用方案
如果域名只解析到一台Ingress controller,是存在单点的,挂了就不能提供服务了。这就需要具备高可用,有两种常见方案:
左边:双机热备,选择两台Node专门跑Ingress controller,然后通过keepalived对其做主备。用户通过VIP访问。
固定ingress controller到两个node上(daemonset+nodeselector),user -> 域名 -> vip(keepalived) ha -> pod
右边:高可用集群(推荐),前面加一个负载均衡器,转发请求到后端多台Ingress controller。
固定ingress controller到两个node上(daemonset+nodeselector),user -> 域名 -> LB(nginx、lvs、haproxy)-> ingress controller -> pod