本文是《浅入浅出k8s实战-k8s初体验篇的第五篇 》
此文为提取大纲,按照互动教程进行分享教学用。另补充一些知识点,和关联操作。
原文:
- 中:https://kubernetes.io/zh/docs/tutorials/kubernetes-basics/expose-intro/
- 英:https://kubernetes.io/docs/tutorials/kubernetes-basics/expose/expose-intro/
- 互动:https://kubernetes.io/docs/tutorials/kubernetes-basics/expose/expose-interactive/
课程说明
目标
- 了解 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 | NAME READY STATUS RESTARTS AGE |
然后再看看已有的 Services:
1 | kubectl get services |
1 | NAME TYPE CLUSTER-IP EXTERNAL-IP PORT(S) AGE |
我们可以看到一个已经存在的名叫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 | ➜ clark kubectl get services |
- 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 | ➜ clark minikube.exe service list |
通过上面的地址,你应该可以访问到Pod的应用,如:
1 | ➜ clark curl http://192.168.99.101:30095/ |
我们可以尝试在集群中访问这几个服务,启动新的一次性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 | ➜ clark kubectl describe deployments |
可以看到其中有如下两行:
1 | Pod Template: |
我们尝试用这个 Labels 获取 Pods:
1 | ➜ clark kubectl get pods -l run=first-deploy-whoami |
同样用这个 Labels 获取 Services:
1 | ➜ clark kubectl get services -l run=first-deploy-whoami |
我们把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 | Labels: pod-template-hash=68dd7db55 |
现在我们给这个Pod增加一个应用版本号 app_version
:
1 | kubectl label pod $POD_NAME app_version=v1 |
再来看一下Pod的Labels,会看到如下输出:
1 | ➜ clark kubectl describe pod $POD_NAME |
我们可以用新的Labels来获取Pods:
1 | ➜ clark kubectl get pods -l app_version=v1 |
删除 Service
我们之前创建了几个Service,我们当然可以沿用笨方法去删除,但有了Labels后,我们就可以这样做:
1 | kubectl delete service -l run=first-deploy-whoami |
在我们的例子中,你会看到如下输出:
1 | service "first-deploy-whoami-svc-1" 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 | ➜ clark kubectl exec -it $POD_NAME -- wget -q -O - http://localhost |