第九周作业

service

什么是service

Pod存在生命周期,有销毁,有重建,无法提供一个固定的访问接口给客户端。并且为了同类的Pod都能够实现工作负载的价值,由此Service资源出现了,可以为一类Pod资源对象提供一个固定的访问接口负载均衡,类似于阿里云的负载均衡或者是LVS的功能。

但是要知道的是,Service和Pod对象的IP地址,一个是虚拟地址,一个是Pod IP地址,都仅仅在集群内部可以进行访问,无法接入集群外部流量。而为了解决该类问题的办法可以是在单一的节点上做端口暴露(hostPort)以及让Pod资源共享工作节点的网络名称空间(hostNetwork)以外,还可以使用NodePort或者是LoadBalancer类型的Service资源,或者是有7层负载均衡能力的Ingress资源。

Service的作用:

  1. 暴露流量: 让用户可以通过ServiceIP+ServicePort访问对应后端的Pod应用;
  2. 负载均衡: 提供基于4层的TCP/IP负载均衡,并不提供HTTP/HTTPS等负载均衡;
  3. 服务发现: 当发现新增Pod则自动加入至Service的后端,如发现Pod异常则自动从Service后端删除Pod的IP

暴露流量

  1. ClusterIP: 这是默认的Service类型。它会创建一个虚拟IP地址,供集群内部的Pod访问。这种类型的Service只能在集群内部访问,无法从集群外部访问。

  2. NodePort: 这种Service类型除了分配一个集群IP外,还会在每个Node的某个端口(非著名端口)上暴露服务。集群外部可以通过访问任意Node的该端口来访问服务。

  3. LoadBalancer: 这是公有云环境下常用的Service类型。它不仅会分配集群IP,还会自动在公有云上创建一个负载均衡器,暴露一个外部可访问的IP地址。

  4. ExternalName: 这种Service类型不会分配集群IP,而是将服务映射到一个外部的DNS名称。当集群内部访问这种Service时,会自动解析到外部服务的地址

  5. Headless Service: 这种Service不会分配集群IP,而是直接将后端的Pod IP暴露给客户端。适用于不需要负载均衡的场景,比如DNS服务等。

负载均衡

  1. Round-Robin 负载均衡:

    • 默认情况下,Service会使用Round-Robin的方式在后端的Pod之间分配流量。
    • 这种方式简单快捷,能够将流量平均分配到各个Pod上。
  2. 会话亲和性:

    • Service支持基于客户端IP的会话亲和性。
    • 对于同一个客户端IP,后续的请求会被转发到之前处理该客户端请求的Pod上。
    • 适用于需要保持会话状态的应用程序。
  3. 健康检查与自动故障转移:

    • Service会定期检查后端Pod的健康状态。
    • 当某个Pod发生故障时,Service会自动将其从负载均衡池中剔除,将流量转发到健康的Pod上。
    • 这可以提高应用程序的可用性和容错性

服务发现

  • 说明

    • Kubernetes 服务发现通过创建和管理服务(Service)资源来实现。服务资源定义了如何访问一组 Pod,这些 Pod 通常执行相同的任务
    • 通过 标签选择器 筛选同一名称空间下的 Pod 资源 的 标签,完成 Pod 筛选。
      • 例如,定义一个服务时,可以指定一个标签选择器,服务会自动发现并包含所有匹配该标签的 Pod。
    • Kubernetes 内部 DNS 服务器会自动为每个服务创建一个 DNS 条目,使得其他 Pod 可以通过服务名称进行访问
  • 本质

    • 当一个 Service 资源创建后,Kubernetes 会自动创建一个与该 Service 同名EndpointEndpointSlice 资源。
    • EndpointEndpointSlice 资源包含了服务所对应的 Pod 的 IP 地址和端口信息
sequenceDiagram
    participant User
    participant API_Server
    participant Service
    participant Controller
    participant Endpoint

    User ->> API_Server: 创建 Service 资源
    API_Server ->> Service: 创建 Service 对象
    API_Server ->> Controller: 通知创建 Service
    Controller ->> API_Server: 创建与 Service 同名的 Endpoint 或 EndpointSlice
    API_Server ->> Endpoint: 包含 Pod 的 IP 地址和端口信息
    loop 监视 Pod 的变化
        Controller ->> API_Server: 监视 Pod 的变化
        API_Server ->> Controller: Pod 状态变化通知
        Controller ->> Endpoint: 更新 Endpoint 或 EndpointSlice 资源
    end

