K8S互动教程4-使用服务发布您的应用程序

本文是《浅入浅出k8s实战-k8s初体验篇的第五篇 》

此文为提取大纲,按照互动教程进行分享教学用。另补充一些知识点,和关联操作。

原文:

课程说明

目标

  • 了解 Kubernetes Service(服务)
  • 了解 labels 和 LabelSelector 对象如何与 Service 关联
  • 通过 Service 将应用暴露到集群外部

操作内容

  • 创建新的服务
  • 使用Labels
  • 删除Service

Kubernetes Service 概述

Kubernetes Pods 不是永久存在的,它有一个生命周期。当一个工作Node宕机是,运行在这个Node上的Pods也会丢失。ReplicatSet 可能会通过创建新的Pod,动态地调度集群使之恢复到目标状态来保证您的应用继续运行。比如说,有个拥有3个副本的图形处理后端程序,这些副本是可以被替换的(简单理解为无状态的)。其前端系统不应该关心后端的副本情况,即使副本的Pod丢失后被重建。Kubernetes集群中每一个Pod有一个独立的IP,即使在同一个Node上也是这样,这样的话我们就需要一种能够自动应对Pod间变化的机制来保证我们的程序能持续的运行。

Kubernetes 中的Service是一个抽象资源,它定义了一组Pod的逻辑集合和通过Service访问这些Pod的策略。Services支持有依赖关系的Pods松耦合。Service 像所有Kubernetes对象一样,支持使用Yaml(推荐)或JSON。Service 指向的一组Pods通常由 LabelSelector 指定(为什么你可能不想要在Service 的 spce 中包含 selector 请参见下文)。

尽管每个Pod都有一个唯一的IP地址,但不通过Service这些IP不会被暴露到集群外部(一般意义上是这样,但存在网络互通方案)。Services允许你的应用接收流量。通过指定ServiceSpec中的 type 字段,Services可以被不同的方式暴露

  • ClusterIP(默认)- 在集群内部IP上暴露Service。这种方式Service只能在集群内部被访问。
  • NodePort - 通过NAT在每个选中(未找到限定方式,应该是所有Node?)Node上的相同端口暴露Service。使一个Service可以通过 <NodeIP>:<NodePort> 的形式从外部访问,这种方式是 ClusterIP 的超集。
  • LoadBalancer - 在当前云平台创建外部的负载均衡器(如果支持的话) ,并为Service分配一个固定的集群外部IP,是NodePort的超集。
  • ExternalName - 使用任意的名称暴露Service(通过spec的 externalName 指定),其行为是返回使用这个名称的CNAME记录。无需使用代理。这种方式要求 V1.7或更高版本的 kube-dns

关于不同类型 Services 的更多信息,可以参见Using Source IP教程和Connecting Applications with Services
另外,需要注意的是有些情况下Services不在spec中定义 selector 。没有 selector 定义创建的Service也不会创建相应的端点(Endpoints)。这样用户可以手动映射Service到特定的endpoints。另外一种没有selector的情况是严格用了 type: ExternalName 类型的Service。

服务和标签


服务可以路由流量到一组Pods上。服务是一个抽象资源,允许Kubernetes中Pod在宕掉和复制的情况下不对应用造成冲击。有依赖关系的Pods间(比如一个应用的前端和后端组件)发现和路由通过Kubernetes Services处理。

Services使用labels and selectors匹配一组Pods。lables和selectors是Kubernetes可以在对象上进行逻辑运算的分组原语。Labels 是附加到对象上的键值对,会在多种场景中被使用:

  • 指定和区分对象是开发、测试还是生产环境
  • 嵌入版本号
  • 用标签(tags)分类对象

    你可以使用kubectl 的 --expose 参数,在创建 Deployment 的同时创建 Service。


Labels 可以创建对象的时候或之后附加到对象上。可以在任何时候被修改。我们一起使用Service暴露我么的应用,并应用一些labels吧。

互动教程

创建新的服务

我们先看下基于上一节之后我们系统里有哪些Pods:

1
kubectl get pods
1
2
NAME                                  READY   STATUS    RESTARTS   AGE
first-deploy-whoami-68dd7db55-bpr52 1/1 Running 0 4d1h

然后再看看已有的 Services:

1
kubectl get services
1
2
NAME         TYPE        CLUSTER-IP   EXTERNAL-IP   PORT(S)   AGE
kubernetes ClusterIP 10.96.0.1 <none> 443/TCP 10d

我们可以看到一个已经存在的名叫kubernetes的服务,这个是minikube在集群启动的时候自动创建的。这个是 API Server 的 Service。非 minikube 创建的集群default namespace下也有。

我们来创建如下几个不同的 Service:

1
kubectl expose deployment first-deploy-whoami --name first-deploy-whoami-svc-1 --type LoadBalancer --port 8080 --target-port 80
1
kubectl expose deployment first-deploy-whoami --name first-deploy-whoami-svc-2 --type NodePort --port 80
1
kubectl expose deployment first-deploy-whoami --name first-deploy-whoami-svc-3 --type ClusterIP --port 8080 --target-port 80

