K8S 之 Service

摘要

Service 介绍

  • Service(缩写为 svc)是一个抽象层,它定义了一组Pod的逻辑集,并为这些Pod支持外部流量暴露、负载均衡和服务发现。

  • 尽管每个Pod 都有一个唯一的IP地址,但是如果没有Service,这些IP不会暴露在群集外部。

  • Service允许您的应用程序接收流量。

  • Service也可以用在ServiceSpec标记type的方式暴露,type类型如下:

    • ClusterIP(默认):在集群的内部IP上公开Service。这种类型使得Service只能从集群内访问。
    • NodePort:使用NAT在集群中每个选定Node的相同端口上公开Service。使用 : 从集群外部访问Service。是ClusterIP的超集。
    • LoadBalancer:在当前云中创建一个外部负载均衡器(如果支持的话),并为Service分配一个固定的外部IP。是NodePort的超集。
    • ExternalName:通过返回带有该名称的CNAME记录,使用任意名称(由spec中的externalName指定)公开Service。不使用代理。

创建Service

ClusterIP

  • 只能在集群内部访问

1
2
3
4
5
6
7
8
# 先创建deployment,此时会为每个pod添加一个label app=nginx
kubectl create deployment nginx --image=nginx --replicas=2

# 创建service,,将deployment的pod暴露出来,暴露类型为ClusterIP
kubectl expose deployment nginx --type=ClusterIP --port=80
$ kubectl get svc nginx
NAME TYPE CLUSTER-IP EXTERNAL-IP PORT(S) AGE
nginx ClusterIP 10.96.14.90 <none> 80/TCP 60s

NodePort

  • 暴露宿主机的端口,供外部访问

1
2
3
4
5
6
# 创建service,将deployment的pod暴露出来,暴露类型为NodePort
kubectl expose deployment nginx --type=NodePort --port=80
# 查看service,此时可以看到service的端口和节点的端口,与 ClusterIP 的区别就是是否暴露在节点上的端口
$ kubectl get svc nginx
NAME TYPE CLUSTER-IP EXTERNAL-IP PORT(S) AGE
nginx NodePort 10.96.7.8 <none> 80:32691/TCP 31s

yaml文件创建service

  • yaml文件格式

1
2
3
4
5
6
7
8
9
10
11
12
13
apiVersion: v1      # api版本
kind: Service # 资源类型
metadata: # 元数据
name: nginx # service名称
spec: # 配置
ports: # 端口
- port: 80 # 集群内访问端口,service的端口,一般配置为与 targetPort 一致,但是非必须
protocol: TCP # 协议
targetPort: 80 # 容器端口, pod的端口,这个必须与实际容器端口一致
nodePort: 30080 # node暴露的端口,service类型为 NodePort 时使用,默认范围在 30000-32767 之间
selector: # 选择器
app: nginx # pod的标签,即匹配pod的标签 app=nginx
type: NodePort # service类型
  • 这里有个问题需要注意,service 默认是通过标签来匹配pod的,所以创建service的时候,一定要保证pod的标签是存在的,否则service无法匹配pod,另外虽然我们通过命令行创建service时是通过kubectl expose deployment nginx --type=NodePort --port=80创建的,但也并不表示service只会匹配这个deployment创建的pod,而是会匹配所有具有指定标签的pod(app=nginx)。

1
2
3
4
5
6
7
8
9
10
11
12
# 获取service的 selector
$ k get svc -owide
NAME TYPE CLUSTER-IP EXTERNAL-IP PORT(S) AGE SELECTOR
kubernetes ClusterIP 10.96.0.1 <none> 443/TCP 4d5h <none>
nginx NodePort 10.96.9.77 <none> 80:30080/TCP 8s app=nginx

# 这里有个名称为 kubernetes 的 service,其作用是为了方便集群内部的 Pod 调用 API Server 的统一入口
# 无论 API Server 实际运行在哪个节点哪个 IP,集群内部只要访问如下地址就能访问 API Server。
# https://kubernetes.default.svc
# 或者
# https://10.96.0.1
# 这个 Service 是 系统自带的,不建议删除或修改。

ExternalName

  • 可以将其它 namespace 的 service 别名到 当前 namespace,这样访问 service 时就不需要加上命名空间名称了

  • ExternalName Service 是纯 DNS CNAME 映射,我们不经可以映射集群内容服务,也可以映射集群外部服务。

  • 原先的 service 访问方式:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
# 在不同命名空间下创建service
# 创建两个命名空间以及下面的资源
## ns1
kubectl create namespace ns1
kubectl create deployment alpine-demo -n ns1 --image=alpine/curl --replicas=1 -- /bin/sh -c "sleep infinity"

