前言
每个 Pod 都有自己的 IP 地址。当 controller 用新 Pod 替代发生故障的 Pod 时,新 Pod 会分配到新的 IP 地址。这样就产生了一个问题:
如果一组 Pod 对外提供服务(比如 HTTP),它们的 IP 很有可能发生变化,那么客户端如何找到并访问这个服务呢?
换句话说,我们的诉求就是希望这组pod有一个统一的对外的访问地址,并且,更进一步,如果这组pod的里面的个别出现故障,能够屏蔽这些情况。
Kubernetes 给出的解决方案是 Service。
创建 Service
Kubernetes Service 从逻辑上代表了一组 Pod,具体是哪些 Pod 则是由 label 来挑选。Service 有自己 IP,而且这个 IP 是不变的。客户端只需要访问 Service 的 IP,Kubernetes 则负责建立和维护 Service 与 Pod 的映射关系。无论后端 Pod 如何变化,对客户端不会有任何影响,因为 Service 没有变。
第一步:创建下面的这个 Deployment:
查看支持的apiversion使用命令kubectl api-versions
apiVersion: apps/v1
kind: Deployment
metadata:
name: httpd
spec:
selector:
matchLabels:
run: httpd
replicas: 3
template:
metadata:
labels:
run: httpd
spec:
containers:
- name: httpd
image: registry.cn-beijing.aliyuncs.com/qingfeng666/nginx:latest
ports:
- containerPort: 80
第二步:部署并查看pod
我们启动了三个 Pod,运行 httpd 镜像,label 是 run: httpd,Service 将会用这个 label 来挑选 Pod。
[root@aliyun-master k8s]# kubectl apply -f httpd.yml
deployment.apps/httpd created
[root@aliyun-master k8s]# kubectl get pod -owide|grep httpd
httpd-79ccb4cb4c-24p74 1/1 Running 0 2m4s 10.244.2.4 aliyun-node <none> <none>
httpd-79ccb4cb4c-rb92r 1/1 Running 0 2m5s 10.244.2.3 aliyun-node <none> <none>
httpd-79ccb4cb4c-sbp8v 1/1 Running 0 2m4s 10.244.2.5 aliyun-node <none> <none>
第三步:集群内部测试连通性 Pod 分配了各自的 IP,这些 IP 只能被 Kubernetes Cluster 中的容器和节点访问。
[root@ken ~]# curl 10.244.2.4
<html><body><h1>It works!</h1></body></html>
[root@ken ~]# curl 10.244.2.3
<html><body><h1>It works!</h1></body></html>
[root@ken ~]# curl 10.244.2.5
<html><body><h1>It works!</h1></body></html>
第四步:接下来创建 Service,其配置文件如下:
apiVersion: v1
kind: Service
metadata:
name: httpd-svc
spec:
selector:
run: httpd
ports:
- port: 80
targetPort: 80
v1 是 Service 的 apiVersion。 kind: Service:指明当前资源的类型为 Service。 metadata.name:Service 的名字为 httpd-svc。 selector 指明挑选那些 label 为 run: httpd 的 Pod 作为 Service 的后端。我们可以使用下面命令看下是不是有pod上配置了这些标签
#查看当前命名空间下所有pod并显示这些pod的标签
kubectl get pod --show-labels
#查看某个标签下的pod -l后面跟上key=value
kubectl get pod -l name=httpd
最后将 Service 的 80 端口映射到 Pod 的 80 端口,使用 TCP 协议。
我们也可以使用命令行的方式快速暴露这个deployment
# 等同于没有--type的
kubectl expose deployment my-dep --port=8000 --target-port=80 --type=ClusterIP
第五步: 执行 kubectl apply 创建 Service httpd-svc。
[root@aliyun-master k8s]# kubectl apply -f httpd-svc.yml
service/httpd-svc created
[root@aliyun-master k8s]# kubectl get svc
NAME TYPE CLUSTER-IP EXTERNAL-IP PORT(S) AGE
httpd-svc ClusterIP 10.1.69.105 <none> 80/TCP 22s
kubernetes ClusterIP 10.1.0.1 <none> 443/TCP 15h
httpd-svc 分配到一个 CLUSTER-IP 10.1.69.105。可以通过该 IP 访问后端的 httpd Pod。注意这个ip也只能在集群内使用
[root@ken ~]# curl 10.1.69.105
<html><body><h1>It works!</h1></body></html>
注意:这里使用port不是targetPort
通过 kubectl describe 可以查看 httpd-svc 与 Pod 的对应关系。
[root@aliyun-master k8s]# kubectl describe service httpd-svc
Name: httpd-svc
Namespace: default
Labels: <none>
Annotations: <none>
Selector: run=httpd
Type: ClusterIP
IP: 10.1.69.105
Port: <unset> 80/TCP
TargetPort: 80/TCP
Endpoints: 10.244.2.3:80,10.244.2.4:80,10.244.2.5:80
Session Affinity: None
Events: <none>
Endpoints 罗列了三个 Pod 的 IP 和端口。
DNS访问service
在 Cluster 中,除了可以通过 Cluster IP 访问 Service,Kubernetes 还提供了更为方便的 DNS 访问。
第一步:查看coredns
kubeadm 部署时会默认安装 coredns 组件。
[root@aliyun-master k8s]# kubectl get deployment --namespace=kube-system
NAME READY UP-TO-DATE AVAILABLE AGE
coredns 2/2 2 2 15h
coredns 是一个 DNS 服务器。每当有新的 Service 被创建,coredns 会添加该 Service 的 DNS 记录。Cluster 中的 Pod 可以通过 <SERVICE_NAME>.<NAMESPACE_NAME> 访问 Service。
第二步:dns访问
比如可以用 httpd-svc.default 访问 Service httpd-svc。由于这个 Pod 与 httpd-svc 同属于 default namespace,可以省略 default 直接用 httpd-svc 访问 Service。
//进入某个容器
kubectl exec -it httpd-79ccb4cb4c-24p74 bas
//发起请求
curl httpd-svc
第三步:查看namespace
如果要访问其他 namespace 中的 Service,就必须带上 namesapce 了。kubectl get namespace 查看已有的 namespace。
[root@aliyun-master k8s]# kubectl get namespace
NAME STATUS AGE
default Active 15h
kube-flannel Active 15h
kube-node-lease Active 15h
kube-public Active 15h
kube-system Active 15h
kubernetes-dashboard Active 15h
第四步:在 kube-public 中部署 Service httpd2-svc
apiVersion: apps/v1
kind: Deployment
metadata:
name: httpd2
namespace: kube-public
spec:
selector:
matchLabels:
run: httpd
replicas: 3
template:
metadata:
labels:
run: httpd
spec:
containers:
- name: httpd
image: registry.cn-beijing.aliyuncs.com/qingfeng666/nginx:latest
ports:
- containerPort: 80
---
apiVersion: v1
kind: Service
metadata:
name: httpd-svc2
namespace: kube-public
spec:
selector:
run: httpd
ports:
- port: 80
targetPort: 80
查看执行结果 在kube-public下创建了3个pod
[root@aliyun-master k8s]# kubectl get pod -n kube-public|grep http
httpd-79ccb4cb4c-95zlp 1/1 Running 0 42s
httpd-79ccb4cb4c-bcrf2 1/1 Running 0 42s
httpd-79ccb4cb4c-jjtd7 1/1 Running 0 42s
同样也创建了service
[root@aliyun-master k8s]# kubectl get svc -n kube-public
NAME TYPE CLUSTER-IP EXTERNAL-IP PORT(S) AGE
httpd-svc ClusterIP 10.1.39.114 <none> 80/TCP 71s
下面我们进入httpd1容器访问httpd2的service名字
root@httpd-79ccb4cb4c-6x7jq:/# curl httpd-svc2
curl: (6) Could not resolve host: httpd-svc2
因为属于不同的 namespace,必须使用 httpd2-svc.kube-public 才能访问到。
curl httpd-svc2.kube-public
<html><body><h1>It works!</h1></body></html>
外网访问service
除了 Cluster 内部可以访问 Service,很多情况我们也希望应用的 Service 能够暴露给 Cluster 外部。Kubernetes 提供了多种类型的 Service,默认是 ClusterIP。
ClusterIP
Service 通过 Cluster 内部的 IP 对外提供服务,只有 Cluster 内的节点和 Pod 可访问,这是默认的 Service 类型,前面实验中的 Service 都是 ClusterIP。
NodePort
Service 通过 Cluster 节点的静态端口对外提供服务。Cluster 外部可以通过 : 访问 Service。
LoadBalancer
Service 利用 cloud provider 特有的 load balancer 对外提供服务,cloud provider 负责将 load balancer 的流量导向 Service。目前支持的 cloud provider 有 GCP、AWS、Azur 等。
第一步:实践 NodePort,Service httpd-svc 的配置文件修改如下:
apiVersion: v1
kind: Service
metadata:
name: httpd-svc2
namespace: kube-public
spec:
type: NodePort
selector:
run: httpd
ports:
- port: 80
targetPort: 80
添加 type: NodePort
第二步:重新创建 httpd-svc。
[root@aliyun-master k8s]# kubectl apply -f httpd-svc2.yml
service/httpd-svc2 configured
[root@aliyun-master k8s]# kubectl get svc -n kube-public
NAME TYPE CLUSTER-IP EXTERNAL-IP PORT(S) AGE
httpd-svc2 NodePort 10.1.188.224 <none> 80:32396/TCP 45m
Kubernetes 依然会为 httpd-svc 分配一个 ClusterIP,不同的是:
TYPE 为 NodePort,表示可通过 Cluster 每个节点自身的 IP 访问 Service。
PORT(S) 为 80:32396。80 是 ClusterIP 监听的端口(每个节点都有该端口),32396 则是节点上监听的端口。Kubernetes 会从 30000-32767 中分配一个可用的端口,每个节点都会监听此端口并将请求转发给 Service。
使用命令行方式创建如下
kubectl expose deployment my-dep --port=8000 --target-port=80 --type=NodePort
第三步:测试nodeport是否正常工作
第四步:指定特定端口 NodePort 默认是的随机选择,不过我们可以用 nodePort 指定某个特定端口。增加一个nodePort配置
apiVersion: v1
kind: Service
metadata:
name: httpd-svc2
namespace: kube-public
spec:
type: NodePort
selector:
run: httpd
ports:
- port: 80
nodePort: 30000
targetPort: 80
现在配置文件中就有三个 Port 了: nodePort 是节点上监听的端口。 port 是 ClusterIP 上监听的端口。 targetPort 是 Pod 监听的端口。
第四步:应用新的 nodePort 并验证:
[root@aliyun-master k8s]# kubectl apply -f httpd-svc2.yml
service/httpd-svc2 configured
[root@aliyun-master k8s]# kubectl get svc -n kube-public
NAME TYPE CLUSTER-IP EXTERNAL-IP PORT(S) AGE
httpd-svc2 NodePort 10.1.188.224 <none> 80:30000/TCP 57m
总结
使用service暴露pod有下面几种方式
ClusterIP:
仅仅使用一个集群内部的IP地址 - 这是默认值。选择这个值意味着你只想这个服务在集群内部才可以被访问到
NodePort:
在集群内部IP的基础上,在集群的每一个节点的端口上开放这个服务。你可以在任意:NodePort地址上访问到这个服务。
LoadBalancer:
在使用一个集群内部IP地址和在NodePort上开放一个Service的基础上,还可以向云提供者申请一个负载均衡器,将流量转发到已经以NodePort形式开发的Service上。