目录:
(1)k8s指南-概述
(2)k8s指南-架构
(3)k8s指南-工作负载(1)
(4)k8s指南-工作负载(2)
(5)k8s指南-工作负载(3)
(6)k8s指南-工作负载(4)
(7)k8s指南-Service
(8)k8s指南-Ingress
(9)k8s指南-DNS与服务发现
(10)K8S指南-平滑升级与自动扩缩容
Ingress是k8s集群中负责管理服务外部访问的API对象。
概述
什么是Ingress
通常情况下,service和pod仅可在集群内部网络中通过ip地址访问。如果想从集群外部访问集群内部,则必须要有Ingress。
Ingress则相当于一个服务网关,控制着从集群外部到集群内部的HTTP/HTTPS的访问路由。下面是一个简单的ingress对流量的路由示意图:

可以给Ingress配置提供外部可访问的URL、负载均衡、SSL和基于名称的虚拟主机等。
为什么需要Ingress
在Kubernetes中,为了使外部的应用能够访问集群内的service,最为常用的是使用NodePort和LoadBalancer两种类型的service。但这两种类型的服务各有缺点:NodePort方式会占用很多集群机器的端口;而LoadBanlancer类型要求k8s必须跑在支持的云上。当同时存在多个LoadBanlancer类型的Service时,就会占用大量公网ip地址,而Ingress正式为了解决以上两种问题而生的。
Ingress资源
Ingress必须要有Ingress控制器,单独的创建一个Ingress是没有任何效果的。通常我们说Ingress,实际上指的是Ingress配置和Ingress控制器。
Ingress控制器类似于nginx,Ingress类似于nginx的配置文件。如果只安装nginx,而没有nginx的配置文件,则此nginx将毫无意义;反过来,若只有nginx的配置文件,但没安装nginx,则配置文件无运行载体
下面是一个流量路由示意图:

从中可以看出,客户端首先对 ngdemo.qikqiak.com 执行 DNS 解析,得到 Ingress 控制器所在节点的 IP,然后客户端向 Ingress 控制器发送 HTTP 请求,然后根据 Ingress 对象里面的描述匹配域名,找到对应的 Service 对象,并获取关联的 Endpoints 列表,将客户端的请求转发给其中一个 Pod。
理想情况下,所有的Ingress控制器都应该符合参考规范,但实际上不同的Ingress控制器操作略有不同。
Ingress 控制器
Ingress控制器有很多种,有官方维护的如ingress-nginx,AWS和GCE,也有第三方的。
下面安装Ingress-nginx控制器。
按照官方文档官方yaml文,直接运行yaml文件的话国内会由于访问不了google仓库而报错。网上有人建了一个中转站,将google镜像转成docker镜像,然后通过脚本拉下来之后重新打原来的标签。因此只需要执行脚本就能将镜像拉到本地了(传送门)。
下载官方yaml文件,将其中的image中的数字摘要去掉(结果如:image: registry.k8s.io/ingress-nginx/controller:v1.6.4),这样就能启动Ingress控制器了。执行kubectl get all -n ingress-nginx,结果如下所示:
NAME READY STATUS RESTARTS AGE
pod/ingress-nginx-admission-create-9jpfg 0/1 Completed 0 5h54m
pod/ingress-nginx-admission-patch-5c6qd 0/1 Completed 1 5h54m
pod/ingress-nginx-controller-5b648bf48-wrsnd 1/1 Running 0 5h54m
NAME TYPE CLUSTER-IP EXTERNAL-IP PORT(S) AGE
service/ingress-nginx-controller LoadBalancer 10.98.207.53 localhost 80:31871/TCP,443:31482/TCP 5h54m
service/ingress-nginx-controller-admission ClusterIP 10.104.227.61 <none> 443/TCP 5h54m
NAME READY UP-TO-DATE AVAILABLE AGE
deployment.apps/ingress-nginx-controller 1/1 1 1 5h54m
NAME DESIRED CURRENT READY AGE
replicaset.apps/ingress-nginx-controller-5b648bf48 1 1 1 5h54m
NAME COMPLETIONS DURATION AGE
job.batch/ingress-nginx-admission-create 1/1 7s 5h54m
job.batch/ingress-nginx-admission-patch 1/1 8s 5h54m
这个Ingress控制器创建之后会带一个名为nginx的IngressClass,接下来我们创建Ingress,指定ingressClassName: nginx 即可使用ingress-nginx控制器。当然,也要创建Ingress路由的service和pod:
# my-nginx.yaml
apiVersion: apps/v1
kind: Deployment
metadata:
name: my-nginx
spec:
selector:
matchLabels:
app: my-nginx
template:
metadata:
labels:
app: my-nginx
spec:
containers:
- name: my-nginx
image: nginx
ports:
- containerPort: 80
---
apiVersion: v1
kind: Service
metadata:
name: my-nginx
labels:
app: my-nginx
spec:
ports:
- port: 80
protocol: TCP
name: http
selector:
app: my-nginx
---
apiVersion: networking.k8s.io/v1
kind: Ingress
metadata:
name: my-nginx
namespace: default
spec:
ingressClassName: nginx # 使用 nginx 的 IngressClass(关联的 ingress-nginx 控制器)
rules:
- host: ngdemo.qikqiak.com # 将域名映射到 my-nginx 服务
http:
paths:
- path: /
pathType: Prefix
backend:
service: # 将所有请求发送到 my-nginx 服务的 80 端口
name: my-nginx
port:
number: 80
# 不过需要注意大部分Ingress控制器都不是直接转发到Service
# 而是只是通过Service来获取后端的Endpoints列表,直接转发到Pod,这样可以减少网络跳转,提高性能
执行kubectl get ingress -n ingress-nginx查看Ingress:
NAME CLASS HOSTS ADDRESS PORTS AGE
my-nginx nginx ngdemo.qikqiak.com localhost 80 94m
可以看到Ingress控制器地址为localhost,修改/etc/hosts,将域名ngdemo.qikqiak.com指向本地。然后在浏览器访问该域名,结果如下:

