概述

Pod的IP是在docker0网段动态分配的,当发生重启,扩容等操作时,IP地址会随之变化。当某个Pod(frontend)需要去访问其依赖的另外一组Pod(backend)时,如果backend的IP发生变化时,如何保证fronted到backend的正常通信变的非常重要。由此,引出了Service的概念。
这里docker0是一个网桥,docker daemon启动container时会根据docker0的网段来划粉container的IP地址Docker网络
在实际生产环境中,对Service的访问可能会有两种来源:Kubernetes集群内部的程序(Pod)和Kubernetes集群外部,为了满足上述的场景,Kubernetes service有以下三种类型:

  • ClusterIP:提供一个集群内部的虚拟IP以供Pod访问。
  • NodePort:在每个Node上打开一个端口以供外部访问。
  • LoadBalancer:通过外部的负载均衡器来访问。

那么它是怎么实现的,通过下面的示例来理解。(创建方式可以是通过yaml文件或者是命令行方式,这里为了理解我先用命令行方式创建,如果不合适的地方我们通过kubectl edit (RESOURCE/NAME | -f FILENAME) [options]这种方式先修改)

其次,还需要理解NodePort,TargetPort以及port他们的区别:
NodePort
外部机器可访问的端口。
比如一个Web应用需要被其他用户访问,那么需要配置type=NodePort,而且配置nodePort=30001,那么其他机器就可以通过浏览器访问scheme://node:30001访问到该服务,例如http://node:30001。
例如MySQL数据库可能不需要被外界访问,只需被内部服务访问,那么不必设置NodePort

TargetPort
容器的端口(最根本的端口入口),与制作容器时暴露的端口一致(DockerFile中EXPOSE),例如docker.io官方的nginx暴露的是80端口。
docker.io官方的nginx容器的DockerFile参考https://github.com/nginxinc/docker-nginx

1
2
3
4
5
6
7
8
9
10
11
12
apiVersion: v1
kind: Service
metadata:
name: nginx-service
spec:
type: NodePort # 有配置NodePort,外部流量可访问k8s中的服务
ports:
- port: 30080 # 服务访问端口
targetPort: 80 # 容器端口
nodePort: 30001 # NodePort
selector:
name: nginx-pod

 

