Kubernetes 学习笔记(二) - 部署和访问应用

前边折腾了各种安装 Kubernetes 集群的操作,还跑到 AWS 上撸了一把 EKS,也在 Kubernetes 上部署过服务。继续更深一步的学习如何部署应用和怎么通过 Service 去访问 Pod 中的应用,顺带看看内部的网络是怎么流转的。

测试平台还是以本地启动的三个 Vagrant 虚拟机组成的 Kubernetes 集群,安装方法见 Kubernetes 学习笔记(一) - 初上手

  1. k8s-master  (172.28.128.14)
  2. k8s-node1    (172.28.128.10)
  3. k8s-node2   (172.28.128.11)

测试应用的镜像为 yanbin/python-web, 代码见 github 上的 yabqiu/python-web-docker/app.py, 一个默认启动在  80 端口上的 Flask Web 应用,输出为当前 hostname  和一个唯一标识符。

部署应用

《每天5分玩转Kubernetes》里用的 Kubernetes 是 1.7 版本,其中还在用 kubectl run 的方式来部署应用(它会产生一个隐式的 deployment 对象),该方式已在 Kubernetes 1.12 中不推荐使用了,建议用 kubectl create deployment...,而实际中更应该用 yaml 文件编排后再 kubectl apply -f <your-yaml-file>, 这样多种对象可以编写在一起,更方便日后同样的命令更新各种对象,或者用 kubectl delete -f <your-yam-file> 批量删除所创建的对象。

命令方式部署

命令方式创建一个 deployment 对象,并让它启动 3 个 pod

$ kubectl create deployment python-web-app --image=yanbin/python-web:latest
$ kubectl scale -n default deployment python-web-app --replicas=3

等它们全部就续后

看到创建了一个 python-web-app deployment 对象,并按照要求启动了三个 pod,分配到了两个工作节点上。先不讨论怎么去访问上面启动的服务,而是应该了解用 yaml 文件的部署方式。在这之前我们把前面的 deployment  对象删掉,命令是

$ kubectl delete deploy python-web-app

这会把 deployment  对象并先前的三个  python-web-app-* pod 全部清除掉。

yaml 文件方式部署

需创建一个描述文件, 我们命名为 python-web-app.yaml(也可用 yml 为文件后缀),内容如下:

然后只要一条命令就完成了部署并启动三个 pod

$ kubectl apply -f python-web-app.yaml

相应的删除都不用事先查找到 deployment 对象是什么名称,而只需

$ kubectl delete -f python-web-app.yaml

以后有什么修改的话,只要改下 python-web-app.yaml 文件,比如把 replicas 调整为 4, 接着再做一遍 kubectl apply -f python-web-app.yaml 就行了。

所以从以上操作不难想像,使用 yaml 文件来管理 Kubernetes 中的对象还能把对象状态存储到版本服务器上。

通过 Service 访问 Pod 应用

回到前面搁下的话题,这是个大话题,将会涉及到 LoadBalance, Cluster IP 和  NodePort 的概念。现在我们看到了启动了三个 python-web-app pod, 它们是运行在不同工作节点上的 docker 容器,隔着有两层,又没有暴露出端口,该如何它们呢?

Pod IP 地址访问

最直接的想法是可以登陆到具体的 pod(docker 容器, 这儿一个 pod 中只运行了一个 docker 容器),访问它的 80 端口上的 web 服务

root@k8s-master# kubectl exec -it python-web-app-68d7bbd7f5-dzjxf -- /bin/sh
/ # hostname
python-web-app-68d7bbd7f5-dzjxf
/ # ifconfig | grep 10.244
inet addr:10.244.2.3 Bcast:0.0.0.0 Mask:255.255.255.0
/ # wget -qO- 10.244.2.3:80
Served by python-web-app-68d7bbd7f5-dzjxf/4a8a9e75
/ # exit
root@k8s-master# kubectl exec -it python-web-app-68d7bbd7f5-l5wkb -- /bin/sh
/ # hostname
python-web-app-68d7bbd7f5-l5wkb
/ # wget -qO- localhost
Served by python-web-app-68d7bbd7f5-l5wkb/916a8ed9

Alpine Linux 自带  wget 命令,不用安装  curl 也行

进入到 docker  容器去访问服务肯定是没什么意义的,或许通过 worker 节点稍微现实一些,可是我们启动 docker 容器时未映射端口。来到

python-web-app-68d7bbd7f5-dzjxf 1/1 Running 0 3m43s 10.244.2.3 k8s-node2

所在的 worker 节点 k8s-node2 上

端口未映射没法通过节点访问

root@k8s-node1:/# curl 10.244.2.3
Served by python-web-app-68d7bbd7f5-dzjxf/4a8a9e75

发现不需要在  python-web-app.yaml 中加

ports:
- containerPort: 80

也能在各工作节点上访问到所运行的容器的 80 端口

ClusterIP 访问服务

我们进一步修改部署文件为

再次 kubectl apply -f python-web-app.yaml, 查看 service 的 CLUSTER-IP

--- 可以把多个对象的配置放在同一个文件中,其实上面的 Service 等价于下面的命令(中括号中内容可选)