我们使用的方式是暴露我们前两节创建的 deployment 对应的 Pods。 --name 后就是我们指定的 Service name, --type 是指定Service的暴露方式。 --port 是Service对外服务的端口。 --target-port 是Service映射的Endpoints 端口部分(Pods的对外服务端口)。需要注意的是,如果--port--target-port 没有指定,将默认取另一项的值。都没指定则取Pods暴露的端口。

当然, expose 可以将很多kubernetes资源对应的Pods暴露为服务,包含:

1
pod (po), service (svc), replicationcontroller (rc), deployment (deploy), replicaset (rs)

我们之前暴露的几个服务通过

1
kubectl get services

1
kubectl describe services first-deploy-whoami-svc-1 first-deploy-whoami-svc-2 first-deploy-whoami-svc-3

来看一下。着重关注IP,Port,TargetPort,NodePort和 Endpoints字段。

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
  clark kubectl get services
NAME TYPE CLUSTER-IP EXTERNAL-IP PORT(S) AGE
first-deploy-whoami-svc-1 LoadBalancer 10.103.234.1 <pending> 8080:30095/TCP 18h
first-deploy-whoami-svc-2 NodePort 10.102.32.78 <none> 80:31502/TCP 18h
first-deploy-whoami-svc-3 ClusterIP 10.101.43.23 <none> 8080/TCP 18h
kubernetes ClusterIP 10.96.0.1 <none> 443/TCP 11d
clark kubectl describe services first-deploy-whoami-svc-1 first-deploy-whoami-svc-2 first-deploy-whoami-svc-3
Name: first-deploy-whoami-svc-1
Namespace: default
Labels: run=first-deploy-whoami
Annotations: <none>
Selector: run=first-deploy-whoami
Type: LoadBalancer
IP: 10.103.234.1
Port: <unset> 8080/TCP
TargetPort: 80/TCP
NodePort: <unset> 30095/TCP
Endpoints: 172.17.0.5:80
Session Affinity: None
External Traffic Policy: Cluster
Events: <none>


Name: first-deploy-whoami-svc-2
Namespace: default
Labels: run=first-deploy-whoami
Annotations: <none>
Selector: run=first-deploy-whoami
Type: NodePort
IP: 10.102.32.78
Port: <unset> 80/TCP
TargetPort: 80/TCP
NodePort: <unset> 31502/TCP
Endpoints: 172.17.0.5:80
Session Affinity: None
External Traffic Policy: Cluster
Events: <none>


Name: first-deploy-whoami-svc-3
Namespace: default
Labels: run=first-deploy-whoami
Annotations: <none>
Selector: run=first-deploy-whoami
Type: ClusterIP
IP: 10.101.43.23
Port: <unset> 8080/TCP
TargetPort: 80/TCP
Endpoints: 172.17.0.5:80
Session Affinity: None
Events: <none>
  • IP 就是集群内的IP
  • Port和TargetPort上面介绍过,对应 --port--target-port
  • NodePort通过命令的方式未能找到指定的参数(如果发现请留言告诉我),但YAML等方式可以指定。所以此处不指定会默认从30000-32767中随机一个端口,这个随机范围可以在API server的配置文件中,用–service-node-port-range定义。
  • Endpoints 就是Service映射的Pod ip+port 了,会根据Pod情况动态变化。

根据我们创建的不同Service类型,也可以印证我们之前提到的“超集”概念。LoadBalancer 包含 NodePort 包含 ClusterIP。

minikube 并不支持 LoadBalancer 类型的Service,所以其 External-IP 一直是 <pending> 状态。对于有node port的Service,minikube 可以通过 service 命令来在浏览器打开或者显示其访问地址:

1
minikube.exe service first-deploy-whoami-svc-1
1
minikube.exe service first-deploy-whoami-svc-2 --url

也可以列出所有的Service的访问地址:

1
2
3
4
5
6
7
8
9
10
11
➜  clark minikube.exe service list                                        
|-------------|---------------------------|-----------------------------|
| NAMESPACE | NAME | URL |
|-------------|---------------------------|-----------------------------|
| default | first-deploy-whoami-svc-1 | http://192.168.99.101:30095 |
| default | first-deploy-whoami-svc-2 | http://192.168.99.101:31502 |
| default | first-deploy-whoami-svc-3 | No node port |
| default | kubernetes | No node port |
| kube-system | kube-dns | No node port |
| kube-system | kubernetes-dashboard | No node port |
|-------------|---------------------------|-----------------------------|

通过上面的地址,你应该可以访问到Pod的应用,如:

