K8S之Service详解

K8S之Service详解

简介

在K8S中,Pod是容器运行的载体,我们可以访问Pod内部的容器来访问相对应的服务,但是Pod的IP地址不是固定的,因此这也就意味我们不方便直接采用IP地址对Pod进行访问管理。

在K8S中提供我们 service 服务来解决上述问题, service 对一组pod进行聚合,并且提供一个统一的外部接口地址,我们通过访问该接口地址就可以访问其所对应的pod服务。

本质上来说其实 service 只是一个概念,真正起到转发数据的是 kube-proxy 服务进程,每个节点之上都运行一个 kube-proxy 服务进程,当创建 service 的时候 API Service 会监听 service 然后向 etcd 写入 service 的信息, kube-proxy 会监听 etcdservice 的信息并且将 service 信息转发成对应的访问规则。

s

kube-proxy工作模式

userspace

在该模式下 kube-proxy 会为每一个 service 创建一个监听端口,发送给 Cluseter IP 请求会被 iptable 重定向给 kube-proxy 监听的端口上,其中 kube-proxy 会根据 LB 算法将请求转发到相应的pod之上。

该模式下,kube-proxy充当了一个四层负载均衡器的角色。由于kube-proxy运行在userspace中,在进行转发处理的时候会增加内核和用户空间之间的数据拷贝,虽然比较稳定,但是效率非常低下。

user

iptables

iptables模式下 kube-proxy 为每一个pod创建相对应的 iptables 规则,发送给 ClusterIP 的请求会被直接发送给后端pod之上

在该模式下 kube-proxy 不承担负载均衡器的角色,其只会负责创建相应的转发策略,该模式的优点在于较userspace模式效率更高,但是不能提供灵活的LB策略,当后端Pod不可用的时候无法进行重试。

iptab

ipvs模式

ipvs模式与iptable模式类型, kube-proxy 会根据pod的变化创建相应的 ipvs 转发规则,ipvs相对iptable来说转发效率更加高效,同时提供了大量的负责均衡算法。

ipvs

使用ipvs模式必须安装ipvs内核模块,否则会自动降级为iptables

# 编辑配置文件 搜索43行mode将其修改为ipvs
[root@master k8s]# kubectl edit cm kube-proxy -n kube-system

# 删除原有的代理
[root@master k8s]# kubectl delete pod -l k8s-app=kube-proxy -n kube-system

# 查看
[root@master k8s]# ipvsadm -Ln

image-20211030172547155

service类型

资源清单

apiVersion: v1 # 版本
kind: Service # 类型
metadata: # 元数据
  name: # 资源名称
  namespace: # 命名空间
spec:
  selector: # 标签选择器,用于确定当前Service代理那些Pod
    app: nginx
  type:  # Service的类型,指定Service的访问方式
  clusterIP: # 虚拟服务的IP地址
  sessionAffinity: # session亲和性,支持ClientIP、None两个选项,默认值为None
  ports: # 端口信息
    - port: 8080 # Service端口
      protocol: TCP # 协议
      targetPort : # Pod端口
      nodePort:  # 主机端口
  • ClusterIP:默认类型,处于该模式下K8S会自动为service分配地址,其只能在集群内部访问。
  • NodePort:将Service通过指定的Node上的端口暴露给外部,通过此方法,就可以在集群外部访问服务。
  • LoadBalancer:使用外接负载均衡器完成到服务的负载分发,注意此模式需要外部云环境的支持。
  • ExternalName:把集群外部的服务引入集群内部,直接使用。

service应用

Clusterip service

cat > service-clusterip.yaml << EOF
apiVersion: v1
kind: Service
metadata:
  name: service-clusterip
  namespace: dev
spec:
  selector:
    app: nginx-pod
  type: ClusterIP
  # service IP地址 如果不写默认会生成一个
  clusterIP: 10.97.97.97
  ports:
    # service端口
    - port: 80
      # 目标pod端口
      targetPort: 80
EOF

# 创建service
[root@master k8s]# kubectl create -f service-clusterip.yaml 

# 查看service
[root@master k8s]# kubectl get -n dev service service-clusterip -o wide

创建pod

cat > deployment.yaml << EOF
apiVersion: apps/v1
kind: Deployment
metadata:
  name: pc-deployment
  namespace: dev
spec:
  replicas: 3
  selector:
    matchLabels:
      app: nginx-pod
  template:
    metadata:
      labels:
        app: nginx-pod
    spec:
      containers:
        - name: nginx
          image: nginx:1.17.1
          ports:
            - containerPort: 80
              protocol: TCP
EOF

# 创建pod
[root@master k8s]# kubectl create -f deployment.yaml 

