目录
- Service 的用法
- docker 对外提供服务
- service 对外提供服务
- 从集群外部访问 Pod 或 Service
- 将容器应用的端口号映射到物理机
- 将 Service 的端口号映射到物理机
- Ingress:HTTP 7层路由机制
- 创建Ingress Controller和默认的backend服务
k8s 通过创建 Service,可以为一组具有相同功能的容器应用提供一个统一的入口地址,并且将请求负载分发到后端的各个容器应用上。
Service 的用法
docker 对外提供服务
一般来说,对外提供服务的应用程序,对于容器应用最简便的方式就是通过 TCP/IP 机制及监听 IP 和端口号来实现。例如,定义一个提供Web 服务的 RC,由两个 Tomcat 容器副本组成,每个容器都通过 containerPort 设置提供服务的端口号为 8080:
# webapp-rc.yamlapiVersion: v1
kind: ReplicationController
metadata:name: webapp
spec:replicas: 2template:metadata:name: webapplabels:app: webappspec:containers:- name: webappimage: tomcatports:- containerPort: 8080
创建 RC,获取 IP:
kubectl create -f webapp-rc.yamlkubectl get pods -l app=webapp -o yaml | grep podIP
# podIP: 172.17.1.3
# podIP: 172.17.1.4
可以通过这两个 Pod 的 IP 地址和端口号访问 Tomcat 服务:
curl 172.17.1.3:8080curl 172.17.1.4:8080
但是,这种方式是不可靠的。例如当 Pod 所在的 Node 发生故障,Pod 将被 k8s 重新调度到另一个 Node,Pod 的 IP 地址将发生变化。我们着重考虑的是,如果容器应用本身是分布式的部署方式,通过多个实例共同提供服务,就需要在这些实例的前端设置一个负载均衡器来实现请求的分发。k8s 中的 Service 就是用于解决这些问题的核心组件。
service 对外提供服务
以上面的 webapp 为例,为了访问 Tomcat Pod 实例,我们来创建一个 Service 提供服务:
kubectl expose rc webapp
# service "webapp" exposed
# 查看新创建的 Service,可以看到系统为它分配了一个虚拟的IP地址(ClusterIP),Service 所需的端口号则从 Pod 中的 containerPort 复制而来:
kubectl get svc
# NAME CLUSTER-IP EXTERNAL-IP PORT(S) AGE
# webapp 169.169.235.79 <none> 8080/TCP 3scurl 169.169.235.79:8080
这里,对 Service 地址 169.169.235.79:8080 的访问被自动负载分发到了后端两个 Pod 之一:172.17.1.3:8080或172.17.1.4:8080。
除了使用 kubectl expose 命令创建 Service,也可以通过配置文件定义 Service,再通过 kubectl create 命令进行创建:
apiVersion: v1
kind: Service
metadata:name: webapp
spec:ports:- port: 8081targetPort: 8080selector:app: webapp
Service 定义中的关键字段是 ports 和 selector。上面 ports 定义部分指定了 Service 所需的虚拟端口号为 8081,由于与 Pod 容器端口号8080 不一样,所以需要再通过 targetPort 来指定后端 Pod 的端口号。selector 定义部分设置的是后端 Pod所拥有的 label:app=webapp。
创建该Service并查看其 ClusterIP 地址:
kubectl create -f webapp-svc.yaml
# service "webapp" created
kubectl get svc
# NAME CLUSTER-IP EXTERNAL-IP PORT(S) AGE
# webapp 169.169.28.190 <none> 8081/TCP 3scurl 169.169.28.190:8081
目前 k8s 提供了两种负载分发策略:RoundRobin 和 SessionAffinity:
- RoundRobin:轮询模式(默认),即轮询将请求转发到后端的各个 Pod 上
- SessionAffinity:基于客户端 IP 地址进行会话保持的模式,即第1次将某个客户端发起的请求转发到后端的某个 Pod 上,之后从相同的客户端发起的请求都将被转发到后端相同的 Pod 上
在默认情况下,k8s 采用 RoundRobin 模式对客户端请求进行负载分发,也可以通过设置 service.spec.sessionAffinity=ClientIP 来启用SessionAffinity 策略。这样,同一个客户端 IP 发来的请求就会被转发到后端固定的某个 Pod 上了。通过 Service 的定义,k8s 实现了一种分布式应用统一入口的定义和负载均衡机制。
从集群外部访问 Pod 或 Service
Pod 和 Service 都是 k8s 的虚拟概念,集群外的客户端系统无法通过 Pod 的 IP 地址或者 Service 的虚拟 IP 地址和虚拟端口号访问他们。我们需要将 Pod 或 Service 的端口号映射到宿主机,以使客户端应用能够通过物理机访问容器应用。
将容器应用的端口号映射到物理机
- 通过设置容器级别的 hostPort,将容器应用的端口号映射到物理机上:
# pod-hostport.yaml
apiVersion: v1
kind: Pod
metadata:name: webapplabels:app: webapp
spec:containers:- name: webappimage: tomcatports:- containerPort: 8080hostPort: 8081
kubectl create -f pod-hostport.yaml # pod "webapp" createdcurl 192.168.18.3:8081 # 宿主机ip
- 通过设置 Pod 级别的 hostNetwork=true,该 Pod 中所有容器的端口号都将被直接映射到物理机上。在设置 hostNetwork=true 时需要注意,在容器的 ports 定义部分如果不指定 hostPort,则默认 hostPort 等于 containerPort,如果指定了 hostPort,则 hostPort 必须等于containerPort 的值:
# pod-hostnetwork.yaml
apiVersion: v1
kind: Pod
metadata:name: webapplabels:app: webapp
spec:hostNetwork: true # 设置宿主机网络containers:- name: webappimage: tomcatimagePullPolicy: Neverports:- containerPort: 8080
kubectl create -f pod-hostnetwork.yaml # pod "webapp" createdcurl 192.168.18.4:8080
将 Service 的端口号映射到物理机
- 通过设置 nodePort 映射到物理机,同时设置 Service 的类型为 NodePort:
apiVersion: v1
kind: Service
metadata:name: webapp
spec:type: NodePort # service 类型设置为 NodePortports:- port: 8080targetPort: 8080nodePort: 8081 # 指定 nodePort 端口号selector:app: webapp
kubectl create -f webapp-svc-nodeport.yaml
# service "webapp" created
# 注意,这里系统提示:由于要使用物理机的端口号,所以需要在防火墙上做好相应的配置,以使外部客户端能够访问到该端口
curl 192.168.18.4:8081
- 通过设置 LoadBalancer 映射到云服务商提供的 LoadBalancer 地址。这种用法仅用于在公有云服务提供商的云平台上设置 Service 的场景。
示例中,status.loadBalancer.ingress.ip 设置的 146.148.47.155 为云服务商提供的负载均衡器的 IP 地址。对该 Service 的访问请求将会通过 LoadBalancer 转发到后端 Pod 上,负载分发的实现方式则依赖于云服务商提供的 LoadBalancer 的实现机制:
kind: Service
apiVersion: v1
metadata:name: my-service
spec:selector:app: MyAppports:- protocol: TCPport: 80targetPort: 9376nodePort: 30061clusterIP: 10.0.171.239loadBalancerIP: 78.11.24.19type: LoadBalancer
status:loadBalancer:ingress:- ip: 146.148.47.155
Ingress:HTTP 7层路由机制
如前所述,Service 的表现形式为 IP:Port,即工作在 TCP/IP 层。而对于基于 HTTP 的服务来说,不同的 URL 地址经常对应到不同的后端服务或者虚拟服务器(Virtual Host),这些应用层的转发机制仅通过 k8s 的 Service 机制是无法实现的。
Ingress 资源对象,可以将不同 URL 的访问请求转发到后端不同的 Service,以实现 HTTP 层的业务路由机制。k8s 使用了一个 Ingress 策略定义和一个具体的 Ingress Controller,两者结合并实现了一个完整的 Ingress 负载均衡器。代理不同后端 Service 而设置的负载均衡服务,就是 k8s 里的 Ingress 服务。
可以这样理解:Ingress 就是 Service 的 “Service”,是“反向代理”的一种抽象。
使用 Ingress 进行负载分发时,Ingress Controller 基于 Ingress 规则将客户端请求直接转发到 Service 对应的后端 Endpoint(Pod)上,这样会跳过 kube-proxy 的转发功能,kube-proxy 不再起作用。如果 Ingress Controller 提供的是对外服务,则实际上实现的是边缘路由器的功能。
为使用 Ingress,需要创建 Ingress Controller(带一个默认 backend 服务)和 Ingress 策略设置来共同完成。
创建Ingress Controller和默认的backend服务
在定义 Ingress 策略前,先部署 Ingress Controller,来实现为所有后端 Service 提供统一入口。Ingress Controller 需要实现基于不同 HTTP URL 向后转发的负载分发规则,并可以灵活设置7层负载分发策略。如果公有云服务商能够提供该类型的 HTTP 路由 LoadBalancer,则也可设置其为 Ingress Controller。
下面示例中,使用谷歌提供的 nginx-ingress-controller 镜像来创建 Ingress Controller。该 Ingress Controller 以 daemonset 的形式进行创建,在每个 Node 上都将启动一个Nginx服务。
Nginx 容器设置了 hostPort,将容器应用监听的 80 和 443 端口号映射到物理机上,使得客户端应用可以通过 URL 地址“http://物理机IP:80”或“https://物理机IP:443”来访问该 Ingress Controller。这使得 Nginx 类似于通过 NodePort 映射到物理机的 Service,成为代替 kube-proxy的 HTTP 层的 Load Balancer:
# nginx-ingress-daemonset.yaml
apiVersion: extensions/v1beta1
kind: DaemonSet
metadata:name: nginx-ingress-lblabels:name: nginx-ingress-lbnamespace: kube-system
spec:template:metadata:labels:name: nginx-ingress-lbspec:terminationGracePeriodSeconds: 60containers:- image: gcr.io/google_containers/nginx-ingress-controller:0.9.0-beta.2name: nginx-ingress-lbreadinessProbe:httpGet:path: /healthzport: 10254scheme: HTTPlivenessProbe:httpGet:path: /healthzport: 10254scheme: HTTPinitialDelaySeconds: 10timeoutSeconds: 1ports:- containerPort: 80hostPort: 80 # 映射到主机80端口- containerPort: 443hostPort: 443env:- name: POD_NAMEvalueFrom: # Downward API,将Pod信息注入为环境变量fieldRef:fieldPath: metadata.name- name: POD_NAMESPACEvalueFrom:fieldRef:fieldPath: metadata.namespaceargs:- /nginx-ingress-controller- --default-backend-service=$(POD_NAMESPACE)/default-http-backend
为了让 Ingress Controller 正常启动,我们配置一个默认的 backend,用于在客户端访问的 URL 地址不存在时,返回404。这个 backend 服务用任何应用实现都可以,只要满足对根路径“/”的访问返回404应答,并且提供 /healthz 路径以使 kubelet 完成对它的健康检查。另外,由于 Nginx 通过 default-backend-service 的服务名称(Service Name)去访问它,所以需要 DNS 服务正确运行:
# default-backend.yamlapiVersion: extensions/v1beta1
kind: Deployment
metadata:name: default-http-backendlabels:k8s-app: default-http-backendnamespace: kube-system
spec:replicas: 1template:metadata:labels:k8s-app: default-http-backendspec:
terminationGracePeriodSeconds: 60
containers:
- name: default-http-backendimage: gcr.io/google_containers/defaultbackend:1.0livenessProbe:httpGet:path: /healthzport: 8080scheme: HTTPinitialDelaySeconds: 30timeoutSeconds: 5ports:- containerPort: 8080resources:limits:cpu: 10mmemory: 20Mirequests:cpu: 10mmemory: 20Mi
---
apiVersion: v1
kind: Service
metadata:name: default-http-backendnamespace: kube-systemlabels:k8s-app: default-http-backend
spec:ports:- port: 80targetPort: 8080selector:k8s-app: default-http-backend
创建 backend 服务,并且创建 nginx-ingress-controller:
kubectl create -f default-backend.yaml
# deployment "default-http-backend" created
# service "default-http-backend" createdkubectl create -f nginx-ingress-daemonset.yaml # daemonset "nginx-ingress-lb" created# 查看 default-http-backend 和 nginx-ingress-controller 容器是否正确运行
kubectl get pod --namespace=kube-system
# NAME READY STATUS RESTARTS AGE
# default-http-backend-1132503640-84lnv 1/1 Running 0 3m
# kube-dns-v11-z3cb0 4/4 Running 0 10m
# nginx-ingress-lb-5jbwv 1/1 Running 0 3m
# nginx-ingress-lb-60j7h 1/1 Running 0 3m
# nginx-ingress-lb-dttr9 1/1 Running 0 3m# 用 curl 访问任意 Node 的 80 端口号,验证 nginx-ingress-controller 和 default-http-backend 服务正常工作:
curl k8s-node-2 # default backend -404