## ns2
kubectl create namespace ns2
kubectl create deployment nginx-demo -n ns2 --image=nginx --replicas=1
kubectl expose deployment nginx-demo -n ns2 --type=ClusterIP --port=80 --name=nginx-service

# 进入 ns1 中的 pod 访问 ns2 中的 nginx-service
kubectl exec -it -n ns1 alpine-demo-66895487c8-sk4t4 -- /bin/sh
curl nginx-service.ns2.svc.cluster.local
curl nginx-service.ns2
  • ExternalName 访问

1
2
# 在 ns1 中创建 externalName 类型的 service,--external-name 指定 ns2 中的 nginx-service
kubectl create service externalname nginx-service -n ns1 --external-name=nginx-service.ns2.svc.cluster.local
  • 也可以通过 yaml 创建

1
2
3
4
5
6
7
8
9
10
apiVersion: v1
kind: Service
metadata:
name: nginx-service # service 名称
namespace: ns1 # 指定命名空间 为 ns1
spec:
externalName: nginx-service.ns2.svc.cluster.local # 指定外部服务名,比如 www.baidu.com
type: ExternalName # 指定为 ExternalName 类型
selector: # 不需要配置 selector
app: nginx-service
  • 查看 service

1
2
3
$ k get svc -n ns1
NAME TYPE CLUSTER-IP EXTERNAL-IP PORT(S) AGE
nginx-service ExternalName <none> nginx-service.ns2.svc.cluster.local <none> 16s
  • 此时再次进入 ns1 中的 pod 访问 ns2 中的 nginx-service 服务

1
2
3
kubectl exec -it -n ns1 alpine-demo-66895487c8-sk4t4 -- /bin/sh
# 此时就不需要加上 ns2 的 namespace 了
curl nginx-service

LoadBalancer

  • LoadBalancer 是 Kubernetes Service 的一种类型,用于自动申请一个云厂商的负载均衡器(如 AWS ELB、GCP LB、阿里云 SLB),将外部流量转发到 Kubernetes 集群内部的 Pod 上。

  • 在私有云上,需要借助第三方负载均衡器来实现,比如 MetalLBgithub

安装 MetallB

  • 如果您在IPVS模式下使用kube代理,从Kubernetes v1.14.2开始,您必须启用严格的ARP模式。请注意,如果您使用kube-router作为服务代理,则不需要这个,因为它默认启用了严格的ARP。

1
2
3
4
5
6
7
8
9
# 查看将要产生的变更,如果有变更则返回非零状态码
kubectl get configmap kube-proxy -n kube-system -o yaml | \
sed -e "s/strictARP: false/strictARP: true/" | \
kubectl diff -f - -n kube-system

# 实际应用变更,只有发生错误时才返回非零状态码
kubectl get configmap kube-proxy -n kube-system -o yaml | \
sed -e "s/strictARP: false/strictARP: true/" | \
kubectl apply -f - -n kube-system
  • 安装MetalLB

1
2
3
export METLB_VERSION=v0.15.2
curl -L -o metallb.yaml https://raw.githubusercontent.com/metallb/metallb/${METLB_VERSION}/config/manifests/metallb-native.yaml
kubectl apply -f metallb.yaml
  • 查看MetalLB资源

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
$ k get all -n metallb-system -owide
NAME READY STATUS RESTARTS AGE IP NODE NOMINATED NODE READINESS GATES
pod/controller-58fdf44d87-bsp4z 1/1 Running 0 4m13s 10.244.194.104 k8s-worker1 <none> <none>
pod/speaker-sjvq5 1/1 Running 0 4m13s 10.211.55.15 k8s-worker1 <none> <none>
pod/speaker-srpp4 1/1 Running 0 4m13s 10.211.55.16 k8s-worker2 <none> <none>
pod/speaker-tbnls 1/1 Running 2 (98s ago) 4m13s 10.211.55.11 k8s-master <none> <none>

NAME TYPE CLUSTER-IP EXTERNAL-IP PORT(S) AGE SELECTOR
service/metallb-webhook-service ClusterIP 10.96.233.8 <none> 443/TCP 4m14s component=controller

NAME DESIRED CURRENT READY UP-TO-DATE AVAILABLE NODE SELECTOR AGE CONTAINERS IMAGES SELECTOR
daemonset.apps/speaker 3 3 3 3 3 kubernetes.io/os=linux 4m13s speaker quay.io/metallb/speaker:v0.15.2 app=metallb,component=speaker

NAME READY UP-TO-DATE AVAILABLE AGE CONTAINERS IMAGES SELECTOR
deployment.apps/controller 1/1 1 1 4m14s controller quay.io/metallb/controller:v0.15.2 app=metallb,component=controller

NAME DESIRED CURRENT READY AGE CONTAINERS IMAGES SELECTOR
replicaset.apps/controller-58fdf44d87 1 1 1 4m13s controller quay.io/metallb/controller:v0.15.2 app=metallb,component=controller,pod-template-hash=58fdf44d87
  • 配置地址池:metallb-pool.yaml

