侧边栏壁纸
  • 累计撰写 51 篇文章
  • 累计创建 23 个标签
  • 累计收到 0 条评论

目 录CONTENT

文章目录

K8s Pod 和 Service 9

zhanjie.me
2020-11-15 / 0 评论 / 0 点赞 / 1 阅读 / 0 字

9.1 Service 简介

Service 的概念

在 Kubernetes 集群中,Service 其实是一个抽象的概念,通过虚拟 IP(VIP) 映射出指定的端口,将客户端发来的请求进行代理并转发到后端具有相同 Label 的 Pods 中。它是 Kubernetes 常用的向外部暴露内部服务的方式。

Service 的作用

试想当我们使用 Deployment 创建多个 Pod 副本时,这时一个服务后端就有了多个 Pod 在同时工作,而 Pod 完全由 Kubernetes 集群自动进行调度,只能保证集群中一直都有符合数量要求的 Pod 存在,但是可能具体的 Pod 一直都在变动,外部访问如何才能够稳定呢?以及负载均衡如何实现呢?因此 Kubernetes 虚拟出了一个更高层次的抽象概念 Service,它来负责集群内部的负载均衡,Service 通过标签选择器 Label 可以选择具有相同标签的 Pod 形成一个集合。

Service 的工作原理

Service 根据标签选择器查找 Pod,并创建和 Service 同名的 Endpoints 对象,Endpoints 是组成 Service 的一组 IP 地址和端口 Node 资源,当 Pod 地址发生变化时,Endpoints 也会随之发生变化,Service 接收请求后通过 Endpoints 查找请求转发的目标地址。

如下是 Service 工作原理图:

image-tbmgbeit.png

Endpoints Controller 的主要作用是负责生成和维护所有 Endpoints 对象,通过监听 Service 和对应 Pods 的变化,及时更新 Service 的 Endpoints 对象。当 Service 被创建后 Endpoints Controller 会监听符合标签选择器对应的 Pod 的状态,当 Pod 处于 Running 并准备就绪时,Endpoints Controller 就会将 Pod ip 记录到 Endpoints 对象中,所以 Service 发现就是通过 Endpoints 实现的。

具体到每个 Node 上会使用 kube-proxy 实现负载均衡,kube-proxy 会根据 Service 和 Endpoints 的变化更新每个 Node 节点上 Iptables 或 Ipvs 中保存的路由转发规则。


9.2 Service 的类型

根据 type 类型分为 4 种模式

1.ClusterIP

ClusterIP:这是 Kubernetes 的默认方式。这其中根据是否会生成 ClusterIP 又分为普通 Service 和 Headless Service。

  • 普通 Service:创建的 Service 会分配一个集群内部可访问的固定虚拟 IP,这是最常使用的方式。
  • Headless Service:创建的 Service 不会分配固定的虚拟 IP,同样也不会通过 kube-proxy 做反向代理和负载均衡,主要通过 DNS 提供稳定的网络 ID 进行访问,通常用于 StatefulSet 中。

2.NodePort

NodePort:使用 ClusterIP,并且将 Service 的 port 端口映射到集群中每个 Node 节点的相同端口 port,这样在集群外部访问服务可以直接使用 nodeIP:nodePort 进行访问。

3.LoadBalancer

LoadBalancer:在 NodePort 的基础上(也就是拥有 ClusterIP 和 nodePort),还会向所处的公有云申请负载均衡器 LB(负载均衡器的后端直接映射到各 Node 节点的 nodePort 上),这样就实现了通过外部的负载均衡器访问服务。

4.ExternalName

ExternalName:这是 Service 的一种特例形式。主要用于解决运行在集群外部的服务问题,这种方式下会返回外部服务的别名来为集群内部提供服务。上述提到的 3 种模式主要依赖于 kube-proxy,而这种模式依赖于 kube-dns 的层级。

这里可能理解起来比较抽象,举一个例子进行说明,请看下面的 YAML 文件:

kind: Service
apiVersion: v1
metadata:
  name: my-service
  namespace: test
spec:
  type: ExternalName
  externalName: api.github.com

当使用上面的 YAML 文件创建服务以后,DNS 服务会给 ..svc.cluster.local(在这里就为 my-service.test.svc.cluster.local)创建一个 CNAME 记录,并向其中写入 api.github.com。