标签选择器

标签:附加在资源对象上的键值型元数据 (pod.metadata.labels)

!!! tip 标签格式

  • 键标识:由“键前缀(可选)”和“键名”组成,格式为 [key_prefix]/key_name
    • 键前缀 key_prefix必须使用DNS域名格式
    • 键名 key_name的命名格式:支持字母数字连接号下划线点号,且只能以字母或数字开头;最长63个字符;
  • “kubectl label”命令可管理对象的标签
    !!!

!!! tip 标签选择器 -- 支持类型

  • 基于等值关系
    • ===!=
  • 基于集合关系
    • in: 表示keyvalue集合里即命中

      • KEY in (VALUE1, VALUE2, …)
        spec:
          selector:
            matchExpressions:
              - key: app
                operator: In
                values: 
                  - web
                  - db
    • notin: 表示key value集合里即命中

      • KEY notin (VALUE1, VALUE2, …)
        spec:
          selector:
            matchExpressions:
              - key: app
                operator: NotIn
                values: 
                  - web
                  - db
    • exists: 筛选存在某个标签的资源,而不关心标签的具体值

      • KEY1
        spec:
          selector:
            matchExpressions:
              - key: app
                operator: Exists

!!!

服务注册

服务注册服务发现过程中的一个关键步骤。
服务注册可以被视为服务发现实现的一个方式或步骤。

  • 本质

    • 控制器会不断 监视 Pod 的变化,确保 Endpoint 或 EndpointSlice 资源始终准确反映当前状态。
  • 工作方式

    1. 注册服务:

      • 当用户创建一个 Service 资源时,Kubernetes 自动注册该服务,即创建一个与服务同名的 Endpoint 或 EndpointSlice 资源。
      • 这些资源包含了所有匹配服务标签选择器的 Pod 的 IP 地址和端口信息。
    2. 更新服务信息:

      • 控制器持续监视 Pod 的变化(如新增、删除、更新等),并相应地更新 Endpoint 或 EndpointSlice 资源。
      • 确保服务始终反映当前的实际 Pod 状态。
    3. 服务发现机制:

      • Kubernetes 内部的 DNS 服务器会为每个服务创建一个 DNS 条目,这使得其他 Pod 可以通过服务名称进行访问。
      • 当一个 Pod 需要访问某个服务时,DNS 解析服务名称并返回与该服务关联的 Pod 的 IP 地址,从而实现服务发现。

资源规范

apiVersion: v1
kind: Service
metadata:
name: 
namespace: 
spec:
  # Service类型,默认为ClusterIP
  type <string>
  # 等值类型的标签选择器,内含“与”逻辑
  selector <map[string]string>
  # Service的端口对象列表
  ports:
  # 端口名称
  - name <string>
    # 协议,目前仅支持TCP、UDP和SCTP,默认为TCP
    protocol <string>
    # Service的端口号
    port <integer>
    # 后端目标进程的端口号或名称,名称需由Pod规范定义
    targetPort <string>
    # 节点端口号,仅适用于NodePort和LoadBalancer类型
    nodePort <integer>
  # Service的集群IP,建议由系统自动分配
  clusterIP <string>
  # 外部流量策略处理方式
  # Local表示由当前节点处理,Cluster表示向集群范围调度
  externalTrafficPolicy <string>
  # 外部负载均衡器使用的IP地址,仅适用于LoadBlancer
  loadBalancerIP <string>
  # 外部服务名称,该名称将作为Service的DNS CNAME值
  externalName <string>

层次说明

负载均衡器入口:ClusterIP及相关的Service Port、NodePort
标签选择器:用于筛选Pod,并基于筛选出的Pod的IP生成后端端点列表

service 工作

工作逻辑

  1. 创建service任务下发
  2. service通过label selector获取匹配的pod
  3. 将匹配的pod汇总到endpoint对象中(pod IP,端口信息)进行维护

具体实现

