背景: 众所周知的是在构建一个Kubernetes集群时,容器网络通常会使用一个独立的私有子网来构建Kubernetes集群内部的pod网络和service网络,但在实际的业务场景中,没有企业会在一段时间内将内部全部的服务都迁移到Kubernetes集群中(因为涉及到业务架构以及整体业务的可靠性),因而会产生一些Kubernetes集群内部服务和集群外部服务互相调用的场景,当然如果是HTTP服务,我们可以采用LVS、Nginx、HAProxy之类的代理工具工具进行集群内外的流量转发,但如果是TCP服务,比如使用Dubbo框架时,生产者和消费者需要直连,当生产者和消费者不在一个可以互联互通的网络下会比较麻烦,这也就是为什么大厂在规模化使用Kubernetes时首先需要解决的就是网络问题的原因了。比如我们在数科的时候就采用的是Contiv+BGP的模式来实现容器网络和容器外网络的互联互通的,而这通常需要一个比较专业的SDN团队来构建和维护。而作为创业公司通常会使用公有云来承载自己的业务,这种轻资产模式的好处就是底层会有专业的团队来提供保障,因此考虑到业务需求我们采用了阿里云的terway网络插件来实现内部的Kubernetes集群网络.

现有网络插件

  • Flannel: Flannel是最早CoreOS团队开源的网络插件,用于让集群中不同节点创建的容器都具有集群内全局唯一的网络(集群外无法感知),也是当前Kubernetes开源方案中比较成熟的方案,支持HostGW和VXLAN模式
  • Calico: Calico是一个纯3层的数据中心网络方案,支持IPIP和BGP模式,后者可以无缝集成像OpenStack这种IaaS云架构,能够提供可控的VM、容器、裸机之间的IP通信,但是需要网络设备对BGP的支持(阿里云vpc子网内应该是不支持BGP的); 同时可以支持基于iptables的网络策略控制
  • Contiv: Contiv是思科开源的用于跨虚拟机、裸机、公有云或私有云的异构容器部署的开源容器网络架构,可支持2层、3层网络(通常也需要BGP的支持)
  • Terway: Terway是阿里云开源的基于VPC网络的CNI插件,支持VPC和ENI模式,后者可实现容器网络使用vpc子网网络

以上就是当前开源Kubernetes集群中使用较多的集中网络方案,我们的业务需求中也是需要打通容器内外的网络,因此在成本、效率以及稳定性上优先选择采用阿里云的Terway网络方案来满足我们的Kubernetes集群需求.

基于阿里云ECS搭建Terway网络的Kubernetes集群

注意: 阿里云容器服务ACK默认也支持两种网络,Flannel和Terway,前者和开源插件基本一致,后者支持VPC模式和ENI模式,VPC模式可实现容器网络使用vpc内交换机子网地址,但是默认无法和其他交换机下的ecs主机通信,ENI模式会给pod容器组分配一块弹性网卡来实现和集群外网络的互联互通,但Terway网络下的ENI模式需要部分特殊机型才可以支持。

由于ACK下Terway的ENI模式对机型的要求,我们采用购买ECS来自己搭建单节点集群测试Terway网络下容器的互联互通.

前提条件:

  • 已经创建了VPC子网
  • 在VPC子网下创建2个虚拟交换机(模拟Kubernetes集群网络和ECS网络)
  • 分别在两个子网购买两台ECS主机(模拟ECS到容器的互联互通)

注意: Terway网络插件官方验证过的os镜像为Centos 7.4/7.6,购买ecs时需要注意

1.使用kubeadm安装k8s单节点集群

注意: 因为要使用terway网络将pod和ecs网络打通,因此需要将内核参数rp_filter全部设置为0(对数据包源地址不进行校验)

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
# 更新yum源并安装k8s相关组件
$ yum update
$ cat <<EOF > /etc/yum.repos.d/kubernetes.repo
[kubernetes]
name=Kubernetes
baseurl=http://mirrors.aliyun.com/kubernetes/yum/repos/kubernetes-el7-x86_64
enabled=1
gpgcheck=0
repo_gpgcheck=0
gpgkey=http://mirrors.aliyun.com/kubernetes/yum/doc/yum-key.gpg
        http://mirrors.aliyun.com/kubernetes/yum/doc/rpm-package-key.gpg
EOF

$ yum clean all
$ yum install kubelet kubeadm kubectl --disableexcludes=kubernetes -y
$ yum install docker -y