1
2
3
4
5
6
7
8
➜  clark curl http://192.168.99.101:30095/
Hostname: first-deploy-whoami-68dd7db55-8rhd2
IP: 127.0.0.1
IP: 172.17.0.5
GET / HTTP/1.1
Host: 192.168.99.101:30095
User-Agent: curl/7.58.0
Accept: */*

我们可以尝试在集群中访问这几个服务,启动新的一次性Pods访问:

1
kubectl run test-service-pod -it --rm --restart=Never --image=busybox --port=80 --generator=run-pod/v1 -- wget -q -O - http://first-deploy-whoami-svc-1:8080

如果你想在Service映射的Pod中通过Service访问Pod自己,可能会遇到无法访问的情况,请参照这篇文章:
Pods 无法通过 Service 访问自己

使用 Labels

复习获取 deployments 的命令:

1
kubectl describe deployments

我们可以看到类似的输出:

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
  clark kubectl describe deployments                                                                              
Name: first-deploy-whoami
Namespace: default
CreationTimestamp: Tue, 28 May 2019 13:20:39 +0800
Labels: run=first-deploy-whoami
Annotations: deployment.kubernetes.io/revision: 1
Selector: run=first-deploy-whoami
Replicas: 1 desired | 1 updated | 1 total | 1 available | 0 unavailable
StrategyType: RollingUpdate
MinReadySeconds: 0
RollingUpdateStrategy: 25% max unavailable, 25% max surge
Pod Template:
Labels: run=first-deploy-whoami
Containers:
first-deploy-whoami:
Image: registry.cn-hangzhou.aliyuncs.com/khan/whoami:1.0
Port: 80/TCP
Host Port: 0/TCP
Environment: <none>
Mounts: <none>
Volumes: <none>
Conditions:
Type Status Reason
---- ------ ------
Available True MinimumReplicasAvailable
Progressing True NewReplicaSetAvailable
OldReplicaSets: <none>
NewReplicaSet: first-deploy-whoami-68dd7db55 (1/1 replicas created)
Events:
Type Reason Age From Message
---- ------ ---- ---- -------
Normal ScalingReplicaSet 10m deployment-controller Scaled up replica set first-deploy-whoami-68dd7db55 to 1

可以看到其中有如下两行:

1
2
Pod Template:                                                                                                      
Labels: run=first-deploy-whoami

我们尝试用这个 Labels 获取 Pods:

1
2
3
➜  clark kubectl get pods -l run=first-deploy-whoami
NAME READY STATUS RESTARTS AGE
first-deploy-whoami-68dd7db55-qtfdq 1/1 Running 0 12m

同样用这个 Labels 获取 Services:

1
2
3
4
5
➜  clark kubectl get services -l run=first-deploy-whoami
NAME TYPE CLUSTER-IP EXTERNAL-IP PORT(S) AGE
first-deploy-whoami-svc-1 LoadBalancer 10.103.234.1 <pending> 8080:30095/TCP 19h
first-deploy-whoami-svc-2 NodePort 10.102.32.78 <none> 80:31502/TCP 19h
first-deploy-whoami-svc-3 ClusterIP 10.101.43.23 <none> 8080/TCP 19h

我们把pod放到 POD_NAME 变量中:

1
POD_NAME=$(kubectl get pods -o go-template --template '{{range .items}}{{.metadata.name}}{{"\n"}}{{end}}')
1
echo $POD_NAME

我们可以先看下这个Pod的Labels:

1
kubectl describe pod $POD_NAME

其中有如下输出:

1
2
Labels:             pod-template-hash=68dd7db55
run=first-deploy-whoami

现在我们给这个Pod增加一个应用版本号 app_version :

1
kubectl label pod $POD_NAME app_version=v1

再来看一下Pod的Labels,会看到如下输出:

1
2
3
4
5
6
➜  clark kubectl describe pod $POD_NAME
...
Labels: app_version=v1
pod-template-hash=68dd7db55
run=first-deploy-whoami
...

我们可以用新的Labels来获取Pods:

1
2
3
➜  clark kubectl get pods -l app_version=v1
NAME READY STATUS RESTARTS AGE
first-deploy-whoami-68dd7db55-qtfdq 1/1 Running 0 37m

删除 Service

我们之前创建了几个Service,我们当然可以沿用笨方法去删除,但有了Labels后,我们就可以这样做:

1
kubectl delete service -l run=first-deploy-whoami

在我们的例子中,你会看到如下输出:

1
2
3
service "first-deploy-whoami-svc-1" deleted
service "first-deploy-whoami-svc-2" deleted
service "first-deploy-whoami-svc-3" deleted

通过下面的命令应该发现Service不见了:

1
kubectl get services

删除Service之后,讲道理,我们之前的访问方式应该是不可用了,我们可以使用之前的命令校验下:

1
curl http://192.168.99.101:30095/
1
minikube.exe service list
1
kubectl run test-service-pod -it --rm --restart=Never --image=busybox --port=80 --generator=run-pod/v1 -- wget -q -O - http://first-deploy-whoami-svc-1:8080

但是,Pod中访问自己,应该还是一样:

1
2
3
4
5
6
7
8
➜  clark kubectl exec -it $POD_NAME -- wget -q -O - http://localhost
Hostname: first-deploy-whoami-68dd7db55-qtfdq
IP: 127.0.0.1
IP: 172.17.0.7
GET / HTTP/1.1
Host: localhost
User-Agent: Wget
Connection: close
谢谢鼓励