Service 与Ingress解耦好帮手

1,294 阅读2分钟

如何存取 Pod----为什么会有Service ?

上几篇文章中,我们成功的

  • 将 Deployment 物件部署到 k8s 丛集中
  • 运行了三个 Pod

查看一下 Pod 的运行状态

$ kubectl get pods
NAME                    READY     STATUS    RESTARTS   AGE
nginx-75f4785b7-6fbd4   1/1       Running   0          1m
nginx-75f4785b7-g284n   1/1       Running   0          1m
nginx-75f4785b7-vd4tt   1/1       Running   0          1m

接下来就是讨论如何存取 Pod。由于每个 Pod 被建立的时候 k8s 会动态的分配 IP 给每个 Pod,最简单的方式当然就是直接透过 IP 存取。

image.png

但由于 Pod 有临时的特性,因此当 Pod 发生错误需要重新被产生时 IP 也会被重新分配,如果透过上述方式,存取 Pod 将会相当的不方便,因为使用者要一直更新 IP 才知道该如何正确的存取 Pod。

image.png

为什么其中一个 Pod 坏掉 k8s 会重新帮我们建立一个 Pod 呢?还记得我们说过,k8s 会尽力去满足我们想要物件的状态吗?当 Pod 发生问题无法存取的时候,k8s 会察觉到系统的变化也会发现运行三个 Pod 是使用者想要的状态,因此 k8s 就会再重新帮我们建立一个新的 Pod 以符合使用者想要的状态。

如果想查看 Pod 被分配的 IP 可以用下列指令,请记得把 Pod 名称 (nginx-75f4785b7-6fbd4) 置换成正确名字

$ kubectl get pods nginx-75f4785b7-6fbd4 -o jsonpath --template={.status.podIP}

因此 k8s 引用了一个更高阶的物件 Service 来处理连接到 Pod。

Service

Service 物件利用 Label 与 Selector 来决定如何存取 (policy) 以及可以存取哪些 Pod (logical group)。 我们在使用者跟 Pod 中间加了一个 Service 物件。透过 Selector (app==nginx) ,而使用者可以透过 Service 存取两个 Pod。当其中一个 Pod 故障需要被重新产生时,新产生的 Pod 也能够透过 Service 正确存取

image.png

Service Type

Service 物件根据使用方式有三个不同的形态

  • 仅供丛集内部存取:ClusterIP
  • 供丛集及外部存取:NodePort, LoadBalancer
  • 对应丛集外其他资源:ExternalName

由于我们需要透过 Service 存取 Pod,因此,NodePort 形态的 Service 正符合我们的需求。

LoadBalancer 一般需搭配云端服务使用 (AWS, GCP, ...)。

ExternalName 是透过 CNAME 与外部连接

首先,建立 service.yaml

# service.yaml

---yaml
apiVersion: v1
kind: Service
metadata:
  name: web
spec:
  type: NodePort
  selector:
    app: nginx
  ports:
    - protocol: TCP
      port: 80
  • kind: Service:指定为 Service 物件
  • spec.type: NodePort:指定 Service 型态。
  • spec.selector.app: nginx:指定 Service 绑定 Label 有 app: nginx 的 Pod 请注意,如果没有指定则预设会是 ClusterIP 型态则仅供丛集内部存取,外部无法存取

接着,部署到 k8s

$ kubectl apply -f service.yaml
service "web" created

再透过指令查看 Service

$ kubectl get services
NAME         TYPE        CLUSTER-IP   EXTERNAL-IP   PORT(S)        AGE
kubernetes   ClusterIP   10.0.0.1     <none>        443/TCP        1d
web          NodePort    10.0.0.221   <none>        80:31063/TCP   6s

这里会看到两个 Service 物件,其中 web 就是我们新增加的物件。

无特别指定下,k8s 会自动配置 30000 ~ 32767 区间的 port 供 Service 使用

这里有点复杂,我们用一个图来说明 k8s 丛集中的状态

image.png 当我们部署了一个名为 web 的 Service 物件时,在 k8s 内部就会建立一个对应的 web: 10.0.0.242 (IP 会自动配置不用担心),并且将所有的 Node 的 32080 port 对应到 web 这个 Service。因此无论使用者从哪一个 Node 存取 32080 port 都会被指到 web 并透过 web 找到对应的 Pod (app: nginx)。

Pod 可能会分散在不同的 Node,如同上图中 other-svc 对应的 Pod,但是不用担心存取问题 K8s 会帮你处理。这也正是抽象化的好处,存取将透过单一入口而不需要知道各个主机的设定。

连接到 Pod

首先透过下面指令查看 minikube ip

$ minikube ip
192.168.99.100

接着打开浏览器输入http://192.168.99.100:32758

image.png

这里说明了一个很重要的概念:解耦。大家应该有发现,Servcie 与 Pod 之间透过 Label 跟 Selector 绑定,但是 Service 与 Pod 产生的顺序并没有限制。只要需要绑定的 Pod 被产生,k8s 就会自动帮我们建立关系,当然,前提是 Label 与 Selector 要正确。

Ingress解耦

我们成功地利用 Service 把存取 Pod 的工作抽象化,即把存取 Pod 的工作转换成存取 Service,我们可以透过 Node IP:Port 的方式存取 Pod,但是在 k8s 中 Node 是可以视需求随时增加或减少的,也因此 Node IP 随时有可能会有变动,因此 Ingress 物件就是再一次帮我们把存取 Service 抽象化的好东西。

底下我们来操作一下,先看个图