当查询服务 my-service.test.svc.cluster.local 时,集群中的 DNS 服务就会返回映射的 api.github.com。

Service 涉及到的 3 种 Port

Service 服务涉及到的 Port 概念比较多,这里做一个概念上的区分:

  • port:指 Service 暴露在 ClusterIP 上的端口 port,在 Kubernetes 集群内部就是通过 ClusterIP:port 访问服务的。
  • nodePort:指节点 Node 开放的端口,从 Kubernetes 集群外部可以通过 nodeIP:nodePort 访问服务。nodePort 的范围是 30000-32767,设置端口时不能够与已经在使用的有冲突。
  • targetPort:指 Pod 开放的端口,外部访问的请求经过 port 端口和 nodePort 端口,以及 kube-proxy 服务最终转发到后端 Pod 的 targetPort 端口,然后进入到 Pod 中的容器内进行处理。

4 种 IP

Service 服务涉及到的 IP 概念也比较多,我们这里同样做一个概念上的区分:

  • ClusterIP:这是一个虚拟地址(VIP),没有实际的网络设备承载这个地址,它的底层实现原理是依靠 kube-proxy 通过 iptables 规则重定向到 Node 节点的端口上,然后再负载均衡到后端 Pod。具体过程为:kube-proxy 发现新 Service,在 Node 节点上打开一个任意的 nodePort 端口,创建对应的 iptables 规则或是 IPVS 规则,重定向 Service 的 ClusterIP:port 到新建的 nodePort 端口上,然后开始处理关于新 Service 的连接访问。
  • NodeIP:上面提到的 ClusterIP 由于是虚拟的,只能用于 Kubernetes 集群内部的访问。当集群需要提供外部服务,这时就需要为服务提供公共 IP,通过定义 YAML 文件指定 Service 类型 spec.type=NodePort,然后集群会在所有 Node 节点上开放一个指定的 nodePort,集群外部通过 nodeIP:nodePort 就可以访问服务了。
  • PodIP:当每个新 Pod 创建时,集群都会先使用 gcr.io/google_containers/pause 镜像创建一个容器,然后再创建其它要求的容器。在 Pod 内部其它容器都使用 container 网络模式,并指定其值为 pause 容器的 ID(即:network_mode: "container: pause 容器 ID"),这样 Pod 内所有容器将共享 pause 容器的网络,所有其他容器在 Pod 内部都处于同一个网络模式下,可以彼此间直接通信,而与外部通信都会经过 pause 容器进行代理,因此 pause 容器 IP 才是真正实际意义上的 PodIP。
  • ExternalIP:外部 IP,通过负载均衡(LB)方式发布服务的话,也就是 LoadBalancer Service 类型,公有云提供的负载均衡器的访问地址。

Service 服务发现

Service 服务发现目前有两种类型:环境变量和 DNS。通常而言更加推荐使用后一种,也就是 DNS。

  1. 环境变量

当新创建 Pod 时,kubelet 会向该 Pod 中注册所有已经创建的与 Service 相关的环境变量。注意这里就存在一个问题,当某个特定的 Service 晚于 Pod 的创建,那么先创建的 Pod 就不会注册该 Service 的环境变量。因此更加推荐使用 DNS 来进行服务发现。

2.DNS

通过在集群中部署 CoreDNS 服务(在老版本的 Kubernetes 集群中使用的是 KubeDNS)来实现集群内部 Pod 通过 DNS 方式进行通讯。

CoreDNS 是以插件方式集成到 Kubernetes 集群中作为默认的 DNS 服务,这样可以更加灵活方便的进行拓展。


9.3 使用命令创建服务

创建一个名为 webapp 的 Deployment,它可以创建两个 Nginx Pod 副本,并且指定容器提供服务的端口号为 80 端口。

新建 nginx-deployment.yaml 文件,并向其中写入如下内容:

apiVersion: apps/v1
kind: Deployment
metadata:
  name: myweb
spec:
  selector:
    matchLabels:
      app: nginx
  replicas: 2
  template:
    metadata:
      labels:
        app: nginx
    spec:
      containers:
        - name: nginx
          image: nginx:1.19
          ports:
            - containerPort: 80

执行创建:

$ kubectl create -f nginx-deployment.yaml
deployment.apps/myweb created

查看新创建的 Pods:

