概述 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 ports: - port: 30080 targetPort: 80 nodePort: 30001 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 : v1kind : Servicemetadata : 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 $ kubectl edit svc my-svc-cp 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: {}
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 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 36 m 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 1 h <none> svc/my-svc-cp ClusterIP 10.247 .52 .210 <none> 8080 /TCP 3 m 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 1 h ep/my-svc-cp 172.16 .0 .36 :80 3 m $ 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 NotReadyAddresses: <none> Ports: Name Port Protocol - --- ---- -------- 80 -8080 80 TCP Events: <none> $ 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 $ 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
二、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> $ 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: {} .. .. .. .. .. ..
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 $ 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 strategy: {} template: metadata: creationTimestamp: null labels: app: my-svc-np-pod spec: containers: - image: nginx name: nginx-deployment resources: {} status: {} EOF $ kubectl create -f nginx-deployment.yaml deployment.apps/nginx-deployment created $ kubectl get pod -o wide NAME READY STATUS RESTARTS AGE IP NODE NOMINATED NODE nginx-deployment-6794c779fb-8x92g 1 /1 Running 0 4 m 10.244 .1 .19 k8s-m2 <none> nginx-deployment-6794c779fb-rf8qj 1 /1 Running 0 4 m 10.244 .0 .19 k8s-m3 <none> nginx-deployment-6794c779fb-tdscx 1 /1 Running 0 4 m 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 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 42 m $ 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 $ 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 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 strategy: {} template: metadata: creationTimestamp: null labels: app: my-svc-headless 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 40 s 10.244 .1 .21 k8s-m2 <none> nginx-deployment-54bfc79477-dvp2t 1 /1 Running 0 40 s 10.244 .0 .22 k8s-m3 <none> nginx-deployment-54bfc79477-x6v7s 1 /1 Running 0 40 s 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> kube-dns ClusterIP 10.96 .0 .10 <none> 53 /UDP,53/TCP 13 d $ 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 $ 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 ;; 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这种访问方式之前也在学习实践过程中已经实现过了,感兴趣可以撩我