k8s五: 深入理解Service
为什么需要Service?因为直接通过pod的IP+port获取服务存在如下两个问题:1)调用地址不稳定,pod可能出现故障,那么新的pod产生的IP和port都会发生变化2)集群(多实例)场景无法自动实现负载均衡service怎么解决的问题?service本身有固定的Ip和port,且内部有负载均衡的实现,所以解决了上述问题service的实例和创建一个提供Web服务的RC:webapp-rc.
为什么需要Service?
因为直接通过pod的IP+port获取服务存在如下两个问题:
1)调用地址不稳定,pod可能出现故障,那么新的pod产生的IP和port都会发生变化
2)集群(多实例)场景无法自动实现负载均衡
service怎么解决的问题?
service本身有固定的Ip和port,且内部有负载均衡的实现,所以解决了上述问题
service相关指令
service选中的pod称为其endpoints
kubectl get endpoints hostnames
#获取hostnames对应的service
kubectl get svc hostnames
只有处于 Running 状态,且 readinessProbe 检查通过的 Pod,才会出现在 Service 的 Endpoints 列表里。并且,当某一个 Pod 出现问题时,Kubernetes 会自动把它从 Service 里摘除掉。
service 获取service和对应的endpoints
kubectl get endpoints
获取服务详情
kubectl get svc hostnames
service的实例和创建
一个提供Web服务的RC:webapp-rc.yaml
apiVersion: v1
kind: ReplicationController
metadata:
name: webapp
spec:
replicas: 2
template:
metadata:
name: webapp
labels:
app: webapp
spec:
containers:
- name: webapp
image: tomcat
ports:
- containerPort: 8080
创建该RC kubectl create -f webapp-rc.yaml
获取Pod的Ip: kubectl get pods -l app=webapp -o yaml | grep podIP
通过Pod 的Ip和端口号直接访问服务 curl ip:8080
通过创建service,让客户端能够访问到两个Tomcat Pod的实例
方式1: kubectl expose rc webapp #通过kubectl expose实现
方式2: 定义并创建service:webapp-svc.yaml
apiVersion: v1
kind: Service
metadata:
name: webapp
spec:
ports:
- port: 8081 #指定service的虚拟端口号
targetPort: 8080 #对应到Pod容器的8080端口
selector:
app: webapp #对应到拥有label(app=webapp)的Pod
kubectl create -f webapp-svc.yaml
kubectl get svc #对应端口 ip
访问service: surl ip:8081
service提供服务的实现原理:kube-proxy 组件,加上 iptables 来共同实现的。
一旦service被提交给 Kubernetes,那么 kube-proxy 就可以通过 Service 的 Informer 感知到这样一个 Service 对象的添加。而作为对这个事件的响应,它就会在宿主机上创建一条 iptables 规则。
经过该iptables 处理之后,访问 Service VIP 的 IP 包就已经变成了访问具体某一个后端 Pod 的 IP 包了。不难理解,这些 Endpoints 对应的 iptables 规则,正是 kube-proxy 通过监听 Pod 的变化事件,在宿主机上生成并维护的。
不难想到,当你的宿主机上有大量 Pod 的时候,成百上千条 iptables 规则不断地被刷新,会大量占用该宿主机的 CPU 资源,甚至会让宿主机“卡”在这个过程中。所以说,一直以来,基于 iptables 的 Service 实现,都是制约 Kubernetes 项目承载更多量级的 Pod 的主要障碍。
IPVS模式可以解决上述问题。
核心:把对这些“规则”的处理放到了内核态,从而极大地降低了维护这些规则的代价。
k8s提供的负载均衡策略:
1)轮询模式,默认模式
2)会话模式:基于客户端IP地址进行的花花保持的模式,service.spec.sessionAffinity=ClientIP启用。
多端口service
使用不同协议的两个端口号service
apiVersion: v1
kind: Service
metadata:
name: webapp
spec:
ports:
- port: 8080 #service对外提供的端口
targetPort: 8080 #对应到pod的8080端口
name: web #对应的服务名称
protocol: TCP #网络类型
- port: 8005
targetPort: 8080
name: web1
protocol: TCP
外部服务service
如系统需要将一个外部数据库作为后端服务进行连接。
1) 创建一个无label selector的service
apiVersion: v1
kind: Service
metadata:
name: my-service
spec:
ports:
- protocol: TCP
port: 80
targetPort: 80
因为定义的service没有labelSelector,所以不会定义到任何Pod,需要手动创建一个和该service同名的Endpoint,用于指向实际的后端访问地址,
2) endPoint的配置文件内容如下:
kind: Endpoints
apiVersion: v1
metadat:
name: my-service #和上面service名称完全一致
subjects:
- address:
- IP: 1.2.3.4
ports:
- port: 80
说明:上面没有标签选择器的Service建会被路由到用户手动定义的后端Endpoint上。
2.4.3 Headless Service
应用场景:
1)开发人员希望自己控制负载均衡的策略,不使用Service提供的默认负载均衡的功能。
2)应用程序希望知道属于同组服务的其他实例
原理:不为service设置clusterIp,仅通过Label selector将后端的Pod列表返回给调用的客户端。
下面是一个Hedaless Service定义实例:
apiVersion: v1
kind: Service
metadata:
name: nginx
labels:
app: nginx
spec:
ports:
- port: 80
clusterIP: None
selector:
app: nginx
说明:该service不再具有特定的clusterIp地址,对其进行访问会返回包含Label“app=nginx”的全部Pod列表。
2.4.4 集群外部访问Pod 或 service
由于Pod和service的IP和port都是k8s集群范围内的虚拟概念,所以集群外的客户端无法访问。
解决方案:
将Pod和Service的端口号映射到宿主机,使得客户端应用能够通过物理机访问容器应用。
Pod的两种实现方案:
1)设置容器级别的hostPort: 将容器应用的端口映射到物理机上
apiVersion: v1
kind: Pod
metadata:
name: webapp
labels:
app: webapp
spec:
containers:
- name: webapp
image: tomcat
ports:
- containerPort: 8080 #容器的端口
hostPort: 8081 #物理机端口,通过物理机IP和该port可以访问
2)设置Pod级别的hostNetwork=true,将该Pod中所有容器的端口号都直接映射到物理机上。且默认hostPort等于containerPort
apiVersion: v1
kind: Pod
metadata:
name: webapp
labels:
app: webapp
spec:
hostNetwork: true
containers:
- name: webapp
image: tomcat
ports:
- containerPort: 8080 #容器的端口,自动映射到物理机8080的端口
Service的实现方案
1)通过nodePort映射到物理机,同时设置service的类型为NodePort
通过node的物理Ip和暴露的nodePort暴露服务,但是没有办法进行负载均衡;所以此时一般会搭配别的slb服务
apiVersion: v1
kind: Service
metadata:
name: my-nginx
labels:
run: my-nginx
spec:
type: NodePort
ports:
- nodePort: 8080 #物理机Ip+端口可以访问服务
targetPort: 80
protocol: TCP
name: http
- nodePort: 443
protocol: TCP
name: https
selector:
run: my-nginx
2 1.7版本之后支持的一个新特性,叫作 ExternalName和externalIPs。
kind: Service
apiVersion: v1
metadata:
name: my-service
spec:
type: ExternalName
externalName: my.database.example.com
externalIPs:
kind: Service
apiVersion: v1
metadata:
name: my-service
spec:
selector:
app: MyApp
ports:
- name: http
protocol: TCP
port: 80
targetPort: 9376
externalIPs:
- 80.11.12.10
在这里 Kubernetes 要求 externalIPs 必须是至少能够路由到一个 Kubernetes 的节点。
3 公有云上的 Kubernetes 服务。这时候,你可以指定一个 LoadBalancer 类型的 Service
如:
---
kind: Service
apiVersion: v1
metadata:
name: example-service
spec:
ports:
- port: 8765
targetPort: 9376
selector:
app: example
type: LoadBalancer
在上述 LoadBalancer 类型的 Service 被提交后,Kubernetes 就会调用 CloudProvider 在公有云上为你创建一个负载均衡服务,并且把被代理的 Pod 的 IP 地址配置给负载均衡服务做后端。
kuebctl get svc :可以看到external-ip。我们就可以通过该ip+port来访问了。
由于每个 Service 都要有一个负载均衡服务,所以这个做法实际上浪费成本,更希望看到 Kubernetes 为我内置一个全局的负载均衡器。然后,通过我访问的 URL,把请求转发给不同的后端 Service。
对应的就是Kubernetes 里的 Ingress 服务。
ingress对象的定义如下:
apiVersion: extensions/v1beta1
kind: Ingress
metadata:
name: cafe-ingress
spec:
tls:
- hosts:
- cafe.example.com
secretName: cafe-secret
rules:
- host: cafe.example.com
http:
paths:
- path: /tea
backend:
serviceName: tea-svc
servicePort: 80
- path: /coffee
backend:
serviceName: coffee-svc
servicePort: 80
重点关注rules字段。
host:该Ingress对象的入口,即当用户访问 cafe.example.com 的时候,实际上访问到的是这个Ingress对象。
对应的值必须是一个标准的域名格式的字符串,而不能是 IP 地址。
path:定义具体规则,一个 path 都对应一个后端 Service,并且指明了对应的服务名称和端口。
实际的使用中,需要从社区里选择一个具体的 Ingress Controller,把它部署在 Kubernetes 集群里。
目前, Nginx、HAProxy、Envoy、Traefik 等,都已经为 Kubernetes 专门维护了对应的 Ingress Controller。
大概以Nginx Ingress Controller 为例,看下实际使用过程。
部署 Nginx Ingress Controller:
1 创建pod:
kubectl apply -f https://raw.githubusercontent.com/kubernetes/ingress-nginx/master/deploy/mandatory.yaml
mandatory.yaml内容如下:
kind: ConfigMap
apiVersion: v1
metadata:
name: nginx-configuration
namespace: ingress-nginx
labels:
app.kubernetes.io/name: ingress-nginx
app.kubernetes.io/part-of: ingress-nginx
---
apiVersion: extensions/v1beta1
kind: Deployment
metadata:
name: nginx-ingress-controller
namespace: ingress-nginx
labels:
app.kubernetes.io/name: ingress-nginx
app.kubernetes.io/part-of: ingress-nginx
spec:
replicas: 1
selector:
matchLabels:
app.kubernetes.io/name: ingress-nginx
app.kubernetes.io/part-of: ingress-nginx
template:
metadata:
labels:
app.kubernetes.io/name: ingress-nginx
app.kubernetes.io/part-of: ingress-nginx
annotations:
...
spec:
serviceAccountName: nginx-ingress-serviceaccount
containers:
- name: nginx-ingress-controller
image: quay.io/kubernetes-ingress-controller/nginx-ingress-controller:0.20.0
args:
- /nginx-ingress-controller
- --configmap=$(POD_NAMESPACE)/nginx-configuration
- --publish-service=$(POD_NAMESPACE)/ingress-nginx
- --annotations-prefix=nginx.ingress.kubernetes.io
securityContext:
capabilities:
drop:
- ALL
add:
- NET_BIND_SERVICE
# www-data -> 33
runAsUser: 33
env:
- name: POD_NAME
valueFrom:
fieldRef:
fieldPath: metadata.name
- name: POD_NAMESPACE
- name: http
valueFrom:
fieldRef:
fieldPath: metadata.namespace
ports:
- name: http
containerPort: 80
- name: https
containerPort: 443
mandatory.yaml定义了nginx-ingress-controller 镜像的 Pod,就是一个监听 Ingress 对象以及它所代理的后端 Service 变化的控制器。即一个新的 Ingress 对象由用户创建后,nginx-ingress-controller 就会根据 Ingress 对象里定义的内容,生成一份对应的 Nginx 配置文件(/etc/nginx/nginx.conf),并使用这个配置文件启动一个 Nginx 服务。而一旦 Ingress 对象被更新,nginx-ingress-controller 就会更新这个配置文件。
一个 Nginx Ingress Controller 为你提供的服务,其实是一个可以根据 Ingress 对象和被代理后端 Service 的变化,来自动进行更新的 Nginx 负载均衡器。
2 创建对应的service
为了让用户能够用到这个 Nginx,我们就需要创建一个 Service 来把 Nginx Ingress Controller 管理的 Nginx 服务暴露出去,如下所示:
$ kubectl apply -f https://raw.githubusercontent.com/kubernetes/ingress-nginx/master/deploy/provider/baremetal/service-nodeport.yaml
service-nodeport.yaml内容如下:
apiVersion: v1
kind: Service
metadata:
name: ingress-nginx
namespace: ingress-nginx
labels:
app.kubernetes.io/name: ingress-nginx
app.kubernetes.io/part-of: ingress-nginx
spec:
type: NodePort
ports:
- name: http
port: 80
targetPort: 80
protocol: TCP
- name: https
port: 443
targetPort: 443
protocol: TCP
selector:
app.kubernetes.io/name: ingress-nginx
app.kubernetes.io/part-of: ingress-nginx
Service 的唯一工作,就是将所有携带 ingress-nginx 标签的 Pod 的 80 和 433 端口暴露出去。
记录下这个 Service 的访问入口,即:宿主机的地址和 NodePort 的端口,如下所示:
$ kubectl get svc -n ingress-nginx
NAME TYPE CLUSTER-IP EXTERNAL-IP PORT(S) AGE
ingress-nginx NodePort 10.105.72.96 <none> 80:30044/TCP,443:31453/TCP 3h
把上述访问入口设置为环境变量:
$ IC_IP=10.168.0.2 # 任意一台宿主机的地址
$ IC_HTTPS_PORT=31453 # NodePort端口
然后:就是部署应用的Pod、service、secret、ingress对象
$ kubectl create -f cafe.yaml
#创建 Ingress 所需的 SSL 证书(tls.crt)和密钥(tls.key)
$ kubectl create -f cafe-secret.yaml
$ kubectl create -f cafe-ingress.yaml
访问服务:
$ curl --resolve cafe.example.com:$IC_HTTPS_PORT:$IC_IP https://cafe.example.com:$IC_HTTPS_PORT/coffee --insecure
$ curl --resolve cafe.example.com:$IC_HTTPS_PORT:$IC_IP https://cafe.example.com:$IC_HTTPS_PORT/tea --insecure
容器port ---> pod的port
pod定义中containerPort: 80
pod的port ---> service的port
service中的定义:
spec:
ports:
- port: 8080 #service对外提供的端口
targetPort: 8080 #对应到pod的8080端口
name: web #对应的服务名称
protocol: TCP #网络类型
service的port --> node的port
spec:
type: NodePort
ports:
- port: 8080
targetPort: 8080
nodePort: 8081 #物理机Ip+端口可以访问服务
此时可以通过node的ip+node的port访问单个节点的服务
node的port --> slb的port
slb配置多个node的ip和port进行负载均衡
2.4.5 DNS服务搭建指南
为了实现集群内能够通过服务名对服务进行访问。
k8s提供的虚拟DNS服务名为skydns,由四个组件组成:
1) etcd: DNS存储
2)kube2sky:将kubenetes Msater中的service注册到etcd
3)skyDNS:提供DNS域名解析服务
4)healthz: 提供对skydns服务的健康检查功能
2.4.6 自定义DNS和上游DNS服务器
问题:
线上的ssh的k8s集群怎么向外提供的服务?
怎么向外部提供服务的?
jude的k8s集群中的slb的service 和 slb服务是怎样的关系?
附:service定义属性
更多推荐
所有评论(0)