云原生 05(安装 Minikube 并使用 Traefik 做为网关暴露到外网)

系列专栏声明:比较流水,主要是写一些踩坑的点,和实践中与文档差距较大的地方的思考。这个专栏的典型特征可能是 次佳实践,争取能在大量的最佳实践中生存。
一、怎么在墙内安装 minikube
minikube 默认使用的是源是 gcr.io,所以很多组件是装不上的。官方自某版本之后提供了指定镜像源的方式,可以指定到阿里云的源,但社区对这个源的风评不好,所以没有研究这个方案。
选定的方案是在阿里云香港区用 Packer 制作镜像,在制作镜像的过程中把可能用到的组件都装好,然后将镜像复制回杭州区。关键脚本如下:

$ curl -LO https://github.com/kubernetes/minikube/releases/download/v1.24.0/minikube-linux-amd64 $ install minikube-linux-amd64 /usr/local/bin/minikube$ su core -c "minikube config set WantUpdateNotification false"$ su core -c "minikube start --kubernetes-version=1.22.3 --profile=prd" $ su core -c "minikube profile prd" $ su core -c "minikube addons enable ingress" $ su core -c "minikube stop"

几个要注意的点是:
  1. minikube 和 kubernetes 都显式指明了版本,尽管这与拥抱云原生的理念略有悖,但确实是一个成本相对划算的策略;目前考虑的版本升级策略是完全新建镜像,等应用迁移完成后把流量转过去
  2. minikube addons enable ingress 阶段也需要访问源,所以也要在香港区完成
  3. 不确定 profile 的实践意义,实践中应该是物理隔离的多个集群吧,应该不会用同一套 k8s 的不同 profile 做软隔离吧?但是 minikube delete 删掉的是 profile,包括所有基础镜像,和 ingress,所以不要使用 delete,否则要去香港区重装
二、怎样使用自有镜像
官方有提供配置项可以指定私有源,不过也暂时不研究这个方案。大体的思路仍然是去香港区下载需要的镜像,然后用 tar 传到杭州区,然后使用 docker load。需要注意的是,minikube 使用的是 host 上的 docker,但是独立的工作区,所以要从 host 往 minikube 内部再 load 一次。大体的脚本如下:
$ wget http://path/to/httpbin.latest.tar $ docker load < httpbin.latest.tar $ docker tag kennethreitz/httpbin:latest kennethreitz/httpbin:1.0 $ docker rmi kennethreitz/httpbin:latest $ minikube image load kennethreitz/httpbin:1.0

几个需要注意的点是:
  1. k8s 对 :latest 这类 tag 的启动时的默认拉镜像策略是 Always,所以这里把它改成 :1.0 使得默认拉镜像策略变为 IfNotPresent,也就是优先使用本地镜像,不要尝试获取更新,因为连不上源
三、启动一个最简单的示例
$ kubectl create deployment httpbin --image=docker.io/kennethreitz/httpbin:1.0 $ kubectl describe deployment httpbin$ kubectl expose deployment httpbin --type=ClusterIP --port=80 $ kubectl describe service httpbin

这个命令为什么不是 kubectl create service httpbin
观察得到的网络拓扑情况如下(非官方文档):
云原生 05(安装 Minikube 并使用 Traefik 做为网关暴露到外网)
文章图片

  1. ClusterIP 指的是 pod 的 ip;clusterprofile 看上去是同一个东西,一个 cluster 可以有多个 node,minikube 默认是单 node
  2. minikube 是跑在 host docker 里的,所以 minikube 和 node 使用的是 host docker 的网络,即 192.168.49.2;我试了一下新建第二个 profile,它的 ip 会是 192.168.58.2;所以似乎 profile 之间是不会共享网络的
  3. ingress 的规则写的是 service,但实际上似乎是指向 pod,所以 service 仅仅是提供了注册中心效果,但流量没有从这里做负载均衡吗
  4. 没有找到 host.minikube.internal,由于下面的方案不需要这个,所以没有继续研究
以下才是重点:
  1. pod 之间可以通过 ClusterIP 互相访问,即 172.17.0.4 可以访问 172.17.0.5,既然它叫做 ClusterIP,那我猜是在整个 cluster 范围内生效的;但感觉实际意义不大,因为 ip 是动态变化的,写在代码里的应该还是用 httpbin.default.svc.cluster.local 互相访问
  2. pod 可以访问 192.168.49.1,这很重要,比如可以在 host 上安装 MySQL,并将 MySQL containerporthostport 绑定起来,这样就可以在 pod 里访问 192.168.49.1:3306
  3. pod 也可以访问 192.168.49.3 即 host docker 上的 traefik,细节后面会讲,这是因为我们将 traefik 放在了 prd 这个网络里
  4. pod 可以访问 10.1.x.x,即可以在整个 VPC 内畅通无阻(当然我猜需要对 IP 端做一些规划,不要冲突),所以 pod 可以访问到另一台虚机上的任何产品,和托管的产品如 RDS,只要纳入到 VPC 范围里