# 启动kubelet
## 此时kubelet会无限重试,因为会链接apiserver
$ systemctl restart kubelet

# 启动docker
## 注意:需要注意kubelet中的cgroupfs类型要和docker的cgroupfs一致
$ systemctl restart docker

# 查看kubeadm 启动集群时所需镜像
# 注意:kubeadm默认使用的是谷歌的镜像仓库,可将镜像仓库换成阿里云镜像仓库
# 将k8s.gcr.io 替换成registry.cn-hangzhou.aliyuncs.com/google_containers 即可
$ kubeadm config images list
k8s.gcr.io/kube-apiserver:v1.16.2
k8s.gcr.io/kube-controller-manager:v1.16.2
k8s.gcr.io/kube-scheduler:v1.16.2
k8s.gcr.io/kube-proxy:v1.16.2
k8s.gcr.io/pause:3.1
k8s.gcr.io/etcd:3.3.15-0
k8s.gcr.io/coredns:1.6.2

# 初始化集群
## 注意:初始化时需要指定vpc的子网,否则后期可能会发现无法识别vpc子网
$ kubeadm  init  --pod-network-cidr=172.16.48.0/20
....
....
Your Kubernetes control-plane has initialized successfully!

To start using your cluster, you need to run the following as a regular user:

  mkdir -p $HOME/.kube
  sudo cp -i /etc/kubernetes/admin.conf $HOME/.kube/config
  sudo chown $(id -u):$(id -g) $HOME/.kube/config

You should now deploy a pod network to the cluster.
Run "kubectl apply -f [podnetwork].yaml" with one of the options listed at:
  https://kubernetes.io/docs/concepts/cluster-administration/addons/

Then you can join any number of worker nodes by running the following on each as root:

kubeadm join 172.16.62.70:6443 --token j4b3xp.78izi2bmitxxx \
    --discovery-token-ca-cert-hash sha256:fd1ff50cbabd4fb22cb9a866052fbdc0db7da662168cda702exxxxxxxx

# 接下来按照上述提示创建配置文件
$ mkdir -p $HOME/.kube
$ sudo cp -i /etc/kubernetes/admin.conf $HOME/.kube/config
$ sudo chown $(id -u):$(id -g) $HOME/.kube/config

# 查看k8s的node节点(当前处于NotReady状态,因为kubelet还没有成功启动)
$ # kubectl version
Client Version: version.Info{Major:"1", Minor:"16", GitVersion:"v1.16.2", GitCommit:"c97fe5036ef3df2967d086711e6c0c405941e14b", GitTreeState:"clean", BuildDate:"2019-10-15T19:18:23Z", GoVersion:"go1.12.10", Compiler:"gc", Platform:"linux/amd64"}
Server Version: version.Info{Major:"1", Minor:"16", GitVersion:"v1.16.2", GitCommit:"c97fe5036ef3df2967d086711e6c0c405941e14b", GitTreeState:"clean", BuildDate:"2019-10-15T19:09:08Z", GoVersion:"go1.12.10", Compiler:"gc", Platform:"linux/amd64"}

$ kubectl  get nodes
NAME                      STATUS     ROLES    AGE     VERSION
izbp18diszrt8m41b2fbpsz   NotReady   master   7m19s   v1.16.2

2. 给k8s集群创建terway网络

注意: 使用kubeadm创建的k8s集群是v1.16的,官方提供的yaml文件中需要稍微修改下DaemonSet的相关部分

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
# 给集群创建k8s的cni网络插件,也就是前面说的terway插件
# 需要修改阿里云相关的配置(ak,as,subnet,security_group)
$ curl -O https://raw.githubusercontent.com/BGBiao/k8s-ansible-playbooks/master/manifest/cni/terway/podnetwork.yaml

# 修改podnetwork.yaml中的配置(指定阿里云的ak和as认证信息以及vpc子网和安全组信息)
$ cat podnetwork.yaml
...
...
  eni_conf: |
    {
      "version": "1",
      "access_key": "your ak",
      "access_secret": "your as",
      "service_cidr": "your vpc subnet",
      "security_group": "your 安全组id",
      "max_pool_size": 5,
      "min_pool_size": 0
    }
....
....
          - name: Network
            value: "your vpc subnet"
....

