目录
- 概述
- Service 原理
- Service 四种类型
- 创建 Service
- 代理 k8s 外部服务
- 反向代理外部域名
概述
在 Kubernetes 中,Pod 是应用程序的载体,我们可以通过 Pod 的 IP 来访问应用程序,但是 Pod 的 IP 地址不是固定的,这就意味着不方便直接采用 Pod 的 IP 对服务进行访问。
为了解决这个问题,Kubernetes 提供了 Service 资源,Service 会对提供同一个服务的多个 Pod 进行聚合,并且提供一个统一的入口地址,通过访问 Service 的入口地址就能访问到后面的 Pod 服务。
Service不仅提供了服务发现的功能,还有负载均衡的能力。
Service 原理
Service 在很多情况下只是一个概念,真正起作用的其实是 kube-proxy 服务进程,每个 Node 节点上都运行了一个 kube-proxy 的服务进程。当创建 Service 的时候会通过 API Server 向 etcd 写入创建的 Service 的信息,而 kube-proxy 会基于监听的机制发现这种 Service 的变化,然后它会将最新的 Service 信息转换为对应的访问规则(其实,就是 EndPoint,后面讲)。
Service整体的网络架构如下:
步骤如下:
- 创建Service时,会通过标签选择器,来选择为哪些pod维护网络,内部维护一个Endpoint,用来找到对应的pod。
- 当我们在集群内部访问其他pod,先找到service
- 在通过service里的Endpoint找到对应pod
- 通过iptables(或其他方式)转发到kube-proxy
- kube-proxy在找到对应的pod容器
Service 四种类型
- ClusterIP:默认类型,自动分配一个仅 cluster 内部可以访问的虚拟 IP。选择该值,服务只能够在集群内部访问,这也是默认的 ServiceType。
- NodePort:在 ClusterIP 基础上为 Service 在每台机器上绑定一个端口,这样就可以通过
<NodeIP>:NodePort
来访问该服务。如果 kube-proxy 设置了--nodeport-addresses=10.240.0.0/16
(v1.10 支持),那么仅该 NodePort 仅对设置在范围内的 IP 有效。 - LoadBalancer:在 NodePort 的基础上,借助 cloud provider 创建一个外部的负载均衡器,并将请求转发到
<NodeIP>:NodePort
- ExternalName:将服务通过 DNS CNAME 记录方式转发到指定的域名(通过
spec.externlName
设定)。需要 kube-dns 版本在 1.7 以上。
另外,也可以将已有的服务以 Service 的形式加入到 Kubernetes 集群中来,只需要在创建 Service 的时候不指定 Label selector,而是在 Service 创建好后手动为其添加 endpoint。
创建 Service
创建文件nginx-svc.yaml,内容如下:
apiVersion: v1
kind: Service
metadata:
name: nginx-svc # Service名字
labels:
app: nginx-svc # Service自己的标签
spec:
selector: # 选中当前 service 匹配哪些 pod,对哪些 pod 的东西流量进行代理
app: nginx-deploy
ports:
- name: http # service 端口配置的名称
protocol: TCP # 端口绑定的协议,支持 TCP、UDP、SCTP,默认为 TCP
port: 80 # service 自己的端口
targetPort: 80 # 目标 pod 的端口
type: NodePort # 随机启动一个端口(30000-32767),映射到ports中的端口,该端口直接绑定到node上,集群中每个node都绑定这个端口
创建Service管理的pod,使用前面章节用过的nginx-deploy.yaml,内容如下:
apiVersion: apps/v1 # deployment api 版本
kind: Deployment # 资源类型为deployment
metadata: # 元信息
labels: # 标签
app: nginx-deploy
name: nginx-deploy # deployment的名字
namespace: default # 所在命名空间
spec:
replicas: 3 # 期望副本数
revisionHistoryLimit: 10 # 进行滚动更新后,保留的历史版本数
selector: # 选择器,用于找到匹配的RS,管理指定标签的Rs
matchLabels: # 按照标签匹配
app: nginx-deploy # 匹配的标签
strategy: # 更新策略
rollingUpdate: # 滚动更新配置
maxSurge: 25% # 进行滚动更新时,更新的个数超过期望副本数的比例
maxUnavailable: 25% # 进行滚动更新时,最大不可用更新比例,也就是更新不成功最多能有多少个
type: RollingUpdate # 更新策略采用滚动更新
template: # pod模板
metadata: # pod的元信息
labels: # pod的标签
app: nginx-deploy
spec: # pod的描述信息
containers: # pod的描述信息
- image: nginx:1.7.9 # pod使用镜像
imagePullPolicy: IfNotPresent # 镜像拉取策略
name: nginx # 容器名称
restartPolicy: Always # 重启策略
terminationGracePeriodSeconds: 30 # 容器删除等待时间
部署deployment:
kubectl apply -f nginx-deploy.yaml
# deployment.apps/nginx-deploy created
查看po信息如下:
重点看下3个pod的IP和lables。
然后部署Service:
kubectl apply -f nginx-svc.yaml
# service/nginx-svc created
查看信息:
kubectl get svc
# 结果如下
NAME TYPE CLUSTER-IP EXTERNAL-IP PORT(S) AGE
nginx-svc NodePort 10.108.244.247 <none> 80:30087/TCP 2m45s
查看具体信息,可以看到NodePort绑定的端口信息和Endpoints信息:
kubectl describe svc nginx-svc
# 结果如下
Name: nginx-svc
Namespace: default
Labels: app=nginx-svc
Annotations: <none>
Selector: app=nginx-deploy
Type: NodePort
IP Family Policy: SingleStack
IP Families: IPv4
IP: 10.108.244.247
IPs: 10.108.244.247
LoadBalancer Ingress: localhost
Port: http 80/TCP
TargetPort: 80/TCP
NodePort: http 30087/TCP # NodePort绑定的端口信息
Endpoints: 10.1.0.112:80,10.1.0.113:80,10.1.0.114:80 # Endpoints信息
Session Affinity: None
External Traffic Policy: Cluster
Events: <none>
可以看到Endpoints对应的3个IP分别是3个Pod的IP。
下面我们就创建另外一个Pod来访问前面创建的Pod。
还创建我们前面用过的busybox:
kubectl run -it --image busybox dns-test --restart=Never --rm /bin/sh
创建完成后我们使用通过服务名来访问:
# 使用wget命令访问
wget http://nginx-svc
# 结果如下,成功下载到了index.html
Connecting to nginx-svc (10.108.244.247:80) # 请求到了svc地址,然后svc找到pod访问
saving to 'index.html'
index.html 100% |*******************************************************************| 612 0:00:00 ETA
'index.html' saved
也可以在后面加上命名空间实现跨命名空间访问:
wget http://nginx-svc.defaul
代理 k8s 外部服务
虽然Service是用来负责集群内部服务互相访问,但是有时我们想要Service代理外部服务也是可以的,也就是当我们访问Service时,它可以把请求转发到集群外部去。
例如,当我们的服务部署时,分为开发环境和测试环境,那一个服务部署请求另一个服务时,希望是开发环境的服务请求另一个服务时,也请求到开发环境,同理测试环境也是。
再比如,当我们把一部分服务迁移到K8s中时,还有一部分没有迁移,这时候也需要能够通过Service调用即可。
要实现这个效果,需要额外做以下两件事:
- 编写 service 配置文件时,不指定 selector 属性(这时不会自动创建endpoint)
- 自己创建 endpoint
下面就来看看如何实现。
- 创建Service
创建文件pgsql-svc-external.yaml,去掉selector,内容如下:
apiVersion: v1
kind: Service
metadata:
name: pgsql-svc-external # Service名字
labels:
app: pgsql # Service自己的标签
spec:
ports:
- name: pgsql # service 端口配置的名称
port: 80 # service 自己的端口
targetPort: 5432 # 目标端口
type: ClusterIP
创建Service:
kubectl apply -f pgsql-svc-external.yaml
# service/nginx-svc-external created
查看endpoint:
kubectl get ep
# 发现没有为我们自动创建endpoint即可
- 创建endpoint
创建资源文件pgsql-ep.yaml,内容如下:
apiVersion: v1
kind: Endpoints
metadata:
labels:
app: pgsql # 与 service 一致
name: pgsql-svc-external # 与 service 一致
namespace: default # 与 service 一致
subsets:
- addresses:
- ip: 10.2.8.79
ports:
- port: 5432
假如要在集群内部访问集群外部的pg数据库。
创建endpoint:
kubectl apply -f pgsql-ep.yaml
# endpoints/nginx-svc-external created
查看创建的:
kubectl get ep
# 结果如下
NAME ENDPOINTS AGE
pgsql-svc-external 10.2.8.79:5432 5s
kubectl describe ep pgsql-svc-external
# 结果如下
Name: pgsql-svc-external
Namespace: default
Labels: app=pgsql
Annotations: <none>
Subsets:
Addresses: 10.2.8.79
NotReadyAddresses: <none>
Ports:
Name Port Protocol
---- ---- --------
<unset> 5432 TCP
Events: <none>
以上就是将外部 IP 地址和服务引入到 k8s 集群内部(其他节点),由 service 作为一个代理来达到能够访问外部服务的目的。
没实操实现,有时间再看看。
反向代理外部域名
使用ExternalName类型的Service即可,配置如下:
apiVersion: v1
kind: Service
metadata:
name: baidu-svc # Service名字
labels:
app: baidu # Service自己的标签
spec:
type: ExternalName
externalName: www.baidu.com
不再演示。