$ kubectl get pods
NAME                              READY   STATUS    RESTARTS   AGE
myweb-687548bb8c-2ndff            1/1     Running   0          21s
myweb-687548bb8c-z8rmt            1/1     Running   0          21s

可以使用命令 kubectl expose 创建服务,该命令主要用于将一个已经存在的 Pod、RC、Service、或是 Deployment 暴露为一个新的 Service。

具体的命令格式如下所示:

kubectl expose (-f FILENAME|TYPE NAME|TYPE/NAME) [--port=port] [--protocol=TCP|UDP] [--target-port=number-or-name][--name=name][--external-ip=external-ip-of-service][--type=type][flags]

执行如下命令:

$ kubectl expose deployment myweb
service/myweb exposed

直接使用命令创建,不指定额外参数的话,新建的 Service 使用与 deploy 相同的名称,端口也会直接使用 Pod 中定义的 containerPort。我们来查看创建好的服务:

$ kubectl get svc
NAME         TYPE        CLUSTER-IP      EXTERNAL-IP   PORT(S)   AGE
myweb        ClusterIP   10.96.121.66    <none>        80/TCP    29s

查找新建的两个 Pod 的 IP 地址,然后查看 myweb 服务的详细信息:

$ kubectl get pods -l app=nginx -o yaml|grep podIP
    podIP: 10.20.177.107
    podIP: 10.20.138.21
$ kubectl describe svc/myweb
Name:              myweb
Namespace:         user-test
Labels:            <none>
Annotations:       <none>
Selector:          app=nginx
Type:              ClusterIP
IP:                10.96.121.66
Port:              <unset>  80/TCP
TargetPort:        80/TCP
Endpoints:         10.20.138.21:80,10.20.177.107:80
Session Affinity:  None
Events:            <none>

测试一下这个 ClusterIP:Port(即:10.96.121.66:80) 是否能够访问:

$ curl http://10.96.121.66
<!DOCTYPE html>
<html>
<head>
...
</head>
<body>
<h1>Welcome to nginx!</h1>
<p>If you see this page, the nginx web server is successfully installed and
working. Further configuration is required.</p>

<p>For online documentation and support please refer to
<a href="http://nginx.org/">nginx.org</a>.<br/>
Commercial support is available at
<a href="http://nginx.com/">nginx.com</a>.</p>

<p><em>Thank you for using nginx.</em></p>
</body>
</html>

可以看到在通过 ClusterIP 类型的 Service 已经可以成功实现集群内部的互相访问。


9.4 使用 YAML 文件创建服务

新建 tomcat-deployment.yaml,并向其中写入如下内容:

apiVersion: apps/v1
kind: Deployment
metadata:
  name: myapp
spec:
  selector:
    matchLabels:
      app: tomcat
  replicas: 2
  template:
    metadata:
      labels:
        app: tomcat
    spec:
      containers:
        - name: tomcat
          image: kubeguide/tomcat-app:v1
          ports:
            - containerPort: 8080

执行创建:

$ kubectl create -f tomcat-deployment.yaml
deployment.apps/myapp created

查看新创建的 Pods:

$ kubectl get pods
NAME                              READY   STATUS    RESTARTS   AGE
myapp-78c89857d6-mldzb            1/1     Running   0          51s
myapp-78c89857d6-pkxhg            1/1     Running   0          51s

新建 myapp-svc.yaml 文件,并向其中写入如下代码。这里设置服务类型为 NodePort,集群内部服务的端口为 8081,对外提供服务的端口为 30001,并且 Pod 的端口为 8080:

apiVersion: v1
kind: Service
metadata:
  name: myapp
spec:
  type: NodePort # 指定服务类型为 NodePort
  ports:
    - port: 8081 # 指定集群内部 service 的端口
      targetPort: 8080 # 指定 Pod 的端口
      nodePort: 30001 # 指定外部连接的端口
  selector: # 标签选择器,选择带有 app=tomcat 的 Pod
    app: tomcat

执行创建:

$ kubectl create -f myapp-svc.yaml
service/myapp created

查看服务详细信息:

# 可以发现新建的 myapp 服务,类型为 NodePort;PORT 部分,8081 为集群 VIP 端口,30001 为外部连接 Node 的端口
$ kubectl get svc
NAME         TYPE        CLUSTER-IP      EXTERNAL-IP   PORT(S)          AGE
myapp        NodePort    10.96.116.208   <none>        8081:30001/TCP   32s
$ kubectl get pods -l app=tomcat -o yaml|grep podIP
    podIP: 10.20.177.105
    podIP: 10.20.177.106