# 查看pod
[root@master k8s]# kubectl get pod -n dev -o wide --show-labels

image-20211031172624022

# 任意节点发起请求
[root@master k8s]# curl 10.244.2.64

image-20211031173316240

# 由于pod节点都是nginx默认界面都是一样的为了方便测试修改默认界面
# 进入容器
[root@master k8s]# kubectl exec -it -n dev pc-deployment-7d7dd5499b-j5hnp /bin/sh

# 三个节点写入数据
echo "Current Request is 10.244.1.64" > /usr/share/nginx/html/index.html
echo "Current Request is 10.244.1.63" > /usr/share/nginx/html/index.html
echo "Current Request is 10.244.1.62" > /usr/share/nginx/html/index.html


# 发起请求
[root@master k8s]# curl 10.244.2.64
[root@master k8s]# curl 10.244.2.63
[root@master k8s]# curl 10.244.2.62

image-20211031173932500

创建services

cat > service-clusterip.yaml << EOF
apiVersion: v1
kind: Service
metadata:
  name: service-clusterip
  namespace: dev
spec:
  selector:
    app: nginx-pod
  type: ClusterIP
  # service IP地址 如果不写默认会生成一个
  clusterIP: 10.97.97.97
  ports:
    # service端口
    - port: 80
      # 目标pod端口
      targetPort: 80
EOF

# 创建service
[root@master k8s]# kubectl create -f service-clusterip.yaml 

# 查看service
[root@master k8s]# kubectl get -n dev service service-clusterip -o wide

image-20211031174525393

# 查看详情
[root@master k8s]# kubectl describe svc service-clusterip -n dev

image-20211031174702153

# 多次访问后端节点
[root@master k8s]# curl 10.97.97.97

image-20211031174928125

Endpoint

Endpoint是kubernetes中的一个资源对象,存储在etcd中,用来记录一个service对应的所有Pod的访问地址,它是根据service配置文件中的selector描述产生的。

一个service由一组Pod组成,这些Pod通过Endpoints暴露出来,Endpoints是实现实际服务的端点集合。换言之,service和Pod之间的联系是通过Endpoints实现的。

end

# 查看endpoints
[root@master k8s]# kubectl get endpoints -n dev 

image-20211031222101664

负载分发策略

对Service的访问被分发到了后端的Pod上去,目前kubernetes提供了两种负载分发策略:

  • 如果不定义,默认使用kube-proxy的策略,比如随机、轮询等。
  • 基于客户端地址的会话保持模式,即来自同一个客户端发起的所有请求都会转发到固定的一个Pod上,这对于传统基于Session的认证项目来说很友好,此模式可以在spec中添加sessionAffinity: ClusterIP选项。
# 修改分发策略
apiVersion: v1
kind: Service
metadata:
  name: service-clusterip
  namespace: dev
spec:
  selector:
    app: nginx-pod
  clusterIP: 10.97.97.97 # service的IP地址,如果不写,默认会生成一个
  type: ClusterIP
  sessionAffinity: ClientIP # 修改分发策略为基于客户端地址的会话保持模式
  ports:
    - port: 80 # Service的端口
      targetPort: 80 # Pod的端口    
[root@master k8s]# kubectl create -f service-clusterip.yaml

# 循环测试
[root@master k8s]# while true;do curl 10.97.97.97:80; sleep 5; done;

image-20211031222807592

HeadLiness services

在某些场景中,开发人员可能不想使用Service提供的负载均衡功能,而希望自己来控制负载均衡策略,针对这种情况,kubernetes提供了HeadLinesss Service,这类Service不会分配Cluster IP,如果想要访问Service,只能通过Service的域名进行查询。

创建service

cat > service-headliness.yaml << EOF
apiVersion: v1
kind: Service
metadata:
  name: service-headliness
  namespace: dev
spec:
  selector:
    app: nginx-pod
  clusterIP: None # 将clusterIP设置为None,即可创建headliness Service
  type: ClusterIP
  ports:
    - port: 80 # Service的端口
      targetPort: 80 # Pod的端口
EOF

[root@master k8s]# kubectl create -f service-headliness.yaml

# 查看service
[root@master k8s]# kubectl get svc service-headliness -n dev -o wide

image-20211031233312112

# 查看详情
[root@master k8s]# kubectl describe svc service-headliness -n dev

image-20211031233406900

# 查看pod
[root@master k8s]# kubectl get pod -n dev

image-20211031233453549

# 进入Pod
[root@master k8s]# kubectl exec -it -n dev pc-deployment-7d7dd5499b-jc84t /bin/bash
# 查看域名
root@pc-deployment-7d7dd5499b-jc84t:/# cat /etc/resolv.conf 

