k8s中的Service与Ingress

集群中的服务要想向外提供服务,就不得不提到Service和Ingress。 下面我们就介绍一下两者的区别和关系。

Service

必须了解的一点是 Service 的访问信息在 Kubernetes 集群内是有效的,集群之外是无效的。

Service可以看作是一组提供相同服务的Pod对外的访问接口。借助Service,应用可以方便地实现服务发现和负载均衡。对于Service 的工作原理请参考https://time.geekbang.org/column/article/68636

当需要从集群外部访问k8s里的服务的时候,方式有四种:ClusterIP、NodePort、LoadBalancer、ExternalName 。

下面我们介绍一下这几种方式的区别

一、ClusterIP

通过集群的内部 IP 暴露服务,选择该值,服务只能够在集群内部可以访问,这也是默认的 ServiceType。

我们先看一下最简单的Service定义

apiVersion: v1
kind: Service
metadata:
  name: hostnames
spec:
  selector:
    app: hostnames
  ports:
  - name: default
    protocol: TCP
    port: 80
    targetPort: 9376

这里我使用了 selector 字段来声明这个 Service 只代理携带了 app=hostnames 标签的 Pod。并且这个 Service 的 80 端口,代理的是 Pod 的 9376 端口。

然后我们再看一下 Deployment 的定义

apiVersion: apps/v1
kind: Deployment
metadata:
  name: hostnames
spec:
  selector:
    matchLabels:
      app: hostnames
  replicas: 3
  template:
    metadata:
      labels:
        app: hostnames
    spec:
      containers:
      - name: hostnames
        image: k8s.gcr.io/serve_hostname
        ports:
        - containerPort: 9376
          protocol: TCP

我们这里声明的Pod份数是3份,使用的镜像为 k8s.gcr.io/serve_hostname,主要提供输出hostname的功能,如果我们依次访问 curl 10.0.1.175:80 的话,会发现每次响应的内容,这是因为 Service 提供的是 Round Robin 方式的负载均衡。这个10.0.1.175是集群的IP地址,俗称VIP,是 Kubernetes 自动为 Service 分配的。对于这种方式,我们称为:ClusterIP 模式的 Service。

二、NodePort

通过每个 Node 上的 IP 和静态端口(NodePort)暴露服务。NodePort 服务会路由到 ClusterIP 服务,这个 ClusterIP 服务会自动创建。通过请求 NodeIP:Port,可以从集群的外部访问一个 NodePort 服务。

apiVersion: v1
kind: Service
metadata:
  name: my-nginx
  labels:
    run: my-nginx
spec:
  type: NodePort
  ports:
  - nodePort: 8080
    targetPort: 80
    protocol: TCP
    name: http
  - nodePort: 443
    protocol: TCP
    name: https
  selector:
    run: my-nginx

Service描述文件:
spec.type 声明Service的type
spec.selector 字段确定这个Service将要使用哪些Label。在本例中,会管理所有具有 run: my-nginx 标签的Pod。
spec.ports.nodePort 表明此Service将会监听8080端口,并将所有监听到的请求转发给其管理的Pod。
spec.ports.targetPort 表明此Service监听到的8080端口的请求都会被转发给其管理的Pod的80端口。此字段可以省略,省略后其值会被设置为spec.ports.port的值。

如果你未指定 spec.ports.nodePort 的话,则系统会随机选择一个范围为 30000-32767的端口号使用。

这时要访问这个Service的话,只需要通过访问

<任何一台宿主机器的IP>:8080

这样可以访问到某一个被代理Pod的80端口。后端的Pod并不是一定在当前Node上,有可能你访问的Node1:8080,而后端对应的Pod是在Node2上。
可以看到这种方式很好理解,非常类似于平时我们使用docker部署容器应用后,将容器服务端口暴露出来宿主端口就可以了。

三、LoadBalancer

在NodePort基础上,Kubernetes可以请求底层云平台cloud provider 创建一个外部的负载均衡器,并将请求转发到每个Node作为后端,进行服务分发。
该模式需要底层云平台(例如GCE、亚马孙AWS)支持。

---
kind: Service
apiVersion: v1
metadata:
  name: example-service
spec:
  ports:
  - port: 8765
    targetPort: 9376
  selector:
    app: example
  type: LoadBalancer