1.创建Service资源后,会分配一个随机的ServiceIP,返回给用户,然后写入etcd
2.endpoints-controller负责生成和维护所有endpoints,它会监听Service和Pod的状态,当Pod处于running且准备就绪时endpoints-controller会将Pod IP更新对应Service的endpoints对象中,然后写入Etcd
3.kube-proxy通过API-Server监听ServiceEndpoints的资源变动,一旦Service或Endpoints资源发生变化,kube-proxy会将最新的信息转换为对应的iptables/ipvs访问规则,而后在本地主机上执行;
4.当客户端访问Service时,会经由iptables/ipvs规则,路由到对应节点;

service 创建 通过 label selector 选定 pod
endpoint 隐含创建 监听 service 和 pod状态, 记录IP端口信息
kube-proxy 监听 service和endpoint 状态,创建对应的 iptables/ipvs 路由规则

client 访问 service 查询endpoint,通过 iptables/ipvs 规则最总到达pod, 资源创建信息均写入etcd

kube-proxy模型

kube-proxy 负责为 Service 实现了一种 VIP(虚拟 IP)的形式,而不是 ExternalName 的形式。
Kubernetes v1.0 版本,代理完全在 userspace
Kubernetes v1.1 版本,新增了 iptables 代理 1.2 中设定为默认模式
Kubernetes v1.8.0-beta.0中,添加了ipvs代理 1.11 设为为默认模式
iptables/ipvs 被定位为 (TCP/UDP over IP)4层服务
Kubernetes v1.1 版本,新增了 Ingress API(beta 版),引入7层 (HTTP)服务

kube-proxy 这个组件始终监视着apiserver中有关Service的变动信息,获取任何一个与Service资源相关的变动状态,通过watch监视,一旦有Service资源相关的变动和创建,kube-proxy都要转换为当前节点上的能够实现资源调度规则

userspace mode

当客户端Pod请求内核空间的Service iptables后,把请求转到给用户空间监听的kube-proxy 的端口,由kube-proxy来处理后,再由kube-proxy将请求转给内核空间的 Service ip,再由Service iptalbes根据请求转给各节点中的的Service pod

问题:
由客户端请求先进入内核空间的,又进去用户空间访问kube-proxy,由kube-proxy封装完成后再进去内核空间的iptables,再根据iptables的规则分发给各节点的用户空间的pod。这样流量从用户空间进出内核带来的性能损耗是不可接受的。在Kubernetes 1.1版本之前,userspace是默认的代理模型。

iptables mode

kube-proxy为service后端的所有pod创建对应的iptables规则,当用户向serviceIP发送请求.
1.首先iptables会拦截用户请求
2.然后直接将请求调度到后端的Pod

优化:
减少再调度过程中,内核空间和用户空间的多次切换。

问题:
一个Service会创建出大量的规则,且不支持更高级的调度算法,当Pod不可用也无法重试。

ipvs mode

客户端IP请求时到达内核空间时,根据ipvs的规则直接分发到各pod上。kube-proxy会监视Kubernetes Service对象和Endpoints,调用netlink接口以相应地创建ipvs规则并定期与Kubernetes Service对象和Endpoints对象同步ipvs规则,以确保ipvs状态与期望一致。

ipvs为负载均衡算法提供了更多选项

  • rr:轮询调度
  • lc:最小连接数
  • dh:目标哈希
  • sh:源哈希
  • sed:最短期望延迟
  • nq:不排队调度

优化:
提供了丰富的负载均衡调度算法,对service和endpoint的监视有助于同步规则减少故障。

!!! warning 降级注意
ipvs模式假定在运行kube-proxy之前在节点上都已经安装了IPVS内核模块。当kube-proxy以ipvs代理模式启动时,kube-proxy将验证节点上是否安装了IPVS模块,如果未安装则kube-proxy将回退到iptables代理模式
!!!

ClusterIP

通过集群的内部IP暴露服务,选择ServiceIP只能在集群内部访问。这也是默认的ServiceType.
ClusterIP: This is the default ServiceType. This makes the Service only reachable from within the cluster and allows applications within the cluster to communicate with each other. There is no external access.

  • 接入方式

    支持Service_IP:Service_Port接入;
    serviceIPclusterIP