image-20211031233631713

# 通过Service的域名进行查询:
# 默认访问规则service名称.名称空间.svc.cluster.local

# 安装dig
[root@master k8s]# yum -y install bind-utils

# 访问
[root@master k8s]# dig @10.96.0.10 service-headliness.dev.svc.cluster.local

NodePort service

在上述案例之中 service 暴露的IP 只能供集群内部访问,但是我们创建资源即暴露给用户使用,因此 K8S 为我们提供了 NodePort service

其会将service 的端口与 node 的端口进行映射,当我们访问 node 的 IP + Port 即为访问 service 所对应的资源

nodeport

创建 service

# 创建 service
cat > cat service-nodeport.yaml << EOF
apiVersion: v1
kind: Service
metadata:
  name: service-nodeport
  namespace: dev
spec:
  selector:
    app: nginx-pod
  type: NodePort # Service类型为NodePort
  ports:
    - port: 80 # Service的端口
      targetPort: 80 # Pod的端口
      nodePort: 30002 # 指定绑定的node的端口(默认取值范围是30000~32767),如果不指定,会默认分配
EOF

# 创建pod
[root@master k8s]# kubectl create -f service-nodeport.yaml

# 查看详情
[root@master k8s]# kubectl get svc -n dev -o wide

image-20211108102608132

# 访问测试 由于更换机器此时 master 的地址由 10.1.1.2 更改为 172.16.137.128
curl 172.16.137.128:30002

image-20211108103002569

LoadBalancer service

通过上图可以看到使用 NodePort 模式中对负载均衡能力不是很友好,external 类型与 nodeport 类似都是对于外部暴露一个访问端口

区别在于使用该模式会在集群外部添加一个负载均衡设备,外部访问负载均衡设置,由负载均衡设备在根据一定的算法转发到后端服务节点

load

ExternalName service

ExternalName类型的Service用于引入集群外部的服务,它通过externalName属性指定一个服务的地址,然后在集群内部访问此Service就可以访问到外部的服务了。

exte

创建 service

cat > service-externalname.yaml << EOF
apiVersion: v1
kind: Service
metadata:
  name: service-externalname
  namespace: dev
spec:
  type: ExternalName # Service类型为ExternalName
  externalName: www.baidu.com # 改成IP地址也可以
EOF

[root@master k8s]# kubectl create -f service-externalname.yaml

# 查看 service
[root@master k8s]# kubectl get svc service-externalname -n dev

image-20211108140945724

# 安装域名解析
[root@master k8s]# yum install bind-utils

# 域名解析
[root@master k8s]# dig @10.96.0.10 service-externalname.dev.svc.cluster.local

Ingress

简介

在上述中 service 对外提供负载均衡主要有 nodeport 与 loadblancer 两种方式,但是这两种方式各自都有一定的缺点,在 nodeport 方式中 service 会与 node 节点进行映射,这样会占用大量的端口,当 service 过多的时候可能会导致节点端口不够,在 loadblancer 中每一个service 都需要一个 LB,并且需要外部的负载均衡设备进行支持

基于上述问题,在 K8S 中提出了 ingress 资源对象,该资源对象只需要一个 nodeport 或者一个 LB 就可以满足暴露多个 service 需求

ingress
实际上 Ingress 类似于一个七层的负载均衡器,是由 K8S 对反向代理的抽象,其工作原理类似于 Nginx 可以理解为Ingress里面建立了诸多映射规则,Ingress Controller通过监听这些配置规则并转化为Nginx的反向代理配置,然后对外提供服务。

  • Ingress:kubernetes中的一个对象,作用是定义请求如何转发到Service的规则。

  • Ingress Controller:具体实现反向代理及负载均衡的程序,对Ingress定义的规则进行解析,根据配置的规则来实现请求转发,实现的方式有很多,比如Nginx,Contour,Haproxy等。

  • 其工作原理如下

    • 用户编写 Ingress 规则说明那个域名对应那个 service
    • Ingress Contoller 动态感知 ingress 编写的规则,然后生成对应的反向代理规则
    • ingress 控制器会根据生成代理规则写入到代理服务中
    • 客户端请求代理服务,由代理服务转发到后端 pod 节点

ha

Ingress 使用

环境搭建

mkdir ingress-controller && cd ingress-controller

# 下载service 与控制器
wget https://raw.githubusercontent.com/kubernetes/ingress-nginx/nginx-0.30.0/deploy/static/mandatory.yaml
  
wget https://raw.githubusercontent.com/kubernetes/ingress-nginx/nginx-0.30.0/deploy/static/provider/baremetal/service-nodeport.yaml
  
