从第三节:的示例中,已经运行起来了一个 Nginx 的 Pod,处于好奇,当运行起来之后及时看到状态是 Runing 的,也会想尝试用浏览器访问试试看是否真的运行了。但是,事情就是这么尴尬,刚打开浏览器,然后就发现不知道该访问什么地址,第三篇文章里面也没说啊,假设我告诉你查询 Pod 可以加一个参数,可以看到 IP:
[root@liqiang.io]# kubectl get pods first-pod -o wide
NAME READY STATUS RESTARTS AGE IP NODE
first-pod 1/1 Running 0 14h 172.17.0.2 host-79
这里有一个 IP: 172.17.0.2,然而,当你尝试访问的时候,一般情况下是访问不通的(当然,不排除你网络设置等原因可以访问通),这就很尴尬了啊,我运行的应用不就是想被访问的吗,现在访问不了是几个意思?
这个时候就要聊聊 K8S 对于 Pod 的设计理念了,Pod 是最小的单元,往往当我们运行一个应用的时候,对 Pod 有一些额外的要求,例如高可用或者多实例,这就意味着如果你只记住一个 Pod 的 IP,那么很多时候都是会有问题的,例如高可用,可能会因为 Pod 的重建而改变,这样你记住的那个 IP 就失效了。一个很自然的想法就是,能不能把这个 IP 固定住,这是个好的想法,但是实现的时候有一些可以扩展的地方,例如,高可用类型的 Pod 可以固定 IP,那么我多个实例的 IP 做负载均衡要不要固定 IP 呢?于是乎,就到了这篇文章要介绍的新的资源:Service。
为什么要存在 Service
从前面的描述中,我们已经知道了 Service 存在的意义,总结起来一下亮点:
- 因为 k8s 里面 POD 是可以被调度,并且重建的,所以没有固定 IP;
- POD 的数量可能不是一个,当有多个 POD 实例的时候后负载均衡的需求。
所以可以用这个图来展示一下 Pod 和 Service 的关系:
Service 操作
按照惯例,要开始介绍一些例子了,承接前面将 Pod 的描述,在 K8S 中,资源通用的字段 Service 都会有,下面就给出一个示例:
[root@liqiang.io]# cat /home/liqiang.io/blog/kubernetes/guide/chap04/00-simple-service.yaml
---
apiVersion: v1
kind: Service
metadata:
name: first-svc
spec:
type: NodePort
selector:
app: nginx
ports:
- protocol: TCP
port: 5580
targetPort: 80
nodePort: 32280
[root@liqiang.io]# kubectl apply -f 00-simple-service.yaml
service "first-svc" created
[root@liqiang.io]# kubectl get svc first-svc
NAME CLUSTER-IP EXTERNAL-IP PORT(S) AGE
first-svc 10.110.87.116 <nodes> 5580:32280/TCP 3m
这里有几个地方需要说明一下:
selector
:过滤携带 labelsapp=nginx
的 pod- pod 提供服务的端口:80
- service 代理 POD 的内部端口为:5580
- service 代理 POD 的外部端口为:32280
可能你会认为这个时候打开浏览器就可以通过 10.110.87.116
这个 IP 访问到这个 Service 了,但是事实上,一般情况下也是不行的。这个 IP 是集群内部使用的,集群外部也是访问不了的,那么如果你想测试验证一下该怎么办?你需要访问的是 K8S 在的集群的 IP,以及上面这里指定的 32280 端口,也就是说:
[root@liqiang.io]# curl -I 192.168.62.255:32280
HTTP/1.1 200 OK
Server: nginx/1.17.0
Date: Sun, 30 Jun 2019 05:53:09 GMT
Content-Type: text/html
Content-Length: 612
Last-Modified: Tue, 21 May 2019 14:23:57 GMT
Connection: keep-alive
ETag: "5ce409fd-264"
Accept-Ranges: bytes
这样就访问通了这个 Pod,这个 IP 可以是集群中任意一个节点的 IP,都没有关系。
外部访问 Service 的几种方式
这里之所以我们在集群外部能访问到 Service,是因为我指定了 Service 的 Type 是 NodePort,在 K8S 中,如果不指定这个 Type 的话,Service 是只能在内部访问的,集群外部是访问不了的。要想在集群外部访问,就需要特别的设置,这里有几种可以设置的方式:
- NodePort:我们这里已经介绍过了
- LoadBalancer:这个一般只有在公有云上使用,部分私有云可支持
- Ingress:这不是 L4 的代理,而是一种 L7 的方式。
NodePort 的使用就如示例中介绍的那样就可以访问了。LoadBalancer 一般也不在私有云中使用,所以比较难以尝试,就不介绍了。最后一种就是 Ingress,在一般的尝试中,我们都是通过 Nginx-Ingress 的方式进行介绍,这里因为入门的缘故,也就不介绍得那么深了,可以理解成我们将以前写 Nginx 的配置的方式转化成写 K8S 资源配置的形式来处理就可以了。
内部访问 Service
那么在我们集群内部又要如何访问 Service 呢?例如我设置了一个名字叫做 MySQL 的 Service,在我的一个应用 service-blog 中要怎么访问 MySQL?是指定 Service 的 IP 的形式吗?但是在开发的时候我都不知道部署在哪个集群,这个集群里面 MySQL 的 service ip 是什么,这就没法写了。
K8S 的设计肯定是考虑到了这种情况,对于 Service 的访问,内部都是通过 DNS 来进行的,也就是说,当你定义了一个 Service 之后,K8S 就响应得生成了一个 DNS 记录,这个 Service 对应的内部域名就是: <cluster-name>.<namespace>.svc.cluster.local
,在没有特别指定 Service 类型的时候,访问这个 A 记录,它返回的是 Service 的 ClusterIP;如果你设置了 Service 的 ClusterIP 为 None,那么返回的就是所有被代理的 Pod 的 IP 地址的集合。
小结
在这篇文章中,我介绍了什么是一个 Service 以及 Service 存在的意义,还有 Service 是怎么和 Pod 联系起来并且对外提供服务的。但是,还有一些内容没有提及,这个都不能简单的属于入门内容,后面有入门之后的进阶系列,你可以多关注了解一下。
- ClusterIP 模式的 Service 提供的是一个 Pod 的稳定的 IP 地址,即 VIP,这里的 Pod 和 Service 的关系是通过 Label 确认的。
- Headless Service 提供的则是一个 Pod 的稳定的 DNS 名字,并且这个名字是通过 Pod 名字和 Serviec 名字拼接出来的。