Kubernetes 学习笔记(二) - 部署和访问应用
前边折腾了各种安装 Kubernetes 集群的操作,还跑到 AWS 上撸了一把 EKS,也在 Kubernetes 上部署过服务。继续更深一步的学习如何部署应用和怎么通过 Service 去访问 Pod 中的应用,顺带看看内部的网络是怎么流转的。
测试平台还是以本地启动的三个 Vagrant 虚拟机组成的 Kubernetes 集群,安装方法见 Kubernetes 学习笔记(一) - 初上手。
测试应用的镜像为
看到创建了一个 python-web-app deployment 对象,并按照要求启动了三个 pod,分配到了两个工作节点上。先不讨论怎么去访问上面启动的服务,而是应该了解用
需创建一个描述文件, 我们命名为
然后只要一条命令就完成了部署并启动三个 pod
所以从以上操作不难想像,使用
进入到 docker 容器去访问服务肯定是没什么意义的,或许通过 worker 节点稍微现实一些,可是我们启动 docker 容器时未映射端口。来到
端口未映射没法通过节点访问
再次
现在可以在任意工作节点上用上面的 CLUSTER-IP 来访问容器内的服务
DNS 是给 Pod 用的,所以只要工作节点上有。
Kubernetes 中每一个服务都有自己的名称,完整名称为 <SERVICE_NAME>.<NAMESPACE_NAME>。所以我们进到某一个 Pod
虽然在 Pod 内部,对
这里有点绕,要进到 Pod 容器内部才能使用域名来访问 Service 对应的 ClusterIP,显然不是为外部提供服务的,它的关键用途是让 Pod(容器) 之间用 Service 名称互相访问。
下边是两种可由外部访问 Service
对于需向外提供服务的应用,这个才是 Kubernetes 的关键。可以通过 NodePort 或 LoadBalancer 的方式。
应用它
通过 Kubernetes 集群中任意节点的 IP 就可以访问容器内部的应用。工作节点的机器从架构上虽然是可以让外部进行访问,但这样做是不安全的。通过 NodePort 访问服务并非只是访问内部实现依然是借助于 iptables。
type 为 LoadBalancer 时会请求云服务提供商给它一个 EXTERAL-IP, 由此来向外提供服务。目前支持提供 LoadBalancer 的有 GCP, AWS, Azure 等。下面以 AWS 为例
这样通过那个 ELB 域名就能连接到节点的 32624 端口,像 NodePort 一样访问到了每一个 Pod 的服务。
以上除第一种方式外,只要发起了请求或者是外部的请求进入了集群,Kubernetes 自己的负载均衡(请求分布)便介入了工作。请求首先会被引到 ClusterIP 上来,这是一个 iptables 实现的虚拟 IP。在集群内部协助它实现的是节点之间的网络服务,如 flannel 网络,它是运行在每一个节点上的 Daemon Pod。
查看某一个 kube-flannel 上的日志如下:
在任意节点上也可以用
我们可以查看一个服务的描述
上面服务显示出各种可能的访问方式,包括:ClusterIP(IP), LoadBalancer Ingress, NodePort, 以及请求会被分发到的 Pod 容器(即 Endpoints)
以下图片来自于 《Kubernetes in Action》(Second Edition) 一书,它有助于我们理解 Kubernetes 集群请求分发的逻辑。
从外部的 Load balancer 不管连接到哪个节点的 NodePort(30838), 进一步经由节点上
最后应该还有一种 kube-proxy 的方式可以访问到 Service 的,下次用到时再细究。 还是官方的关于 Services 的文档非常详细,根本没必要看我前面乱七八糟写的什么东西啊。
链接:
永久链接 https://yanbin.blog/kubernetes-learning-2-run-service/, 来自 隔叶黄莺 Yanbin's Blog
[版权声明]
本文采用 署名-非商业性使用-相同方式共享 4.0 国际 (CC BY-NC-SA 4.0) 进行许可。
测试平台还是以本地启动的三个 Vagrant 虚拟机组成的 Kubernetes 集群,安装方法见 Kubernetes 学习笔记(一) - 初上手。
- k8s-master (172.28.128.14)
- k8s-node1 (172.28.128.10)
- 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
1$ kubectl get deploy -A
2NAMESPACE NAME READY UP-TO-DATE AVAILABLE AGE
3default python-web-app 3/3 3 3 3m47s
4kube-system coredns 2/2 2 2 117m
5$ kubectl get pods -o wide
6NAME READY STATUS RESTARTS AGE IP NODE ...
7python-web-app-68d7bbd7f5-dzjxf 1/1 Running 0 3m43s 10.244.2.3 k8s-node2 ...
8python-web-app-68d7bbd7f5-jt54g 1/1 Running 0 3m42s 10.244.2.4 k8s-node2 ...
9python-web-app-68d7bbd7f5-l5wkb 1/1 Running 0 3m48s 10.244.1.2 k8s-node1 ...看到创建了一个 python-web-app deployment 对象,并按照要求启动了三个 pod,分配到了两个工作节点上。先不讨论怎么去访问上面启动的服务,而是应该了解用
yaml 文件的部署方式。在这之前我们把前面的 deployment 对象删掉,命令是$ kubectl delete deploy python-web-app这会把 deployment 对象并先前的三个
python-web-app-* pod 全部清除掉。yaml 文件方式部署
需创建一个描述文件, 我们命名为 python-web-app.yaml(也可用 yml 为文件后缀),内容如下: 1apiVersion: apps/v1
2kind: Deployment
3metadata:
4 name: python-web-app
5spec:
6 replicas: 3
7 selector:
8 matchLabels:
9 app: python-web-app
10 template:
11 metadata:
12 labels:
13 app: python-web-app
14 spec:
15 containers:
16 - name: python-web
17 image: yanbin/python-web:latest然后只要一条命令就完成了部署并启动三个 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/shAlpine Linux 自带 wget 命令,不用安装 curl 也行
/ # 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
进入到 docker 容器去访问服务肯定是没什么意义的,或许通过 worker 节点稍微现实一些,可是我们启动 docker 容器时未映射端口。来到
python-web-app-68d7bbd7f5-dzjxf 1/1 Running 0 3m43s 10.244.2.3 k8s-node2所在的 worker 节点 k8s-node2 上
1root@k8s-node2:/# docker ps
2CONTAINER ID IMAGE COMMAND CREATED STATUS PORTS ...
310ebc5d4ecdd yanbin/python-web "python app.py" 11 hours ago Up 11 hours ...端口未映射没法通过节点访问
root@k8s-node1:/# curl 10.244.2.3发现不需要在 python-web-app.yaml 中加
Served by python-web-app-68d7bbd7f5-dzjxf/4a8a9e75
ports:也能在各工作节点上访问到所运行的容器的 80 端口
- containerPort: 80
ClusterIP 访问服务
我们进一步修改部署文件为 1apiVersion: apps/v1
2kind: Deployment
3metadata:
4 name: python-web-app
5spec:
6 replicas: 3
7 selector:
8 matchLabels:
9 app: python-web-app
10 template:
11 metadata:
12 labels:
13 app: python-web-app
14 spec:
15 containers:
16 - name: python-web
17 image: yanbin/python-web:latest
18 ports:
19 - containerPort: 80
20---
21apiVersion: v1
22kind: Service
23metadata:
24 name: python-web-svc
25spec:
26 selector:
27 app: python-web-app
28 ports:
29 - protocol: TCP
30 port: 80
31 targetPort: 80再次
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]
1root@k8s-master:/# kubectl get svc
2NAME TYPE CLUSTER-IP EXTERNAL-IP PORT(S) AGE
3kubernetes ClusterIP 10.96.0.1 <none> 443/TCP 6h19m
4python-web-svc ClusterIP 10.110.181.172 <none> 80/TCP 6h15m现在可以在任意工作节点上用上面的 CLUSTER-IP 来访问容器内的服务
root@k8s-master:/# curl 10.110.181.172这里的 ClusterIP 10.110.181.172 实质是一个 iptables 实现的虚拟 IP,同样是由 iptables 实现了负载均衡
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
域名访问 Service
这里的域名是 Service 的 ClientIP 地址对应有一个域名,所以它仍然是局限于内部访问服务。Kubernetes 集群有自己的 DNS 服务(kube-dns)1root@k8s-master:/# k get deploy -n kube-system
2NAME READY UP-TO-DATE AVAILABLE AGE
3coredns 2/2 2 2 6h39m
4root@k8s-master:/home/vagrant# k get pods -n kube-system -o wide | grep dns
5coredns-66bff467f8-2m26v 1/1 Running 0 6h39m 10.244.2.2 k8s-node2
6coredns-66bff467f8-5gvf9 1/1 Running 0 6h39m 10.244.1.2 k8s-node1DNS 是给 Pod 用的,所以只要工作节点上有。
Kubernetes 中每一个服务都有自己的名称,完整名称为 <SERVICE_NAME>.<NAMESPACE_NAME>。所以我们进到某一个 Pod
1root@k8s-master:/# kubectl get svc
2NAME TYPE CLUSTER-IP EXTERNAL-IP PORT(S) AGE
3kubernetes ClusterIP 10.96.0.1 <none> 443/TCP 6h43m
4python-web-svc ClusterIP 10.104.132.106 <none> 80/TCP 11m
5root@k8s-master:/# kubectl exec -it python-web-app-68d7bbd7f5-dzjxf -- sh
6/ # cat /etc/resolv.conf
7nameserver 10.96.0.10
8search default.svc.cluster.local svc.cluster.local
9options ndots:5
10/ # wget -qO- python-web-svc
11Served by python-web-app-78b9d9d7f-f2gxt/c5413dba
12/ # wget -qO- python-web-svc.default
13Served by python-web-app-78b9d9d7f-m478c/32c2f2bd
14/ # ping python-web-svc
15PING python-web-svc (10.104.132.106): 56 data bytes虽然在 Pod 内部,对
python-web-svc 和 python-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 部分如下: 1apiVersion: v1
2kind: Service
3metadata:
4 name: python-web-svc
5spec:
6 type: NodePort
7 selector:
8 app: python-web-app
9 ports:
10 - protocol: TCP
11 port: 80
12 targetPort: 80应用它
kubectl apply -f python-web-app.yaml,默认时每个节点上会分配一个 3000~32767 之间的端口与服务端口映射,我们也可以在 yaml 文件中指定 nodePort 的值。 1root@k8s-master:/# kubectl get svc
2NAME TYPE CLUSTER-IP EXTERNAL-IP PORT(S) AGE
3kubernetes ClusterIP 10.96.0.1 <none> 443/TCP 7h13m
4python-web-svc NodePort 10.104.132.106 <none> 80:32592/TCP 41m
5root@k8s-master:/# k get nodes -o wide
6NAME STATUS ROLES AGE VERSION INTERNAL-IP
7k8s-master Ready master 7h13m v1.18.1 172.28.128.10
8k8s-node1 Ready <none> 7h13m v1.18.1 172.28.128.11
9k8s-node2 Ready <none> 7h13m v1.18.1 172.28.128.12
10
11root@k8s-master:/# curl 172.28.128.11:32592
12Served by python-web-app-78b9d9d7f-f2gxt/c5413dba
13root@k8s-master:/# curl 172.28.128.12:32592
14Served by python-web-app-78b9d9d7f-m478c/32c2f2bd通过 Kubernetes 集群中任意节点的 IP 就可以访问容器内部的应用。工作节点的机器从架构上虽然是可以让外部进行访问,但这样做是不安全的。通过 NodePort 访问服务并非只是访问内部实现依然是借助于 iptables。
LoadBalancer 方式
需像 NodePort 一样,把 Service 部分的 type 改为 LoadBalancer 1apiVersion: v1
2kind: Service
3metadata:
4 name: python-web-svc
5spec:
6 type: LoadBalancer
7 selector:
8 app: python-web-app
9 ports:
10 - protocol: TCP
11 port: 80
12 targetPort: 80type 为 LoadBalancer 时会请求云服务提供商给它一个 EXTERAL-IP, 由此来向外提供服务。目前支持提供 LoadBalancer 的有 GCP, AWS, Azure 等。下面以 AWS 为例
1➜ $ kubectl get svc
2NAME TYPE CLUSTER-IP EXTERNAL-IP PORT(S) AGE
3kubernetes ClusterIP 10.100.0.1 <none> 443/TCP 45m
4python-web-svc LoadBalancer 10.100.87.28 af611e...2083.us-east-1.elb.amazonaws.com 80:32624/TCP 36m这样通过那个 ELB 域名就能连接到节点的 32624 端口,像 NodePort 一样访问到了每一个 Pod 的服务。
集群内部的 LoadBalance
前面列出各种访问 Kubernetes 内服务的方式:- Pod IP 地址访问:确切来讲,直接用 Pod IP 访问的是容器内应用而非 Kubernetes 服务,只宜用于诊断容器内程序
- Cluster IP 访问 Service
- 域名访问 Service:在 Pod 容器内部用域名方式解析到 Cluster IP 上
- NodePort 访问 Service
- LoadBalancer 访问 Service
以上除第一种方式外,只要发起了请求或者是外部的请求进入了集群,Kubernetes 自己的负载均衡(请求分布)便介入了工作。请求首先会被引到 ClusterIP 上来,这是一个 iptables 实现的虚拟 IP。在集群内部协助它实现的是节点之间的网络服务,如 flannel 网络,它是运行在每一个节点上的 Daemon Pod。
1root@k8s-master:~# kubectl get pod -n kube-system -o wide
2NAME READY STATUS RESTARTS AGE IP NODE
3kube-flannel-ds-amd64-jfn2s 1/1 Running 0 9h 172.28.128.12 k8s-node2
4kube-flannel-ds-amd64-sqh42 1/1 Running 0 9h 172.28.128.10 k8s-master
5kube-flannel-ds-amd64-zzgsr 1/1 Running 0 9h 172.28.128.11 k8s-node1 查看某一个 kube-flannel 上的日志如下:
1root@k8s-master:~# kubectl logs kube-flannel-ds-amd64-jfn2s -n kube-system | tail -15
2I0409 17:42:14.180010 1 vxlan_network.go:60] watching for new subnet leases
3I0409 17:42:14.182489 1 iptables.go:145] Some iptables rules are missing; deleting and recreating rules
4I0409 17:42:14.182506 1 iptables.go:167] Deleting iptables rule: -s 10.244.0.0/16 -j ACCEPT
5I0409 17:42:14.182891 1 iptables.go:145] Some iptables rules are missing; deleting and recreating rules
6I0409 17:42:14.183330 1 iptables.go:167] Deleting iptables rule: -s 10.244.0.0/16 -d 10.244.0.0/16 -j RETURN
7I0409 17:42:14.185017 1 iptables.go:167] Deleting iptables rule: -d 10.244.0.0/16 -j ACCEPT
8I0409 17:42:14.279752 1 iptables.go:167] Deleting iptables rule: -s 10.244.0.0/16 ! -d 224.0.0.0/4 -j MASQUERADE --random-fully
9I0409 17:42:14.280162 1 iptables.go:155] Adding iptables rule: -s 10.244.0.0/16 -j ACCEPT
10I0409 17:42:14.282071 1 iptables.go:167] Deleting iptables rule: ! -s 10.244.0.0/16 -d 10.244.2.0/24 -j RETURN
11I0409 17:42:14.383168 1 iptables.go:155] Adding iptables rule: -d 10.244.0.0/16 -j ACCEPT
12I0409 17:42:14.384063 1 iptables.go:167] Deleting iptables rule: ! -s 10.244.0.0/16 -d 10.244.0.0/16 -j MASQUERADE --random-fully
13I0409 17:42:14.386864 1 iptables.go:155] Adding iptables rule: -s 10.244.0.0/16 -d 10.244.0.0/16 -j RETURN
14I0409 17:42:14.389205 1 iptables.go:155] Adding iptables rule: -s 10.244.0.0/16 ! -d 224.0.0.0/4 -j MASQUERADE --random-fully
15I0409 17:42:14.484011 1 iptables.go:155] Adding iptables rule: ! -s 10.244.0.0/16 -d 10.244.2.0/24 -j RETURN
16I0409 17:42:14.488072 1 iptables.go:155] Adding iptables rule: ! -s 10.244.0.0/16 -d 10.244.0.0/16 -j MASQUERADE --random-fully在任意节点上也可以用
iptables-save 查看当前节点的 iptables 怎么去按比例分发请求到相应的服务对应的 Pod 上去的。每次新建一个 Kubernetes 服务,或者 Deployment 发生更新后都会有一系列的 iptables 规则的变更。我们可以查看一个服务的描述
1root@k8s-master:~# kubectl describe svc python-web-svc
2Name: python-web-svc
3Namespace: default
4Labels: <none>
5Annotations: Selector: app=python-web-app
6Type: NodePort
7IP: 10.104.132.106
8LoadBalancer Ingress: af611e.....082083.us-east-1.elb.amazonaws.com
9Port: <unset> 80/TCP
10TargetPort: 80/TCP
11NodePort: <unset> 32592/TCP
12Endpoints: 10.244.1.6:80,10.244.2.7:80,10.244.2.8:80
13Session Affinity: None
14External Traffic Policy: Cluster
15Events: <none>上面服务显示出各种可能的访问方式,包括:ClusterIP(IP), LoadBalancer Ingress, NodePort, 以及请求会被分发到的 Pod 容器(即 Endpoints)
以下图片来自于 《Kubernetes in Action》(Second Edition) 一书,它有助于我们理解 Kubernetes 集群请求分发的逻辑。
从外部的 Load balancer 不管连接到哪个节点的 NodePort(30838), 进一步经由节点上 iptables 规则的转发,请求最终有可能被任意一个 Pod 容器进行处理。最后应该还有一种 kube-proxy 的方式可以访问到 Service 的,下次用到时再细究。 还是官方的关于 Services 的文档非常详细,根本没必要看我前面乱七八糟写的什么东西啊。
链接:
永久链接 https://yanbin.blog/kubernetes-learning-2-run-service/, 来自 隔叶黄莺 Yanbin's Blog
[版权声明]
本文采用 署名-非商业性使用-相同方式共享 4.0 国际 (CC BY-NC-SA 4.0) 进行许可。