# 更换网络源
sed -i 's#quay.io/kubernetes-ingress-controller/nginx-ingress-controller#registry.cn-qingdao.aliyuncs.com/kubernetes_xingej/nginx-ingress-controller#g' mandatory.yaml

# 使用配置文件
kubectl apply -f ./

# 查看pod
[root@master ~]# kubectl get -n ingress-nginx pod

# 查看svc
[root@master ~]# kubectl get -n ingress-nginx svc

image-20211109224550664

准备Service和pod

cat > tomcat-nginx.yaml << EOF
apiVersion: apps/v1
kind: Deployment
metadata:
  name: nginx-deployment
  namespace: dev
spec:
  replicas: 3
  selector:
    matchLabels:
      app: nginx-pod
  template:
    metadata:
      labels:
        app: nginx-pod
    spec:
      containers:
      - name: nginx
        image: nginx:1.17.1
        ports:
        - containerPort: 80

---

apiVersion: apps/v1
kind: Deployment
metadata:
  name: tomcat-deployment
  namespace: dev
spec:
  replicas: 3
  selector:
    matchLabels:
      app: tomcat-pod
  template:
    metadata:
      labels:
        app: tomcat-pod
    spec:
      containers:
      - name: tomcat
        image: tomcat:8.5-jre10-slim
        ports:
        - containerPort: 8080

---

apiVersion: v1
kind: Service
metadata:
  name: nginx-service
  namespace: dev
spec:
  selector:
    app: nginx-pod
  clusterIP: None
  type: ClusterIP
  ports:
  - port: 80
    targetPort: 80

---

apiVersion: v1
kind: Service
metadata:
  name: tomcat-service
  namespace: dev
spec:
  selector:
    app: tomcat-pod
  clusterIP: None
  type: ClusterIP
  ports:
  - port: 8080
    targetPort: 8080
EOF

# 创建pod
[root@master ~]# kubectl create -f tomcat-nginx.yaml

# 查看
[root@master ~]# kubectl get svc,pod -n dev

image-20211109225128565

HTTP代理

cat > ingress-http.yaml << EOF
apiVersion: extensions/v1beta1
kind: Ingress
metadata:
  name: ingress-http
  namespace: dev
spec:
    # 代理规则
  rules:
    # 绑定域名
  - host: nginx.sr.com
    http:
      paths:
        # 绑定路径
      - path: /
        backend:
          serviceName: nginx-service
           # service暴露端口
          servicePort: 80
  - host: tomcat.sr.com
    http:
      paths:
      - path: /
        backend:
          serviceName: tomcat-service
          servicePort: 8080
EOF
 
# 创建
[root@master ~]# kubectl create -f ingress-http.yaml

# 查看
[root@master ~]# kubectl get pod -n dev -o wide

image-20211109225900016

# 查看详情
[root@master ~]# kubectl describe ingress ingress-http -n dev

image-20211109225951385

配置本地host文件将master的IP地址与上述域名绑定

# 访问nginx
curl nginx.sr.com:30771

image-20211109230744863

# 访问tomcat
curl tomcat.sr.com:30378

image-20211109230847610

HTTPS 代理

# 生成证书
[root@master ~]# openssl req -x509 -sha256 -nodes -days 365 -newkey rsa:2048 -keyout tls.key -out tls.crt -subj "/C=CN/ST=BJ/L=BJ/O=nginx/CN=sr.com"

# 生成秘钥
[root@master ~]# kubectl create secret tls tls-secret --key tls.key --cert tls.crt

# 生成配置文件
cat > ingress-https.yaml << EOF
apiVersion: extensions/v1beta1
kind: Ingress
metadata:
  name: ingress-https
  namespace: dev
spec:
  tls:
    - hosts:
      - nginx.sr.com
      - tomcat.sr.com
      secretName: tls-secret # 指定秘钥需要与上述生成的秘钥名称相同
  rules:
  - host: nginx.sr.com
    http:
      paths:
      - path: /
        backend:
          serviceName: nginx-service
          servicePort: 80
  - host: tomcat.sr.com
    http:
      paths:
      - path: /
        backend:
          serviceName: tomcat-service
          servicePort: 8080
EOF

# 加载配置文件
[root@master k8s]# kubectl create -f ingress-https.yaml

# 查看配置文件
[root@master k8s]# kubectl get ingress ingress-https -n dev

image-20211118164057887

# 查看详情
[root@master k8s]# kubectl describe -n dev ingress ingress-https

image-20211118165502336

# 查看service
[root@master k8s]# kubectl get -n ingress-nginx service

image-20211118165641222

posted @ 2021-11-18 20:14  SR丶  阅读(3112)  评论(1编辑  收藏  举报