AZURE K8S NETWROK 实战
今儿我们来念叨念叨 AZURE NETWROK 在K8S下的实战,有童鞋可能说这东西有啥可说的对容器来说不都是透明的嘛,这么说大体上也没啥毛病,但是捋清楚它的实现会有助于我们来做一些有意思的事情,这里先卖个关子。那我们先来看看要容器网络何用?
1. 容器网络的由来
容器单机也能玩,CLUSTER下也能玩,跟虚拟化的思路是差不多的。那么问题就来的单节点内部解决通信问题容易,集群下解决跨节点通信问题就得权衡一下了。先来看看在没有类 K8S 这种控制器出现之前,单节点上不同容器之间是如何通信的。假设目前有三个容器Container-1,Container-2, Container-3,Container-1 和 Container-2 在 Node-1 上,Container-3 在 Node-2 上。原生 Docker 实现是将 Container 的网卡桥接在宿主机(Node)的 Bridge 设备上,Container 像直接接入到网络上一样,本节点(Node)内,不同 Container 在一个 L2 的域内,可以直接互相通信(即 Container-1,Container-2 不需要额外配置即可进行通信)。跨节点通信(Container-2 和 Container-3 通信)就是不是那么简单了,第一我们并不一定能够满足 Node-1 和 Node-2 之间穿过的网络在一个 L2 的域内,其次每个节点(Node)上 Container 地址分配上如何避免冲突。从设计思路上讲 Docker 是一个独立的管理域,从设计初衷上一定是 Day-0 去适配各种物理网络(不对物理网络配置提出要求),所以 Docker 在原生的解决方案中提供的跨节点(Node)的通信方法是通过端口映射转发的方式实现的,即 Container-2 要访问 Container-3 时,目的访问地址并非 Container-3 本身的地址,而是 Container-3 所在 Node 的节点IP 地址,这样一来,用户只需要满足在 Cluster 下的所有 Node 的节点 IP 地址之间可以互联互通即可。但上述方案的弊端也是显而易见的,第一,由于在节点(Node)上 运行的所有 Container 如果对其他节点发布自己的访问地址都需要复用该节点(Node)的 IP 地址,这里为服务端口管理带来了极高的复杂度,而且由于避免冲突的问题丧失了极大的灵活性;第二,在 Cluster 下从 Container 的创建到服务发现需要一整套的自动化管理工具,手工干预是不现实的,基于上述方案实现复杂度太高。
=======Example Container-1 to Container-2==========
Container-1 IP address --- Node-1 docker0 bridge --- Container-2 IP address √
=======Example Container-2 to Container-3==========
S: Container-2 IP address, D: Node-2 IP address --- Node-1 Docker0 Bridge --- SNAT to Node-1 IP address --- DNAT to Container-3 IP address --- Node-2 Docker0 Bridge --- S: Node-1 IP address, D: Container-3 IP address √
K8S 作为容器集群的控制器 Day 1 设计的时候就将上述问题考虑在内,提出了几点设计要求:
1. 所有 Cluster 内的 Container 可以互相通信(跨节点或节点内)且不需要 NAT ;
2. 所有 Cluster 内的节点(Node)可以与 Cluster 内的 Container 互相通信且不需要 NAT ;
3. 所有 Container 自己的真实地址与其他 Container 访问它所用地址相同 ;
其实上述要求的实现核心问题就是,Node IP 地址这个平面,Container IP 地址这个平面,可以互联互通(适配各种物理网络环境)
上述要求实现可以通过下述几种方法实现:
1). Overlay :这个其实是设计起来最清晰以及最容易实现的方法,将上述所有互联互通的需求都封装在一个 Full Control 的逻辑网络中,说白就是想怎么通怎么通,对底层物理网络啥依赖也没有。通常的实现通过隧道技术如 GRE,IPinIP,VXLAN 等;
2). Underlay :把上述的 Node IP 和 Container IP 地址平面和物理网络 L2 打通,扁平化实现,Container 如同直接连接在物理网络上一样,如果部署在物理IDC中扩展性性受制于传统网络在 L2 Domian 的一些限制,如果部署在云中受制于单网卡支持多 IP 的数量;
3). Native L3 Routing :类似 Underlay 方案,最大区别是将 L2 Domian 限制在 Node 内部,支持良好的扩展性,反之如果部署在物理IDC中,对物理网络有相应要求,物理网路需要拥有所有 Container IP 地址平面的路由,如果部署在云中,通常的方式是通过客户自定义路由的方式来模拟物理网络的路由表要求,所以扩展性受制于支持UDR的数量;
除了上述的基本的容器网络内互联互通的要求外,容器网络中还有一个很重好的需求就是混合部署互通,有些有些资源是部署在容器中,有些资源是部署在虚拟机中,这个时候,容器网络外部虚拟机如何和虚拟机通信,这个需求的满足上其实 Underlay 模式是有先天优势的,因为容器其实无异于虚拟机。另外就是如何在容器网络中实现今天物理网络中的安全隔离策略,在安全需求中,策略的一致性是非常重要的,如何实现容器网络内部外部的策略一致性管理,举例在云上,有些资源是部署在容器中,有些资源是部署在虚拟机中,如何做到安全策略的统一管理而不是两个孤岛。
2. K8S Network on AZURE 实现
了解实现之前,需要先问清楚自己想获得什么目标,因为第一在 AZURE 上 K8S 部署有不同的方式,第二在不同的部署方式上 Network 的实现各有不同,并且在不同的 Network 实现下支持的功能也各有不同。
先来看一下 K8S on Azure 的部署选项:
1). ACS(Azure Container Service) 服务,Azure 第一代托管的 K8S 容器服务,K8S Cluster 的生命周期管理由 Azure 平台完成;
2). ACS-Engine, 类似ACS服务,不同在于把更多的 K8S Cluster 的管理控制权给到用户,MS 通过在 Github (https://github.com/Azure/acs-engine) 上的 ACS-Engine 工具帮助用户生成一个 K8S Cluster的 AZURE ARM(Azure Resource Management)部署模板,暴露给用户更多的集群可配置参数,并且在没有 ACS 服务的地区可以通过 ACS-Engine 获得类 ACS 服务;
3). AKS (Azure Kubernetes Service)服务,Azure 第二代托管的 K8S 容器服务,和第一代的区别 K8S Cluster 的 Master 节点由 Azure 平台托管,客户看不到 Master 节点 VM 且不会收取 Master 节点费用;
再来看一下 K8S Network 在 Azure 上的实现由哪些:
1). Kubenet :配合 AZURE 上 UDR 自定义路由实现,可以理解为上述的 Native L3 Routing 模式, K8S 为每个 Node 分配一个地址空间,该地址空间内的地址用来为生根在该 Node 上的所有 POD (Container)分配地址,举例两个 Node 场景,为 Node-1 分配地址空间 (10.244.0.0/24),为 Node-2 分配地址空间 (10.244.1.0/24),所有生根在 Node-1 的 POD (Container)从 地址空间 (10.244.0.0/24)中获取可用地址。由于 Node 上 POD (Container)地址空间对 Vnet 是无感的,所以当需要跨节点 (Node)实现 POD (Container)互通时候,Vnet 路由是不知道该将该数据报文转发给哪一个 Node 的,所以通过配置 UDR 自定义路由实现 POD 地址空间和 Node 的对应关系。这种方案的扩展性受制于 Azure 下 UDR 支持的路由条目数,默认100条路由最大可支持400,看起来还不错,比 AWS 还是多了很多的 ( AWS 自定义路由表最大路由条目数50)。
2). Azure:将 POD (Container)直接桥接在 Vnet 网络上,POD (Container)和 VM 是一样的,可以获得所有 Vnet 内的功能。通过在所有 Node 节点网卡上关联多个 Vnet Private IP ,在创建 POD (Container)时将 Vnet Private IP 直接给到 POD (Container)。扩展性的限制,目前 VM 单网支持的 Private IP 数量为默认256最大1024,所以单节点上支持最大的 POD 数量默认为256最大为1024,集群下 VNET 内支持的 Private IP 数量默认为4096最大支持8192。AWS 类似方案基本不太可行,因为每个 ENI 上支持的 IP 地址数量太少 (依照机器规格大小不同单个 ENI 支持的 IP 数量不同,最大支持50个)
3). Calico:Calico 支持通过 UDR 方式实现 Native L3 Routing,也支持 Overlay 方式(由于 Azure Vnet 内不支持 IPinIP 封装,所以原生的 Overlay 方式不支持,可以通过 Canal -- Calico + Flannel 方式实现)。该方案的优势是支持 K8S 的 Network Policies,对于用户由 Micro Segment 需求的用户这个是个不错的选择。
K8S on Azure Network Support Matrix
Kubenet | Azure | Calico | |
ACS | Supported | Supported | Supported |
ACS Engine | Supported | Supported(as default) | Supported |
AKS | Supported (as default) | N/A | On Roadmap |
3. K8S on Azure 通过 ACS-Engine 部署使能 Azure Network CNI Plug-IN
本部分和大家一个来看一下如何通过 ACS-Engine 部署使能 AZURE CNI 网络的 K8S 集群。
ACS-Engine 部署 K8S 的详细方法及操作步骤,大家可参阅如下连接:https://github.com/Azure/acs-engine/blob/master/docs/kubernetes.md 。这里不做赘述,下面主要针对在部署中影响 Network 的几部分着重介绍。ACS-Engine 通过用户定义的 Kubernetes Cluster Json 描述文件来生成 Azure 部署的 ARM 模板,ACS-Engine 从 0.11.0 版本开始默认采用 Azure CNI 进行网络实现,所以如果用户使用 0.11.0 或者之后的版本则无需额外的配置。对于 0.11.0 版本之前的 ACS-Engine,需要在生成的 ARM 参数模板中进行指定,可参考如下模板示例:
1 #azuredeploy.parameters.json 2 { 3 "$schema": "http://schema.management.azure.com/schemas/2015-01-01/deploymentParameters.json#", 4 "contentVersion": "1.0.0.0", 5 "parameters": { 6 ... 7 "networkPolicy": { 8 "value": "azure" 9 }, 10 ... 11 } 12 }
除此之外 ACS-Engine 所使用的 Kubernetes Cluster Json 描述文件中需要定义单个 Compute Node 上配置多个 Private IP,以满足 POD (Container)创建时候使用,因为目前该网络插件的实现还不支持动态创建分配,所以需要进行与配置。
#kubernetes.json
"masterProfile": {
"count": 1,
"ipAddressCount": 64, # Master 节点配置64个 Private IP
...
},
"agentPoolProfiles": [
{
"name": "agentpool1",
"count": 8,
...
"ipAddressCount": 150, # Agent 节点配置150个 Private IP
...
}
],
通过 ACS-Engine 生成的 Azure ARM 部署完 K8S 集群后,登入 K8S Master 节点检查部署情况:
/var/log/azure 路径下查看 cluster-provision.log 中,有如下日志
+ configNetworkPolicy
+ [[ azure = \a\z\u\r\e ]]
+ configAzureNetworkPolicy
+ CNI_CONFIG_DIR=/etc/cni/net.d
进入 /etc/cni/net.d 目录,确认有 10-azure.conf 文件存在。
进入 /opt/cni/bin 目录,确认有 azure-vnet, azure-vnet-ipam, loopback 三个 bin 文件存在。
登入 Agent 节点检查基础网络情况,存在 bridge azure0,该设备由 azure cni 创建,
adminuser@k8s-agentpool1-26563307-1:~$ ip link show type bridge
3: docker0: <NO-CARRIER,BROADCAST,MULTICAST,UP> mtu 1500 qdisc noqueue state DOWN mode DEFAULT group default
link/ether 02:42:5a:0c:b0:2a brd ff:ff:ff:ff:ff:ff
6: azure0: <BROADCAST,MULTICAST,UP,LOWER_UP> mtu 1500 qdisc noqueue state UP mode DEFAULT group default qlen 1000
link/ether 00:17:fa:00:f7:e7 brd ff:ff:ff:ff:ff:ff
=====================================================================================================
adminuser@k8s-agentpool1-26563307-1:~$ ip link show master azure0
2: eth0: <BROADCAST,UP,LOWER_UP> mtu 1500 qdisc mq master azure0 state UP mode DEFAULT group default qlen 1000
link/ether 00:17:fa:00:f7:e7 brd ff:ff:ff:ff:ff:ff
创建一个 stateless deployment 验证 POD 地址分配
#nginxdeployment.yaml
apiVersion: apps/v1beta1 # for versions before 1.9.0 use apps/v1beta2
kind: Deployment
metadata:
name: nginx-deployment
spec:
selector:
matchLabels:
app: nginx
replicas: 2 # tells deployment to run 2 pods matching the template
template: # create pods using pod definition in this template
metadata:
labels:
app: nginx
spec:
containers:
- name: nginx
image: nginx:1.7.9
ports:
- containerPort: 80
查看 POD (Container)IP 地址, 可以看到 POD 分配的 IP 地址在 VNET Subnet 内,并且所用地址为 Agent 节点网卡的辅助 Private IP
adminuser@k8s-master-26563307-0:~$ kubectl get pod -o wide
2018-02-05 15:13:55.571574 I | proto: duplicate proto type registered: google.protobuf.Any
2018-02-05 15:13:55.571653 I | proto: duplicate proto type registered: google.protobuf.Duration
2018-02-05 15:13:55.571677 I | proto: duplicate proto type registered: google.protobuf.Timestamp
NAME READY STATUS RESTARTS AGE IP NODE
nginx-deployment-431080787-f3pf6 1/1 Running 1 5d 10.240.0.15 k8s-agentpool1-26563307-1
nginx-deployment-431080787-wp4fc 1/1 Running 0 5d 10.240.0.7 k8s-agentpool1-26563307-0
登入 POD 查看 POD 内地址与路由表, POD 内地址与在 NODE 中所查看 POD 地址一致,且 POD 中路由表默认网关为 VNET Subnet 网关
weikang@k8s-master-26563307-0:~$ kubectl exec -i -t nginx-deployment-431080787-f3pf6 -- /bin/bash
2018-02-05 15:17:40.701659 I | proto: duplicate proto type registered: google.protobuf.Any
2018-02-05 15:17:40.701730 I | proto: duplicate proto type registered: google.protobuf.Duration
2018-02-05 15:17:40.701886 I | proto: duplicate proto type registered: google.protobuf.Timestamp
root@nginx-deployment-431080787-f3pf6:/# ip addr show
1: lo: <LOOPBACK,UP,LOWER_UP> mtu 65536 qdisc noqueue state UNKNOWN qlen 1000
link/loopback 00:00:00:00:00:00 brd 00:00:00:00:00:00
inet 127.0.0.1/8 scope host lo
valid_lft forever preferred_lft forever
inet6 ::1/128 scope host
valid_lft forever preferred_lft forever
16: eth0@if15: <BROADCAST,UP,LOWER_UP,M-DOWN> mtu 1500 qdisc noqueue state UP qlen 1000
link/ether 06:9f:93:f2:69:32 brd ff:ff:ff:ff:ff:ff
inet 10.240.0.15/16 scope global eth0
valid_lft forever preferred_lft forever
inet6 fe80::49f:93ff:fef2:6932/64 scope link
valid_lft forever preferred_lft forever
=======================================================================================
root@nginx-deployment-431080787-f3pf6:/# ip route show
default via 10.240.0.1 dev eth0 proto static
10.240.0.0/16 dev eth0 proto kernel scope link src 10.240.0.15
在 Agent 节点上查看 ip link show type veth,可以看到很多 veth 设备存在,这些 veth 和 POD 相对应,实现了 POD 的网卡桥接到 azure0 网桥上,并通过 azure0 网桥的上联接口 eth0 接入到 Vnet 中。
adminuser@k8s-agentpool1-26563307-1:~$ ip link show type veth
7: azveth9d2d732@if8: <BROADCAST,UP,LOWER_UP> mtu 1500 qdisc noqueue master azure0 state UP mode DEFAULT group default qlen 1000
link/ether 06:72:7e:d3:72:30 brd ff:ff:ff:ff:ff:ff link-netnsid 0
9: azveth76bb202@if10: <BROADCAST,UP,LOWER_UP> mtu 1500 qdisc noqueue master azure0 state UP mode DEFAULT group default qlen 1000
link/ether 7e:e6:03:38:8a:e6 brd ff:ff:ff:ff:ff:ff link-netnsid 1
11: azveth78715da@if12: <BROADCAST,UP,LOWER_UP> mtu 1500 qdisc noqueue master azure0 state UP mode DEFAULT group default qlen 1000
link/ether 02:08:c2:de:79:44 brd ff:ff:ff:ff:ff:ff link-netnsid 2
13: azvethb9e59f4@if14: <BROADCAST,UP,LOWER_UP> mtu 1500 qdisc noqueue master azure0 state UP mode DEFAULT group default qlen 1000
link/ether 2e:7f:1c:ba:0c:bd brd ff:ff:ff:ff:ff:ff link-netnsid 3
15: azvethc1ed481@if16: <BROADCAST,UP,LOWER_UP> mtu 1500 qdisc noqueue master azure0 state UP mode DEFAULT group default qlen 1000
link/ether 0a:32:5a:b1:98:ec brd ff:ff:ff:ff:ff:ff link-netnsid 4
Note:如何获取 veth 和 POD 的对应关系
Docker 为每个 POD 创建独立的 namespace,进入 /var/run/docker/netns 目录可以看到多个已生成 netns。通过 sudo ln -s /var/run/docker/netns /var/run/netns 将 docker 的 ns 目录链接到 agent 节点的 ns 目录, 执行 sudo ip netns exec namespace_name ip addr show
adminuser@k8s-agentpool1-26563307-0:~$ sudo ip netns exec c657dc01301f ip addr show
1: lo: <LOOPBACK,UP,LOWER_UP> mtu 65536 qdisc noqueue state UNKNOWN group default qlen 1000
link/loopback 00:00:00:00:00:00 brd 00:00:00:00:00:00
inet 127.0.0.1/8 scope host lo
valid_lft forever preferred_lft forever
inet6 ::1/128 scope host
valid_lft forever preferred_lft forever
12: eth0@if11: <BROADCAST,UP,LOWER_UP> mtu 1500 qdisc noqueue state UP group default qlen 1000
link/ether d2:af:35:15:c1:ad brd ff:ff:ff:ff:ff:ff link-netnsid 0
inet 10.240.0.7/16 scope global eth0
valid_lft forever preferred_lft forever
inet6 fe80::d0af:35ff:fe15:c1ad/64 scope link
valid_lft forever preferred_lft forever
xx:eth0@ifyy 字样代表 POD 的接口 index 为xx,NODE 节点与之成对的 veth index 为yy,在 NODE 节点上执行 ip link show type veth
adminuser@k8s-agentpool1-26563307-0:~$ ip link show type veth
9: azvethca929c7@if10: <BROADCAST,UP,LOWER_UP> mtu 1500 qdisc noqueue master azure0 state UP mode DEFAULT group default qlen 1000
link/ether d6:d4:04:b6:40:81 brd ff:ff:ff:ff:ff:ff link-netnsid 0
11: azveth547dc55@if12: <BROADCAST,UP,LOWER_UP> mtu 1500 qdisc noqueue master azure0 state UP mode DEFAULT group default qlen 1000
link/ether c6:a7:78:5e:ae:56 brd ff:ff:ff:ff:ff:ff link-netnsid 1
与前面获得的 POD 信息进行比较,可以看到信息完全一致。
adminuser@k8s-master-26563307-0:~$ kubectl get pod -o wide
2018-02-05 15:13:55.571574 I | proto: duplicate proto type registered: google.protobuf.Any
2018-02-05 15:13:55.571653 I | proto: duplicate proto type registered: google.protobuf.Duration
2018-02-05 15:13:55.571677 I | proto: duplicate proto type registered: google.protobuf.Timestamp
NAME READY STATUS RESTARTS AGE IP NODE
nginx-deployment-431080787-f3pf6 1/1 Running 1 5d 10.240.0.15 k8s-agentpool1-26563307-1
nginx-deployment-431080787-wp4fc 1/1 Running 0 5d 10.240.0.7 k8s-agentpool1-26563307-0
4. 总结
通过上述的介绍,相信大家对 K8S on Azure 的 AZURE CNI 实现已经有了一定了解,目前 AZURE CNI 的实现还是为用户带来了不少便利,让 POD (Container)与 VM 一样直接接入到 AZURE Vnet 中,享受所有 Vnet 内的功能。但是 AZURE CNI 也还有需要继续完善的地方,容器网络安全是很多客户非常迫切的需求,目前 AZURE CNI 还不支持 K8S Network Policies,无法实现 micro segment 的安全隔离,如果有这部分需求,可以考虑 Calico 的方案。容器网络是一个非常大的话题,本文希望大家深入浅出,快速入门,最后为大家分享一些学习资料,感兴趣的同学可以扩展阅读。
1. K8S 网络解决方案介绍:https://kubernetes.io/docs/concepts/cluster-administration/networking/ 这里面列出的比较全面的解决方案,可以逐个深入了解
2. Calico 网络组件介绍:https://docs.projectcalico.org/master/introduction/ 个人认为实现最完整的容器网络组件,从连通性到可扩展性再到安全性
3. AZURE Container Network Github:https://github.com/Azure/azure-container-networking 详细的 AZURE CNI 实现说明
万变不离其宗,小伙伴们在花些时间揣摩 Overlay, Underlay, Native L3 Routing 三种方式的意义,相信一定会有更深刻的理解。