# 创建terway网络
$ kubectl apply -f podnetwork.yaml
serviceaccount/terway created
clusterrole.rbac.authorization.k8s.io/terway-pod-reader created
clusterrolebinding.rbac.authorization.k8s.io/terway-binding created
configmap/eni-config created
daemonset.apps/terway created
customresourcedefinition.apiextensions.k8s.io/felixconfigurations.crd.projectcalico.org created
customresourcedefinition.apiextensions.k8s.io/bgpconfigurations.crd.projectcalico.org created
customresourcedefinition.apiextensions.k8s.io/ippools.crd.projectcalico.org created
customresourcedefinition.apiextensions.k8s.io/hostendpoints.crd.projectcalico.org created
customresourcedefinition.apiextensions.k8s.io/clusterinformations.crd.projectcalico.org created
customresourcedefinition.apiextensions.k8s.io/globalnetworkpolicies.crd.projectcalico.org created
customresourcedefinition.apiextensions.k8s.io/globalnetworksets.crd.projectcalico.org created
customresourcedefinition.apiextensions.k8s.io/networkpolicies.crd.projectcalico.org created

# 查看cni相关容器以及node状态
$ kubectl  get nodes
NAME                      STATUS   ROLES    AGE   VERSION
izbp18diszrt8m41b2fbpsz   Ready    master   28m   v1.16.2

$ kubectl  get pods -A | grep terway
kube-system   terway-b9vm8                                      2/2     Running   0          6m53s

至此,我们就已经完成了kubernetes的terway网络单节点集群,接下来就可以尝试让k8s集群中的pod来使用vpc的网络了,以便可以实现k8s集群内部的容器网络和其他ecs主机的网络是平行的.

3. 测试terway网络

注意: 我们使用kubeadm构建的k8s单节点集群,而kubeadm默认给master节点设置了taint,因此测试前需要去除taint

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
# 去除taint
$ kubectl taint nodes --all node-role.kubernetes.io/master-
node/izbp18diszrt8m41b2fbpsz untainted

# 默认创建一个vpc模式的deployment
$ kubectl  apply -f https://raw.githubusercontent.com/BGBiao/k8s-ansible-playbooks/master/manifest/cni/terway/nginx.yaml
namespace/myapp configured
deployment.apps/nginx-test created

# 可以看到容器网络地址其实是指定的vpc子网内地址
$ kubectl  get pods -n myapp  -o wide
NAME                         READY   STATUS    RESTARTS   AGE     IP            NODE                      NOMINATED NODE   READINESS GATES
nginx-test-d56c87dd9-26mzs   1/1     Running   0          2m40s   172.16.48.5   izbp18diszrt8m41b2fbpsz   <none>           <none>
nginx-test-d56c87dd9-hp2rv   1/1     Running   0          2m40s   172.16.48.4   izbp18diszrt8m41b2fbpsz   <none>           <none>

$ curl 172.16.48.4 -I
HTTP/1.1 200 OK
Server: nginx/1.17.5
Date: Sat, 26 Oct 2019 08:21:28 GMT
Content-Type: text/html
Content-Length: 612
Last-Modified: Tue, 22 Oct 2019 14:30:00 GMT
Connection: keep-alive
ETag: "5daf1268-264"
Accept-Ranges: bytes

$ curl 172.16.48.5 -I
HTTP/1.1 200 OK
Server: nginx/1.17.5
Date: Sat, 26 Oct 2019 08:21:31 GMT
Content-Type: text/html
Content-Length: 612
Last-Modified: Tue, 22 Oct 2019 14:30:00 GMT
Connection: keep-alive
ETag: "5daf1268-264"
Accept-Ranges: bytes

可以发现,在集群内部使用terway网络已经没有任何问题了,但是我们在其他ECS主机去访问pod网络时发现依然无法访问(因为默认使用的是terway的VPC模式,其实就是类似于calico的模式了.这个时候就需要用到eni模式了,即给k8s节点增加eni弹性网卡,然后pod的网络流量统一通过node节点的eni网卡传输,此时就可以很好的和整个内网vpc打通了)

4.测试ENI模式

注意: 在上面的nginx配置中增加limits: aliyun/eni: N 即可,需要注意的是N表示node节点上eni弹性网卡的数量,该数量取决于阿里云ecs不同规格对eni的限制.

阿里云ecs规格详情

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
# 注意:
# 由于实验中采用的是4c8g的k8s单节点集群,因此只能创建2个弹性网卡,这也就意味着如果不增加任何网络配置,该node节点最多只能运行2个和整个VPC网络中其他ecs主机互联互通的pod

