K8S中网络这块主要考虑 如何访问外部网络以及外部如何访问内部网络
访问外网服务的两种方式
需求
k8s集群内的pod需要访问mysql,由于mysql的性质,不适合部署在k8s集群内,故k8s集群内的应用需要链接mysql时,需要配置链接外网的mysql,本次测试 k8s集群ip段为
192.168.23.xx
。以下提供两种方式,Endpoint
和ExternalName
方式。
一、创建Endpoint类型的服务
创建命名空间
新建命名空间my-first-app
,需要访问外网的Pod,svc,endpoints等 都需要在该命名空间下。
> kubectl create namespace my-first-app
创建 endpoints
创建my-mysql-endpoints.yaml
> mkdir -p ~/mysql-endpoint
> cd ~/mysql-endpoint
> cat <<EOF > my-mysql-endpoints.yaml
apiVersion: v1
kind: Endpoints
apiVersion: v1
metadata:
name: my-mysql-endpoint #此名字需与 my-mysql-service.yaml文件中的 metadata.name 的值一致
namespace: my-first-app #在固定的命名空间下
subsets:
- addresses:
- ip: 192.168.23.1 ## 宿主机,由于我的虚拟机ping不通我本机的mysql(安装mysql时候禁用了未打开)
- ip: 220.181.38.148 ## 随便取的一个 公网Ip
ports:
- port: 3306
EOF
创建service
创建my-mysql-service.yaml
> cat <<EOF > my-mysql-service.yaml
apiVersion: v1
kind: Service
metadata:
name: my-mysql-endpoint #此名字需与 my-mysql-endpoints.yaml文件中的 metadata.name 的值一致
namespace: my-first-app #在固定的命名空间下
spec:
ports:
- port: 3306
### 验证,进入,ping一下配置的Ip地址。
# kubectl exec -it my-first-springcloud-94cdd7487-xxxxx -n my-first-app -- /bin/sh
# ping 220.181.38.148
EOF
部署pod 进行验证
部署自己的服务到 my-first-app
命名空间下,并且进入容器,进行ping测试
> kubectl exec -it my-first-springcloud-94cdd7487-xxxxx -n my-first-app -- /bin/sh
> ping 220.181.38.148 ## 也可以在容器中直接调用对应的端口测试,此处只演示ping通,就代表能访问了
PING 220.181.38.148 (220.181.38.148): 56 data bytes
64 bytes from 220.181.38.148: seq=0 ttl=127 time=5.356 ms
64 bytes from 220.181.38.148: seq=1 ttl=127 time=4.946 ms
64 bytes from 220.181.38.148: seq=2 ttl=127 time=18.165 ms
。。。。
> 也可以查看service的 Endpoints 信息
> kubectl describe svc my-mysql-endpoint -n my-first-app
Name: my-mysql-endpoint
Namespace: my-first-app
Labels: <none>
Annotations: <none>
Selector: <none>
Type: ClusterIP
IP: 10.102.160.141
Port: <unset> 3306/TCP
TargetPort: 3306/TCP
Endpoints: 192.168.23.1:3306,220.181.38.148:3306
Session Affinity: None
Events: <none>
注意
此种方式只适合Ip访问,对于像阿里云rds等数据库的。需要用域名。则需要用ExternalName
方式不而不是Endpoints
方式。
二、创建ExternalName类型的服务
创建``
**> mkdir -p ~/mysql-endpoint
> cd ~/mysql-endpoin
> cat <<EOF > my-mysql-external.yaml
apiVersion: v1
kind: Service
metadata:
name: my-mysql-external #此名字随便起
namespace: my-first-app #在固定的命名空间下
spec:
type: ExternalName
externalName: www.baidu.com ##提供方的服务完全限定域名,如rds域名等。
ports:
- port: 80
### ExternalName类型的服务创建后,pod可以通过my-mysql-external.default.svc.cluster.local域名连接到外部服务,
#### 或者通过my-mysql-external。当需要指向其他外部服务时,只需要修改spec.externalName的值即可。
EOF
暴露服务给外网访问的三种方式
NodePort
kubectl run 创建 pod
[root@master ~]#kubectl run nginx --image=nginx:1.14 --port=80 --replicas=3
kubectl run --generator=deployment/apps.v1 is DEPRECATED and will be removed in a future version. Use kubectl run --generator=run-pod/v1 or kubectl create instead.
deployment.apps/nginx created
[root@master ~]#kubectl get pods
NAME READY STATUS RESTARTS AGE
nginx-59d795d786-5ln28 1/1 Running 0 34s
nginx-59d795d786-xnfjq 1/1 Running 0 34s
nginx-59d795d786-z86nn 1/1 Running 0 34s
[root@master ~]#kubectl get all
NAME READY STATUS RESTARTS AGE
pod/nginx-59d795d786-5ln28 1/1 Running 0 59s
pod/nginx-59d795d786-xnfjq 1/1 Running 0 59s
pod/nginx-59d795d786-z86nn 1/1 Running 0 59s
NAME TYPE CLUSTER-IP EXTERNAL-IP PORT(S) AGE
service/kubernetes ClusterIP 10.96.0.1 <none> 443/TCP 3d9h
NAME READY UP-TO-DATE AVAILABLE AGE
deployment.apps/nginx 3/3 3 3 59s
NAME DESIRED CURRENT READY AGE
replicaset.apps/nginx-59d795d786 3 3 3 59s
kubectl expose 发布容器
[root@master ~]#kubectl expose deployment nginx --port=80 --target-port=80 --name=nginx-service --type=NodePort
service/nginx-service exposed
# 查看 Pod 网络状态详细信息和 Service 暴露的端口
[root@master ~]#kubectl get svc,pod -o wide
NAME TYPE CLUSTER-IP EXTERNAL-IP PORT(S) AGE SELECTOR
service/kubernetes ClusterIP 10.96.0.1 <none> 443/TCP 3d9h <none>
service/nginx-service NodePort 10.96.158.55 <none> 80:31083/TCP 3m37s run=nginx
NAME READY STATUS RESTARTS AGE IP NODE NOMINATED NODE READINESS GATES
pod/nginx-59d795d786-5ln28 1/1 Running 0 7m48s 10.244.1.3 node01 <none> <none>
pod/nginx-59d795d786-xnfjq 1/1 Running 0 7m48s 10.244.2.3 node02 <none> <none>
pod/nginx-59d795d786-z86nn 1/1 Running 0 7m48s 10.244.2.4 node02 <none> <none>
2. Loadbalancer
k8s 之 PodIP、ClusterIP 和 ExternalIP
在 k8s 中创建 service 时,需要指定 type 类型,可以分别指定 ClusterIP、NodePort、LoadBalancer 三种,其中前面两种无论在内网还是公网环境下使用都很常见,只有 LoadBalancer 大部分情况下只适用于支持外部负载均衡器的云提供商(AWS、阿里云、华为云等)使用。
本地自己安装的 k8s 集群,默认是不支持 LoadBalancer 的,需要自己安装一个组件来支持。而云上的 k8s,肯定是都支持 LoadBalancer 的。如果自己公司搭建集群,那肯定也是需要安装 LoadBalancer 的,支持本地集群 LoadBalancer 的组件:
- metalLB: Netlify 是一家位于旧金山的云计算公司,为 Web 应用程序和静态网站提供托管和无服务器后端服务。
- openelb: 之前是 PorterLB,KubeSphere 公司开源的,是有中文文档的,不过改名的过程中, 有点乱。
LoadBalancer 示意图
2.1 确认 strictARP 模式
如果你的网络是运行在 IPVS 模式下(默认是 iptables),那么需要设置 strictARP 模式。 链接
[root@master ~]# curl localhost:10249/proxyMode
ipvs
[root@master ~]# kubectl edit configmap -n kube-system kube-proxy
# 修改其中的 strictARP 为 true,下面字段不是连续的上下行,只展示了需要注意的行数
......
apiVersion: kubeproxy.config.k8s.io/v1alpha1
kind: KubeProxyConfiguration
mode: "ipvs"
ipvs:
strictARP: true
......
2.2 安装 metalLB
官网可能执行不了
kubectl apply -f https://raw.githubusercontent.com/metallb/metallb/v0.10.2/manifests/namespace.yaml
kubectl apply -f https://raw.githubusercontent.com/metallb/metallb/v0.10.2/manifests/metallb.yaml
[root@master ~]#kubectl apply -f http://49.232.8.65/yaml/metallb/namespace.yaml
namespace/metallb-system created
[root@master ~]#kubectl apply -f http://49.232.8.65/yaml/metallb/metallb.yaml
......
[root@master ~]#kubectl get pods -n metallb-system
NAME READY STATUS RESTARTS AGE
controller-857846f7df-l245m 1/1 Running 0 42s
speaker-5ln28 1/1 Running 0 42s
speaker-xnfjq 1/1 Running 0 42s
speaker-z86nn 1/1 Running 0 42s
# metallb-system 命名空间下有 controller,speaker 等进程已经在 Running 状态,说明正常。
# metallb-system/controller deployment。用于处理 IP 分配的控制器。
# metallb-system/speakerdaemonset。集群中每个节点启动一个协议服务守护进程。
2.3 配置 IP 池
这里我们使用 layer2 协议,毕竟本地测试没有 BGP 设备,如果是正式用,还是要上 BGP 设备的。先申请预留的 IP 端,配置如下:
[root@master ~]#vim metallb.ip.yaml
[root@master ~]#cat metallb.ip.yaml
# 添加一个 ConfigMap 配置 metallb IP 池
apiVersion: v1
kind: ConfigMap
metadata:
namespace: metallb-system
name: config
data:
config: |
address-pools:
- name: default
protocol: layer2
addresses:
- 192.168.10.170-192.168.10.200
[root@master ~]#kubectl apply -f metallb.ip.yaml
这样当我们创建一个 loadbalancer 类型的 service 时,EXTERNAL-IP 将会从地址池中获取一个用于外部访问的 IP,当外部流量进入时,ARP 将我们的请求地址广播以获取所属的 service,接着 k8s 内部通过 iptables 规则和 kube-proxy 将流量从服务端点引导到后端。
BGP 类型参考官方文档:
链接
2.4 测试效果
现在部署一个 Nginx 看看效果
[root@master ~]#vim nginx-test.yaml
[root@master ~]#cat nginx-test.yaml
apiVersion: apps/v1
kind: Deployment
metadata:
name: nginx-metallb-test
spec:
selector:
matchLabels:
app: nginx-metallb-test
template:
metadata:
labels:
app: nginx-metallb-test
spec:
containers:
- name: nginx
image: nginx:1.8
ports:
- name: http
containerPort: 80
---
apiVersion: v1
kind: Service
metadata:
name: nginx-service
spec:
ports:
- name: http
port: 80
protocol: TCP
targetPort: 80
selector:
app: nginx-metallb-test
type: LoadBalancer
[root@master ~]#kubectl apply -f nginx-test.yaml
deployment.apps/nginx-metallb-test created
service/nginx-service created
上面声明的 Service 的类型为 LoadBalancer,无其他特殊设置。
[root@master ~]#kubectl get svc -o wide
NAME TYPE CLUSTER-IP EXTERNAL-IP PORT(S) AGE SELECTOR
kubernetes ClusterIP 10.96.0.1 <none> 443/TCP 9d <none>
nginx-service LoadBalancer 10.96.158.55 192.168.10.170 80:31083/TCP 70s app=nginx-metallb-test
打开浏览器,访问 EXTERNAL-IP 即可访问 nginx:http://192.168.10.170
如果是正式环境,则使用 DNS 解析到此 IP,使用域名访问服务即可
如果有多个服务,可以使用 Nginx Ingress 来通过域名和路径区分不同的服务
负载均衡可以建立在 OSI 网络模型的不同级别上,主要是在 L4(传输层,例如 TCP/UDP)和 L7(应用层,例如 HTTP)上。在 Kubernetes 中,Services是 L4 的抽象,LoadBalancer 类型负载均衡依然有局限性,同时我们看到每创建一个 service 对应的负载均衡器都会消耗一个静态 IP,这并不合理。当然 k8s 中的另一种资源对象 ingress 可工作在 L7 层实现应用程序协议(HTTP/HTTPS)的负载均衡。
[root@master ~]#curl 192.168.10.170 # 反应可能比较慢,我等待了几分钟,不知道为啥
<!DOCTYPE html>
<html>
<head>
<title>Welcome to nginx!</title>
<style>
body {
width: 35em;
margin: 0 auto;
font-family: Tahoma, Verdana, Arial, sans-serif;
}
</style>
</head>
<body>
<h1>Welcome to nginx!</h1>
<p>If you see this page, the nginx web server is successfully installed and
working. Further configuration is required.</p>
<p>For online documentation and support please refer to
<a href="http://nginx.org/">nginx.org</a>.<br/>
Commercial support is available at
<a href="http://nginx.com/">nginx.com</a>.</p>
<p><em>Thank you for using nginx.</em></p>
</body>
</html>
3. Ingress
我们所说的 Ingress 包含两个部分:
- ingress 资源对象:流量路由规则的控制。提供 Ingress Kubernetes 对象,能够通过 yaml 进行创建和更新,将服务与域名对应起来。
- ingress-controller 控制器:控制器的实现有非常多,可参考官方文档中列表 Ingress 控制器,这里我们使用 k8s 官方维护的控制器 NGINX Ingress Controller。
外部流量进入集群时先经过 ingress-controller,然后根据 ingress 配置的路由规则将请求转发到后端 service。
3.1 安装 ingress controller
ingress-controller 其实就是守护进程加一个反向代理的应用,守护进程不断监听集群中资源的变化,将 ingress 中的配置信息生成反向代理配置。在 nginx-ingress controller 中即生成 nginx.conf 的配置文件。
我们上面已经配置好了 loadbalancer 的服务,这样我们创建一个 type 为 LoadBalancer 的 service 关联这组 pod,再把域名解析指向该地址,就实现了集群服务的对外暴露。当然也可以使用 NodePort、Hostnetwork 的方式,本文不讨论。
PS:先前实验用的 v1.17.0,做 ingress 实验的时候换成了 v1.22.5,LoadBalancer 实验按照这个链接重做了一下。
ingress-controller 不是 k8s 内部组件,可以通过 helm 或资源清单方式安装。
[root@master ~]#kubectl apply -f http://49.232.8.65/yaml/deploy.yaml
......
[root@master ~]#kubectl get pods,svc -n ingress-nginx -o wide
NAME READY STATUS RESTARTS AGE IP NODE NOMINATED NODE READINESS GATES
pod/ingress-nginx-admission-create--1-srl4n 0/1 Completed 0 2m12s 10.244.1.3 node01 <none> <none>
pod/ingress-nginx-admission-patch--1-vczxf 0/1 Completed 2 2m12s 10.244.2.3 node02 <none> <none>
pod/ingress-nginx-controller-867b8ccd99-7gdhf 1/1 Running 0 2m12s 10.244.2.4 node02 <none> <none>
NAME TYPE CLUSTER-IP EXTERNAL-IP PORT(S) AGE SELECTOR
service/ingress-nginx-controller NodePort 10.101.83.140 <none> 80:31700/TCP,443:30401/TCP 2m12s app.kubernetes.io/component=controller,app.kubernetes.io/instance=ingress-nginx,app.kubernetes.io/name=ingress-nginx
service/ingress-nginx-controller-admission ClusterIP 10.101.43.212 <none> 443/TCP 2m12s app.kubernetes.io/component=controller,app.kubernetes.io/instance=ingress-nginx,app.kubernetes.io/name=ingress-nginx
编辑 service 修改 spec.type 为 LoadBalancer
[root@master ~]#kubectl edit service/ingress-nginx-controller -n ingress-nginx
......
spec:
clusterIP: 10.101.83.140
clusterIPs:
- 10.101.83.140
......
selector:
app.kubernetes.io/component: controller
app.kubernetes.io/instance: ingress-nginx
app.kubernetes.io/name: ingress-nginx
sessionAffinity: None
type: LoadBalancer
......
这样我们创建好了 nginx-ingress controller,下一步就要配置 ingress 路由规则。
3.2 配置 ingress 路由规则
host:http://k8s.com
基于 url 的路由:
/api/v1
/api/v2
这两个 url 分别路由到不同的 service 中。
[root@master ~]#vim ingress.yaml
[root@master ~]#cat ingress.yaml
apiVersion: networking.k8s.io/v1
kind: Ingress
metadata:
name: test
namespace: training
annotations:
ingress.kubernetes.io/rewrite-target: /
spec:
rules:
- host: k8s.com
http:
paths:
- pathType: Prefix
path: /api/v1
backend:
service:
name: service-apiv1
port:
number: 80
- pathType: Prefix
path: /api/v2
backend:
service:
name: service-apiv2
port:
number: 80
[root@master ~]#kubectl create ns training
namespace/training created
[root@master ~]#kubectl apply -f ingress.yaml
ingress.networking.k8s.io/test created
[root@master ~]#kubectl get ingress -n training -o wide
NAME CLASS HOSTS ADDRESS PORTS AGE
test <none> k8s.com 80 45s
ingress.kubernetes.io/rewrite-target
是 nginx-ingress controller 的一个注解,当后端服务中暴露的 URL 与 Ingress 规则中指定的路径不同时可以通过此重定向。
查看 svc 可以看到此时控制器已经获得了一个 EXTERNAL-IP
现在 nginx-ingress controller 和 ingress 路由规则都有了
通过 CCE 使用 K8S_Ingress
[root@master ~]#kubectl get svc -n ingress-nginx # 这个 EIP 是弹性 IP,我们是模拟出来的,在公有云上是公网的 IP,是购买的 LB 给的(假如我们用的百度云,这个 EIP 就是 BLB 给的,百度的公网负载均衡器,一个集群只需要一个 EIP,作为七层的集群入口。PS:在公有云上 ingress 一般是一台单独的机器,它和 nginx 类似,如果运行在 node 节点,流量太大可能接不住。可以将节点 IP 作为 EIP。)
NAME TYPE CLUSTER-IP EXTERNAL-IP PORT(S) AGE
ingress-nginx-controller LoadBalancer 10.101.83.140 192.168.1.241 80:31700/TCP,443:30401/TCP 44m
ingress-nginx-controller-admission ClusterIP 10.101.43.212 <none> 443/TCP
我们可以进入到 nginx-ingress controller pod 中查看 nginx.conf 可以看到此时我们的 ingress 配置已经被生成为路由规则
[root@master ~]#kubectl get pods -n ingress-nginx
NAME READY STATUS RESTARTS AGE
ingress-nginx-admission-create--1-srl4n 0/1 Completed 0 90m
ingress-nginx-admission-patch--1-vczxf 0/1 Completed 2 90m
ingress-nginx-controller-867b8ccd99-7gdhf 1/1 Running 0 90m
[root@master ~]#kubectl exec -it ingress-nginx-controller-867b8ccd99-7gdhf -n ingress-nginx /bin/bash
kubectl exec [POD] [COMMAND] is DEPRECATED and will be removed in a future version. Use kubectl exec [POD] -- [COMMAND] instead.
bash-5.1$ ls
fastcgi.conf mime.types scgi_params
fastcgi.conf.default mime.types.default scgi_params.default
fastcgi_params modsecurity template
fastcgi_params.default modules uwsgi_params
geoip nginx.conf uwsgi_params.default
koi-utf nginx.conf.default win-utf
koi-win opentracing.json
lua owasp-modsecurity-crs
bash-5.1$ cat nginx.conf
......
3.3 指定后端服务
接下来就是指定我们的 backend,即上面的 server-apiv1/2。我们添加两个用于暴露的 service 和 deployment,和 loadbalancer 中测试清单一样,我们稍稍修改一下名称即可。
[root@master ~]#vim backend1.yaml
[root@master ~]#vim backend2.yaml
[root@master ~]#cat backend1.yaml
apiVersion: apps/v1
kind: Deployment
metadata:
name: nginx-apiv1
namespace: training
spec:
selector:
matchLabels:
app: nginx-apiv1
template:
metadata:
labels:
app: nginx-apiv1
spec:
containers:
- name: nginx-apiv1
image: nginx:latest
ports:
- name: http
containerPort: 80
---
apiVersion: v1
kind: Service
metadata:
namespace: training
name: service-apiv1
spec:
ports:
- name: http
port: 80
protocol: TCP
targetPort: 80
selector:
app: nginx-apiv1
type: NodePort
[root@master ~]#cat backend2.yaml
apiVersion: apps/v1
kind: Deployment
metadata:
name: nginx-apiv2
namespace: training
spec:
selector:
matchLabels:
app: nginx-apiv2
template:
metadata:
labels:
app: nginx-apiv2
spec:
containers:
- name: nginx-apiv2
image: nginx:latest
ports:
- name: http
containerPort: 80
---
apiVersion: v1
kind: Service
metadata:
namespace: training
name: service-apiv2
spec:
ports:
- name: http
port: 80
protocol: TCP
targetPort: 80
selector:
app: nginx-apiv2
type: NodePort
[root@master ~]#kubectl apply -f backend1.yaml
deployment.apps/nginx-apiv1 created
service/service-apiv1 created
[root@master ~]#kubectl apply -f backend2.yaml
deployment.apps/nginx-apiv2 created
service/service-apiv2 created
[root@master ~]#kubectl get pods,svc -o wide -n training
NAME READY STATUS RESTARTS AGE IP NODE NOMINATED NODE READINESS GATES
pod/nginx-apiv1-587fb4fd66-ndvhp 1/1 Running 0 48s 10.244.1.4 node01 <none> <none>
pod/nginx-apiv2-c55b5bf96-dkkfj 1/1 Running 0 45s 10.244.2.5 node02 <none> <none>
NAME TYPE CLUSTER-IP EXTERNAL-IP PORT(S) AGE SELECTOR
service/service-apiv1 NodePort 10.105.238.31 <none> 80:31845/TCP 48s app=nginx-apiv1
service/service-apiv2 NodePort 10.98.58.30 <none> 80:31481/TCP 45s app=nginx-apiv2
修改 hosts 解析,C:\Windows\System32\drivers\etc\hosts,我们是模拟的假的域名,所以需要修改,真的域名有公网的 DNS 解析
修改后端访问的 nginx pod 的 index.html,不如访问会 404
pod 中无法使用 vi 解决:
apt-get update
apt-get install vim
[root@master ~]#kubectl get pods -n training
NAME READY STATUS RESTARTS AGE
nginx-apiv1-587fb4fd66-ndvhp 1/1 Running 0 25m
nginx-apiv2-c55b5bf96-dkkfj 1/1 Running 0 25m
[root@master ~]#kubectl exec -it nginx-apiv1-587fb4fd66-ndvhp -n training /bin/bash
kubectl exec [POD] [COMMAND] is DEPRECATED and will be removed in a future version. Use kubectl exec [POD] -- [COMMAND] instead.
root@nginx-apiv1-587fb4fd66-ndvhp:/# ls
bin boot dev docker-entrypoint.d docker-entrypoint.sh etc home lib lib64 media mnt opt proc root run sbin srv sys tmp usr var
root@nginx-apiv1-587fb4fd66-ndvhp:/# cd /usr/share/nginx/html/
root@nginx-apiv1-587fb4fd66-ndvhp:/usr/share/nginx/html# ls
50x.html index.html
注意:Linux 访问就修改 /etc/hosts,Windows 访问就修改 C:\Windows\System32\drivers\etc\hosts