port
 kubernetes中的服务之间访问的端口,尽管mysql容器暴露了3306端口(参考https://github.com/docker-library/mysql/的DockerFile),但是集群内其他容器需要通过33306端口访问该服务,外部机器不能访问mysql服务,因为他没有配置NodePort类型

1
2
3
4
5
6
7
8
9
10
apiVersion: v1
kind: Service
metadata:
name: mysql-service
spec:
ports:
- port: 33306
targetPort: 3306
selector:
name: mysql-pod

一、Create service (type: ClusterIP)

此模式会提供一个集群内部的虚拟IP(与Pod不在同一网段),以供集群内部的pod之间通信使用。
ClusterIP也是Kubernetes service的默认类型。

为了实现图上的功能主要需要以下几个组件的协同工作

apiserver 用户通过kubectl命令向apiserver发送创建service的命令,apiserver接收到请求以后将数据存储到etcd中。

kube-proxy kubernetes的每个节点中都有一个叫做kube-proxy的进程,这个进程负责感知service,pod的变化,并将变化的信息写入本地的iptables中。

iptables 使用NAT等技术将virtualIP的流量转至endpoint中。

下面我们实际发布一个Service,能够更清晰的了解到Service是如何工作的。

1. 通过命令行方式创建service,类型为clusterip

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
$ kubectl create service clusterip my-svc-cp --tcp=8080:80

$ kubectl describe service/my-svc-cp
Name: my-svc-cp
Namespace: default
Labels: app=my-svc-cp
Annotations: <none>
Selector: app=my-svc-cp
Type: ClusterIP # 类型
IP: 10.247.52.210
Port: 80-8080 8080/TCP # 映射到集群的端口
TargetPort: 80/TCP # 目标pod暴露端口
Endpoints: <none> # 此时还没有后端容器
Session Affinity: None
Events: <none>

$ kubectl get svc -o wide
my-svc-cp ClusterIP 10.247.52.210 <none> 80/TCP 6s app=my-svc-cp

# 以上我创建了一个svc, 那么他是为pod服务的,
# 那么 pod跟service是通过`selector label`来做关联的, 所以我们还需要对这个svc做下调整

$ kubectl edit svc my-svc-cp
# Please edit the object below. Lines beginning with a '#' will be ignored,
# and an empty file will abort the edit. If an error occurs while saving this file will be
# reopened with the relevant failures.
#
apiVersion: v1
kind: Service
metadata:
creationTimestamp: 2018-12-12T17:41:54Z
labels:
app: my-svc-cp
name: my-svc-cp
namespace: default
resourceVersion: "32742"
selfLink: /api/v1/namespaces/default/services/my-svc-cp
uid: 372f6f2c-fe35-11e8-b967-fa163e874e90
spec:
clusterIP: 10.247.52.210
ports:
- name: 80-8080
port: 8080
protocol: TCP
targetPort: 80
selector:
app: my-svc-cp-pod # 这里我修改了selector ,需要与pod name与之对应起来
sessionAffinity: None
type: ClusterIP
status:
loadBalancer: {}


# :wq 保存退出
# kubectl describe service/my-svc-cp 查看修改是否成功

2.创建pod

注意selector label

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
$ vim my-svc-cp-pod.yaml
apiVersion: v1
kind: Pod
metadata:
name: my-svc-cp-pod
labels:
app: my-svc-cp-pod
spec:
containers:
- image: nginx:latest
imagePullPolicy: Always
name: nginx
# 注意这里的亲和性是为了将pod调度至有外网的node节点便于pull镜像
affinity:
nodeAffinity:
requiredDuringSchedulingIgnoredDuringExecution:
nodeSelectorTerms:
- matchExpressions:
- key: kubernetes.io/hostname
operator: In
values:
- 192.168.253.183
restartPolicy: Always
schedulerName: default-scheduler

# 执行:
$ kubectl create -f my-svc-cp-pod.yaml

$ kubectl get pods,svc,endpoints,deployment -o wide
NAME READY STATUS RESTARTS AGE IP NODE
po/my-svc-cp-pod 1/1 Running 0 36m 172.16.0.36 192.168.253.183

NAME TYPE CLUSTER-IP EXTERNAL-IP PORT(S) AGE SELECTOR
svc/kubernetes ClusterIP 10.247.0.1 <none> 443/TCP 1h <none>
svc/my-svc-cp ClusterIP 10.247.52.210 <none> 8080/TCP 3m app=my-svc-cp-pod

NAME ENDPOINTS AGE
ep/kubernetes 192.168.103.50:5444,192.168.174.46:5444,192.168.236.124:5444 1h
ep/my-svc-cp 172.16.0.36:80 3m

# 再次查看service是否关联到了pod
$ kubectl describe ep/my-svc-cp
Name: my-svc-cp
Namespace: default
Labels: app=my-svc-cp
Annotations: <none>
Subsets:
Addresses: 172.16.0.36 # 可见endpoints中已经有了后端的那个pod
NotReadyAddresses: <none>
Ports:
Name Port Protocol
---- ---- --------
80-8080 80 TCP

Events: <none>

# 集群内部通过Cluster IP访问nginx服务
# 指定了端口映射到Cluster IP
$ curl -I 10.247.52.210:8080
HTTP/1.1 200 OK
Server: nginx/1.15.5
Date: Wed, 12 Dec 2018 17:48:56 GMT
Content-Type: text/html
Content-Length: 612
Last-Modified: Tue, 02 Oct 2018 14:49:27 GMT
Connection: keep-alive
ETag: "5bb38577-264"
Accept-Ranges: bytes

# 集群内部通过POD IP访问nginx服务
# 因为pod暴露的是80 默认不用加端口
$ curl -I 172.16.0.36
HTTP/1.1 200 OK
Server: nginx/1.15.5
Date: Wed, 12 Dec 2018 17:47:52 GMT
Content-Type: text/html
Content-Length: 612
Last-Modified: Tue, 02 Oct 2018 14:49:27 GMT
Connection: keep-alive
ETag: "5bb38577-264"
Accept-Ranges: bytes

# 通过这种方式也只是集群内部能够访问到,如果集群外部要访问我们的pod又该如何做呢?
# 下面继续

二、Create service (type: NodePort)

1.通过命令行方式创建service,类型为nodeport

Cluster service 的 IP 地址是虚拟的,因此,只能从node节点上使用该IP 地址访问应用。为了从集群外访问应用,K8S 提供了使用 node 节点的IP 地址访问应用的方式。

基本上,NodePort 服务与普通的 “ClusterIP” 服务 YAML 定义有两点区别。 首先,type 是 “NodePort”。还有一个称为 nodePort 的附加端口,指定在节点上打开哪个端口。 如果你不指定这个端口,它会选择一个随机端口。

下图中是 32591. 该端口号的范围是 kube-apiserver 的启动参数 –service-node-port-range指定的,在当前测试环境中其值是 30000-32767。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
$ kubectl create service nodeport my-svc-np --tcp=8080:80

[root@linux-node1 ~]# kubectl describe service my-svc-np
Name: my-svc-np
Namespace: default
Labels: app=my-svc-np
Annotations: <none>
Selector: app=my-svc-np
Type: NodePort # 类型
IP: 10.103.115.169 # 分配的ClusterIP
Port: 8080-80 8080/TCP # 容器映射到node节点的端口
TargetPort: 80/TCP # 容器将要暴露的端口
NodePort: 8080-80 32591/TCP # 附加端口
Endpoints: <none> # 此时后端没有pod
Session Affinity: None
External Traffic Policy: Cluster
Events: <none>


# 有了svc我们就需要创建一个或者一组pod来跟这个svc建立连接
# 像前面一样我们需要更改这个svc的选择器:
$ kubectl edit svc my-svc-np
......
......
externalTrafficPolicy: Cluster
ports:
- name: 8080-80
nodePort: 32591
port: 8080
protocol: TCP
targetPort: 80
selector:
app: my-svc-np-pod # 与即将创建的pod对应上
sessionAffinity: None
type: NodePort
status:
loadBalancer: {}
......
......
# :wq保存退出

2.创建pod与之建立关系

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
# 我这里通过命令行方式创建一个deployment的3个副本的nginx pod
# 1. 首先通过命令行生成yaml文件
$ kubectl run nginx-deployment --image=nginx --replicas=3 --dry-run -o yaml > nginx-deployment.yaml

# 2. 进行调整
$ cat >> nginx-deployment.yaml << EOF
apiVersion: apps/v1beta1
kind: Deployment
metadata:
creationTimestamp: null
labels:
run: nginx-deployment
name: nginx-deployment
spec:
replicas: 3
selector:
matchLabels:
app: my-svc-np-pod # 此处修改为svc selector相同
strategy: {}
template:
metadata:
creationTimestamp: null
labels:
app: my-svc-np-pod # 此处修改为svc selector相同
spec:
containers:
- image: nginx
name: nginx-deployment
resources: {}
status: {}
EOF
# 3. 执行
$ kubectl create -f nginx-deployment.yaml
deployment.apps/nginx-deployment created

# 4. 查看状态:
$ kubectl get pod -o wide
NAME READY STATUS RESTARTS AGE IP NODE NOMINATED NODE
nginx-deployment-6794c779fb-8x92g 1/1 Running 0 4m 10.244.1.19 k8s-m2 <none>
nginx-deployment-6794c779fb-rf8qj 1/1 Running 0 4m 10.244.0.19 k8s-m3 <none>
nginx-deployment-6794c779fb-tdscx 1/1 Running 0 4m 10.244.2.35 k8s-m1 <none>
$ kubectl describe svc my-svc-np
Name: my-svc-np
Namespace: default
Labels: app=my-svc-np
Annotations: <none>
Selector: app=my-svc-np-pod
Type: NodePort
IP: 10.103.115.169
Port: 8080-80 8080/TCP
TargetPort: 80/TCP
NodePort: 8080-80 32591/TCP
Endpoints: 10.244.0.19:80,10.244.1.19:80,10.244.2.35:80 # 可以看到我们的三个endpoint
Session Affinity: None
External Traffic Policy: Cluster
Events: <none>
$ kubectl get svc my-svc-np
NAME TYPE CLUSTER-IP EXTERNAL-IP PORT(S) AGE
my-svc-np NodePort 10.103.115.169 <none> 8080:32591/TCP 42m

# 5.如何访问?
# a.集群内部访问pod ip
$ curl -I 10.244.0.19
HTTP/1.1 200 OK
Server: nginx/1.15.7
Date: Thu, 13 Dec 2018 08:43:25 GMT
Content-Type: text/html
Content-Length: 612
Last-Modified: Tue, 27 Nov 2018 12:31:56 GMT
Connection: keep-alive
ETag: "5bfd393c-264"
Accept-Ranges: bytes

# b.集群内部clusterip+端口
$ curl -I 10.103.115.169:8080
HTTP/1.1 200 OK
Server: nginx/1.15.7
Date: Thu, 13 Dec 2018 08:42:53 GMT
Content-Type: text/html
Content-Length: 612
Last-Modified: Tue, 27 Nov 2018 12:31:56 GMT
Connection: keep-alive
ETag: "5bfd393c-264"
Accept-Ranges: bytes

# c.集群外部访问nodeip+附加端口
curl -I 192.168.56.111:32591
HTTP/1.1 200 OK
Server: nginx/1.15.7
Date: Thu, 13 Dec 2018 08:45:39 GMT
Content-Type: text/html
Content-Length: 612
Last-Modified: Tue, 27 Nov 2018 12:31:56 GMT
Connection: keep-alive
ETag: "5bfd393c-264"
Accept-Ranges: bytes

以上就是nodeport网络类型的简单实现

三、Create service (type: HeadlessClusterIP)

所谓的HeadlessClusterIP 就是我们在创建这种网络类型的时候将 spec.clusterIP 设置成 None,这样k8s就不会给service分配clusterIp了。但指定了selector,那么endpoints controller还是会创建Endpoints的,会创建一个新的DNS记录直接指向这个service描述的后端pod。否则,不会创建Endpoints记录。这种ClusterIP,kube-proxy 并不处理此类服务,因为没有load balancing或 proxy 代理设置,在访问服务的时候回返回后端的全部的Pods IP地址,主要用于开发者自己根据pods进行负载均衡器的开发(设置了selector)。

下面通过实践理解

1.通过命令行方式创建svc,类型为headlessCluserIP

1
2
3
4
5
6
7
8
9
10
11
12
13
$ kubectl create svc clusterip my-svc-headless --clusterip="None" --tcp=8080:80
service/my-svc-headless created

$ kubectl describe svc my-svc-headless
Name: my-svc-headless
Namespace: default
Labels: app=my-svc-headless
Annotations: <none>
Selector: app=my-svc-headless
Type: ClusterIP
IP: None
Session Affinity: None
Events: <none>

2. 创建pod与之关联:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
$ cat nginx-deployment.yaml
apiVersion: apps/v1beta1
kind: Deployment
metadata:
creationTimestamp: null
labels:
run: nginx-deployment
name: nginx-deployment
spec:
replicas: 3
selector:
matchLabels:
app: my-svc-headless # 对应到svc
strategy: {}
template:
metadata:
creationTimestamp: null
labels:
app: my-svc-headless # 对应到svc

spec:
containers:
- image: nginx
name: nginx-deployment
resources: {}
status: {}

$ kubectl create -f nginx-deployment.yaml
deployment.apps/nginx-deployment created

$ [root@k8s-m1 ~]# kubectl get pod -o wide
NAME READY STATUS RESTARTS AGE IP NODE NOMINATED NODE
nginx-deployment-54bfc79477-b78c6 1/1 Running 0 40s 10.244.1.21 k8s-m2 <none>
nginx-deployment-54bfc79477-dvp2t 1/1 Running 0 40s 10.244.0.22 k8s-m3 <none>
nginx-deployment-54bfc79477-x6v7s 1/1 Running 0 40s 10.244.2.38 k8s-m1 <none>


$ kubectl describe svc my-svc-headless
Name: my-svc-headless
Namespace: default
Labels: app=my-svc-headless
Annotations: <none>
Selector: app=my-svc-headless
Type: ClusterIP
IP: None
Port: 8080-80 8080/TCP
TargetPort: 80/TCP
Endpoints: 10.244.0.20:80,10.244.1.20:80,10.244.2.36:80
Session Affinity: None
Events: <none>

# 以上可以看出后端的pod列表已经加到该svc

# 我们看通过内部域名是否能够解析访问到pod
# 1.我们先获取到dns服务的IP
#kubectl get svc -n kube-system | grep dns
kube-dns ClusterIP 10.96.0.10 <none> 53/UDP,53/TCP 13d

# 2.登录到pod 内部获取dns域
$ kubectl exec -it nginx-deployment-54bfc79477-b78c6 -- cat /etc/resolv.conf
nameserver 10.96.0.10
search default.svc.cluster.local svc.cluster.local cluster.local
options ndots:5

# 3.指定dns服务器并查询该域名:`nginx-deployment.default.svc.cluster.local`
$ dig @10.96.0.10 my-svc-headless.default.svc.cluster.local

; <<>> DiG 9.9.4-RedHat-9.9.4-72.el7 <<>> @10.96.0.10 my-svc-headless.default.svc.cluster.local
; (1 server found)
;; global options: +cmd
;; Got answer:
;; ->>HEADER<<- opcode: QUERY, status: NOERROR, id: 41743
;; flags: qr aa rd ra; QUERY: 1, ANSWER: 3, AUTHORITY: 0, ADDITIONAL: 0

;; QUESTION SECTION:
;my-svc-headless.default.svc.cluster.local. IN A

;; ANSWER SECTION:
my-svc-headless.default.svc.cluster.local. 30 IN A 10.244.0.22
my-svc-headless.default.svc.cluster.local. 30 IN A 10.244.1.21
my-svc-headless.default.svc.cluster.local. 30 IN A 10.244.2.38

;; Query time: 29 msec
;; SERVER: 10.96.0.10#53(10.96.0.10)
;; WHEN: Thu Dec 13 04:37:13 EST 2018
;; MSG SIZE rcvd: 107
由上可见 dns服务为service和pod生成不同格式的DNS记录
Service
  • A记录:生成my-svc.my-namespace.svc.cluster.local域名,解析成 IP 地址,分为两种情况:
    1.普通 Service:解析成 ClusterIP
    2.Headless Service:解析为指定 Pod的IP列表(上述示例就是headless)

  • SRV记录:为命名的端口(普通 Service 或 Headless Service)生成_my-port-name._my-port-protocol.my-svc.my-namespace.svc.cluster.local的域名

Pod
  • A记录:生成域名 pod-ip.my-namespace.pod.cluster.local

上述示例个人理解是 这种无头ClusterIP类型 在其集群内部直接可以通过该域名去访问pod,
并且该域名也起到了通过dns做负载的能力。

其他访问法方式可查阅官网学习,此篇博文主要是再次学习巩固一下知识~
诸如ingress这种访问方式之前也在学习实践过程中已经实现过了,感兴趣可以撩我