K8S 的 Service 类型
ClusterIP
默认类型,集群内部使用,集群外部无法访问
apiVersion: v1
kind: Service
metadata:
name: my-service
spec:
type: ClusterIP ## service 类型,不填的话默认 ClusterIP,自动分配 IP,或添加 spec.clusterIP 字段指定
ports:
- name: http ## 定义多个端口的话需要为每个端口指定名字
port: 80 ## service 暴露的端口
targetPort: 80 ## 关联的 Pod 的端口,不填的话默认和 port 字段相同
protocol: TCP ## 不填的话默认 TCP
- name: app-2
port: 5000
selector: ## service 会关联定义了 app:my-app 和 component:my-component 两个 label 的 Pod
app: my-app
component: my-component
比如通过 deployment 启动 pod (3 个副本)
apiVersion: apps/v1
kind: Deployment
metadata:
name: my-deployment
spec:
selector:
matchLabels:
app: my-app
component: my-component
replicas: 3
template:
metadata:
labels:
app: my-app
component: my-component
spec:
containers:
- name: my-app
image: my-image
ports:
- containerPort: 80
name: http
- containerPort: 5000
name: app-2
这样 service 和 pod 关联了起来,系统会自动为 service 分配 cluster ip,也可以通过 spec.clusterIP 指定
可以通过 service name 访问 pod,比如
curl my-service:5000/xxx
如果执行 curl 的地方和 service 不在一个 namespace,需要加上 namespace
curl my-service.my-namespace:5000/xxx
也可以通过 cluster ip 访问
curl 10.10.1.10:5000/xxx
service 会把请求转发给关联的 pod
由于 pod 有多个副本,也就实现了负载均衡
service 是一个固定的名字,所以还相当于实现了服务发现
这些 service 的建立和数据转发是由 kube-proxy 和 iptables 实现的
Services without selectors
如果 service 关联的不是 Pod,比如要创建一个 service 关联宿主机的 redis 用于测试,这时不指定 selector
apiVersion: v1
kind: Service
metadata:
name: redis
labels:
app: redis
spec:
ports:
- port: 6379
再定义一个 endpoint
apiVersion: v1
kind: Endpoints
metadata:
name: redis
subsets:
- addresses:
- ip: 10.0.2.10 # redis ip,不能是 loopback (127.0.0.0/8) 或 link-local (169.254.0.0/16)
ports:
- port: 6379
这样就可以通过 service 访问那些不是部署在 K8S 上的服务
NodePort
可以被集群外访问
同样会有一个 ClusterIP 的 service 被创建,同时在集群的每台机器上暴露同一个端口,比如都暴露 31000 端口,每台机器的 31000 端口收到的请求都会转发到 ClusterIP,这样就可以从外网访问内部服务
同时内部网络依然可以使用 ClusterIP 访问
apiVersion: v1
kind: Service
metadata:
name: my-service
spec:
type: NodePort
selector:
app: MyApp
ports:
- port: 80 ## clusterIP 暴露的端口
targetPort: 80 ## 关联的 Pod 的端口,不指定的话默认和 port 字段相同
nodePort: 31000 ## 集群的每台机器上要暴露的端口,不填的话由系统随机指定 (30000-32767)
这种方法有一定缺点:由系统随机指定的话端口号在 30000~32767,自己指定的话有可能会有端口冲突,比如有两个微服务都希望暴露 HTTP 80 端口给外部使用
正式生产环境用的比较少,主要用于 Demo,或者是低要求低成本的应用
LoadBalancer
如果云厂商(比如 AWS,AKS 等)提供外部负载均衡器,那么可以将 service 类型设置为 LoadBalancer
这样云厂商会创建一个外部公网的 load balancer,有单独的域名或 IP 地址,连接到 K8S 的 service,这样比如说有多个服务要暴露 HTTP 80 端口给外部使用,可以申请多个 LoadBalancer,就不会有冲突,每个都有单独的地址
默认依然会创建 ClusterIP 和 NodePort,外部的 LoadBalancer 是把数据发给系统创建的 NodePort,在不同的 NodePort 间做负载均衡,从 v1.20 开始可以通过设置 spec.allocateLoadBalancerNodePorts 为 false 避免创建 NodePort,这依赖于云厂商的 LoadBalancer 能不能直接把数据发给内部的 service
LoadBalancer 的 IP 有可能自己指定,也可能是云厂商自动分配
LoadBalancer 的端口默认必须都是相同的类型,比如都是 TCP,从 v1.20 开始,可以指定 MixedProtocolLBService 允许使用不同的端口类型,支持的端口类型取决于云厂商
apiVersion: v1
kind: Service
metadata:
name: my-service
spec:
type: LoadBalancer
selector:
app: MyApp
ports:
- protocol: TCP
port: 80
targetPort: 9376
name: http
- protocol: TCP
port: 443
targetPort: 9375
name: https
status:
loadBalancer:
ingress:
- ip: 192.0.2.127 ## 指定 loadBalancer 的 IP,也可能有云厂商自动分配
NAME TYPE CLUSTER-IP EXTERNAL-IP PORT(S) AGE
my-nginx-ingress-svc LoadBalancer 10.101.15.17 192.0.2.127 80:31684/TCP,443:30608/TCP 21d
EXTERNAL-IP 就是可以在公网访问的 IP
端口 80 和 443 是公网访问的端口
31684 和 30608 系统自动分配的 NodePort 端口
会转发到 MyApp 的 9375、9376 端口
由于配置 LoadBalancer 厂商需要收取费用,如果有多个服务要申请 LoadBalancer,经济上会增加负担
ExternalName
ExternalName 将一个 service 映射到一个域名上
apiVersion: v1
kind: Service
metadata:
name: my-service
namespace: prod
spec:
type: ExternalName
externalName: my.database.example.com
就像是做了一个软连接,如果要访问的域名改了,就改这个软连接就可以,其他使用这个 service 的地方都不用改
Headless Services
前面的配置中,要访问 Pod 都是通过访问 ClusterIP 实现的,无法直接访问 Pod,因为 DNS 里没有 Pod 的信息
比如下面是一个正常的 ClusterIP service
NAME TYPE CLUSTER-IP EXTERNAL-IP PORT(S) AGE
my-service ClusterIP 10.102.78.24 <none> 80/TCP,5000/TCP 22m
进入任意 Pod 后通过 nslookup 查看域名对应的 IP,域名格式是 [servicename].[namespace].svc.cluster.local
/# nslookup my-service.default.svc.cluster.local. ## 最后加点号,不然 nslookup 会自动加个后缀
Server: 10.96.0.10 ## 自己的服务器
Address: 10.96.0.10#53 ## 自己的IP
Name: my-service.default.svc.cluster.local ## 目标域名
Address: 10.102.78.24 ## 目标IP
可以看到返回的地址就是 ClusterIP 的地址,这种配置无法访问指定的 Pod,只能由 service 决定访问哪个 Pod
而 Headless 就是将 ClusterIP 指定为 None 的 service
apiVersion: v1
kind: Service
metadata:
name: my-headless
spec:
clusterIP: None ## 指定 IP 为 None
ports:
- name: http
port: 80
- name: app-2
port: 5000
selector:
app: my-app-headless
component: my-component-headless
查看创建的 service 可以看到没有分配 Cluster IP
NAME TYPE CLUSTER-IP EXTERNAL-IP PORT(S) AGE
my-headless ClusterIP None <none> 80/TCP,5000/TCP 87m
进入任意 Pod 后通过 nslookup 查看域名对应的 IP
/# nslookup my-headless.default.svc.cluster.local.
Server: 10.96.0.10
Address: 10.96.0.10#53
Name: my-headless.default.svc.cluster.local
Address: 172.17.0.5
Name: my-headless.default.svc.cluster.local
Address: 172.17.0.8
Name: my-headless.default.svc.cluster.local
Address: 172.17.0.9
可以看到返回了三个 IP,通过 kubectl describe 查看关联的三个 Pod,可以看到返回的就是三个关联 Pod 的 IP
所以 Headless 的作用就是绕过 Cluster IP 提供的负载均衡,由自己决定要访问哪个 Pod
配了 Headless 后除了可以使用 IP 访问 Pod,还可以使用域名 [podname].[servicename].[namespace].svc.cluster.local
通常是有状态服务,或者集群业务(比如部署 zookeeper 集群),才需要能知道具体的 Pod 的地址
StatefulSet
Headless 通常和 StatefulSet 结合使用,因为就是访问有状态的服务,所以才要找到具体的 Pod
apiVersion: apps/v1
kind: StatefulSet
metadata:
name: my-stateful-set
spec:
serviceName: my-headless ## 指定关联的 headless service
selector:
matchLabels:
app: my-app-headless
component: my-component-headless
replicas: 3
template:
metadata:
labels:
app: my-app-headless
component: my-component-headless
spec:
containers:
- name: my-app-headless
image: my-image
ports:
- containerPort: 80
name: http
- containerPort: 5000
name: app-2
StatefulSet 创建的 Pod 名字是固定的,通常还会关联固定的 PVC,这样重启后不仅名字一样,状态也可以恢复
NAME READY STATUS RESTARTS AGE
my-stateful-set-0 1/1 Running 0 107m
my-stateful-set-1 1/1 Running 0 107m
my-stateful-set-2 1/1 Running 0 107m
进入任意 Pod,通过 nslookup 可以得到 Pod 的 IP
/# nslookup my-headless.default.svc.cluster.local.
Server: 10.96.0.10
Address: 10.96.0.10#53
Name: my-headless.default.svc.cluster.local
Address: 172.17.0.5
Name: my-headless.default.svc.cluster.local
Address: 172.17.0.8
Name: my-headless.default.svc.cluster.local
Address: 172.17.0.9
/# nslookup my-stateful-set-0.my-headless.default.svc.cluster.local.
Server: 10.96.0.10
Address: 10.96.0.10#53
Name: my-stateful-set-0.my-headless.default.svc.cluster.local
Address: 172.17.0.5
这样就可以通过固定的名字,访问具体的 Pod
Ingress
前面提到,如果要暴露服务到外网,使用 LoadBalancer 的话成本比较高,使用 NodePort 的话不能重用端口
Ingress 可以暴露端口给外网,然后将不同的 URL 映射到不同的 Service,类似于 Nginx 之类的代理服务器
Ingress 只支持 HTTP 或 HTTPS
Ingress 配置例子
apiVersion: networking.k8s.io/v1
kind: Ingress
metadata:
name: minimal-ingress
annotations:
kubernetes.io/ingress.class: "nginx" ## 指定 Ingress Controller 的类型,比如 nginx
## 这里的 nginx 需要和 ingress controller 的配置匹配
## 比如 ingress nginx controller 的配置 --ingress-class=nginx
## 可以改成其他名字
nginx.ingress.kubernetes.io/rewrite-target: / ## 配置 Ingress Controller
spec:
## ingressClassName: external-lb ## 1.18 版本引入,替代 kubernetes.io/ingress.class
rules:
- host: "foo.bar.com" ## 可选项,可以匹配不同域名,就算同一个 IP 也可以有多个域名
http:
paths: ## 配置映射,可以配多个
- path: /testpath ## 结合 pathType: Prefix 表示匹配所有 /testpath/ 为前缀的 URL
pathType: Prefix ## 除了 Prefix 还有 Exact 和 ImplementationSpecific
backend:
service:
name: test ## 要转发的 service
port:
number: 80 ## 要转发的端口
低版本的 K8S 的配置
apiVersion: networking.k8s.io/v1beta1
kind: Ingress
metadata:
name: my-ingress
annotations:
kubernetes.io/ingress.class: "nginx"
nginx.ingress.kubernetes.io/rewrite-target: /
spec:
rules:
- http:
paths:
- path: /testpath
backend:
serviceName: test
servicePort: 80
这里的 ingress 只是配置映射关系,需要相应的 ingress controller 执行,官方提供了很多种 controller
https://kubernetes.io/docs/concepts/services-networking/ingress-controllers/
比如 nginx
https://kubernetes.github.io/ingress-nginx/deploy
上面提供了 image 和不同环境下(AKS,AWS,Minikube 等)的 yaml 文件可以直接 apply 部署
以云厂商的 nginx 为例,通常是部署一个 ingress controller 的 deployment,用于运行 nginx,并监控 Ingress 配置,如果 Ingress 配置有变化,可以相应的更改 nginx 的配置,更改 nginx 的映射关系,同时会部署一个 LoadBalancer 或 NodePort 的 service 将 nginx 和外网连上,这个 LoadBalancer、NodePort 的 targetPort 指向的是 ingress controller pod 的端口
在 v1.18 前,ingress controller 通过 kubernetes.io/ingress.class 字段指定
从 v1.18 开始,则通过 ingressClassName 字段,配合 IngressClass 资源指定
spec:
ingressClassName: external-lb
apiVersion: networking.k8s.io/v1
kind: IngressClass
metadata:
name: external-lb
spec:
controller: k8s.io/ingress-nginx
parameters:
apiGroup: k8s.example.com
kind: IngressParameters
name: external-lb
可以通过 ingress class 的 annotation 的 ingressclass.kubernetes.io/is-default-class 字段将 ingress class 指定为默认 controller,这样 ingress 配置就可以不用指定 controller
NAME HOSTS ADDRESS PORTS AGE
my-ingress * 20.102.6.105 80, 443 21d
HOSTS 是 ingress 监听的域名,不指定的话就监听 IP 对应的所有 host
ADDRESS 是外网可以访问的地址,也是和 Ingress Controller 关联的 LoadBalancer 的外部地址
ingress 的一些属性比如负载均衡、安全性等,取决于选择的 controller 类型
总结起来,数据流就是
Client -> LoadBalancer/NodePort -> Ingress Controller (如 nginx,并监控 Ingress 的配置) -> Service -> Pod