!!! info 实例信息

  • 命令执行

    kubectl create service clusterip NAME [--tcp=<port>:<targetPort>] [--dry-run=server|client|none] [options]

  • 案例

    创建一个名为demoappClusterIP类型的service
    标签过滤器选中 app=demoapp 的 pod
    pod由80(targetPort)/tcp端口提供服务
    clusterIP也从80(port) 端口提供服务

kind: Service
apiVersion: v1
metadata:
  name: demoapp
spec:
  # 类型标识,默认即为ClusterIP;
  type: ClusterIP
  selector:
    app: demoapp
  # 端口名称标识
  ports:
  - name: http
    # 协议,支持TCP、UDP和SCTP
    protocol: TCP
    # Service的端口号
    port: 80
    # 目标端口号,即后端端点提供服务的监听端口号
    targetPort: 80

!!!

NodePort

NodePort类型是对ClusterIP类型Service资源的扩展。它通过每个节点上的IP和端口接入集群外部流量,并分发给后端的Pod处理和响应。因此通过<节点IP>:<节点端口>,可以从集群外部访问服务。
NodePort: This allows the external traffic to access the Service by opening a specific port on all the nodes. Any traffic that is sent to this Port is then forwarded to the Service.

  • 接入方式

    支持Node_IP:Node_Port接入,同时支持ClusterIP;

!!! info 实例信息

  • 命令执行

    kubectl create service nodeport NAME [--tcp=port:targetPort] [--dry-run=server|client|none] [options]

  • 案例

创建一个demoappnodePort类型的service
通过app:demoapp 进行标签选择
node上使用30080端口提供服务映射到cluster的80端口,到pod上的80端口
对于nodePort仅能使用30000以上的端口
终端用户通过访问node的30080端口进行请求

kind: Service
apiVersion: v1
metadata:
  name: demoapp
spec:
  # 必须明确给出Service类型
  type: NodePort
  selector:
    app: demoapp
  ports:
  - name: http
    protocol: TCP
    port: 80
    targetPort: 80
    # 可选,为避免冲突,建议由系统动态分配
    nodePort: 30080 

!!!

LoadBalancer

依赖云厂商,需要通过云厂商调用API接口创建软件负载均衡将服务暴露到集群外部。当创建LoadBalance类型的Service对象时,它会在集群上自动创建一个NodePort类型的Service。集群外部的请求流量会先路由至该负载均衡,并由该负载均衡调度至各个节点的NodePort
LoadBalancer: This ServiceType exposes the Services externally using the cloud provider’s load balancer. Traffic from the external load balancer is directed at the backend Pods. The cloud provider decides how it is load-balanced.

NodePort存在着几个方面的问题
非知名端口、私网IP地址、节点故障转移、节点间负载均衡、识别能适配到某Service的Local流量策略的节点等
外置的Cloud Load Balancer可以解决以上诸问题

  • 接入方式

    支持通过外部的LoadBalancer的LB_IP:LB_Port接入,同时支持NodePort和ClusterIP;

    • LoadBalancer需与相关的NodePort的Service生命周期联动
      • LoadBalancer应该是由软件定义
      • Kubernetes需要同LoadBalancer所属的管理API联动

!!! info 实例信息

  • 命令执行

    kubectl create service loadbalancer NAME [--tcp=port:targetPort] [--dry-run=server|client|none] [options]

  • 案例

创建一个名为my-web-serviceloadbalancer
对外服务的端口为80, pod的端口为8080
pod需要具备的标签为app:web-app

apiVersion: v1
kind: Service
metadata:
  name: my-web-service
spec:
  type: LoadBalancer
  ports:
  - port: 80
    targetPort: 8080
  selector:
    app: web-app

!!!

ExternalName

ExternalName: 此类型不是用来定义如何访问集群内服务的,而是把集群外部的某些服务以DNS CNAME方式映射到集群内,从而让集群内的Pod资源能够访问外部服务的一种实现方式

ExternalName: This type of Service maps a Service to a DNS name by using the contents of the externalName field by returning a CNAME record with its value. No proxying of any kind is set up.

  • 说明
    • 负责将集群外部的服务引入到集群中
    • 需要借助于ClusterDNS上的CNAME资源记录完成
    • 特殊类型,无需ClusterIP和NodePort
    • 无须定义标签选择器发现Pod对象