$ kubectl describe svc myapp
Name:                     myapp
Namespace:                user-test
Labels:                   <none>
Annotations:              <none>
Selector:                 app=tomcat
Type:                     NodePort
IP:                       10.96.116.208
Port:                     <unset>  8081/TCP
TargetPort:               8080/TCP
NodePort:                 <unset>  30001/TCP
Endpoints:                10.20.177.105:8080,10.20.177.106:8080
Session Affinity:         None
External Traffic Policy:  Cluster
Events:                   <none>

尝试集群内部访问:

$ curl http://10.96.116.208:8081

<!DOCTYPE html>
<html lang="en">
    <head>
        <meta charset="UTF-8" />
        <title>Apache Tomcat/8.0.35</title>
        <link href="favicon.ico" rel="icon" type="image/x-icon" />
        <link href="favicon.ico" rel="shortcut icon" type="image/x-icon" />
        <link href="tomcat.css" rel="stylesheet" type="text/css" />
    </head>
...

尝试在集群外部直接通过浏览器访问:

image-ynsmjgyg.png


9.5 Service Discovery 服务发现

环境变量

在 Node 上新创建一个 Pod 时,kubelet 会为每个 Pod(容器)添加一组环境变量,其中就包括当前系统中已经存在的 Service 的 IP 地址和端口号。

环境变量的格式如下所示:

{SVCNAME}_SERVICE_HOST=host
{SVCNAME}_SERVICE_PORT=port

环境变量名都必须为大写,如果其中有连字符的会被转换为下划线。

比如集群中已经存在一个名为 redis-master 的 Service,它在集群内的 IP 地址为:10.0.0.11,TCP 端口号为 6379,那么新创建的 Pod 中就会被初始化一组环境变量如下所示:

REDIS_MASTER_SERVICE_HOST=10.0.0.11
REDIS_MASTER_SERVICE_PORT=6379
REDIS_MASTER_PORT=tcp://10.0.0.11:6379
REDIS_MASTER_PORT_6379_TCP=tcp://10.0.0.11:6379
REDIS_MASTER_PORT_6379_TCP_PROTO=tcp
REDIS_MASTER_PORT_6379_TCP_PORT=6379
REDIS_MASTER_PORT_6379_TCP_ADDR=10.0.0.11

所以环境变量在大部分情况下已经可以解决服务发现的问题了,但是这里存在一个需要注意的地方就是:服务必须先于 Pods 创建。如果先创建 Pods 后创建 Service,那么后创建的 Service 环境变量不会存在于先创建的 Pods 中。

新建 nginx-deployment.yaml 文件,并向其中写入如下内容:

apiVersion: apps/v1
kind: Deployment
metadata:
  name: myweb
spec:
  selector:
    matchLabels:
      app: nginx
  replicas: 2
  template:
    metadata:
      labels:
        app: nginx
    spec:
      containers:
        - name: nginx
          image: nginx:1.19
          ports:
            - containerPort: 80

执行创建:

$ kubectl create -f nginx-deployment.yaml
deployment.apps/myweb created

查看新创建的 Pods 的名称:

$ kubectl get pods
NAME                              READY   STATUS    RESTARTS   AGE
myapp-76b54cbf85-hs5vs            1/1     Running   0          9m38s
myapp-76b54cbf85-qzrck            1/1     Running   0          9m41s

查看环境变量:

$ kubectl get svc
NAME         TYPE        CLUSTER-IP      EXTERNAL-IP   PORT(S)          AGE
php-apache   ClusterIP   10.96.115.130   <none>        80/TCP           14h

# 可以看到 Pod 已经被初始化注册了 php-apache 服务的相关环境变量
$ kubectl exec myweb-687548bb8c-2ndff env
PATH=/usr/local/sbin:/usr/local/bin:/usr/sbin:/usr/bin:/sbin:/bin
HOSTNAME=myweb-687548bb8c-2ndff
PHP_APACHE_SERVICE_HOST=10.96.115.130
PHP_APACHE_PORT_80_TCP=tcp://10.96.115.130:80
...

直接使用命令创建名为 myweb 的服务:

