Kubernetes中的Pod是很容易因为各种原因发生故障而死掉。Deployment等Controller会通过动态创建和销毁Pod来保证应用整体的健壮性。换句话说,Pod是脆弱的,但应用是健壮的。
每个Pod都有自己的IP地址。当Controller用新的Pod替代发生故障的Pod时,新Pod会分配到新的IP地址。这样就会产生一个问题:访问的地址也会发生变化,怎么保证在替换Pod后依然可以访问这个服务。
在docker run和swarm中使用-p来映射端口来解决这一问题,而Kubernetes给出的解决方案是Service,这里的Service与swarm中的Service是不一样的。同样它的解决方案也是映射端口的原理。
Cluster内部的Service访问
内部的Service访问适用于,前端服务访问后端服务的使用,比如前端nginx集群要访问后端的php来解析动态网页,这个时候php就可以使用内部的Service访问。
Cluster IP访问Service
Kubernetes Service 从逻辑上代表了一组 Pod,具体是哪些 Pod 则是由 label 来挑选。这个label是写在yml中的labels选项,并不是为node设置的label,这个label可以代表运行服务的Pod。
Service 有自己 IP,而且这个 IP 是不变的。客户端只需要访问 Service 的 IP,kube-proxy组件用来建立和维护Service与Pod的映射关系。无论后端的Pod如何变化,对用户访问不会有任何影响,因为Service没有变,相当于集群的代理。
Service的使用依赖于Pod的运行,运行一个httpd的Deployment来创建Pod,进行Service的创建
[root@node1 ~]# vim httpd.yml
apiVersion: apps/v1
kind: Deployment
metadata:
name: httpd
spec:
selector:
matchLabels:
run: httpd # Service会通过这个label来代理服务Pod
replicas: 3
template:
metadata:
labels:
run: httpd
spec:
containers:
- name: httpd
image: httpd:latest
ports:
- containerPort: 80 # 使用Service映射,需要将服务端口监听
运行Pod
[root@node1 ~]# kubectl apply -f httpd.yml
deployment.apps/httpd created
查看Pod的IP
[root@node1 ~]# kubectl get pod -o wide
NAME READY STATUS RESTARTS AGE IP NODE
httpd-5dd67c6b75-5xjg4 1/1 Running 0 4m10s 10.244.1.3 node2
httpd-5dd67c6b75-89hdk 1/1 Running 0 4m10s 10.244.2.3 node3
httpd-5dd67c6b75-ngnk4 1/1 Running 0 4m10s 10.244.2.4 node3
在集群直接访问pod的ip可以访问到内容,外部是访问不到
[root@node1 ~]# curl 10.244.1.3
<html><body><h1>It works!</h1></body></html>
[root@node1 ~]# curl 10.244.2.3
<html><body><h1>It works!</h1></body></html>
[root@node1 ~]# curl 10.244.2.4
<html><body><h1>It works!</h1></body></html>
现在来创建Service,将端口映射出去,使得外部可以访问
[root@node1 ~]# vim httpd-svc.yml
apiVersion: v1
kind: Service
metadata:
name: httpd-svc
spec:
selector:
run: httpd # 选择集群中有此标签的所有Pod进行端口映射
ports:
- protocol: TCP # 采用tcp协议
port: 8080 # 映射以后的访问端口
targetPort: 80 # Pod内监听端口
集群中标签为run: httpd
的Pod容器所监听的80端口全部映射为8080端口
运行Service
[root@node1 ~]# kubectl apply -f httpd-svc.yml
service/httpd-svc created
查看Service
[root@node1 ~]# kubectl get service
NAME TYPE CLUSTER-IP EXTERNAL-IP PORT(S) AGE
httpd-svc ClusterIP 10.102.3.63 <none> 8080/TCP 5m8s
kubernetes ClusterIP 10.96.0.1 <none> 443/TCP 18m
名为httpd-svc的ip就是创建Service之后的ip,只需要访问该ip:8080
即可,第二个ip是在集群内部访问master使用的
查看该Service是否对Pod进行了端口映射
# 根据标签run=httpd,会动态更新Endpoints中的ip,即使新增加或者减少了,也是同样的
[root@node1 ~]# kubectl describe service httpd-svc
Name: httpd-svc
Namespace: default
Labels: <none>
Annotations: Selector: run=httpd
Type: ClusterIP
IP: 10.102.3.63
Port: <unset> 8080/TCP
TargetPort: 80/TCP
Endpoints: 10.244.1.4:80,10.244.2.5:80,10.244.2.6:80
Session Affinity: None
Events: <none>
访问Cluster IP
目前还不知道什么原因,访问阻塞要一直等一会儿才会访问到
[root@node1 ~]# curl 10.102.3.63:8080
<html><body><h1>It works!</h1></body></html>
Service Cluster IP 原理
Service Cluster IP 是一个虚拟 IP,是由 Kubernetes 节点上的 iptables 规则管理的。
通过查看iptables-save
中的规则可以找到这两条
-A KUBE-SERVICES ! -s 10.244.0.0/16 -d 10.102.3.63/32 -p tcp -m comment --comment "default/httpd-svc: cluster IP" -m tcp --dport 8080 -j KUBE-MARK-MASQ
# 如果集群内pod,也就是10.244.0.0网段要访问httpd-svc:8080,则允许
-A KUBE-SERVICES -d 10.102.3.63/32 -p tcp -m comment --comment "default/httpd-svc: cluster IP" -m tcp --dport 8080 -j KUBE-SVC-RL3JAE4GN7VOGDGP
# 如果其他地址访问httpd-svc:8080,则转发到KUBE-SVC-RL3JAE4GN7VOGDGP
第一条规则,是使用Pod容器去访问的时候的转发规则,第二条则是刚才使用集群ip192.168.1.0/24去访问的规则
KUBE-SVC-RL3JAE4GN7VOGDGP规则在iptables中如下
-A KUBE-SVC-RL3JAE4GN7VOGDGP -m comment --comment "default/httpd-svc:" -m statistic --mode random --probability 0.33333333349 -j KUBE-SEP-5PWFTV3INZUJOOJD
# 0.33333333349也就是1/3的概率转发到KUBE-SEP-5PWFTV3INZUJOOJD
-A KUBE-SVC-RL3JAE4GN7VOGDGP -m comment --comment "default/httpd-svc:" -m statistic --mode random --probability 0.50000000000 -j KUBE-SEP-72A5OH6IP5DVGPP6
# 除了第一条以外0.5,也即是1/2的概率转发到KUBE-SEP-72A5OH6IP5DVGPP6
-A KUBE-SVC-RL3JAE4GN7VOGDGP -m comment --comment "default/httpd-svc:" -j KUBE-SEP-AD3FX47FRXVJYUZ4
# 最后的概率转发到KUBE-SEP-AD3FX47FRXVJYUZ4
这三条同样也有对应的规则
# 每条规则又对应的两条规则
-A KUBE-SEP-5PWFTV3INZUJOOJD -s 10.244.1.4/32 -m comment --comment "default/httpd-svc:" -j KUBE-MARK-MASQ
-A KUBE-SEP-5PWFTV3INZUJOOJD -p tcp -m comment --comment "default/httpd-svc:" -m tcp -j DNAT --to-destination 10.244.1.3:80
# 综上所述,除pod容器的ip网段外,其他ip访问时,3/1的概率访问到10.244.1.4的pod
-A KUBE-SEP-72A5OH6IP5DVGPP6 -s 10.244.2.3/32 -m comment --comment "default/httpd-svc:" -j KUBE-MARK-MASQ
-A KUBE-SEP-72A5OH6IP5DVGPP6 -p tcp -m comment --comment "default/httpd-svc:" -m tcp -j DNAT --to-destination 10.244.2.3:80
# 3/1的概率访问到10.244.2.3的pod
-A KUBE-SEP-AD3FX47FRXVJYUZ4 -s 10.244.2.4/32 -m comment --comment "default/httpd-svc:" -j KUBE-MARK-MASQ
-A KUBE-SEP-AD3FX47FRXVJYUZ4 -p tcp -m comment --comment "default/httpd-svc:" -m tcp -j DNAT --to-destination 10.244.2.4:80
# 3/1的概率访问到10.244.2.4的pod
可以看出来,这样的iptables规则,类似于负载均衡的策略,将访问httpd-svc的流量转发到了后端的三个Pod中
集群中每个节点都有这样相同的规则,以做到整个Cluster都可以通过Cluster IP访问到。
DNS访问Service
在k8s集群中,除了通过Cluster IP访问Service,k8s还提供了DNS访问
使用kubeadm部署时会默认安装coredns组件
[root@node1 ~]# kubectl get deployment --namespace=kube-system
NAME READY UP-TO-DATE AVAILABLE AGE
coredns 2/2 2 2 57m
coredns是一个DNS服务器。每当有新的Service被创建,coredns会添加该Service的DNS记录。Cluster中的Pod可以通过service_name:端口
或者service_name.namespace:端口
来访问Service。
比如以上的例子中,可以访问httpd-svc:8080
或者httpd-svc.default:8080
直接使用集群ip去访问DNS是通不了的,所以进入一个pod中去验证DNS的有效性。
同命名空间
Pod与httpd-svc在同一命名空间,可以不加namespace,当然加上也是正确的
[root@node1 ~]# kubectl run busybox --image=busybox --rm -it /bin/sh
If you don't see a command prompt, try pressing enter.
/ # wget httpd-svc:8080
Connecting to httpd-svc:8080 (10.102.3.63:8080)
saving to 'index.html'
index.html 100% |*********************************************| 45 0:00:00 ETA
'index.html' saved
/ # cat index.html
<html><body><h1>It works!</h1></body></html>
/ # rm -rf index.html
不同命名空间
不同命名空间内,必须加namespace,不加访问不到
查看并使用现有的命名空间
[root@node1 ~]# kubectl get namespace
NAME STATUS AGE
default Active 70m
kube-node-lease Active 70m
kube-public Active 70m
kube-system Active 70m
创建一个kube-public命名空间的Pod和Service
[root@node1 ~]# vim httpd2.yml
apiVersion: apps/v1
kind: Deployment
metadata:
name: httpd2
namespace: kube-public # 指定命名空间为kube-public
spec:
replicas: 3
selector:
matchLabels:
run: httpd2
template:
metadata:
labels:
run: httpd2
spec:
containers:
- name: httpd2
image: httpd:latest
ports:
- containerPort: 80
---
apiVersion: v1
kind: Service
metadata:
name: httpd2-svc
namespace: kube-public # 映射kube-public命名空间内的指定标签的指定端口的端口
spec:
selector:
run: httpd2
ports:
- protocol: TCP
port: 8080
targetPort: 80
查看kube-public命名空间内的Service
[root@node1 ~]# kubectl get service --namespace=kube-public
NAME TYPE CLUSTER-IP EXTERNAL-IP PORT(S) AGE
httpd2-svc ClusterIP 10.102.115.129 <none> 8080/TCP 111s
首先访问Cluster IP
[root@node1 ~]# curl 10.102.115.129:8080
<html><body><h1>It works!</h1></body></html>
进入pod使用DNS验证
[root@node1 ~]# kubectl run busybox --rm -it --image=busybox /bin/sh
If you don't see a command prompt, try pressing enter.
# 不加namespace是访问不到的
/ # wget httpd2-svc:8080
wget: bad address 'httpd2-svc:8080'
# 加上namespace才可以,因为当前pod是在default命名空间内的
/ # wget httpd2-svc.kube-public:8080
Connecting to httpd2-svc.kube-public:8080 (10.102.115.129:8080)
saving to 'index.html'
index.html 100% |*********************************************| 45 0:00:00 ETA
'index.html' saved
/ # cat index.html
<html><body><h1>It works!</h1></body></html>
Cluster外部的Service访问
上面说到php可以采用内部的Service访问,外部的Service访问就可以用到Nginx,也就是前端服务需要被外界访问,可以使用外部的Service访问
Service类型
Cluster IP
Service通过Cluster内部的IP对外提供服务,只有Cluster内的节点和Pod可访问,这是默认的Service类型,前面的内部访问就是用了Cluster IP。
NodePort
Service通过Cluster节点的静态端口对外提供服务。Cluster外部可以通过集群中的节点IP:port访问到Service,自动分配的端口号在30000-32767,手动指定也必须是这个范围。
LoadBalancer
Service利用cloud provider特有的load balancer对外提供服务,cloud provider负责将load balancer的流量导向Service。
修改之前的配置文件来完成对外部的端口暴露
[root@node1 ~]# vim httpd-svc.yml
apiVersion: v1
kind: Service
metadata:
name: httpd-svc
spec:
type: NodePort # 只需要添加类型为NodePort即可
selector:
run: httpd
ports:
- protocol: TCP
port: 8080
targetPort: 80
运行修改httpd-svc的Service配置
[root@node1 ~]# kubectl apply -f httpd-svc.yml
service/httpd-svc configured
查看Service
[root@node1 ~]# kubectl get service httpd-svc
NAME TYPE CLUSTER-IP EXTERNAL-IP PORT(S) AGE
httpd-svc NodePort 10.102.3.63 <none> 8080:32558/TCP 131m
以上信息表示,pod容器内的80端口映射为Cluster IP的8080端口,然后再映射到节点ip的32558端口
查看主机端口
[root@node1 ~]# netstat -anput | grep 32558
tcp 0 0 0.0.0.0:32558 0.0.0.0:* LISTEN 5958/kube-proxy
现在可以直接访问k8s集群中的ip:32558即可
[root@node1 ~]# curl 192.168.1.11:32558
^C
[root@node1 ~]# curl 192.168.1.12:32558
<html><body><h1>It works!</h1></body></html>
[root@node1 ~]# curl 192.168.1.13:32558
<html><body><h1>It works!</h1></body></html>
因为master没有参与Scheduler的调度,所以访问master ip是访问不到的
NodeIP的映射原理
与Cluster IP一样,也是借助了iptables。与Cluster IP相比,每个节点的iptables都增加了两条规则,使用iptables-save
查看
-A KUBE-NODEPORTS -p tcp -m comment --comment "default/httpd-svc:" -m tcp --dport 32558 -j KUBE-MARK-MASQ
-A KUBE-NODEPORTS -p tcp -m comment --comment "default/httpd-svc:" -m tcp --dport 32558 -j KUBE-SVC-RL3JAE4GN7VOGDGP
规则表示访问,访问当前节点的32558端口时,会请求规则KUBE-SVC-RL3JAE4GN7VOGDGP
,规则内容为:
-A KUBE-SVC-RL3JAE4GN7VOGDGP -m comment --comment "default/httpd-svc:" -m statistic --mode random --probability 0.33333333349 -j KUBE-SEP-5PWFTV3INZUJOOJD
-A KUBE-SVC-RL3JAE4GN7VOGDGP -m comment --comment "default/httpd-svc:" -m statistic --mode random --probability 0.50000000000 -j KUBE-SEP-72A5OH6IP5DVGPP6
-A KUBE-SVC-RL3JAE4GN7VOGDGP -m comment --comment "default/httpd-svc:" -j KUBE-SEP-AD3FX47FRXVJYUZ4
这几个规则对应的是后端的Pod,不再去继续看每个规则之下的规则,与Cluster IP的原理中是一样的。
手动指定NodePort
直接修改配置文件
[root@node1 ~]# vim httpd-svc.yml
apiVersion: v1
kind: Service
metadata:
name: httpd-svc
spec:
type: NodePort
selector:
run: httpd
ports:
- protocol: TCP
nodePort: 30000 # 添加了nodePort,指定映射到主机的端口,范围30000-32767
port: 8080
targetPort: 80
修改应用Service
[root@node1 ~]# kubectl apply -f httpd-svc.yml
service/httpd-svc configured
直接访问节点ip:30000
[root@node1 ~]# curl 192.168.1.12:30000
<html><body><h1>It works!</h1></body></html>
[root@node1 ~]# curl 192.168.1.13:30000
<html><body><h1>It works!</h1></body></html>
多端口映射写法
apiVersion: v1
kind: Service
metadata:
name: httpd-svc
spec:
type: NodePort
selector:
run: httpd
ports:
- protocol: TCP
nodePort: 30000
port: 8080
targetPort: 80
- protocol: TCP
nodePort: 30443
port: 8081
targetPort: 443