!!! info 实例信息

  • 命令执行

    kubectl create service externalname NAME --external-name external.name [--dry-run=server|client|none] [options]

  • 场景

    1. 应用程序需要访问集群外部的服务,但又不想直接对外部 URL 进行硬编码。
    2. 可以使用 Service 提供一个稳定的内部域名,来访问外部服务。
    3. 当外部服务的 URL 变更时,只需要更新 externalName 字段即可,应用程序无需修改。
  • 案例

创建名为my-serviceexternalName service
当 Kubernetes 集群内部的应用访问 my-service 这个 Service 时,它实际上会被解析到 docs.alfie.com 这个外部 DNS 名称,从而访问到集群外部的服务。
例如: kubectl exec -it my-app -- /bin/bash
curl http://my-service --> 访问的为 docs.alfie.com
或通dig 查看 dig myservice.default.svc.cluster.local @10.96.0.10 +short docs.alfie.com

kind: Service
apiVersion: v1
metadata:
  name: my-service
  namespace: prod
spec:
  type: ExternalName
  externalName: docs.alfie.com

!!!

访问权限

创建token认证用户

  1. 创建令牌认证文件
    !!! abstract 认证文件
    文件格式为CSV,每行定义一个用户:

    格式 :
    token,user,uid,"group1,group2,group3"
    <用户组为可选字段>

    token 生成方式参考:
    echo "$(openssl rand -hex 3).$(openssl rand -hex 8)"

    cat /etc/kubernetes/pki/token.csv
    fab5ec.0c682bef86c83ad9,alfie,100
    8f8340.89a0c3cd39acbd5d,leslie,101
    7a27bf.cdd7e425bac85e03,tom,102

    !!!

  2. apiserver开启token认证

    # 编辑apiserver配置 (应先进行备份)
    vim /etc/kubernetes/manifests/kube-apiserver.yaml
    
    # 添加配置
    spec:
      containers:
      - command:
        - --token-auth-file=/etc/kubernetes/pki/token.csv
        ...
  3. client使用toekn认证

    curl -k -H "Authorization: Bearer fab5ec.0c682bef86c83ad9" -k \
    https://API_SERVER:6443/api/v1/namespaces/default/pods/
    
    {
      "kind": "Status",
      "apiVersion": "v1",
      "metadata": {},
      "status": "Failure",
      "message": "pods is forbidden: User \"alfie\" cannot list resource \"pods\" in API group \"\" in the namespace \"default\"",
      "reason": "Forbidden",
      "details": {
        "kind": "pods"
      },
      "code": 403
    }

创建身份凭据

  1. 定义Cluster

    提供包括集群名称、API Server URL和信任的CA的证书相关的配置;clusters配置段中的各列表项名称需要惟一

    # 设置名称为 kube-demo 的 cluster 输出配置文件到 `$HOME/.kube/kubeusers.conf`
    kubectl config set-cluster kube-demo \
    --embed-certs=true  \
    --kubeconfig=$HOME/.kube/kubeusers.conf \
    --server="https://API_SERVER:6443" \
    --certificate-authority=/etc/kubernetes/pki/ca.crt
  2. 定义User

    添加身份凭据,使用静态令牌文件认证的客户端提供令牌令牌即可

    LESLIE_TOKEN='8f8340.89a0c3cd39acbd5d'
    kubectl config set-credentials leslie \
    --token="$LESLIE_TOKEN" \
    --kubeconfig=$HOME/.kube/kubeusers.conf
  3. 定义Context

    为用户leslie的身份凭据与kube-demo集群建立映射关系名称 leslie@kube-demo

    kubectl config set-context leslie@kube-demo \
    --user=leslie \
    --cluster=kube-demo \
    --kubeconfig=$HOME/.kube/kubeusers.conf
  4. 设定Current-Context

    kubectl config use-context leslie@kube-demo \
    --kubeconfig=$HOME/.kube/kubeusers.conf
  5. 查询current-context

    kubectl config current-context --kubeconfig ~/.kube/kubeusers.conf 
  6. 使用凭据访问

    kubectl get pod \
    --context leslie@kube-demo \
    --kubeconfig $HOME/.kube/kubeusers.conf