创建这个服务后,系统会自动创建一个外部负载均衡器,其端口为8765, 并且把被代理的 地址添加到公有云的负载均衡当中。

四、ExternalName

创建一个dns别名指到service name上,主要是防止service name发生变化,要配合dns插件使用。

通过返回 CNAME 和它的值,可以将服务映射到 externalName 字段的内容(例如 foo.bar.example.com)。
没有任何类型代理被创建,这只有 Kubernetes 1.7 或更高版本的 kube-dns 才支持。

kind: Service
 apiVersion: v1
 metadata:
   name: my-service
 spec:
   type: ExternalName
   externalName: my.database.example.com

指定了一个 externalName=my.database.example.com 的字段。而且你应该会注意到,这个 YAML 文件里不需要指定 selector。
这时候,当你通过 Service 的 DNS 名字访问它的时候,比如访问:my-service.default.svc.cluster.local。
那么,Kubernetes 为你返回的就是my.database.example.com。所以说,ExternalName 类型的 Service,其实是在 kube-dns 里为你添加了一条 CNAME 记录。
这时访问 my-service.default.svc.cluster.local 就和访问 my.database.example.com 这个域名是一个效果了。

Ingress

上面我们提到有一个叫作 LoadBalancer 类型的 Service,它会为你在 Cloud Provider(比如:Google Cloud 或者 OpenStack)里创建一个与该 Service 对应的负载均衡服务。但是,相信你也应该能感受到,由于每个 Service 都要有一个负载均衡服务,所以这个做法实际上既浪费成本又高。作为用户,我其实更希望看到 Kubernetes 为我内置一个全局的负载均衡器。然后,通过我访问的 URL,把请求转发给不同的后端 Service。这种全局的、为了代理不同后端 Service 而设置的负载均衡服务,就是 Kubernetes 里的 Ingress 服务。

Ingress 的功能其实很容易理解:所谓 Ingress 就是 Service 的“Service”,这就是它们两者的关系。

    internet
        |
   [ Ingress ]
   --|-----|--
   [ Services ]

通过使用 Kubernetes 的 Ingress 来创建一个统一的负载均衡器,从而实现当用户访问不同的域名时,访问后端不同的服务。

假如我现在有这样一个站点:https://cafe.example.com。其中 https://cafe.example.com/coffee,对应的是“咖啡点餐系统”。而 https://cafe.example.com/tea,对应的则是“茶水点餐系统”。这两个系统,分别由名叫 coffee 和 tea 这样两个 Deployment 来提供服务。

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 字段。在 Kubernetes 里,这个字段叫作:IngressRule。
IngressRule 的 Key,就叫做:host。它必须是一个标准的域名格式(Fully Qualified Domain Name)的字符串,而不能是 IP 地址。

而接下来 IngressRule 规则的定义,则依赖于 path 字段。你可以简单地理解为,这里的每一个 path 都对应一个后端 Service。
所以在我们的例子里,我定义了两个 path,它们分别对应 coffee 和 tea 这两个 Deployment 的 Service(即:coffee-svc 和 tea-svc)。

通过上面的介绍,不难看到所谓 Ingress 对象,其实就是 Kubernetes 项目对“反向代理”的一种抽象。

一个 Ingress 对象的主要内容,实际上就是一个“反向代理”服务(比如:Nginx)的配置文件的描述。而这个代理服务对应的转发规则,就是 IngressRule。

这就是为什么在每条 IngressRule 里,需要有一个 host 字段来作为这条 IngressRule 的入口,然后还需要有一系列 path 字段来声明具体的转发策略。这其实跟 Nginx、HAproxy 等项目的配置文件的写法是一致的。

在实际使用中,我们一般选择一种 Ingress Controller,将期部署在k8s集群中,这样它就会根据我们定义的 Ingress 对象来提供对应的代理功能。

业界常用的各种反向代理项目,比如 Nginx、HAProxy、Envoy、Traefik 等,都已经为 Kubernetes 专门维护了对应的 Ingress Controller。

Nginx Ingress Controller 的示例请参考 https://time.geekbang.org/column/article/69214

推荐参考官方推荐脚本:https://github.com/resouer/kubernetes-ingress/tree/master/examples/complete-example

mac下利用minikube安装Kubernetes环境