image.png 这里要发挥一下想像力,想像一下整个 k8s 丛集由一群 Node 组成并提供多个不同的服务 (Service),就像图中的四个不同颜色的 Service,我们希望使用者能够从一个单一的入口就能够存取 k8s 中不同的服务,而 Ingress 物件就能帮我们达到这样的目的。

image.png 如上图,我们希望在 k8s 中建立两个 Service 分别是 red-service 与green-service

red-service绑定四个标记为 app: red-nginx 的 Pod 而 green-service 绑定两个标记为 app: green-nginx 的 Pod。使用者可以透过 http://green.myweb.com 与 http://red.myweb.com 来分别浏览 green-service 与red-service

让我们先看看 Deployment 的内容

# ingress.deployment.yaml

---
apiVersion: extensions/v1beta1
kind: Deployment
metadata:
    name: red-nginx
spec:
    replicas: 4   <=== 我们希望 red-service 有四个 Pod 运行 red-nginx
    template:
      metadata:
        labels: 
          app: red-nginx
      spec:
        containers:
        - name: nginx
          image: jlptf/red   <=== 预先做好的 Docker 映像档
          ports:
          - containerPort: 80

---
apiVersion: extensions/v1beta1
kind: Deployment
metadata:
    name: green-nginx
spec:
    replicas: 2   <=== 我们希望 green-service 有两个 Pod 运行 green-nginx
    template:
      metadata:
        labels: 
          app: green-nginx
      spec:
        containers:
        - name: nginx
          image: jlptf/green   <=== 预先做好的 Docker 映像档
          ports:
          - containerPort: 80

部署到 k8s 并观察

$ kubectl apply -f ingress.deployment.yaml 
deployment "red-nginx" created
deployment "green-nginx" created

$ kubectl get pods
NAME                          READY     STATUS    RESTARTS   AGE
green-nginx-f5989dfd9-fcm5q   1/1       Running   0          47s
green-nginx-f5989dfd9-hzbq5   1/1       Running   0          47s
red-nginx-787f484d9c-grbsk    1/1       Running   0          34s
red-nginx-787f484d9c-h7pl6    1/1       Running   0          47s
red-nginx-787f484d9c-m897h    1/1       Running   0          29s
red-nginx-787f484d9c-n946g    1/1       Running   0          47s

很顺利,运行了六个 Pod 预计分别给 red-service 与 green-service 使用,接着看看 Service 的内容

看 prefix 应该不难分辨哪个 Pod 是给哪个 Service 使用的吧!所以说名字取的好,爸爸没烦恼!是吧~~

# ingress.service.yaml

---
apiVersion: v1
kind: Service
metadata:
  name: green-service
spec:
  type: NodePort
  selector:
    app: green-nginx   <=== 绑定标记为 green-nginx  Pod
  ports:
    - protocol: TCP
      port: 80

---

apiVersion: v1
kind: Service
metadata:
  name: red-service
spec:
  type: NodePort
  selector:
    app: red-nginx   <=== 绑定标记为 red-nginx  Pod
  ports:
    - protocol: TCP
      port: 80

然后是今天的主角 Ingress

# ingress.yaml

---
apiVersion: extensions/v1beta1
kind: Ingress   <=== 指定物件种类为 Ingress
metadata:
  name: web
spec:
  rules:
  - host: green.myweb.com   <=== 指定 host 名称为 green.myweb.com
    http:
      paths:
      - backend:
          serviceName: green-service  <=== 绑定名称为 green-service  Service 物件
          servicePort: 80
  - host: red.myweb.com   <=== 指定另一个 host 名称为 red.myweb.com
    http:
      paths:
      - backend:
          serviceName: red-service   <=== 绑定名称为 red-service  Service 物件
          servicePort: 80

类似 Service 绑定 Pod 的概念,Ingress 透过 serviceName 指定需要绑定的 Service。接着部署到 k8s

$ kubectl apply -f ingress.yaml 
ingress "web" created

$ kubectl get ingress
NAME      HOSTS                           ADDRESS          PORTS     AGE
web       green.myweb.com,red.myweb.com   192.168.99.100   80        43s

这里就会看到 green.myweb.com 与 red.myweb.com 都被绑定到 192.168.99.100 的位置 如果在云端提供商上建立 Ingress,你可以在 ADDRESS 看到被分配的 IP

另外,你可能需要执行 minikube addons enable ingress 启用 Ingress。感谢changec大大的提醒,更详细的内容可以参考

由于 green.myweb.com 与 red.myweb.com 这两个网址并非真正存在,因此,如果你在浏览器上打上这两个网址,并不会有任何内容。

我们要让自己的电脑能够理解这两个网址的 IP 位置,Windows 请修改\WINDOWS\system32\drivers\etc\hosts

192.168.99.100   green.myweb.com  <=== 192.168.99.100 是 Ingress 分配的 IP
192.168.99.100   red.myweb.com

你可以使用以下指令将上面的内容加到\WINDOWS\system32\drivers\etc\hosts:

$ echo 192.168.99.100 green.myweb.com >> /etc/hosts 
$ echo 192.168.99.100 red.myweb.com >> /etc/hosts

打开浏览器并键入网址,一切顺利的话你会看到

green.myweb.com 另外,建立 jlptf/red 与 jlptf/green 这两个映像档用到的 Dockerfile 你可以在 ingressDocker 资料夹里面找到,如果你想要自己动手建立映像档可以参考使用。

建立映像档参考指令如下:

$ docker build --build-arg HTML=green.html -t jlptf/green .

$ docker build --build-arg HTML=red.html -t jlptf/red .