k8s——service
我们不应该期望Kubernetes Pod是健壮的,而是要假设Pod中的容器很可能因为各种原因发生故障而死掉。Deployment等Controller会通过动态创建和销毁Pod来保证应用整体的健壮性。换句话说,Pod是脆弱的,但应用是健壮的
Pod的IP地址是Docker Daemon根据docker0网桥的IP地址段进行分配的,但Service的Cluster IP地址是Kubernetes系统中的虚拟IP地址,由系统动态分配。
Service的Cluster IP地址相对于Pod的IP地址来说相对稳定,Service被创建时即被分配一个IP地址,在销毁该Service之前,这个IP地址都不会再变化了。
而Pod在Kubernetes集群中生命周期较短,可能被ReplicationContrller销毁、再次创建,新创建的Pod将会分配一个新的IP地址
每个Pod都有自己的IP地址。当Controller用新Pod替代发生故障的Pod时,新Pod会分配到新的IP地址,这样就产生了一个问题:如果一组Pod对外提供服务(比如HTTP),它们的IP很有可能发生变化,那么客户端如何找到并访问这个服务呢?
Kubernetes给出的解决方案是Service
Service从逻辑上代表了一组Pod,具体是哪些Pod则是由label来挑选的。Service有自己的IP,而且这个IP是不变的。客户端只需要访问Service的IP,Kubernetes则负责建立和维护Service与Pod的映射关系。无论后端Pod如何变化,对客户端不会有任何影响,因为Service没有变
创建下面的这个Deployment
[[email protected] ~]$ cat httpd.yml
apiVersion: apps/v1
kind: Deployment
metadata:
name: httpd
spec:
replicas: 3
selector:
matchLabels:
run: httpd
template:
metadata:
labels: ##标签
run: httpd
spec:
containers:
- name: httpd
image: httpd
ports:
- containerPort: 80
# 注意的是:node节点上的docker服务必须是好的,而且虚拟机要能上网
[[email protected] ~]$ kubectl apply -f httpd.yml
deployment.apps/httpd created
[[email protected] ~]$ kubectl get pod -o wide #注意:ip的出现是需要时间的(容器的准备也是需要时间的)
NAME READY STATUS RESTARTS AGE IP NODE NOMINATED NODE READINESS GATES
httpd-69cb5b9fdd-cwqk2 1/1 Running 0 14s 10.244.2.22 server3 <none> <none>
httpd-69cb5b9fdd-wprs9 1/1 Running 0 14s 10.244.1.24 server2 <none> <none>
httpd-69cb5b9fdd-xj9mp 1/1 Running 0 14s 10.244.1.23 server2 <none> <none>
我们可以看到的是:Pod分配了各自的IP,这些IP只能被Kubernetes Cluster中的容器和节点访问
[[email protected] ~]$ curl 10.244.2.22
<html><body><h1>It works!</h1></body></html>
[[email protected] ~]$ curl 10.244.1.24
<html><body><h1>It works!</h1></body></html>
[[email protected] ~]$ curl 10.244.1.23
<html><body><h1>It works!</h1></body></html>
接下来创建Service
# 前提:之前的httpd pod 不能关闭和删除
[[email protected] ~]$ cat httpd-svc.yml
apiVersion: v1
kind: Service
metadata:
name: httpd-svc
spec:
selector:
run: httpd
ports:
- protocol: TCP
port: 8080
targetPort: 80
v1 | 是Service的apiVersion |
---|---|
kind | 指明当前资源的类型为Service |
name | Service的名字为httpd-svc |
selector | 指明挑选那些label为run: httpd的Pod作为Service的后端 |
ports | 将Service的8080端口映射到Pod的80端口,使用TCP协议 |
执行kubectl apply创建Service httpd-svc
[[email protected] ~]$ kubectl apply -f httpd-svc.yml
service/httpd-svc created
[[email protected] ~]$ kubectl get service
NAME TYPE CLUSTER-IP EXTERNAL-IP PORT(S) AGE
httpd-svc ClusterIP 10.104.247.136 <none> 8080/TCP 9s
kubernetes ClusterIP 10.96.0.1 <none> 443/TCP 22h
# httpd-svc分配到一个CLUSTER-IP
# 以通过该IP 访问后端的httpd Pod
[[email protected] ~]$ curl 10.104.247.136:8080
<html><body><h1>It works!</h1></body></html>
[[email protected] ~]$ curl 10.104.247.136:8080
<html><body><h1>It works!</h1></body></html>
通过kubectl describe可以查看httpd-svc与Pod的对应关系
[[email protected] ~]$ kubectl describe service httpd-svc
Name: httpd-svc
Namespace: default
Labels: <none>
Annotations: kubectl.kubernetes.io/last-applied-configuration:
{"apiVersion":"v1","kind":"Service","metadata":{"annotations":{},"name":"httpd-svc","namespace":"default"},"spec":{"ports":[{"port":8080,"...
Selector: run=httpd
Type: ClusterIP
IP: 10.104.247.136
Port: <unset> 8080/TCP
TargetPort: 80/TCP
Endpoints: 10.244.1.25:80,10.244.2.23:80,10.244.2.24:80
Session Affinity: None
Events: <none>
Cluster IP底层实现
Endpoints罗列了三个Pod的IP和端口。我们知道Pod的IP是在容器中配置的,那么Service的Cluster IP又是配置在哪里的呢?CLUSTER-
IP又是如何映射到Pod IP的呢?
答案是iptables
Cluster IP是一个虚拟IP,是由Kubernetes节点上的iptables规则管理的,可以通过iptables-save命令打印出当前节点的iptables规则
因为输出较多,这里只截取与httpd-svc Cluster IP 相关的信息
# 注意用户的切换:此处使用root用户
[[email protected] ~]# iptables-save |grep httpd-svc
-A KUBE-SERVICES ! -s 10.244.34.0/16 -d 10.104.247.136/32 -p tcp -m comment --comment "default/httpd-svc: cluster IP" -m tcp --dport 8080 -j KUBE-MARK-MASQ
-A KUBE-SERVICES -d 10.104.247.136/32 -p tcp -m comment --comment "default/httpd-svc: cluster IP" -m tcp --dport 8080 -j KUBE-SVC-RL3JAE4GN7VOGDGP
解析:
- (1)如果Cluster内的Pod(源地址来自10.244.34.0/16)要访问httpd-svc,则允许
- (2)其他源地址访问httpd-svc,跳转到规则KUBE-SVC-
RL3JAE4GN7VOG DGP。KUBE-SVC-RL3JAE4GN7VOGDGP规则
[[email protected] ~]# iptables-save |grep KUBE-SVC-RL3JAE4GN7VOGDGP
-A KUBE-SVC-RL3JAE4GN7VOGDGP -m statistic --mode random --probability 0.33332999982 -j KUBE-SEP-N5PH4BBCFTMHGLV6
-A KUBE-SVC-RL3JAE4GN7VOGDGP -m statistic --mode random --probability 0.50000000000 -j KUBE-SEP-UEWSDQT52CMKBNNL
-A KUBE-SVC-RL3JAE4GN7VOGDGP -j KUBE-SEP-BSPNJWUG33XKKPE5
解析:
1/3的概率跳转到规则 KUBE-SEP-N5PH4BBCFTMHGLV6
1/3的概率(剩下2/3的一半)跳转到规则 KUBE-SEP-UEWSDQT52CMKBNNL
1/3的概率跳转到规则 KUBE-SEP-BSPNJWUG33XKKPE5
即将请求分别转发到后端的三个Pod。
通过上面的分析,我们得到结论:iptables将访问Service的流量转发到后端Pod,而且使用类似轮询的负载均衡策略
另外,需要补充一点:Cluster的每一个节点都配置了相同的
iptables规则,这样就确保了整个Cluster都能够通过Service的Cluster IP访问Service
DNS访问Service
- 在Cluster中,除了可以通过Cluster IP访问Service,Kubernetes还提供了更为方便的DNS访问,kubeadm部署时会默认安装kube-dns组件
- kube-dns是一个DNS服务器。每当有新的Service被创建,kube-dns会添加该Service的DNS记录。Cluster中的Pod可以通过<SERVICE_NAME>.<NAMESPACE_NAME>访问Service
kubectl get namespace查看已有的namespace
[[email protected] ~]$ kubectl get namespace
NAME STATUS AGE
default Active 23h
kube-node-lease Active 23h
kube-public Active 23h
kube-system Active 23h
每当有新的Service被创建,kube-dns会添加该Service的DNS记录
[[email protected] ~]$ kubectl run busybox --rm -it --image=busybox /bin/sh
kubectl run --generator=deployment/apps.v1 is DEPRECATED and will be removed in a future version. Use kubectl run --generator=run-pod/v1 or kubectl create instead.
If you don't see a command prompt, try pressing enter.
/ # wget httpd-svc.default:8080
Connecting to httpd-svc.default:8080 (10.99.215.181:8080)
saving to 'index.html'
index.html 100% |********************************| 45 0:00:00 ETA
'index.html' saved
# 如上所示,我们在一个临时的busyboxPod中验证了DNS的有效性。另外,由于这个Pod与httpd-svc同属于default namespace,因此可以省略default直接用httpd-svc访问Service
/ # wget httpd-svc:8080
Connecting to httpd-svc:8080 (10.99.215.181:8080)
wget: can't open 'index.html': File exists
/ # ls
bin etc index.html root tmp var
dev home proc sys usr
/ # rm -rf index.html
/ # wget httpd-svc:8080
Connecting to httpd-svc:8080 (10.99.215.181:8080)
saving to 'index.html'
index.html 100% |********************************| 45 0:00:00 ETA
'index.html' saved
/ # ls
bin etc index.html root tmp var
dev home proc sys usr
通过namespace: kube-public 指定资源所属的namespace。多个资源可以在一个YAML文件中定义,用“—”分割。执行kubectl
[[email protected] ~]$ cat httpd2.yml
apiVersion: apps/v1
kind: Deployment
metadata:
name: httpd2
namespace: kube-public
spec:
replicas: 3
selector:
matchLabels:
run: httpd2
template:
metadata:
labels:
run: httpd2
spec:
containers:
- name: httpd2
image: httpd
ports:
- containerPort: 80
---
apiVersion: v1
kind: Service
metadata:
name: httpd2-svc
namespace: kube-public
spec:
selector:
run: httpd2
ports:
- protocol: TCP
port: 8080
targetPort: 80
[[email protected] ~]$ kubectl apply -f httpd2.yml
deployment.apps/httpd2 created
service/httpd2-svc created
[[email protected] ~]$ kubectl get service --namespace=kube-public
NAME TYPE CLUSTER-IP EXTERNAL-IP PORT(S) AGE
httpd2-svc ClusterIP 10.104.125.247 <none> 8080/TCP 19s
# 在busybox Pod中访问httpd2-svc
# 因为不属于同一个namespace,所以必须使用httpd2-svc.kube-public才能访问到
[[email protected] ~]$ kubectl run busybox --rm -it --image=busybox /bin/sh
kubectl run --generator=deployment/apps.v1 is DEPRECATED and will be removed in a future version. Use kubectl run --generator=run-pod/v1 or kubectl create instead.
If you don't see a command prompt, try pressing enter.
/ # wget httpd2-svc.kube-public:8080
Connecting to httpd2-svc.kube-public:8080 (10.104.125.247:8080)
saving to 'index.html'
index.html 100% |******| 45 0:00:00 ETA
'index.html' saved
注意:Pod与httpd-svc同属于default namespace,因此可以省略default直接用httpd-svc访问Service
外网如何访问Service
除了Cluster内部可以访问Service,很多情况下我们也希望应用的Service能够暴露给Cluster外部。Kubernetes提供了多种类型的Service,默认是ClusterIP
-
(1)ClusterIP
Service通过Cluster内部的IP对外提供服务,只有Cluster内的节点和Pod可访问,这是默认的Service类型,前面实验中的Service都是ClusterIP -
(2)NodePort
Service通过Cluster节点的静态端口对外提供服务。Cluster外部可以通过:访问Service -
(3)LoadBalancer
Service利用cloud provider特有的load balancer对外提供服务,cloud provider负责将load balancer的流量导向Service 目前支持的 cloud provider有GCP、AWS、Azur等
下面我们来实践NodePort
[[email protected] ~]$ cat httpd-svc.yml
apiVersion: v1
kind: Service
metadata:
name: httpd-svc
spec:
type: NodePort
selector:
run: httpd
ports:
- protocol: TCP
port: 8080
targetPort: 80
# 添加type: NodePort,重新创建httpd-svc
# 注意点是:要先运行pod 然后将它们打包成一个整体-service
[[email protected] ~]$ kubectl apply -f httpd.yml
deployment.apps/httpd created
[[email protected] ~]$ kubectl apply -f httpd-svc.yml
service/httpd-svc created
# Kubernetes依然会为httpd-svc分配一个ClusterIP,不同的是
# PORT(S)为8080:30295 8080是ClusterIP监听的端口,30295则是节点上监听的端口。Kubernetes会从30000~32767中分配一个可用的端口,每个节点都会监听此端口并将请求转发给Service
[[email protected] ~]$ kubectl get service httpd-svc
NAME TYPE CLUSTER-IP EXTERNAL-IP PORT(S) AGE
httpd-svc NodePort 10.98.81.225 <none> 8080:30295/TCP 4s
[[email protected] ~]$ kubectl get pod -o wide
NAME READY STATUS RESTARTS AGE IP NODE NOMINATED NODE READINESS GATES
httpd-69cb5b9fdd-7b6gq 1/1 Running 0 28s 10.244.2.27 server3 <none> <none>
httpd-69cb5b9fdd-9k6c8 1/1 Running 0 28s 10.244.1.30 server2 <none> <none>
httpd-69cb5b9fdd-c5rxz 1/1 Running 0 28s 10.244.1.29 server2 <none> <none>
测试
# 通过三个节点IP+PORT 口都能够访问httpd-svc
[[email protected] ~]$ curl 172.25.0.2:30295
<html><body><h1>It works!</h1></body></html>
[[email protected] ~]$ curl 172.25.0.3:30295
<html><body><h1>It works!</h1></body></html>
接下来我们深入探讨一个问题:Kubernetes是如何将:映射到Pod的呢?
与ClusterIP一样,也是借助了iptables。与ClusterIP相比,每个节点的iptables中都增加了下面两条规则
[[email protected] ~]# iptables-save |grep 30295
-A KUBE-NODEPORTS -p tcp -m comment --comment "default/httpd-svc:" -m tcp --dport 30295 -j KUBE-MARK-MASQ
-A KUBE-NODEPORTS -p tcp -m comment --comment "default/httpd-svc:" -m tcp --dport 30295 -j KUBE-SVC-RL3JAE4GN7VOGDGP
规则的含义是:访问当前节点30295端口的请求会应用规则 KUBE-SVC-RL3JAE4GN7VOGDGP 其作用就是负载均衡到每一个Pod
[[email protected] ~]# iptables-save |grep KUBE-SVC-RL3JAE4GN7VOGDGP
:KUBE-SVC-RL3JAE4GN7VOGDGP - [0:0]
-A KUBE-NODEPORTS -p tcp -m comment --comment "default/httpd-svc:" -m tcp --dport 30295 -j KUBE-SVC-RL3JAE4GN7VOGDGP
-A KUBE-SERVICES -d 10.98.81.225/32 -p tcp -m comment --comment "default/httpd-svc: cluster IP" -m tcp --dport 8080 -j KUBE-SVC-RL3JAE4GN7VOGDGP
-A KUBE-SVC-RL3JAE4GN7VOGDGP -m statistic --mode random --probability 0.33332999982 -j KUBE-SEP-Y5EOPQEI6R2HKSKZ
-A KUBE-SVC-RL3JAE4GN7VOGDGP -m statistic --mode random --probability 0.50000000000 -j KUBE-SEP-SACXFJATP2WVU5HD
-A KUBE-SVC-RL3JAE4GN7VOGDGP -j KUBE-SEP-MZTDTCCSTQYQRVK2
NodePort默认的是随机选择,不过我们可以用nodePort指定某个特定端口
[[email protected] ~]$ cat httpd-svc.yml
apiVersion: v1
kind: Service
metadata:
name: httpd-svc
spec:
type: NodePort
selector:
run: httpd
ports:
- protocol: TCP
nodePort: 30000
port: 8080
targetPort: 80
nodePort | 节点上监听的端口 |
---|---|
port | ClusterIP上监听的端口 |
targetPort | Pod监听的端口 |
最终,Node和ClusterIP在各自端口上接收到的请求都会通过iptables转发到Pod的targetPort
[[email protected] ~]$ kubectl apply -f httpd-svc.yml
service/httpd-svc created
[[email protected] ~]$ kubectl get service httpd-svc
NAME TYPE CLUSTER-IP EXTERNAL-IP PORT(S) AGE
httpd-svc NodePort 10.98.239.4 <none> 8080:30000/TCP 10s
[[email protected] ~]$ curl 172.25.0.3:30000
<html><body><h1>It works!</h1></body></html>
[[email protected] ~]$ curl 172.25.0.2:30000
<html><body><h1>It works!</h1></body></html>
上一篇: 企业项目实战k8s篇(五)service
下一篇: 微信小游戏基于微信开发工具入门讲解