1
2
3
4
5
6
7
8
apiVersion: metallb.io/v1beta1
kind: IPAddressPool # 地址池类型
metadata:
name: metallb-pool # 地址池名称
namespace: metallb-system # 命名空间
spec:
addresses: # 配置地址池
- 10.211.55.200-10.211.55.250 # 与 节点 相同的网段,负载均衡器可以分配的IP范围
1
kubectl apply -f metallb-pool.yaml
  • 配置地址池的二级公告:metallb-advertisement.yaml

1
2
3
4
5
6
7
8
apiVersion: metallb.io/v1beta1
kind: L2Advertisement # 地址池二级公告类型
metadata:
name: metallb-advertisement # 公告名称
namespace: metallb-system # 命名空间
spec:
ipAddressPools: # 地址池名称列表,只有被公告的地址池才会被使用
- metallb-pool # 地址池名称
1
kubectl apply -f metallb-advertisement.yaml

创建 LoadBalancer 类型的 service

  • metallb-service.yaml

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
apiVersion: apps/v1                  # 指定使用的 API 版本,这里是 apps/v1,适用于 Deployment 资源
kind: Deployment # Kubernetes 资源类型,这里是部署(Deployment)
metadata:
labels:
app: nginx # 标签,用于标识资源,可与 selector 匹配
name: nginx # 资源名称,必须唯一(在同一命名空间下)
namespace: ns1 # 命名空间,默认为 default
spec: # 配置项
replicas: 3 # 副本数,表示希望运行多少个 Pod 实例
selector: # 选择器,指定要管理的 Pod
matchLabels: # 标签选择器
app: nginx # 选择器,指定 Deployment 管理哪些 Pod(标签必须与 template 中匹配)
template: # 模板,定义 Pod 的内容,具体可以参考 Pod 的配置
metadata:
labels:
app: nginx # Pod 的标签,必须与 selector 中的 matchLabels 一致
spec:
containers:
- image: nginx # 容器使用的镜像,这里是官方的 nginx 镜像
name: nginx # 容器的名称
--- # 分割线
apiVersion: v1 # api版本
kind: Service # 资源类型
metadata: # 元数据
name: nginx # service名称
namespace: ns1 # 命名空间
spec: # 配置
ports: # 端口
- port: 80 # 集群内访问端口,service的端口,一般配置为与 targetPort 一致,但是非必须
protocol: TCP # 协议
targetPort: 80 # 容器端口, pod的端口,这个必须与实际容器端口一致
selector: # 选择器
app: nginx # pod的标签,即匹配pod的标签 app=nginx
type: LoadBalancer # service类型
1
2
3
$ k apply -f metallb-service.yaml
deployment.apps/nginx created
service/nginx created
  • 查看service

1
2
3
4
# 可以看到 service 的类型为 LoadBalancer,并分配了 EXTERNAL-IP,这里还开放了nodePort 30613
$ kubectl get svc -n ns1
NAME TYPE CLUSTER-IP EXTERNAL-IP PORT(S) AGE
nginx LoadBalancer 10.96.210.244 10.211.55.200 80:30613/TCP 52s
  • 访问service

1
2
3
4
# 通过 service 的 port 访问,这里是 80
curl 10.211.55.200
# 同样可以基于 nodeIP+nodePort 访问
curl 10.211.55.11:30613

访问service,轮询pod

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
## 在集群内
# 在相同的 namespace 中,可以通过 serviceName 直接访问
curl nginx

# 也可以通过 CLUSTER-IP 访问
curl 10.96.9.77

# 在不同的 namespace 中,可以通过 <serviceName>.<namespace> 或者 <serviceName>.<namespace>.svc.cluster.local 访问,比如本示例为
curl nginx.default
curl nginx.default.svc.cluster.local
# 这是因为在创建 service前,我们是不知道 service 的 IP 地址的,所以在其它pod中就可以预先使用 <serviceName>.<namespace> 占位,k8s会自动将其解析为 service 的 CLUSTER-IP


## 在集群外
# 通过 NODE-IP:NODE-PORT 访问
curl 10.211.55.16:30080

管理service

  • 查看service

1
2
3
4
5
6
7
8
9
10
11
12
# 查看service
kubectl get svc
# 查看指定namespace下的service
kubectl get svc -n kube-system
# 查看全部service
kubectl get svc -A
# 查看service详情
kubectl get svc nginx -o yaml
kubectl describe svc nginx

# 查看日志
k logs svc/nginx
  • 编辑service,保存(:wq)后生效,不需要额外 apply 或 restart

1
kubectl edit svc nginx
  • 删除service

1
kubectl delete svc nginx