token用户对namespacepods具有查看权限

  1. 创建role

     kubectl create role default-ns-pod-view \
     --verb list,watch --resource=pods \
     --namespace=default --dry-run=client -o yaml
    
    apiVersion: rbac.authorization.k8s.io/v1
    kind: Role
    metadata:
      creationTimestamp: null
      name: default-ns-pod-view
      namespace: default
    rules:
    - apiGroups:
      - ""
      resources:
      - pods
      verbs:
      - list
      - watch
  2. 创建rolebinding

    kubectl create rolebinding leslie@default-ns-pod-view \
    --role default-ns-pod-view \
    --user leslie \
    --namespace default \
    --dry-run=client -o yaml
    
    apiVersion: rbac.authorization.k8s.io/v1
    kind: RoleBinding
    metadata:
      creationTimestamp: null
      name: leslie@default-ns-pod-view
      namespace: default
    roleRef:
      apiGroup: rbac.authorization.k8s.io
      kind: Role
      name: default-ns-pod-view
    subjects:
    - apiGroup: rbac.authorization.k8s.io
      kind: User
      name: leslie
  3. 验证权限

kubectl get pod  --kubeconfig $HOME/.kube/kubeusers.conf  
kubectl get pod -A --kubeconfig $HOME/.kube/kubeusers.conf  

ingress

初始化配置

demoapp-v10

apiVersion: apps/v1
kind: Deployment
metadata:
  labels:
    app: demoapp
  name: demoapp-v10
spec:
  replicas: 1
  selector:
    matchLabels:
      app: demoapp
      version: v1.0
  strategy: {}
  template:
    metadata:
      labels:
        app: demoapp
        version: v1.0
    spec:
      containers:
      - image: ikubernetes/demoapp:v1.0
        name: demoapp
        resources: {}
---
apiVersion: v1
kind: Service
metadata:
  labels:
    app: demoapp
  name: demoapp-v10
spec:
  ports:
  - name: http-80
    port: 80
    protocol: TCP
    targetPort: 80
  selector:
    app: demoapp
    version: v1.0
  type: ClusterIP

demoapp-v11

apiVersion: apps/v1
kind: Deployment
metadata:
  labels:
    app: demoapp
  name: demoapp-v11
spec:
  replicas: 1
  selector:
    matchLabels:
      app: demoapp
      version: v1.1
  strategy: {}
  template:
    metadata:
      labels:
        app: demoapp
        version: v1.1
    spec:
      containers:
      - image: ikubernetes/demoapp:v1.1
        name: demoapp
        resources: {}
---
apiVersion: v1
kind: Service
metadata:
  labels:
    app: demoapp
  name: demoapp-v11
spec:
  ports:
  - name: http-80
    port: 80
    protocol: TCP
    targetPort: 80
  selector:
    app: demoapp
    version: v1.1
  type: ClusterIP

基于URL路由

在同一个FQDN下通过不同的URI完成不同应用间的流量分发

  • 不需要为每个应用配置专用的域名
  • 基于单个虚拟主机接收多个应用的流量
  • 常用于将流量分发至同一个应用下的多个不同子应用,同一个应用内的流量由调度算法分发至该应用的各后端端点

案例

# 对于发往demoapp.alfie.com的请求,将“/v10”代理至service/demoapp10,将“/v11”代理至service/demoapp11
# 此处的重写是将所有的请求都重写到 svc 的 `/` 路径下

#> 此处重写功能是 http://example.com/foo/bar --> http://example.com/

kubectl create ingress demo \
--rule="demoapp.alfie.com/v10=demoapp-v10:80" \
--rule="demoapp.alfie.com/v11=demoapp-v11:80" \
--class=nginx --annotation nginx.ingress.kubernetes.io/rewrite-target="/" \
--dry-run=client -o yaml

# 查看创建的 demo ingress
kubectl describe ingress demo 

Name:             demo
Labels:           <none>
Namespace:        default
Address:          
Ingress Class:    nginx
Default backend:  <default>
Rules:
  Host               Path  Backends
  ----               ----  --------
  demoapp.alfie.com  
                     /v10   demoapp-v10:80 (10.244.1.35:80)
                     /v11   demoapp-v11:80 (10.244.2.16:80)