本机为mac环境,安装有brew工具,所以为了方便这里直接使用brew来安装minikube工具。同时本机已经安装过VirtualBox虚拟机软件。

minikube是一款专门用来创建k8s 集群的工具。

一、安装minikube

参考 https://kubernetes.io/docs/tasks/tools/install-minikube/, 在安装minkube之前建议先了解一下minikube需要的环境https://kubernetes.io/docs/setup/learning-environment/minikube/

1. 先安装一个虚拟化管理系统,如果还未安装,则在 HyperKit、VirtualBox 或 VMware Fusion 三个中任选一个即可,这里我选择了VirtualBox。

如果你想使用hyperkit的话,可以直接执行 brew install hyperkit 即可。

对于支持的driver_name有效值参考https://kubernetes.io/docs/setup/learning-environment/minikube/#specifying-the-vm-driver, 目前docker尚处于实现阶段。

$ brew install minikube

查看版本号

$ minikube version

minikube version: v1.8.2
commit: eb13446e786c9ef70cb0a9f85a633194e62396a1

安装kubectl命令行工具

$ brew install kubectl

二、启动minikube 创建集群

$ minikube start --driver=virtualbox

如果国内的用户安装时提示失败”VM is unable to access k8s.gcr.io, you may need to configure a proxy or set –image-repository”,
则指定参数–image-repository=registry.cn-hangzhou.aliyuncs.com/google_containers

 $ minikube start --driver=virtualbox --image-repository=registry.cn-hangzhou.aliyuncs.com/google_containers

😄 minikube v1.8.2 on Darwin 10.15.3
✨ Using the virtualbox driver based on existing profile
✅ Using image repository registry.cn-hangzhou.aliyuncs.com/google_containers
💾 Downloading preloaded images tarball for k8s v1.17.3 …
⌛ Reconfiguring existing host …
🏃 Using the running virtualbox “minikube” VM …
🐳 Preparing Kubernetes v1.17.3 on Docker 19.03.6 …
> kubelet.sha256: 65 B / 65 B [————————–] 100.00% ? p/s 0s
> kubeadm.sha256: 65 B / 65 B [————————–] 100.00% ? p/s 0s
> kubectl.sha256: 65 B / 65 B [————————–] 100.00% ? p/s 0s
> kubeadm: 37.52 MiB / 37.52 MiB [—————] 100.00% 1.01 MiB p/s 37s
> kubelet: 106.42 MiB / 106.42 MiB [————-] 100.00% 2.65 MiB p/s 40s
> kubectl: 41.48 MiB / 41.48 MiB [—————] 100.00% 1.06 MiB p/s 40s
🚀 Launching Kubernetes …
🌟 Enabling addons: default-storageclass, storage-provisioner
🏄 Done! kubectl is now configured to use “minikube”

另外在minikube start 有一个选项是–image-mirror-country=’cn’  这个选项是专门为中国准备的,还有参数–iso-url,官方文档中已经提供了阿里云的地址……… 这个选项会让你使用阿里云的镜像仓库,我这里直接指定了镜像地址。

对于大部分国内无法访问到的镜像k8s.gcr.io域名下的镜像都可以在http://registry.cn-hangzhou.aliyuncs.com/google_containers 找到。

查看集群状态

$ minikube status

如果输出结果如下,则表示安装成功
host: Running
kubelet: Running
apiserver: Running
kubeconfig: Configured

如果你以前安装过minikube,在 minikube start 的时候返回错误 machine does not exist,则需要执行 minikube delete 清理本地状态。

这时创建完成后,会在VirtualBox软件里出现一个虚拟系统。

另外minikube 也支持 –driver=none参数

三、进阶篇

参考 https://kubernetes.io/docs/tutorials/hello-minikube/#before-you-begin

打开Kubernetes仪表板

$ minikube dashboard

🔌 Enabling dashboard …
🤔 Verifying dashboard health …
🚀 Launching proxy …
🤔 Verifying proxy health …
🎉 Opening http://127.0.0.1:59915/api/v1/namespaces/kubernetes-dashboard/services/http:kubernetes-dashboard:/proxy/ in your default browser…

在浏览器中自动打开上方网址, 就可以看到 k8s 控制台。同时也可以根据上方网址教程来学习。

具体的操作,你可以查看 Dashboard 项目的官方文档

四、停止集群

$ minikube stop

五、删除集群