$ kubectl expose deploy myweb
service/myweb exposed

查看 myweb Service 的集群 IP 和端口:

$ kubectl get svc
NAME         TYPE        CLUSTER-IP      EXTERNAL-IP   PORT(S)          AGE
myweb        ClusterIP   10.96.121.66    <none>        80/TCP           24m
php-apache   ClusterIP   10.96.115.130   <none>        80/TCP           14h

再一次执行 kubectl exec myweb-687548bb8c-2ndff env 命令会发现新创建的服务在 myweb-687548bb8c-2ndff Pod 中依然也没有注册环境变量。

新建名为 tomcat 的 Pod,观察新创建的 Pod 有没有初始化环境变量时就将 myweb Service 注册进去了。

新建 tomcat-deployment.yaml,并向其中写入如下内容:

apiVersion: apps/v1
kind: Deployment
metadata:
  name: myapp
spec:
  selector:
    matchLabels:
      app: tomcat
  replicas: 2
  template:
    metadata:
      labels:
        app: tomcat
    spec:
      containers:
        - name: tomcat
          image: kubeguide/tomcat-app:v1
          ports:
            - containerPort: 8080

执行创建:

$ kubectl create -f tomcat-deployment.yaml
deployment.apps/myapp created

查看新创建的 Pods:

$ kubectl get pods -l app=tomcat
NAME                     READY   STATUS    RESTARTS   AGE
myapp-76b54cbf85-hs5vs   1/1     Running   0          19m
myapp-76b54cbf85-qzrck   1/1     Running   0          19m

任选其中的一个 Pod 查看环境变量:

$ kubectl exec myapp-76b54cbf85-hs5vs env|grep MYWEB
MYWEB_SERVICE_HOST=10.96.121.66
MYWEB_PORT_80_TCP_PROTO=tcp
MYWEB_PORT_80_TCP_PORT=80
MYWEB_SERVICE_PORT=80
MYWEB_PORT_80_TCP=tcp://10.96.121.66:80
MYWEB_PORT=tcp://10.96.121.66:80
MYWEB_PORT_80_TCP_ADDR=10.96.121.66

可以看到与 myweb 服务相关的环境变量都已经注册进了 tomcat Pod 中。

DNS

由于前面提到的环境变量的注意事项,所以更加推荐在服务发现中使用 DNS。

在 Kubernetes 集群中设置 DNS 服务是以插件的方式运行的,kubernetes v1.16.9 版本中使用的 DNS 插件为 CoreDNS,使用如下命令进行查看:

$ kubectl get pods -n=kube-system
NAME                                       READY   STATUS    RESTARTS   AGE
coredns-67c766df46-4vg25                   1/1     Running   0          77d
coredns-67c766df46-wkwf5                   1/1     Running   0          77d

在集群中部署成功 CoreDNS 组件后,它会监视 Kubernetes API 中的新服务,并为每个服务创建一组 DNS 记录,集群中所有 Pod 可以通过 DNS 名称自动解析服务。而具体的实现是通过修改每个容器的 /etc/resolv.conf 实现。

比如前面我们在名为 default 的命名空间下创建了名为 myweb 的服务,那么 CoreDNS 会为 myweb.default 创建 DNS 记录。在相同命名空间下的其它 Pod 可以直接通过 myweb 查找到这个服务;如果处于其它命名空间就需要通过 myweb.default 进行查找。

$ kubectl exec -it myapp-76b54cbf85-hs5vs bash
root@myapp-76b54cbf85-hs5vs:/usr/local/tomcat# curl myweb.default:80
<!DOCTYPE html>
<html>
<head>
<title>Welcome to nginx!</title>
<style>
    body {
        width: 35em;
        margin: 0 auto;
        font-family: Tahoma, Verdana, Arial, sans-serif;
    }
</style>
</head>
<body>
<h1>Welcome to nginx!</h1>
<p>If you see this page, the nginx web server is successfully installed and
working. Further configuration is required.</p>

<p>For online documentation and support please refer to
<a href="http://nginx.org/">nginx.org</a>.<br/>
Commercial support is available at
<a href="http://nginx.com/">nginx.com</a>.</p>

<p><em>Thank you for using nginx.</em></p>
</body>
</html>

可以看到在 tomcat Pod 中成功通过域名发现了 Nginx 服务。

0

评论区