Ingress是管理外部网咯访问K8S集群中Service的API对象(典型就是HTTP),它可以提供负载均衡、SSL终端以及基于名字的虚拟host。简单点来说它就是外网访问集群应用的媒介,流程如下:
+----------+ Ingress +---------+
| internet | ---------> | Service |
+----------+ +---------+
Ingress Controller 负责实现入口(即ingress的意思),通常使用负载均衡器,但它也可以配置边缘路由器或其他前端以帮助处理流量。Ingress不会随意暴露端口或协议,向Internet公开HTTP和HTTPS以外的服务通常使用Service.Type=NodePort
或Service.Type=LoadBalancer
类型的服务。
和其他K8S资源一样,Ingress必须要有一个 Ingress Controller,仅有 Ingress Resource 是无效的,Ingress也是无法正常工作的。Ingress Controller不会随着集群的启动而自动启动,且Ingress Controller有多种,目前K8S只支持和维护GCE(Google Kubernetes Engine)和nginx2种Controller。
【Ingress Resource】
Ingress Resource 则和大部分的K8S资源差不多,需要apiVersion
、kind
和metadata
等字段,下面一个最小ingress resource的示例:
apiVersion: networking.k8s.io/v1beta1
kind: Ingress
metadata:
name: test-ingress
annotations:
nginx.ingress.kubernetes.io/rewrite-target: /
spec:
rules:
- http:
paths:
- path: /testpath
backend:
serviceName: test
servicePort: 80
Ingress通常都是使用注解annotations
来配置某些选项,具体可配置选项、支持哪些注解取决于Ingress Controller类型,上述是一个rewrite-target
的注解,spec
字段包含了负载均衡器或代理服务器的配置,最重要的是它包含了所有传入请求的匹配规则。注:Ingress Resource仅支持HTTP通信的规则。
Ingress rules是Ingress中比较重要的一部分,即上述示例中的rules
字段的含义(它是一个可以包含多种rule
对象的数组),每个Http的rule
都应该包含如下的信息:
host
:可选参数,用于限定rule适用的host,如指定为foo.bar.com
,那该rule只应用于这个host,上述示例中未指定,则该rule
适用于通过指定IP地址的所有入站HTTP流量;-paths
:一系列的path对象组成的数组,上述示例中只有一个path为/testpath
,每个path都有一个backend
对象(代表的是后端服务)与之对应;backend
:是一个serviceName
和servicePort
的组合对象,只有host
和path
都匹配成功的情况下,LoadBalancer才会将流量导向引用的服务。 通常Ingress会有一个 Default Backend,当在上述我们自定义的Ingress Resource没有匹配的rule
对象时,流量就会被导向这个Default Backend。当然这个default backend需要我们自己去配,通常不会在Ingress Resource中配置default backend,而是应该在Ingress Ctontroller中进行指定。
【Ingress的类型】
1.Single Service Ingress
即单服务Ingress,K8S当前是支持暴露单个服务的,可以通过在没有规则的情况下指定默认后端来使用ingress来完成此操作,如:
# ingress.yaml
apiVersion: networking.k8s.io/v1beta1
kind: Ingress
metadata:
name: test-ingress
spec:
backend:
serviceName: testsvc
servicePort: 80
注:
安装完ingress后,还要执行:
# 创建命名空间 ingress-nginx
kubectl create namespace ingress-nginx
kubectl apply -f https://raw.githubusercontent.com/kubernetes/ingress-nginx/master/deploy/rbac.yaml
kubectl apply -f https://raw.githubusercontent.com/kubernetes/ingress-nginx/master/deploy/with-rbac.yaml
kubectl apply -f ingress.yaml
即可,通过kubectl get ingress test-ingress
可以查看详情,包括Ingress分配给这个default backend的IP。
2.Simple fanout
fanout基于请求的HTTP URI将配置流量从单个IP路由到多个服务,Ingress 是允许将负载平衡器的数量降到最低,如:
foo.bar.com -> 178.91.123.132 -> / foo service1:4200
/ bar service2:8080
上述的过程需要一个如下的Ingress(同一个域名配置了2个path):
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
3.基于名字虚拟host
和2类似,这种类型的Ingress支持将HTTP流量路由到在同一个IP上的多个host name,流程如下:
foo.bar.com --| |-> foo.bar.com s1:80
| 178.91.123.132 |
bar.foo.com --| |-> bar.foo.com s2:80
此时的Ingress Resource应该为:
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会告诉负载均衡器将请求根据Host header路由到指定地方。如果不指定host,任意到Ingress Controller IP地址的Web流量都可以匹配,而不需要基于名称的虚拟主机。
4.TLS
可以指定包含一对TLS私钥、证书的方式来保护Ingress,当前Ingress只支持TLS 443端口和TLS终端,TLS必须包含tls.crt
(即证书)和tls.key
(即私钥),如:
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中引用这个密钥将告诉Ingress Controller使用TLS保护Ingress从客户端到负载平衡器的通道。此外,还需要确保创建的TLS机密来自包含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
注:TLS对于nginx和GCE Controller稍有不同。在实际配置中可能在配置完整证书后想让http
请求也强制转到https
,此后可使用如下注解:
# 在配置了证书时,重定向到https
nginx.ingress.kubernetes.io/ssl-redirect: "true"
若没有配置证书仍然想强制跳转https
,需要使用注解nginx.ingress.kubernetes.io/force-ssl-redirect
。上述重定向到https
时默认状态码 308 或者 307,老版本浏览器可能不支持,可选择性配置状态码为 301。
【Update Ingress】
更新一个已有的Ingress,可以使用如下的流程:
# 查看已有配置
kubectl describe ingress test
# 修改配置
kubectl edit ingress test
保存后将会更新资源,它将告诉Ingress Controller重新配置负载均衡器,当然也可以使用kubectl replace -f xx.yaml
来重新载入配置即可。
注:
ingress的文档比较好用,贴一下Annotations,这里有个坑,如果使用rancher进行配置,那所有字段尽量不要再用''
或""
包括,rancher的解析做的不是很好,他会将引号作为内容的一部分设置为对应的注解值,很坑!
常用的ingress配置模板如下:
apiVersion: extensions/v1beta1
kind: Ingress
metadata:
name: {{ .Values.deploy.app }}
annotations:
{{- range $key, $value := .Values.service.ingress.annotations }}
{{ $key }}: {{ $value | quote }}
{{- end }}
labels:
service-cluster: {{ .Values.deploy.serviceCluster }}
app: {{ .Values.deploy.app }}
project-type: java
spec:
rules:
# 可以有多个
- host: {{ .hosts }}
http:
paths:
# 可以有多个(可以正则)
- path: {{ .path }}
backend:
serviceName: {{ .serviceName }}
servicePort: {{ .servicePort }}
# 可以有多个
tls:
# 可以有多个
- hosts:
{{- range .hosts }}
- {{ . }}
{{- end }}
secretName: {{ .secretName }}
在配置跨域时,如果使用rancher可视化面板配置nginx.ingress.kubernetes.io/enable-cors
和nginx.ingress.kubernetes.io/cors-allow-origin
注解时千万不能加引号,不然找半天找不到错误在哪。但写Chart的yaml配置文件时,该怎么写就怎么写,string
类型的还是需要加上引号,跨域的示例配置为:
# 方式1:直接使用注解(但此时只能配置一个域跨域)
annotations:
# 使用rewrite配置contextPath
nginx.ingress.kubernetes.io/rewrite-target: /account/$1
# 配置跨域
nginx.ingress.kubernetes.io/enable-cors: "true"
# 这个注解只能配一个具体的值或者*
nginx.ingress.kubernetes.io/cors-allow-origin: "http://k8s-www.hhu.com"
nginx.ingress.kubernetes.io/cors-allow-methods: "GET, PUT, POST, DELETE, PATCH, OPTIONS"
nginx.ingress.kubernetes.io/cors-allow-headers: "DNT,X-CustomHeader,Keep-Alive,User-Agent,X-Requested-With,If-Modified-Since,Cache-Control,Content-Type,Authorization"
# 方式2:使用自定代码段注解,自定写跨域(这种方式可以配置多个域名跨域)
# 配置server片段
nginx.ingress.kubernetes.io/server-snippet: |
# 配置跨域(多个,单个可用注解)
set $corsHost '';
set $httpHost '';
set $httpHeaders 'DNT,X-CustomHeader,Keep-Alive,User-Agent,X-Requested-With,If-Modified-Since,Cache-Control,Content-Type,Authorization,Content-Type,X-Forwarded-For,xfilecategory,xfilename,xfilesize';
if ( $http_origin ~ http://k8s-www.hhu.com){
set $corsHost $http_origin;
set $httpHost 'k8s-www.hhu.com';
}
if ( $http_origin ~ http://k8s-m.hhu.com){
set $corsHost $http_origin;
set $httpHost 'k8s-m.hhu.com';
}
add_header Access-Control-Allow-Origin $corsHost always;
add_header Access-Control-Allow-Methods 'GET, POST, PUT, DELETE, OPTIONS, PATCH';
add_header Access-Control-Allow-Headers $httpHeaders;
add_header Access-Control-Allow-Credentials 'true';
# 配置location片段,如果是OPTIONS请求(预检)直接返回200
nginx.ingress.kubernetes.io/configuration-snippet: |
if ($request_method = OPTIONS ) {
return 200;
}
比较推崇的是上述第二种的配置方式,注意,在添加Access-Control-Allow-Origin
的header时必须接上always
,否则不生效。
ingress中配置rewrite有一个自己的注解nginx.ingress.kubernetes.io/rewrite-target
,可以自己配置为指定值,我工作中更习惯将其配置为各个服务自己的contextPath,比如:
metadata:
annotations:
...
nginx.ingress.kubernetes.io/rewrite-target: /account/$1
spec:
rules:
# 可以有多个
- host: www.hhu.com
http:
paths:
# 可以有多个(可以正则)
- path: /($/.*)
backend:
serviceName: server-account
servicePort: 80
上述path
使用的是一个正则/($/.*)
,可以匹配/
或者任意以/
开头任意多个字符,我比较推荐使用这个正则/($/.*)
,而不推荐使用/(.*)
,因为.*
要求你至少匹配1个字符,即匹配不到/
的path。可以配置www.hhu.com
域名下任意路径,在其前面加上/account
(比如访问的是www.hhu.com/info
会被路由到www.hhu.com/account/info
)。
ingress中的rewrite同样可以使用nginx.ingress.kubernetes.io/server-snippet
注解来更加多样化的配置,示例如下:
nginx.ingress.kubernetes.io/server-snippet: |
# analyzer
rewrite /behavior/data($|.*) /analyzer/behavior/data$1 break;
rewrite /hhu/event($|.*) /analyzer/hhu/event$1 break;
# mine 或者写成 rewrite /private(/|$)(.*) /mine/private$1$2 break;
rewrite /private($|.*) /mine/private$1 break;
rewrite /mine($|.*) /mine$1 break;
# shop
rewrite /shops($|.*) /shops$1 break;
# task
rewrite /internal($|.*) /task/internal$1 break;
# portal
rewrite /($|.*) /portal/$1 break;
上述注意,break
千万不能丢,像类似于/($|.*)
全匹配的正则一定要写到最下面,否则它会最先匹配(原因未知,不是说好的路径最长匹配吗……)。
hhtps的配置和跳转很简单,添加完证书参照官网配置即可,这里略过,从http重定向到https,可以使用注解:
# 方式1:该注解只在配置了HTTPS之后才会生效进行跳转
nginx.ingress.kubernetes.io/ssl-redirect: "true"
# 方式2:强制跳转到https,不论是否配置了https证书
nginx.ingress.kubernetes.io/force-ssl-redirect: "true"
在配置HTTPS过程中,其他的照常操作,并且在业务应用的ingress配置了https相关协议:
nginx.ingress.kubernetes.io/server-snippet: |
ssl_protocols SSLv3 TLSv1 TLSv1.1 TLSv1.2;
ssl_ciphers RC4:HIGH:!aNULL:!MD5;
ssl_prefer_server_ciphers on;
但在桌面端应用连接HTTPS时出现:The request was aborted: Could not create SSL/TLS secure channel
,通过fiddler抓取https握手过程,发现客户端发送clienthello后,抛出上述异常,握手失败,抓取结果如下
很明显客户端在和服务端建立通道时出现了问题,正常https(即http+SSL/TLS)的请求过程如下(图片源自网络)
对比发现在第三步断开了,在第一步客户端和服务端进行握手过程中,客户端表明自身只支持TLS协议的1.0版本,但服务端并未有响应,握手失败,推测服务端不支持TLS1.0导致,但我已经在ingress配置了对TLS1.0的支持(即TLSv1
),到容器中查看nginx.conf
,也确实出现在配置文件中了,为了确切的验证是否ssl_protocols SSLv3 TLSv1 TLSv1.1 TLSv1.2
生效,又通过SSL Server Test检测,然后问题发现了,发现服务端确实不支持TLS1.0协议(只支持1.2):
最终苦战2天发现和技术支持确认了一个wingress默认只支持TLS1.2
,如何需要开启其他的,必须通过configMap配置如下的data:
kind: ConfigMap
apiVersion: v1
metadata:
name: nginx-config
data:
ssl-ciphers: "ECDHE-RSA-AES128-GCM-SHA256:ECDHE-ECDSA-AES128-GCM-SHA256:ECDHE-RSA-AES256-GCM-SHA384:ECDHE-ECDSA-AES256-GCM-SHA384:DHE-RSA-AES128-GCM-SHA256:DHE-DSS-AES128-GCM-SHA256:kEDH+AESGCM:ECDHE-RSA-AES128-SHA256:ECDHE-ECDSA-AES128-SHA256:ECDHE-RSA-AES128-SHA:ECDHE-ECDSA-AES128-SHA:ECDHE-RSA-AES256-SHA384:ECDHE-ECDSA-AES256-SHA384:ECDHE-RSA-AES256-SHA:ECDHE-ECDSA-AES256-SHA:DHE-RSA-AES128-SHA256:DHE-RSA-AES128-SHA:DHE-DSS-AES128-SHA256:DHE-RSA-AES256-SHA256:DHE-DSS-AES256-SHA:DHE-RSA-AES256-SHA:AES128-GCM-SHA256:AES256-GCM-SHA384:AES128-SHA256:AES256-SHA256:AES128-SHA:AES256-SHA:AES:CAMELLIA:DES-CBC3-SHA:!aNULL:!eNULL:!EXPORT:!DES:!RC4:!MD5:!PSK:!aECDH:!EDH-DSS-DES-CBC3-SHA:!EDH-RSA-DES-CBC3-SHA:!KRB5-DES-CBC3-SHA"
ssl-protocols: "TLSv1 TLSv1.1 TLSv1.2"
当然上述的加密套件ssl-ciphers
可以在configMap配置,也可以在各个应用中的server去自定义配置,所以配置过上述的ConfigMap后即可,但我在工作过程中是只在ConfigMap中配置了支持的协议ssl-protocols
,加密套件是放在各个应用的Ingress中配置的:
# 方式1,注解搞定,这个配置是nginx官方给出来的加密套件
nginx.ingress.kubernetes.io/ssl-ciphers: "ALL:!aNULL:!EXPORT56:RC4+RSA:+HIGH:+MEDIUM:+LOW:+SSLv2:+EXP"
# 方式2,自定义服务片段
nginx.ingress.kubernetes.io/server-snippet: |
ssl_ciphers ALL:!aNULL:!EXPORT56:RC4+RSA:+HIGH:+MEDIUM:+LOW:+SSLv2:+EXP;
ssl_prefer_server_ciphers on;
我不太确定的是使用注解配置时ssl_prefer_server_ciphers
(指定在使用sslv3和tls协议时,服务器密码应优先于客户端密码)是否开启。调式通过后的握手过程如下:
服务端响应ClientHello的结果包含了SSL版本以及加密组件,服务器的加密组件内容是从客户端发来的加密组件列表中选出来的。
附:关于https的简介(其实是我自己不知道(+_+)?,入职后第一次遇到这么让我无从下手的问题)
https,即http+SSL/TLS,https就是在http外层套了一层TSL(Transport Layer Security-传输层安全协议)或者SSL(Secure Socket Layer-安全套接字层)的壳
虽然ingress的文档比较全(这里的全是指覆盖面广,但解释不细致,很多注解需要自己去摸索),下面对某些注解作一些记录说明:
nginx.ingress.kubernetes.io/affinity: "cookie"
该注解用作于session绑定,主要用来在nginx层生成一个黏性cookie以黏住上游服务,正常线上会有多个Pod,多多少少会遇到分布式session如何共享的问题,因为一个请求过来指向的后端服务可能有多个Server,典型的场景就是请求登录时服务端A向response中写了个cookie(可以视作session JSESSIONID
),然后让客户端每次请求时都带上这个cookie,但后续的某些请求发送到了相同Servier的服务器A1了,此时A1不能识别请求带过来的这个Cookie,导致出错。官方的解释如下:
The annotation nginx.ingress.kubernetes.io/affinity enables and sets the affinity type in all Upstreams of an Ingress. This way, a request will always be directed to the same upstream server. The only affinity type available for NGINX is cookie.If more than one Ingress is defined for a host and at least one Ingress uses nginx.ingress.kubernetes.io/affinity: cookie, then only paths on the Ingress using nginx.ingress.kubernetes.io/affinity will use session cookie affinity. All paths defined on other Ingresses for the host will be load balanced through the random selection of a backend server.
实际发现不生效,有些疑问,使用的示例yaml如下:
apiVersion: extensions/v1beta1
kind: Ingress
metadata:
name: nginx-test
annotations:
# nginx只支持 cookie 类型
nginx.ingress.kubernetes.io/affinity: "cookie"
# 自定义黏性cookie的名字,默认为 INGRESSCOOKIE
nginx.ingress.kubernetes.io/session-cookie-name: "route"
# 配置黏性cookie的path,但好像不生效
nginx.ingress.kubernetes.io/session-cookie-path: "/"
# 对应于 Max-Age
nginx.ingress.kubernetes.io/session-cookie-expires: "172800"
# 对应于 Expires
nginx.ingress.kubernetes.io/session-cookie-max-age: "172800"
spec:
rules:
- host: stickyingress.example.com
http:
paths:
- backend:
serviceName: http-svc
servicePort: 80
path: /
在后续调试过程中,需要查看route
这个cookie的Path,默认是ingress中匹配到的路由路径(即path
),其中黏性route
的值是根据路由到的Server生成随机随机值,如果有多个ingress指向相同的Service,那必须在多个ingress同时配置黏性cookie,如果只有其中一个或某些ingress配置了黏性cookie,那只会应用这些配置黏性cookie的ingress,那些未配置的ingress将不会被应用。
nginx.ingress.kubernetes.io/proxy-cookie-path
在代理返回结果时,有时需要更改cookie的path,即可使用nginx.ingress.kubernetes.io/proxy-cookie-path
注解,它的语法为
,否则不生效,示例为nginx.ingress.kubernetes.io/proxy-cookie-path: "/hhu /"
,即对应于ngixn中的proxy_cookie_path /bops /;
。
nginx.ingress.kubernetes.io/server-snippet
该注解中可以做一些较为复杂的定制,对应于nginx中的服务器块(即server
那个块),如上述中的多个rewrite
:
nginx.ingress.kubernetes.io/server-snippet: |
# shop
rewrite /shops($|.*) /shops$1 break;
# task
rewrite /internal($|.*) /task/internal$1 break;
# portal
rewrite /($|.*) /portal/$1 break;
对应生成的nginx.conf就为:
server {
server_name k8s-xx.xxx.com ;
listen 80;
set $proxy_upstream_name "-";
rewrite /shops($|.*) /shops$1 break;
rewrite /internal($|.*) /task/internal$1 break;
rewrite /($|.*) /portal/$1 break;
location /xxx {
...
}
...
}
nginx.ingress.kubernetes.io/configuration-snippet
该注解用于nginx中的location
自定义配置,会应用于所有的location
(location /
可能需要适当调整),示例如下:
# 示例1:在响应中创建一个名为Request-Id的header
nginx.ingress.kubernetes.io/configuration-snippet: |
more_set_headers "Request-Id: $req_id";
# 示例2,替换路由目标的Service
nginx.ingress.kubernetes.io/configuration-snippet: |
serviceName=
# Example location snippet
proxy_request_buffering off;
rewrite_log on;
proxy_set_header "x-additional-test-header" "location-snippet-header";
serviceName=
proxy_set_header Authorization "";
但注意这个注解的配置偶尔不是即时生效的!!
nginx.ingress.kubernetes.io/rewrite-target
注解之前其他项目组的同事有这么的需求,同一个域名下有多个服务,但这多个服务都没有context-path,通过如下的方式进行转发:
# 他们的访问方式
api.xxx.com/comment/go ---> comment服务 ---> 对应于 10.5.3.28:8080/go
api.xxx.com/account/go ---> account服务 ---> 对应于 10.5.3.28:8081/go
api.xxx.com/bops/go ---> bops服务 ---> 对应于 10.5.3.28:8082/go
# 我们的方式方式
api.xxx.com/comment/go ---> comment服务 ---> 对应于 10.5.3.28:8080/comment/go
注意域名都是相同的,那怎么做到这样的需求呢?原来我们服务都是在ingress中rewrite后通过context-path去直接转发到对应的服务,但它这里不一样,暴露出来的URL和真正访问服务的path不一样,如果不处理会出现404,通过nginx.ingress.kubernetes.io/rewrite-target
注解可以实现这个功能,配置如下:
apiVersion: extensions/v1beta1
kind: Ingress
metadata:
annotations:
nginx.ingress.kubernetes.io/rewrite-target: /$2
name: rewrite
namespace: default
spec:
rules:
- host: rewrite.bar.com
http:
paths:
- backend:
serviceName: http-svc
servicePort: 80
path: /something(/|$)(.*)
最终请求Service的是/something
或者/something/
后面的内容,但最终一个域名会出现多个ingress(不易维护),最后还是建议在项目中加上context-path来做各个应用的区分。
目前Ingress的安装很简单,参照官网作如下步骤:
wget https://raw.githubusercontent.com/kubernetes/ingress-nginx/master/deploy/static/mandatory.yaml
;quay.io/kubernetes-ingress-controller/nginx-ingress-controller:0.25.0
更改为registry.cn-shanghai.aliyuncs.com/hhu/nginx-ingress-controller:0.25.0
(已公开);kubectl apply -f mandatory.yaml
;