# kubectl expose deployment python-web-app --name=python-web-svc --port 80 [--target-port 80 --protocol TCP]

现在可以在任意工作节点上用上面的 CLUSTER-IP 来访问容器内的服务

root@k8s-master:/# curl 10.110.181.172
Served by python-web-app-78b9d9d7f-f2gxt/c5413dba
root@k8s-master:/# curl 10.244.2.7
Served by python-web-app-78b9d9d7f-m478c/32c2f2bd
root@k8s-master:/# curl 10.244.2.8
Served by python-web-app-78b9d9d7f-tzqml/98e656a6

这里的  ClusterIP 10.110.181.172 实质是一个 iptables 实现的虚拟 IP,同样是由 iptables 实现了负载均衡

域名访问 Service

这里的域名是 Service 的  ClientIP 地址对应有一个域名,所以它仍然是局限于内部访问服务。Kubernetes 集群有自己的 DNS 服务(kube-dns)

DNS 是给 Pod 用的,所以只要工作节点上有。

Kubernetes 中每一个服务都有自己的名称,完整名称为 <SERVICE_NAME>.<NAMESPACE_NAME>。所以我们进到某一个 Pod

虽然在 Pod 内部,对 python-web-svcpython-web-svc.default 都会解析到  ClusterIP 10.104.132.106 上,因此与访问 ClusterIP  一样是通 iptables 实现的负载均衡。

这里有点绕,要进到 Pod 容器内部才能使用域名来访问 Service 对应的 ClusterIP,显然不是为外部提供服务的,它的关键用途是让 Pod(容器) 之间用 Service 名称互相访问。

下边是两种可由外部访问 Service

对于需向外提供服务的应用,这个才是 Kubernetes 的关键。可以通过 NodePort 或 LoadBalancer 的方式。

NodePort 方式

修改前面的 python-web.app.yaml 文件中的 Service 部分如下:

应用它 kubectl apply -f python-web-app.yaml,默认时每个节点上会分配一个 3000~32767 之间的端口与服务端口映射,我们也可以在 yaml 文件中指定 nodePort 的值。

通过 Kubernetes 集群中任意节点的 IP 就可以访问容器内部的应用。工作节点的机器从架构上虽然是可以让外部进行访问,但这样做是不安全的。通过 NodePort 访问服务并非只是访问内部实现依然是借助于 iptables。

LoadBalancer 方式

需像 NodePort 一样,把 Service 部分的 type 改为  LoadBalancer

type 为 LoadBalancer 时会请求云服务提供商给它一个 EXTERAL-IP, 由此来向外提供服务。目前支持提供 LoadBalancer 的有 GCP, AWS, Azure 等。下面以  AWS 为例

这样通过那个 ELB 域名就能连接到节点的 32624 端口,像 NodePort 一样访问到了每一个 Pod 的服务。

集群内部的 LoadBalance

前面列出各种访问 Kubernetes 内服务的方式:

  1. Pod IP 地址访问:确切来讲,直接用 Pod IP 访问的是容器内应用而非 Kubernetes 服务,只宜用于诊断容器内程序
  2. Cluster IP 访问 Service
  3. 域名访问 Service:在 Pod 容器内部用域名方式解析到 Cluster IP 上
  4. NodePort 访问 Service
  5. LoadBalancer 访问 Service

以上除第一种方式外,只要发起了请求或者是外部的请求进入了集群,Kubernetes 自己的负载均衡(请求分布)便介入了工作。请求首先会被引到 ClusterIP 上来,这是一个 iptables 实现的虚拟 IP。在集群内部协助它实现的是节点之间的网络服务,如 flannel 网络,它是运行在每一个节点上的 Daemon Pod。

查看某一个 kube-flannel 上的日志如下:

在任意节点上也可以用 iptables-save 查看当前节点的 iptables 怎么去按比例分发请求到相应的服务对应的 Pod 上去的。每次新建一个 Kubernetes 服务,或者 Deployment 发生更新后都会有一系列的 iptables 规则的变更。

我们可以查看一个服务的描述

上面服务显示出各种可能的访问方式,包括:ClusterIP(IP), LoadBalancer Ingress, NodePort, 以及请求会被分发到的 Pod 容器(即 Endpoints)

以下图片来自于 《Kubernetes in Action》(Second Edition) 一书,它有助于我们理解 Kubernetes 集群请求分发的逻辑。

从外部的 Load balancer 不管连接到哪个节点的 NodePort(30838), 进一步经由节点上 iptables 规则的转发,请求最终有可能被任意一个 Pod 容器进行处理。

最后应该还有一种 kube-proxy 的方式可以访问到 Service 的,下次用到时再细究。 还是官方的关于 Services 的文档非常详细,根本没必要看我前面乱七八糟写的什么东西啊。

链接:

  1. Metallb - 贫苦 K8S 用户的负载均衡支持

本文链接 https://yanbin.blog/kubernetes-learning-2-run-service/, 来自 隔叶黄莺 Yanbin Blog

[版权声明] Creative Commons License 本文采用 署名-非商业性使用-相同方式共享 4.0 国际 (CC BY-NC-SA 4.0) 进行许可。

Subscribe
Notify of
guest

0 Comments
Inline Feedbacks
View all comments