Annotations:         nginx.ingress.kubernetes.io/rewrite-target: /
Events:
  Type    Reason  Age   From                      Message
  ----    ------  ----  ----                      -------
  Normal  Sync    5s    nginx-ingress-controller  Scheduled for sync

# 修改本地hosts文件
tail -n 1 /etc/hosts
192.168.100.50 demoapp.alfie.com

# 访问测试
curl demoapp.alfie.com/v11
iKubernetes demoapp v1.1 !! ClientIP: 10.244.3.23, ServerName: demoapp-v11-5f64448cd5-8q72l, ServerIP: 10.244.2.16!

curl demoapp.alfie.com/v10
iKubernetes demoapp v1.0 !! ClientIP: 10.244.3.23, ServerName: demoapp-v10-7f44bbc8c5-rk826, ServerIP: 10.244.1.35!

基于主机名路由

每个应用使用一个专有的主机名,并基于这些名称完成不同应用间的流量转发

  • 每个FQDN对应于Ingress Controller上的一个虚拟主机的定义
  • 同一组内的应用的流量,由Ingress Controller根据调度算法完成请求调度

案例

# 对demoapp10.alfie.com的请求代理至service/demoapp10,对demoapp11.alfie.com请求代理至 service/demoapp11
# 此处的重写是将所有的请求都重写到 svc 的 `/` 路径下

#> 此处重写功能是 http://example.com/foo/bar --> http://example.com/
kubectl create ingress name-demo \
--rule="demoappv10.alfie.com/*=demoapp-v10:80" \
--rule="demoappv11.alfie.com/*=demoapp-v11:80" \
--class=nginx --dry-run=client -o yaml

# 查看ingress
k get ingress  name-demo 
NAME        CLASS   HOSTS                                       ADDRESS          PORTS   AGE
name-demo   nginx   demoappv10.alfie.com,demoappv11.alfie.com   192.168.100.50   80      40s

# describe 查看 ingress
k describe ingress  name-demo 
Name:             name-demo
Labels:           <none>
Namespace:        default
Address:          192.168.100.50
Ingress Class:    nginx
Default backend:  <default>
Rules:
  Host                  Path  Backends
  ----                  ----  --------
  demoappv10.alfie.com  
                        /   demoapp-v10:80 (10.244.1.35:80)
  demoappv11.alfie.com  
                        /   demoapp-v11:80 (10.244.2.16:80)
Annotations:            <none>
Events:
  Type    Reason  Age                From                      Message
  ----    ------  ----               ----                      -------
  Normal  Sync    50s (x2 over 61s)  nginx-ingress-controller  Scheduled for sync

# 对hosts文件进行修改
tail -n 2 /etc/hosts
192.168.100.50 demoappv10.alfie.com
192.168.100.50 demoappv11.alfie.com

# 测试验证
curl demoappv10.alfie.com
iKubernetes demoapp v1.0 !! ClientIP: 10.244.3.23, ServerName: demoapp-v10-7f44bbc8c5-rk826, ServerIP: 10.244.1.35!

curl demoappv11.alfie.com
iKubernetes demoapp v1.1 !! ClientIP: 10.244.3.23, ServerName: demoapp-v11-5f64448cd5-8q72l, ServerIP: 10.244.2.16!

基于TLS路由

Ingress也可以提供TLS支持,但仅限于443/TCP端口

  • 若TLS配置部分指定了不同的主机,则它们会根据通过SNI TLS扩展指定的主机名
    • 前提:Ingress控制器支持SNI在同一端口上复用
  • TLS Secret必须包含名为tls.crt和 的密钥tls.key,它们分别含有TLS的证书和私钥

案例

# 生成密钥
(umask 077; openssl genrsa -out alfie.key 2048)

# 自签名证书
# 注意此处证书CN 名称需要和 ingress使用访问的域名保持一致(demo.alfie.com)
openssl req -new -x509 \
-key alfie.key -out alfie.crt \
-subj /C=CN/ST=Guangzhou/L=Guangzhou/O=DevOps/CN=demo.alfie.com

# 创建 tls 类型的 secret 资源
kubectl create secret tls tls-alfie \
--cert=./alfie.crt --key=./alfie.key