四、暴露服务到外网
deployment 有 3 种方式:ClusterIP,NodePort,Load Balancer。粗粗看了一下,主观感觉最云原生的方式应该是 Load Balancer,即直接使用云厂商提供的云原生网关,直接将流量打到 service 甚至是 pod,连 ingress 都不要走。k8s 只是一个容器管理工具,可能恰好提供了 ingress 等能力,但是在 网关 方面,感觉 k8s 并不是一个最佳实践,将网关包成容器部署在 k8s 内也不是一个最佳实践。
上手教程推荐的是 NodePort 模式,非常奇怪,看下来感觉是一个非常差的实践,决定用这个方式来做上手演示的人应该被拖出去打死。
这里选用了 ingress 的方式,所以 deployment 使用了默认的 ClusterIP 模式。
$ kubectl apply -f ingress.yaml $ kubectl describe ingress app-ingress

apiVersion: networking.k8s.io/v1 kind: Ingress metadata: name: app-ingress spec: rules: - host: httpbin.example.com http: paths: - path: / pathType: Prefix backend: service: name: httpbin port: number: 80

关于默认的 nginx ingress 文档有个很奇怪的坑,示例代码里开启了 annotation,会把所有的请求都 rewrite 到首页,即访问 http://httpbin.example.com/a.js 会被 rewrite 到 http://httpbin.example.com,不知道为什么要在新手文档里这么干。
minikube 会在 host docker 上创建一个和 profile 同名的 network,即 prd,并启动自己做为第一个 instance;同时使用了默认的单 Node 模式,所以这个 Node 的 ip 一定是 192.168.49.2;多 Node 会怎样我没有尝试,但是猜测可以指定。
version: '3.8'services: traefik: container_name: traefik image: traefik:v2.2.11 networks: - prd ports: - 80:80 - 443:443 labels: - "traefik.enable=false" volumes: - /var/run/docker.sock:/var/run/docker.sock:ro # 省略其它配置文件 restart: alwaysnetworks: prd: external: true

/path/to/traefik/config/default.toml
[http.services] [http.services.noop.loadBalancer] [[http.services.noop.loadBalancer.servers]] url = ""[http.services.minikube.loadBalancer] [[http.services.minikube.loadBalancer.servers]] url = "http://192.168.49.2:80/"[http.routers] [http.routers.https-redirect] entryPoints = ["http"] rule = "HostRegexp(`{app:[0-9a-z-]+}.example.com`)" middlewares = ["https-redirect"] service = "noop"[http.routers.example] entryPoints = ["https"] rule = "HostRegexp(`{app:[0-9a-z-]+}.example.com`)" service = "minikube" [http.routers.example.tls][http.middlewares.https-redirect.redirectScheme] scheme = "https"

这个方案不一定是最佳实践,但是实现/演示了两个重要的观点:
  1. 拆掉 https 证书的工作应该在网关上完成,不应该交给 k8s,已经进入 k8s 的流量应该是信任的;在 k8s 内部做流量的权限控制,应该使用 role 等策略,不应该使用暴露给用户的那张证书,那张证书是有品牌意义的,有可能是另外一些非运维部门负责的
  2. 目前 traefik 是在 host docker 上用 docker-compose 维护的,这是把之前的用法直接复制过来了,而且直接用同一个网络方便反问。如果也要使用 k8s 管理的话,应该是再起一个独立的 cluster,里面只有这个 traefik,然后做集群间流量。即整个入口网关都不应该和应用放在同一个集群里,这样的设计另一个好处是,应用集群天然是分布式的,天然是支持两地三中心的;单点故障控制在网关集群这里
【云原生 05(安装 Minikube 并使用 Traefik 做为网关暴露到外网)】参考文献:
  1. Minikube Start
  2. Kubernetes 的三种外部访问方式:NodePort、LoadBalancer 和 Ingress
  3. Set up Ingress on Minikube with the NGINX Ingress Controller
  4. Ingress Nginx Controller Troubleshooting
  5. Kompose

    推荐阅读