$ cat nginx.yaml
---
apiVersion: apps/v1
kind: Deployment
metadata:
  name: nginx-v2
  namespace: myapp
spec:
  revisionHistoryLimit: 10
  strategy:
    type: RollingUpdate
    rollingUpdate:
      maxSurge: 1
      maxUnavailable: 1
  replicas: 2
  selector:
    matchLabels:
      app: nginx-v2
      profile: prod
  template:
    metadata:
      labels:
        app: nginx-v2
        profile: prod
    spec:
      containers:
      - name: nginx-v2
        image: nginx:latest
        imagePullPolicy: IfNotPresent
        resources:
          requests:
            cpu: 200m
            memory: 215Mi
          limits:
            cpu: 200m
            memory: 215Mi
            aliyun/eni: 1


# 创建带eni的pod
$ kubectl  apply -f nginx.yaml
deployment.apps/nginx-v2 configured

# 查看pod状态
$ kubectl  get pods -n myapp -o wide
NAME                         READY   STATUS    RESTARTS   AGE   IP             NODE                      NOMINATED NODE   READINESS GATES
nginx-test-d56c87dd9-26mzs   1/1     Running   0          19m   172.16.48.5    izbp18diszrt8m41b2fbpsz   <none>           <none>
nginx-test-d56c87dd9-hp2rv   1/1     Running   0          19m   172.16.48.4    izbp18diszrt8m41b2fbpsz   <none>           <none>
nginx-v2-7548466fc8-d4klv    1/1     Running   0          61s   172.16.62.74   izbp18diszrt8m41b2fbpsz   <none>           <none>
nginx-v2-7548466fc8-x7ft9    1/1     Running   0          61s   172.16.62.75   izbp18diszrt8m41b2fbpsz   <none>           <none>

# 在k8snode节点访问
$ curl 172.16.62.75 -I
HTTP/1.1 200 OK
Server: nginx/1.17.5
Date: Sat, 26 Oct 2019 08:38:20 GMT
Content-Type: text/html
Content-Length: 612
Last-Modified: Tue, 22 Oct 2019 14:30:00 GMT
Connection: keep-alive
ETag: "5daf1268-264"
Accept-Ranges: bytes

$ curl 172.16.62.74 -I
HTTP/1.1 200 OK
Server: nginx/1.17.5
Date: Sat, 26 Oct 2019 08:38:23 GMT
Content-Type: text/html
Content-Length: 612
Last-Modified: Tue, 22 Oct 2019 14:30:00 GMT
Connection: keep-alive
ETag: "5daf1268-264"
Accept-Ranges: bytes

# 此时发现创建的带eni和不带eni的两个pod在k8s集群内部已经完全可以访问

5. 测试集群内外部网络互联互通

注意: k8s集群使用的是vpc网络,因此默认集群访问外部ECS网络默认是没有问题,这里主要测试外部ECS网络是否可以直连pod网络进行通信

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
# 在同vpc环境下其他ecs主机上访问
# 首先分别ping 上述四个pod的网络(可以发现eni模式下容器默认可以ping通)
$ for i in 172.16.48.5 172.16.48.4 172.16.62.74 172.16.62.75 ;do ping -c 1 -w 1 $i;done
PING 172.16.48.5 (172.16.48.5) 56(84) bytes of data.

--- 172.16.48.5 ping statistics ---
2 packets transmitted, 0 received, 100% packet loss, time 999ms

PING 172.16.48.4 (172.16.48.4) 56(84) bytes of data.

--- 172.16.48.4 ping statistics ---
1 packets transmitted, 0 received, 100% packet loss, time 0ms

PING 172.16.62.74 (172.16.62.74) 56(84) bytes of data.
64 bytes from 172.16.62.74: icmp_seq=1 ttl=64 time=0.782 ms

--- 172.16.62.74 ping statistics ---
1 packets transmitted, 1 received, 0% packet loss, time 0ms
rtt min/avg/max/mdev = 0.782/0.782/0.782/0.000 ms
PING 172.16.62.75 (172.16.62.75) 56(84) bytes of data.
64 bytes from 172.16.62.75: icmp_seq=1 ttl=64 time=0.719 ms

--- 172.16.62.75 ping statistics ---
1 packets transmitted, 1 received, 0% packet loss, time 0ms
rtt min/avg/max/mdev = 0.719/0.719/0.719/0.000 ms

