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

评论




正在载入...
PoweredHexo
HostedAliyun
DNSAliyun
ThemeVolantis
UV
PV
BY-NC-SA 4.0