# 创建ingress
# 启用tls后,该域名下的所有URI默认为强制将http请求跳转至https
# 关闭该功能:--annotation nginx.ingress.kubernetes.io/ssl-redirect=false
kubectl create ingress tls-demo \
--rule='demo.alfie.com/*=demoapp-v10:80,tls=tls-alfie' \
--class=nginx --dry-run=client -o yaml

# 查看ingress
kubectl get ingress tls-demo 
NAME       CLASS   HOSTS            ADDRESS          PORTS     AGE
tls-demo   nginx   demo.alfie.com   192.168.100.50   80, 443   4m59s

# describe 查看 ingress
kubectl describe ingress tls-demo 
Name:             tls-demo
Labels:           <none>
Namespace:        default
Address:          192.168.100.50
Ingress Class:    nginx
Default backend:  <default>
TLS:
  tls-alfie terminates demo.alfie.com
Rules:
  Host            Path  Backends
  ----            ----  --------
  demo.alfie.com  
                  /   demoapp-v10:80 (10.244.1.35:80)
Annotations:      <none>
Events:
  Type    Reason  Age                    From                      Message
  ----    ------  ----                   ----                      -------
  Normal  Sync    5m20s (x2 over 5m22s)  nginx-ingress-controller  Scheduled for sync

# 对hosts文件进行修改
tail -n 1 /etc/hosts
192.168.100.50 demo.alfie.com

# 访问测试
## 跳过TLS验证
curl -k  https://demo.alfie.com 
iKubernetes demoapp v1.0 !! ClientIP: 10.244.3.23, ServerName: demoapp-v10-7f44bbc8c5-rk826, ServerIP: 10.244.1.35!

## 使用证书验证
curl --cacert alfie.crt https://demo.alfie.com
iKubernetes demoapp v1.0 !! ClientIP: 10.244.3.23, ServerName: demoapp-v10-7f44bbc8c5-rk826, ServerIP: 10.244.1.35!

## 信任证书访问
cp alfie.crt /usr/local/share/ca-certificates/
update-ca-certificates --verbose --fresh
curl https://demo.alfie.com
iKubernetes demoapp v1.0 !! ClientIP: 10.244.3.23, ServerName: demoapp-v10-7f44bbc8c5-rk826, ServerIP: 10.244.1.35!

rewrite

对于七层路由规则,使用host-path路由时,path作为一个抽象层无法也不需要与真实应用的URL一一对应
因此就有机会需要将用户请求的URL进行重写,例如清除path传递path后续参数

案例

# 传递后续参数给service
# 此处我们对 `demoapp.alfie.com` 末尾的参数进行捕获,将/结尾后的内容作为也进行捕获
# 通过`annotation`书写rewrite规则,通过标识符引用将$2 (即/后的内容) 传递给 svc,让其继续进行解析

kubectl create ingress rewrite-demo \
--rule='demoapp.alfie.com/v10(/|$)(.*)=demoapp-v10:80' \
--rule='demoapp.alfie.com/v11(/|$)(.*)=demoapp-v11:80' \
--class=nginx --annotation nginx.ingress.kubernetes.io/rewrite-target='/$2'

# descripbe 查看 ingress
Name:             rewrite-demo
Labels:           <none>
Namespace:        default
Address:          192.168.100.50
Ingress Class:    nginx
Default backend:  <default>
Rules:
  Host               Path  Backends
  ----               ----  --------
  demoapp.alfie.com  
                     /v10(/|$)(.*)   demoapp-v10:80 (10.244.1.35:80)
                     /v11(/|$)(.*)   demoapp-v11:80 (10.244.2.16:80)
Annotations:         nginx.ingress.kubernetes.io/rewrite-target: /$2
Events:
  Type    Reason  Age               From                      Message
  ----    ------  ----              ----                      -------
  Normal  Sync    5s (x2 over 36s)  nginx-ingress-controller  Scheduled for sync

# 访问测试
curl demoapp.alfie.com/v10
iKubernetes demoapp v1.0 !! ClientIP: 10.244.3.23, ServerName: demoapp-v10-7f44bbc8c5-rk826, ServerIP: 10.244.1.35!

# 测试第二参数
curl demoapp.alfie.com/v10/hostname
ServerName: demoapp-v10-7f44bbc8c5-rk826