访问该域名,实际上就是访问Ingress控制器,Ingress控制器根据Ingress配置的规则,将请求转发给my-nginx服务,从而返回该页面。
Ingress
下面是一个最简单的Ingress定义:
apiVersion: networking.k8s.io/v1
kind: Ingress
metadata:
name: minimal-ingress
annotations:
nginx.ingress.kubernetes.io/rewrite-target: /
spec:
ingressClassName: nginx-example
rules:
- http:
paths:
- path: /testpath
pathType: Prefix
backend:
service:
name: test
port:
number: 80
如上所示,Ingress spec中包含配置loadbalancer或proxy server的所有信息。最重要的是,它包含了一个匹配所有入站请求的规则列表。目前,Ingress只支持HTTP(S)请求。
如果省略ingressClassName,那么你应该定义一个默认的Ingress class。
当然也有一些Ingress控制器不需要默认的IngressClass。比如Ingress-NGINX,它可以通过参数--watch-ingress-without-class来配置,不过仍然推荐指定一个默认IngressClass。
Ingress规则
每个Http规则包含以下内容:
- 一个可选的主机。在示例中没有指定主机,所以该规则适配所有通过指定ip地址进来的http请求。如果指定了host(比如foo.bar.com),则入站请求还需要匹配主机。也就是说请求中的host与规则中的host相同才允许进入。
- 路径列表(例如
/testpath)。每个路径都有一个与之关联的,由service.name和service.port.name或service.port.number定义的后端服务(如test:80)。只有当host和path都匹配了,才能被负载均衡器转发给对应的服务。 - 后端是Service文档中描述的服务和端口的组合。匹配了入站规则的请求将被发送给后端。
通常会在Ingress 控制器中配置一个默认的后端,以便没有匹配入站规则的请求被转发给这个默认后端。
默认后端
如果没有设置Ingress规则,则所有的流量将被发送到默认后端 defaultBackend,也就是说,如果没有设置rules,则defaultBackend是必须有的。注意,默认后端通常是Ingress控制器的常规配置项,而不是在Ingress中指定。
默认后端用于处理未匹配任何规则的请求,如果没有指定默认后端,则这些请求将被转发到Ingress控制器。
资源后端
事实上后端除了引用一个Service之外,还可以通过一个resource资源进行关联,这也是所谓的资源后端。资源后端是一个对象引用,指向同一个命名空间下的另一个k8s资源。资源后端和服务后端是互斥的,在二者均被设置时会无法通过合法性检查。
资源后端的一种常见用法是将所有入站数据转发到对象存储后端进行存储。
下面是一个资源Ingress:
apiVersion: networking.k8s.io/v1
kind: Ingress
metadata:
name: ingress-resource-backend
spec:
defaultBackend:
resource:
apiGroup: k8s.example.com
kind: StorageBucket
name: static-assets
rules:
- http:
paths:
- path: /icons
pathType: ImplementationSpecific
backend:
resource:
apiGroup: k8s.example.com
kind: StorageBucket
name: icon-assets
创建了如上的Ingress之后,你可以通过describe命令查看:
kubectl describe ingress ingress-resource-backend
Name: ingress-resource-backend
Labels: <none>
Namespace: default
Address:
Ingress Class: <none>
Default backend: APIGroup: k8s.example.com, Kind: StorageBucket, Name: static-assets
Rules:
Host Path Backends
---- ---- --------
*
/icons APIGroup: k8s.example.com, Kind: StorageBucket, Name: icon-assets
Annotations: <none>
Events: <none>
该Ingress对象表明所有/icons请求会被路由到相同命名空间下名为icon-assets的StorageBucket资源中去进行处理。
路径类型
Ingress中的每个路径都需要有对应的路径类型,没有路径类型是不合法的。当前支持的路径类型有三种:
- ImplementationSpecific: 该类型的路径匹配取决于IngressClass的具体实现。可以将其作为单独的pathType,也可以将其与Prefix或Exact类型作相同处理。
- Exact:严格匹配,大小写敏感
- Prefix:基于以
/分隔的URL路径前缀匹配,大小写敏感
下面是一个匹配示例
| 路径类型 | 匹配路径 | 请求路径 | 是否匹配 |
|---|---|---|---|
| Prefix | / | (all paths) | 是 |
| Prefix | /foo | /foo | 是 |
| Exact | /foo | /foo/ | 否 |
| Exact | /foo/ | /foo | 否 |
| Prefix | /foo | /foo, /foo/ | 是 |
| Prefix | /foo/ | /foo, /foo/ | 是 |
| Prefix | /aaa/bb | /aaa/bbb | 否 |
| Prefix | /aaa/bbb | /aaa/bbb | 是 |
| Prefix | /aaa/bbb/ | /aaa/bbb | 是,忽略斜杠 |
| Prefix | /aaa/bbb | /aaa/bbb/ | 是,忽略斜杠 |
| Prefix | /aaa/bbb | /aaa/bbb/ccc | 是,子路径匹配 |
| Prefix | /aaa/bbb | /aaa/bbbxyz | 否,前缀不匹配 |
| Prefix | /, /aaa | /aaa/ccc | 是,前缀匹配/aaa |
| Prefix | /, /aaa, /aaa/bbb | /aaa/bbb | 是,前缀匹配/aaa/bbb |
| Prefix | /, /aaa, /aaa/bbb | /ccc | 是,前缀匹配/ |
| Prefix | /aaa | /ccc | 否 |
| Mixed | /foo (Prefix), /foo (Exact) | /foo | 是,严格匹配优先 |
多重匹配
在某些情况下,Ingress中的多条路径会匹配同一请求,这种情况下最长的匹配路径优先。如果有两条相同的匹配路径,则精确路径优先于前缀路径。
主机名通配符
主机名可以精确匹配,也可以通过通配符来匹配。
下面是一个通配符匹配示例:
| host | host header | match? |
|---|---|---|
| *.foo.com | bar.foo.com | 基于相同的后缀匹配 |
| *.foo.com | baz.bar.foo.com | 不匹配 |
| *.foo.com | foo.com | 不匹配 |
Ingress Class
在上文中说过,如果在Ingress定义时省略了ingressClassName,那么需要定义一个默认的Ingress class。在Ingress class中,定义了实现Ingress的控制器。下面是一个示例:
apiVersion: networking.k8s.io/v1
kind: IngressClass
metadata:
name: external-lb
spec:
controller: example.com/ingress-controller
parameters:
apiGroup: k8s.example.com
kind: IngressParameters
name: external-lb
其中,比较重要的属性是metadata.name和spec.controller,前者是这个Ingress Class的名称,也就是Ingress中的spec.ingressClassName,后者是Ingress Controller的名称。另外,parameters字段可用于引用其他资源以提供额外的配置,其类型由controller决定。
默认的Ingress Class用于处理所有没有指定Ingress Class的Ingress资源,只需要将ingressclass.kubernetes.io/is-default-class设置为true即可。
注意,当存在多个默认Ingress Class时,新的Ingress如果没有指定
ingressClassName则不会被允许创建。解决这个问题只需要确保集群中最多只能有一个默认的IngressClass。
多个Ingress Controller
除了可能会有多个不同类型的Ingress Controller外,还可能存在多个相同的Ingress Controller,比如部署了两个NGINX Ingress Controller,一个负载处理外网访问,一个负责处理内网访问。
此时也可以通过上面的方式,为每个Controller指定唯一的一个class。
Ingrss Class作用域
集群作用域和命名空间作用域
如果设置了spec.parameters字段且未设置spec.parameters.scope字段,或者将spec.parameters.scope字段设置为Cluster时,则该IngressClass所引用的是一个集群作用域的资源。
除了集群作用域之外,Ingrss Class还可以设置Namespace作用域。
Ingress类型
单服务Ingress
暴露单个服务的Ingress,如下所示:
apiVersion: networking.k8s.io/v1
kind: Ingress
metadata:
name: test-ingress
spec:
defaultBackend:
service:
name: test
port:
number: 80
Simple fanout
简单扇出,也可以说是多服务Ingress。它可以将来自同一IP地址的流量路由到多个Service。如下图所示:

对应的Ingress如下:
apiVersion: networking.k8s.io/v1
kind: Ingress
metadata:
name: simple-fanout-example
spec:
rules:
- host: foo.bar.com
http:
paths:
- path: /foo
pathType: Prefix
backend:
service:
name: service1
port:
number: 4200
- path: /bar
pathType: Prefix
backend:
service:
name: service2
port:
number: 8080
基于名称的虚拟托管
基于名称的虚拟主机支持将针对多个主机名的HTTP流量路由到同一IP地址上。如下图所示:
apiVersion: networking.k8s.io/v1
kind: Ingress
metadata:
name: name-virtual-host-ingress
spec:
rules:
- host: foo.bar.com
http:
paths:
- pathType: Prefix
path: "/"
backend:
service:
name: service1
port:
number: 80
- host: bar.foo.com
http:
paths:
- pathType: Prefix
path: "/"
backend:
service:
name: service2
port:
number: 80
如果创建的Ingress资源没有在rules中定义任何的hosts,则可以匹配指向Ingress控制器ip地址的任何网络流量,而无需基于名称的虚拟主机。
TLS
你可以通过设定包含TLS私钥和证书的Secret来保护Ingress。
你可以通过以下几种方式获得证书:
- 自签名证书:用自己的CA(证书颁发机构)来创建和签名TLS证书。
- 购买一个TLS证书:从浏览器和操作系统信任的知名CA购买TLS证书。
- 使用LetSencrpt证书:LetSencrpt是一家非盈利的可信证书颁发机构,提供免费的TLS证书。
当你把TLS证书作为Kubernetes Secret添加到Ingress里去时,Ingress 控制器就能获取到证书,并使其变成自己的配置信息。
在Ingress-Nginx控制器中,TLS证书就是由nginx.conf动态地处理:
ssl_certificate_by_lua_block {
certificate.call()
}
下面是Ingress TLS的工作流程:

TLS Secret的数据中必须包含以tls.crt保存的证书和以tls.key保存的私钥。例如:
apiVersion: v1
kind: Secret
metadata:
name: testsecret-tls
namespace: default
data:
tls.crt: base64 编码的证书
tls.key: base64 编码的私钥
type: kubernetes.io/tls
可以使用cert-manager 来自动生成和续期证书,这样就不用手动维护了。
可以用过以下命令来查看证书信息:
kubecctl get secret -o jsonpath='{.data.tls\.crt}' | base64 -d > /tmp/tls.cert
openssl x509 -in /tmp/tls.cert -noout -text
但是需要注意,openssl 默认只能查看一个证书,如果cert 中有多个证书,可以使用 keystore explorer 之类的可视化工具来查看。
把TLS证书添加到Ingress中:
spec:
ingressClassName: nginx
tls:
- hosts:
- foo.bar.com
secretName: testsecret-tls
rules:
- hosts: "foo.bar.com"
http:
paths:
...
注意,默认规则上无法使用 TLS,因为需要为所有可能的子域名发放证书。 因此,tls 字段中的 hosts 的取值需要与 rules 字段中的 host 完全匹配。
默认情况下,TLS连接终止于Ingress节点,也就是说,Ingress控制器与Service及其Pod之间的流量都以明文传输。
如果想要全程加密,可以通过在ingress controller中添加支持的annotation。例如如果使用的是Ingress-nginx控制器,可以在Ingress中添加注解:nginx.ingress.kubernetes.io/backend-protocol: "HTTPS"。
各种Ingress控制器所支持的TLS功能之间存在差异。
Ingress 不会验证后端证书,只要能握手成功就行。如果需要强制校验服务端证书,需要额外配置nginx.ingress.kubernetes.io/proxy-ssl-verify: "on",也就是单向TLS。如果服务端也要校验ingress作为客户端的证书,还需要配置nginx.ingress.kubernetes.io/proxy-ssl-secret: "ms/secret-with-ca。
验证服务端证书时可能涉及证书链: 服务端证书 -> 中间CA -> 根CA。
参数nginx.ingress.kubernetes.io/proxy-ssl-verify-depth: "0"决定在证书链校验中检查多少层证书。0表示只校验服务端证书本身。
Ingress 与后端双向认证
下面用一个例子来演示ingress和后端的双向认证。
ingress配置如下:
...
nginx.ingress.kubernetes.io/backend-protocol: "HTTPS"
nginx.ingress.kubernetes.io/proxy-ssl-secret: "default/internal-tls-secret" # with namespace!
nginx.ingress.kubernetes.io/proxy-ssl-verify: "on" # default is off!
nginx.ingress.kubernetes.io/proxy-ssl-verify-depth: "0" # self-signed
nginx.ingress.kubernetes.io/proxy-ssl-name: "test-srv" # name for SNI and hostname verification
...
为了方便,客户端证书和服务端证书用同一个,并且都放在internal-tls-secret中。cert-manager提供此功能:
apiVersion: cert-manager.io/v1
kind: Issuer
metadata:
name: internal-selfsigned-issuer
spec:
selfSigned: {}
---
apiVersion: v1
kind: Secret
type: Opaque
metadata:
name: internal-tls-truststore-password
stringData:
password: donotchangeit
---
apiVersion: cert-manager.io/v1
kind: Certificate
metadata:
name: internal-tls-certificate
spec:
secretName: internal-tls-secret # secret will contain: tls.crt, tls.key and ca.crt
privateKey:
size: 256
algorithm: ECDSA
encoding: PKCS8
duration: 87600h # ~10 years
renewBefore: 720h # 24*30 = 30 days
usages:
- server auth
- client auth
keystores:
pkcs12: # additionally create a PKCS12 keystore & truststore for Java services
create: true
passwordSecretRef: # the created file is protected by this password:
name: internal-tls-truststore-password
key: password
subject:
localities:
- namespace-internal-communication # to signify that this is only for internal communication
organizations:
- test # to make the certificate easily identifiable with the subject
dnsNames:
- test-srv # preferably the actual service endpoint name, to allow hostname verification
issuerRef:
kind: Issuer
name: internal-selfsigned-issuer
此Certificate可以生成一个secret文件,其中包含客户端证书(tls.crt),客户端私钥(tls.key),服务端证书链和私钥(keystore.p12),服务单信任列表(truststore.p12)。
ingress默认使用 tls.crt 作为客户端证书来使用。
根据certificate配置(server auth & client auth)可知,生成的客户端证书和服务端证书是同一个证书,而且由于Issuer是自签名的,所以该证书本身就是CA。
因此,服务端证书链中只有(服务端)证书自己本身,服务端信任库中只有(客户端)证书本身。换句话说,keystrore.p12只比truststore.p12多一个私钥。
服务端配置:
...
ssl:
certificate:
key-store-file:{keystore-folder}/keystore.p12 # {keystore-folder}为挂载的容器目录
key-store-password: donotchangeit
trust-store-file: {keystore-folder}/keystore.p12
trust-store-password: donotchangeit
client-auth: request
由于 keystore.p12 和 truststore.p12 中包含相同的证书,因此 trust-store-file 配置为 keystore.p12 也是没有问题的。
从安全和实践的角度出发,并不推荐服务端和客户端使用同一张证书。一是身份不可区分,而是最小权限原则被破坏,安全风险更大。
除了ingress与服务端的双向认证外,还可以通过auth-tls-verify-client参数来配置客户端与ingress之间的双向认证,这里就不多述了。
负载均衡
Ingress控制器启动时会使用一些适用于所有Ingress的负载均衡策略设置,例如负载均衡算法等。更高级的负载均衡概念(例如持久会话、动态权重)尚未通过 Ingress 公开。
值得注意的是,健康检查不是通过Ingress直接暴露的。在Kubernetes中存在并行的概念,比如就绪检查,允许你实现相同的目的。
Ingress是7层的负载均衡,负载将HTTP/HTTPS请求路由到Service。接下来再由service通过iptables来将请求转发到具体的pod上[3]。
参考资料
[1]. https://kubernetes.io/docs/concepts/services-networking/ingress/
[2]. https://cloud.tencent.com/developer/article/1925519
[3]. https://tech.citahub.com/k8s-service-iptables/

3402

被折叠的 条评论
为什么被折叠?