$ minikube delete

六、升级集群

$ brew update 
& brew upgrade minikube

mac 的请参考 https://minikube.sigs.k8s.io/docs/start/macos/#upgrading-minikube

参考资料

https://kubernetes.io/docs/setup/learning-environment/minikube/
https://kubernetes.io/docs/tasks/tools/install-minikube/
https://kubernetes.io/docs/tutorials/hello-minikube/#before-you-begin

kubernetes dashboard向外网提供服务

目前新版本的 kubernetes dashboard (https://github.com/kubernetes/dashboard)安装了后,为了安全起见,默认情况下已经不向外提供服务,只能通过 http://localhost:8001/api/v1/namespaces/kube-system/services/https:kubernetes-dashboard:/proxy/ 本机访问。在我们学习过程中,总有些不方便,这时我们可以利用 kubectl proxy 命令来实现。

首先我们看一下此命令的一些想着参数

➜  ~ kubectl proxy -h
To proxy all of the kubernetes api and nothing else, use:

  $ kubectl proxy --api-prefix=/

To proxy only part of the kubernetes api and also some static files:

  $ kubectl proxy --www=/my/files --www-prefix=/static/ --api-prefix=/api/

The above lets you 'curl localhost:8001/api/v1/pods'.

To proxy the entire kubernetes api at a different root, use:

  $ kubectl proxy --api-prefix=/custom/

The above lets you 'curl localhost:8001/custom/api/v1/pods'

Examples:
  # Run a proxy to kubernetes apiserver on port 8011, serving static content from ./local/www/
  kubectl proxy --port=8011 --www=./local/www/

  # Run a proxy to kubernetes apiserver on an arbitrary local port.
  # The chosen port for the server will be output to stdout.
  kubectl proxy --port=0

  # Run a proxy to kubernetes apiserver, changing the api prefix to k8s-api
  # This makes e.g. the pods api available at localhost:8011/k8s-api/v1/pods/
  kubectl proxy --api-prefix=/k8s-api

Options:
      --accept-hosts='^localhost$,^127\.0\.0\.1$,^\[::1\]$': Regular expression for hosts that the proxy should accept.
      --accept-paths='^/.*': Regular expression for paths that the proxy should accept.
      --address='127.0.0.1': The IP address on which to serve on.
      --api-prefix='/': Prefix to serve the proxied API under.
      --disable-filter=false: If true, disable request filtering in the proxy. This is dangerous, and can leave you
vulnerable to XSRF attacks, when used with an accessible port.
  -p, --port=8001: The port on which to run the proxy. Set to 0 to pick a random port.
      --reject-methods='POST,PUT,PATCH': Regular expression for HTTP methods that the proxy should reject.
      --reject-paths='^/api/.*/pods/.*/exec,^/api/.*/pods/.*/attach': Regular expression for paths that the proxy should
reject.
  -u, --unix-socket='': Unix socket on which to run the proxy.
  -w, --www='': Also serve static files from the given directory under the specified prefix.
  -P, --www-prefix='/static/': Prefix to serve static files under, if static file directory is specified.

Usage:
  kubectl proxy [--port=PORT] [--www=static-dir] [--www-prefix=prefix] [--api-prefix=prefix] [options]

Use "kubectl options" for a list of global command-line options (applies to all commands).

这里我们只要关注其中的三个参数就可以了

--accept-hosts='^localhost$,^127\.0\.0\.1$,^\[::1\]$': Regular expression for hosts that the proxy should accept.
--address='127.0.0.1': The IP address on which to serve on.
--port=8001: The port on which to run the proxy. Set to 0 to pick a random port.

–accept-hosts 表示哪些客户端访问,默认只允许 localhost 和 127.0.0.1
–address 表示本机绑定的ip地址,如果值为0.0.0.0 则表示不限,通过任何ip都可以访问.
a
–port 表示代理的接口,如果值为0的话,则随机一个端口

这里为了外网访问,可设置如下

nohup kubectl proxy --address='0.0.0.0' --port=8888 --accept-hosts='^*$'

这时就实现了通过外网访问

http://192.168.0.107:8888/api/v1/namespaces/kube-system/services/https:kubernetes-dashboard:/proxy/

其实说白了,只要你把基本的命令参数搞清楚了,实现起来就方便了,就看你基础牢不牢。