# 测试nginx服务(依然是带eni的网络可达)
$ for i in 172.16.48.5 172.16.48.4 172.16.62.74 172.16.62.75 ;do curl --connect-timeout 1 -I  $i;done
curl: (28) Connection timed out after 1001 milliseconds
curl: (28) Connection timed out after 1001 milliseconds
HTTP/1.1 200 OK
Server: nginx/1.17.5
Date: Sat, 26 Oct 2019 08:44:21 GMT
Content-Type: text/html
Content-Length: 612
Last-Modified: Tue, 22 Oct 2019 14:30:00 GMT
Connection: keep-alive
ETag: "5daf1268-264"
Accept-Ranges: bytes

HTTP/1.1 200 OK
Server: nginx/1.17.5
Date: Sat, 26 Oct 2019 08:44:21 GMT
Content-Type: text/html
Content-Length: 612
Last-Modified: Tue, 22 Oct 2019 14:30:00 GMT
Connection: keep-alive
ETag: "5daf1268-264"
Accept-Ranges: bytes

此时,我们查看该node节点上的网卡信息时可以看到,增加了两块辅助网卡

6. 其他问题

注意:前面我们提到过,如果使用eni模式,不同的ECS规格可以绑定的ENI弹性网卡是有限的,也就是说可以创建互联互通的容器是有限的,我们这里验证下

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
16
17
18
19
20
# 如果我们这个时候再创建带eni的pod时,就会发现无法创建成功(因为4c8g的ecs最大只支持两个eni)
$ kubectl  apply -f nginx-v3.yaml
deployment.apps/nginx-v3 created
[root@iZbp18diszrt8m41b2fbpsZ ~]# kubectl  get pods -n myapp -o wide
NAME                         READY   STATUS    RESTARTS   AGE   IP             NODE                      NOMINATED NODE   READINESS GATES
nginx-test-d56c87dd9-26mzs   1/1     Running   0          48m   172.16.48.5    izbp18diszrt8m41b2fbpsz   <none>           <none>
nginx-test-d56c87dd9-hp2rv   1/1     Running   0          48m   172.16.48.4    izbp18diszrt8m41b2fbpsz   <none>           <none>
nginx-v2-7548466fc8-d4klv    1/1     Running   0          29m   172.16.62.74   izbp18diszrt8m41b2fbpsz   <none>           <none>
nginx-v2-7548466fc8-x7ft9    1/1     Running   0          29m   172.16.62.75   izbp18diszrt8m41b2fbpsz   <none>           <none>
nginx-v3-79dd8fb956-4ghgb    0/1     Pending   0          2s    <none>         <none>                    <none>           <none>
nginx-v3-79dd8fb956-str2k    0/1     Pending   0          2s    <none>         <none>                    <none>           <none>

# 查看Pending的详情
$ kubectl  describe pods -n myapp nginx-v3-79dd8fb956-4ghgb
....
....
Events:
  Type     Reason            Age        From               Message
  ----     ------            ----       ----               -------
  Warning  FailedScheduling  <unknown>  default-scheduler  0/1 nodes are available: 1 Insufficient aliyun/eni.

可以发现,当使用terway网络的ENI模式时,如果该ecs可支持的弹性网卡达到限制,k8s就会调度失败。

所以问题就来了,通常情况下,我们是希望使用k8s来弹性扩容,我们会希望k8s节点上运行更多的pod,但用了terway网络之后我们发现,创建和k8s集群外ecs主机通信的pod数量竟然受eni的限制,这可得了?

其实不用担心,阿里云同学的回复是,这种情况下在vpc上设置静态路由即可实现node节点上的多pod和集群外ecs主机互通,此时ecs主机上的eni仅相当于是整个容器的网络出口,到这里其实我们就可以放心了,因为使用terway后,及时不用eni模式,pod网络也是全局唯一的,这个时候适当增加一些静态路由,即可实现整个vpc内k8s容器网络和容器外的ecs主机网络互联互通,很好的解决了我们一开始的问题。

注意:阿里云容器服务ACK的terway网络模式下的集群会默认创建一些路由规则,因此当你使用ACK集群时,只要购买了支持terway规格的节点,默认创建的容器都可以实现和外部ecs主机的互联互通,此时,该ecs上创建的弹性网卡将作为节点上k8s容器的网络出口,而ecs主机本身的eth0将仅作为管理网络而存在,感兴趣的同学可以点击阅读原文尝试使用阿里云ACK的terway网络模式.

阅读全文

知识星球

公众号