月度归档:2018年11月

从锦囊妙计想到的18–屏幕输出顺序和显示

这次真的很久没写东西了, 今天写点东西

前面说了很多编程的事情, 今天写点特别的东西, 是非常基础的, 但是却是非常重要, 同时也是很多计算机人容易忽略的!

一。 显示器和键盘的介绍

如下图

相信上面的 图片, 和图片里面的内容大家都能理解。

大家若是有用过电脑的人, 大家都知道。

看下面一张图

我们在键盘输入东西屏幕会显示, 这个过程可以叫做回显,  显示顺序是 

从上到下,   一行一行进行显示

 

 

在如下图, 看一行内的情况

我们从这个图片里面看到, 显示  是

一行内  先显示左边的, 然后向右进行

 

 

在如下图

这一行输入完成后, 键入enter后, 换行, 这个时候根据情况进行处理了。 这里是个命令行窗口因此, 键入enter会把输入作为命令的

 

用一句不太严格的话总结一下, 这个显示过程就是:

 

像水流一样, 从,    流动输出, 直到遇到 让他换行的东西, 他就 从上一行换到了, 下一行, 并且仍然是 从左向右 流动输出, 直到遇到 换行, 直到屏幕都满了, 然后屏幕开始向上滚动!

 

 

看似简单要牢记, 否则后面做题你不会!!!

 

二。 了解一下 程序输出的情况

如下图

代码是:   System.out.print("123456789");

通过上面, 我们要了解到

1. 一个黑色的窗口, 同eclipse中的白色控台窗口是 等效的, 就像同一个人 有时候穿了黑色的衣服, 有时候穿了白色的衣服。但是功能还是一样的。

2. System.out   就代表   黑色或者 白色的窗口(就是显示屏

3. print 是打印的意思, 其实就是输出

这个语句下去就是, 在  显示器上 输出一个123456789

 

三 程序换行

一)换行的情况

下面看一下程序换行的情况

如上图, 代码变化在

System.out.print("12\n34567\n89");

如上图,中红色的部分, 多了一个 “\n”的符号, 那么输出结果在 12后面更换了行, 在67后面也更换了行

看到, 字符串是 从左到右的 流动输出, 然后遇到 \n后, 换行到下一行的最前面, 然后接着从左到右输出34567然后遇到了“\n”然后再次换地方到 行首

 

二)多行输出的情况

如下图

代码如下:

System.out.print("1\n");
System.out.print("12\n");
System.out.print("123\n");
System.out.print("1234\n");
System.out.print("12345\n");
System.out.print("123456\n");

上面程序输出了一个  三角形,  程序关键要是  在1 或者2, 或者3 后面都有一个  \n 的字符, 这样 在屏幕上就会换行

那么上面的  的\n是否可以不写?

 

三)换行输出

如下图效果

代码如下:

System.out.println("1");
System.out.println("12");
System.out.println("123");
System.out.println("1234");
System.out.print("12345\n");
System.out.println("123456");

通过上面可以看到

println  是在输出字符串后面, 自动添加了 \n

四)输出一下有效的图形

下面需要输出如下的图形如何输出?

*
**
***
****
***
**
*

分析一下:

因此我们可以用程序打印字符串 *的方式输出

System.out.println("*");   输出一个*  然后换行

System.out.println("**");  输出两个** 然后换行

完整的程序如下:

public class testOutput {
 public static void main(String[] args) {
 System.out.println("*");
 System.out.println("**");
 System.out.println("***");
 System.out.println("****");
 System.out.println("***");
 System.out.println("**");
 System.out.println("*");
 }
}

五)在多点的 输出图形!

看下面的图形

*
**
***
****
****
****
****
****
***
**
*

下面的图形, 同上面四)类似, 无非多了几行, 因此可以简单修改一下:

System.out.println("*");
System.out.println("**");
System.out.println("***");
System.out.println("****");
System.out.println("****");
System.out.println("****");
System.out.println("****");
System.out.println("****");
System.out.println("***");
System.out.println("**");
System.out.println("*");

好了, 那么有了这个基础, 就可以前进一下了!

若是有一万行是否, 要写一万行的输出?  如何解决?

 

四. 重复的工作交给循环去做

一)循环的作用

前面说了,若是输出一万行, 难道还要写一万行代码吗?

先来个简单的

*****
*****
*****

....

....

....
*****

一共输出  100行, 如何写。   若是一行一行写, 就麻烦大了!

代码如下:

public class testOutput {
public static void main(String[] args) {
for(int i = 0; i < 100; i++)
{
System.out.println("*****");
}
}
}

看到上面的 一个循环程序, 能解决的问题

那么若是 需要一千行,  仅仅变为  for(int i = 0; i<1000; i++){ ..... }  就可以了。

 

二) 多层循环

上面是个简单的循环

那么下面图形如何输出?

*
**
***
****
*****
******

关键在 每行的递增, 参考代码如下:

public class testOutput {
public static void main(String[] args) {
for(int i = 0; i < 6; i++)
{
for(int j = 0; j < i; j++){
System.out.print("*");
}
System.out.println("*");
}
}
}

通过2层循环解决 问题!

这个是最好的办法吗?

那么还有什么办法解决这个问题,

更复杂的问题如何解决?

下图如何输出

这个做出来, 才说明上面你明白了!

别看就是一个打印, 估计肯定有不知道如何做的!

用到的源代码:输出

下一篇文章, 说明如何解决这个问题!  并且给出一个  相对标准的思路来解决它!

 

相关文章

计算机介绍                                  从锦囊妙计想到的01

流程图(分支结构)介绍          从锦囊妙计想到的02

线程介绍                                     从锦囊妙计想到的03

循环结构介绍                             从锦囊妙计想到的05

流程线程总结                             从锦囊妙计想到的06

cpu和线程定义、开始               从锦囊妙计想到的07

分布式计算                                  从锦囊妙计想到的08

分布式中事件和计数                 从锦囊妙计想到的09

内容总结                                     从锦囊妙计想到的10

数据类型,变量简介                从锦囊妙计想到的11

函数和参数                                从锦囊妙计想到的12

用户交互与数据输入输出       从锦囊妙计想到的13

人机交互界面                            从锦囊妙计想到的15 

过程与对象                                从锦囊妙计想到的16

同步和异步                                从锦囊妙计想到的17

顺序打印                                    从锦囊妙计想到的18

数据输入输出                            从锦囊妙计想到的19

屏幕坐标和打印                        从锦囊妙计想到的20

java函数控制输出                     从锦囊妙计想到的21

逐步细化解决复杂问题           从锦囊妙计想到的22

java入门                                    从锦囊妙计想到的23

java复杂过程分析                   从锦囊妙计想到的25

中间辅助功能解决问题          从锦囊妙计想到的26

叠加操作输出复杂图形          从锦囊妙计想到的27

时间和空间                               从锦囊妙计想到的28

编写边测解决问题                  从锦囊妙计想到的29

让程序动起来                          从锦囊妙计想到的30

程序往复运动                           从锦囊妙计想到的31

运用Kubernetes进行分布式负载测试

前言

Github地址https://github.com/rootsongjc/distributed-load-testing-using-kubernetes该教程描述如何在Kubernetes中进行分布式负载均衡测试,包括一个web应用、docker镜像和Kubernetes controllers/services。更多资料请查看Distributed Load Testing Using Kubernetes 。

注意:该测试是在我自己本地搭建的kubernetes集群上测试的,不需要使用Google Cloud Platform。

准备

不需要GCE及其他组件,你只需要有一个kubernetes集群即可。

如果你还没有kubernetes集群,可以参考kubernetes-handbook部署一个。

部署Web应用

sample-webapp 目录下包含一个简单的web测试应用。我们将其构建为docker镜像,在kubernetes中运行。你可以自己构建,也可以直接用这个我构建好的镜像index.tenxcloud.com/jimmy/k8s-sample-webapp:latest

在kubernetes上部署sample-webapp。

$ cd kubernetes-config
$ kubectl create -f sample-webapp-controller.yaml
$ kubectl create -f kubectl create -f sample-webapp-service.yaml

部署Locust的Controller和Service

locust-masterlocust-work使用同样的docker镜像,修改cotnroller中spec.template.spec.containers.env字段中的value为你sample-webapp service的名字。

- name: TARGET_HOST
  value: http://sample-webapp:8000

创建Controller Docker镜像(可选)

locust-masterlocust-work controller使用的都是locust-tasks docker镜像。你可以直接下载gcr.io/cloud-solutions-http://olz1di9xf.bkt.clouddn.com/locust-tasks,也可以自己编译。自己编译大概要花几分钟时间,镜像大小为820M。

$ docker build -t index.tenxcloud.com/jimmy/locust-tasks:latest .
$ docker push index.tenxcloud.com/jimmy/locust-tasks:latest

注意:我使用的是时速云的镜像仓库。

每个controller的yaml的spec.template.spec.containers.image 字段指定的是我的镜像:

image: index.tenxcloud.com/jimmy/locust-tasks:latest

部署Locust-Master

$ kubectl create -f locust-master-controller.yaml
$ kubectl create -f locust-master-service.yaml

部署Locust-Worker

Now deploy locust-worker-controller:

$ kubectl create -f locust-worker-controller.yaml

你可以很轻易的给work扩容,通过命令行方式:

$ kubectl scale --replicas=20 replicationcontrollers locust-worker

当然你也可以通过WebUI:Dashboard – Workloads – Replication Controllers –ServiceName – Scale来扩容。

20170425173628

配置Traefik

参考kubernetes的traefik ingress安装,在ingress.yaml中加入如下配置:

  - host: traefik.locust.io
    http:
      paths:
      - path: /
        backend:
          serviceName: locust-master
          servicePort: 8089

然后执行kubectl replace -f ingress.yaml即可更新traefik。

通过Traefik的dashboard就可以看到刚增加的traefik.locust.io节点。

20170425173638

执行测试

打开http://traefik.locust.io页面,点击Edit输入伪造的用户数和用户每秒发送的请求个数,点击Start Swarming就可以开始测试了。

20170425173648

在测试过程中调整sample-webapp的pod个数(默认设置了1个pod),观察pod的负载变化情况。

20170425173657

从一段时间的观察中可以看到负载被平均分配给了3个pod。

在locust的页面中可以实时观察也可以下载测试结果。

20170425173705

 

来源:  https://www.kubernetes.org.cn/1895.html

 

在阿里云上部署生产级别Kubernetes集群

阿里云是国内非常受欢迎的基础云平台,随着Kubernetes的普及,越来越多的企业开始筹划在阿里云上部署自己的Kubernetes集群。 本文将结合实战中总结的经验,分析和归纳一套在阿里云上部署生产级别Kubernetes集群的方法。 文中所采取的技术方案具有一定的主观性,供各位读者参考。在实践中可以根据具体使用场景进行优化。

目标

当我们刚接触Kubernetes进行测试集群的搭建时,往往会选择一篇已有的教程,照着教程完成集群搭建。我们很少去质疑教程作者每一步操作的合理性, 只想快点把集群搭建起来,早点开始实际的上手体验。

与测试集群不同,对于生产级别的部署,我们会有更加严格的要求,更加强调合理的规划以及每个步骤的合理性以及可管理性。以下是我们设定的目标:

  • 没有单点故障。任何一台服务器离线不会影响到整个集群的正常运转
  • 充分利用阿里云原生提供的SLB,云盘等工具,支持Volume挂载,LoadBalancer类型的Service等Kubernetes基础功能。 获得完整的Kubernetes使用体验。
  • 在不牺牲安全性和稳定性的前提下,尽可能降低日常运维所需要的投入,将可以由程序完成的重复性工作自动化
  • 支持随着业务规模扩展的动态集群规模扩展

因为篇幅的原因,以下内容将不作为本文的目标,留待日后再做分享:

  • 集群运行成本控制
  • 监控、日志等运维系统的搭建
  • 安全防护以及权限设计

现状

目前Kubernetes主要支持的云平台还是海外的几大主流平台,但是对比阿里云提供的基础设施,我们已经具有了基本相同的底层环境。 阿里云提供的VPC,云盘,SLB,NAS等组件,都为搭建生成级别Kubernetes集群提供了很好的支持。充分利用这些组件, 我们应该可以搭建出完整可用的Kubernetes集群。但是仔细研究Kubernetes的代码我们会发现,阿里云的CloudProvider暂时没有被合并到上游当中, 所以我们需要设计方案来解决Kubernetes暂时还没有原生阿里云支持的问题。

Kubernetes生态圈发展迅速,目前已经有了像kops这种集群自动创建工具帮助运维工程师创建集群。但是目前已有的集群创建工具均只支持国外的云平台, 使得国内云平台的用户只能采取手动搭建的办法。像kubeadm这种半自动工具还可以使用,也算是减轻了不少负担。从目前状况来看, 运维的负担依然很严重,为我们实现生产级别部署所追求的自动化、规模化的目标带来了不小的障碍。

由于网络原因造成的镜像拉取困难也给我们创建Kubernetes集群制造了不小的麻烦,好在阿里云一直在致力于解决这个问题,为用户提供了镜像拉取加速服务以及重要镜像的Mirror。

另一个问题是操作系统镜像的问题,在后面分析操作系统的时候再详细展开。

架构

基于消除单点故障以及降低复杂度的考虑,我们设计了由5台服务器,分两个服务器组构成的Kubernetes集群的控制节点,并视业务需求情况由N台服务器, 分多个服务器组,构成集群的运行节点。如下图所示:

在设计这一架构时,我们考虑了以下几点:

  • 整个集群的主要构成元素为服务器组。一组服务器具有相同的硬件配置,服务相同的功能,在软件配置上也基本相同。这样为服务器的自动化管理打下了很好的基础。
  • 由3台服务器组成的etcd集群,在其中任何一台服务器离线时,均可以正常工作。为整个Kubernetes集群的数据持久化保存提供了稳定可靠的基础
  • 2台同时运行着Kubernetes核心组件kube-apiserver,kube-controller-manager,kube-scheduler的服务器,为整个集群的控制平面提供了高可用性。
  • 多个运行节点服务器组,有着不同的CPU,内存,磁盘配置。让我们可以灵活的根据业务对运行环境的要求来选择不同的服务器组。

集群搭建

在有了架构蓝图后,接下来让我们来实际搭建这个集群。

操作系统选型

搭建集群首先会面临的问题是,选什么配置的服务器,用什么操作系统。服务器硬件配置相对好解决,控制节点在业务量不大的时候选择入门级别的配置再随着业务增长不断提升即可, 运行节点应当根据业务需要来选择,可能要做一些尝试才能定下来最适合的硬件配置。比较困难的选择是操作系统的选型。

只要是使用较新的Kernel的Linux主机,均可以用来运行Kubernetes集群,但是发行版的选择却需要从多个方面来考虑。在这里我们选择了CoreOS作为最基础的操作系统。 做出这一选择是基于以下这些因素:

  • CoreOS是专门为运行容器设计的操作系统,非常适合用来运行Kubernetes集群
  • CoreOS去除了包管理,使用镜像升级的方式,大大简化了运维的复杂度
  • CoreOS可以使用cloud-init,方便的对服务器进行初始化
  • 阿里云提供了CoreOS的支持

CoreOS的详细介绍,大家可以参考官方的文档,在这里就不展开了。需要指出的是阿里云提供的CoreOS镜像版本较低,需要先进行升级才能正常使用,增加了不少麻烦。 希望以后阿里云能够提供最新版本的CoreOS镜像,改善这一问题。

CoreOS版本升级

由于网络的原因,CoreOS在国内不能正常进行升级。我们需要在国内搭建升级服务器。CoreRoller是一个可选项。具体的搭建可以参考相关文档,在这里就略过了。

在顺利搭建好升级服务器之后,可以修改/etc/coreos/update.conf,添加SERVER=https://YOUR_SERVER/v1/update/这一条配置,然后使用以下指令来升级服务器:

sudo systemctl restart update-engine
update_engine_client -update

我们搭建了自己的升级服务器,如果有需要的朋友可以联系我们获得服务器地址。后面所有启动的CoreOS服务器,我们均假设管理员已经提前完成了版本升级的工作, 在流程中不再重复。如果阿里云开始提供最新的CoreOS镜像,那这一步可以省略掉。

引入kube-aliyun解决兼容问题

在前面分析现状时,我们提到了阿里云的CloudProvider暂时还未被并入Kubernetes,所以我们需要额外的工具来解决原生Kubernetes与阿里云之间的兼容问题。

针对这一问题,我们开发了一款名为kube-aliyun的工具。kube-aliyun以controller的形式运行在集群内部,提供以下的功能:

  • 配置VPC路由,使得集群内的Pod网络互通
  • 使用SLB支持LoadBalancer类型的Service
  • 使用flexv支持云盘类型的Volume的动态挂载以及解除挂载

容器网络方案选型

Kubernetes要求所有集群内部的Pod可以不经过NAT互访,所以我们需要在服务器网络之上再搭建一层容器网络。容器网络的实现方案有多种,比如常见的flannel,calico等。 在这里我们选择了更加简单的kubenet +hostroutes方案。hostroutes是我们专门配合kubenet开发的路由配置工具, 详细的信息可以参考它的Github主页,以及这篇文档。

如果集群规模较小,我们还可以使用kube-aliyun的VPC路由配置功能。这样主机上不用对路由做任何的配置,所有的网络路由交给了VPC来完成,也不失为一种简单易用的方案。

SSL证书管理

SSL证书和配置是使用Kubernetes过程中非常容易出问题的点。这里推荐使用cfssl来做证书的生成和管理,主要看重了cfssl简单易用的特点,比起openssl更加容易操作和自动化。 cfssl的使用方法请参考官方的文档,这里不再重复。

在Kubernetes集群当中,我们一共需要生成4种类型的证书。另外etcd也可以通过证书进行验证和保护。出于复杂度考虑今天暂时不使用。

API Server证书

API Server证书主要用于客户端程序连接apiserver时进行加密和验证。可以使用以下模板作为CSR,填入相应的参数后生成:

{
    "CN": "${CLUSTER_NAME}",
    "hosts": [
        "kubernetes",
        "kubernetes.default",
        "kubernetes.default.svc",
        "kubernetes.default.svc.cluster.local",
        "10.3.0.1",
        "${SERVER_PRIVATE_IP}",
        "${SERVER_PUBLIC_IP}",
        "${LOAD_BALANCER_IP}"
    ],
    "key": {
        "algo": "ecdsa",
        "size": 256
    }
}

之后将以${APISERVER_PEM}和${APISERVER_KEY}分别表示生成出的证书和私匙。

kubelet证书

kubelet证书用于系统组件访问kubelet内置的HTTP Server,获取运行状态或者调用kubelet提供的功能时进行加密和验证。CSR模板如下:

{
    "CN": "${SERVER_NAME}",
    "hosts": [
        "${SERVER_NAME}",
        "${SERVER_PRIVATE_IP}",
        "${SERVER_PUBLIC_IP}"
    ],
    "key": {
        "algo": "ecdsa",
        "size": 256
    }
}

之后将以${SERVER_PEM}和${SERVER_KEY}分别表示生成出的证书和私匙。

Service Account证书

Service Account证书用于生成各个Namespace默认的token,以及进行token验证。在集群内部服务访问API Server时会使用这个token进行身份认证。CSR模板如下:

{
    "CN": "service-account",
    "hosts": [
        "service-account"
    ],
    "key": {
        "algo": "ecdsa",
        "size": 256
    }
}

之后将以${SERVICEACCOUNT_PEM}和${SERVICEACCOUNT_KEY}分别表示生成出的证书和私匙。

kubectl证书

kubectl证书用于管理员或者用户远程访问集群,下达各种指令时的身份认证。CSR模板如下:

{
    "CN": "${USERNAME}",
    "hosts": [
        "${USERNAME}"
    ],
    "key": {
        "algo": "ecdsa",
        "size": 256
    }
}

在创建集群时并不需要这一证书,但是在集群创建完成后需要为所有用户生成证书,才能配置好本地的kubectl,获得访问集群的权限。

创建VPC

在阿里云控制台界面,可以很方便的创建VPC,选择目标可用区,并创建服务器所在网段即可。这里我们用10.99.0.0/24这个网段,读者可以根据自身业务设计选择适合的网段。 这里我们选择的网段,除去阿里云提供的服务以及内网SLB占掉的IP地址,至少有200个以上的空余地址,足以满足绝大部分场景下的规模需要。

搭建etcd集群

CoreOS对etcd有原生的支持,我们可以使用CoreOS官方提供的discovery服务快速的完成etcd集群的搭建。

首先访问https://discovery.etcd.io/new?size=3,将得到的地址以及服务器IP地址填入以下文件当中:

#cloud-config

coreos:
  etcd2:
    discovery: "https://discovery.etcd.io/<token>"
    advertise-client-urls: "http://${SERVER_PRIVATE_IP}:2379"
    initial-advertise-peer-urls: "http://${SERVER_PRIVATE_IP}:2380"
    listen-client-urls: "http://0.0.0.0:2379"
    listen-peer-urls: "http://${SERVER_PRIVATE_IP}:2380"
  units:
    - name: "start-etcd.service"
      command: "start"
      enable: true
      content: |-
        [Service]
        Type=oneshot
        ExecStart=/usr/bin/systemctl start etcd2
        [Install]
        WantedBy=multi-user.target

接下来在VPC中创建3台服务器,并在服务器上创建文件cloud-init.yaml包含上面的内容。再使用coreos-cloudinit -from-file cloud-init.yaml对服务器进行初始化。

一切顺利的话,各台服务器上的etcd服务将找到其他的节点,共同组成一个高可用的etcd集群。

搭建Master服务器组

在Master服务器组上,我们将在每台服务器上通过kubelet运行kube-aliyun、kube-controller-manager、kube-scheduler这几个组件。 为了简化配置流程,我们依然使用cloud-init来进行服务器初始化。将etcd服务器组的内网IP填入以下文件:

#cloud-config

coreos:
  units:
    - name: "docker.service"
      drop-ins:
        - name: "50-docker-opts.conf"
          content: |
            [Service]
            Environment=DOCKER_OPTS='--registry-mirror="https://${YOUR_MIRROR}.mirror.aliyuncs.com"'
    - name: "kubelet.service"
      command: "start"
      enable: true
      content: |-
        [Service]
        Environment=KUBELET_VERSION=v1.5.1_coreos.0
        Environment=KUBELET_ACI=kubeup.com/aci/coreos/hyperkube
        Environment="RKT_OPTS=--uuid-file-save=/var/run/kubelet-pod.uuid \
          --trust-keys-from-https \
          --volume dns,kind=host,source=/etc/resolv.conf \
          --mount volume=dns,target=/etc/resolv.conf \
          --volume var-log,kind=host,source=/var/log \
          --mount volume=var-log,target=/var/log \
          --volume lib-modules,kind=host,source=/lib/modules \
          --mount volume=lib-modules,target=/lib/modules"
        ExecStartPre=/usr/bin/systemctl stop update-engine
        ExecStartPre=/usr/bin/mkdir -p /etc/kubernetes/manifests
        ExecStartPre=/usr/bin/mkdir -p /var/log/containers
        ExecStartPre=-/usr/bin/rkt rm --uuid-file=/var/run/kubelet-pod.uuid
        ExecStart=/usr/lib/coreos/kubelet-wrapper \
          --api_servers=http://localhost:8080 \
          --register-schedulable=false \
          --allow-privileged=true \
          --config=/etc/kubernetes/manifests \
          --cluster-dns=10.3.0.10 \
          --node-ip=${SERVER_PRIVATE_IP} \
          --hostname-override=${SERVER_PRIVATE_IP} \
          --cluster-domain=cluster.local \
          --network-plugin=kubenet \
          --tls-cert-file=/etc/kubernetes/ssl/server.pem \
          --tls-private-key-file=/etc/kubernetes/ssl/server-key.pem \
          --pod-infra-container-image=registry.aliyuncs.com/archon/pause-amd64:3.0
        ExecStop=-/usr/bin/rkt stop --uuid-file=/var/run/kubelet-pod.uuid
        Restart=always
        RestartSec=10
        User=root
        [Install]
        WantedBy=multi-user.target
write_files:
  - path: "/etc/kubernetes/manifests/kube-apiserver.yaml"
    permissions: "0644"
    owner: "root"
    content: |
      apiVersion: v1
      kind: Pod
      metadata:
        name: kube-apiserver
        namespace: kube-system
      spec:
        hostNetwork: true
        containers:
        - name: kube-apiserver
          image: registry.aliyuncs.com/archon/hyperkube-amd64:v1.5.1
          command:
          - /hyperkube
          - apiserver
          - --bind-address=0.0.0.0
          - --etcd-servers=http://${ETCD_SERVER1_IP}:2379,http://${ETCD_SERVER2_IP}:2379,http://${ETCD_SERVER3_IP}:2379
          - --allow-privileged=true
          - --service-cluster-ip-range=10.3.0.0/24
          - --runtime-config=extensions/v1beta1=true,extensions/v1beta1/thirdpartyresources=true
          - --secure-port=443
          - --advertise-address=${LOAD_BALANCER_IP}
          - --admission-control=NamespaceLifecycle,NamespaceExists,LimitRanger,SecurityContextDeny,ServiceAccount,ResourceQuota
          - --tls-cert-file=/etc/kubernetes/ssl/apiserver.pem
          - --tls-private-key-file=/etc/kubernetes/ssl/apiserver-key.pem
          - --service-account-key-file=/etc/kubernetes/ssl/serviceaccount-key.pem
          - --client-ca-file=/etc/kubernetes/ssl/ca.pem
          ports:
          - containerPort: 443
            hostPort: 443
            name: https
          - containerPort: 8080
            hostPort: 8080
            name: local
          volumeMounts:
          - mountPath: /etc/kubernetes/ssl
            name: ssl-certs-kubernetes
            readOnly: true
          - mountPath: /etc/ssl/certs
            name: ssl-certs-host
            readOnly: true
        volumes:
        - hostPath:
            path: /etc/kubernetes/ssl
          name: ssl-certs-kubernetes
        - hostPath:
            path: /usr/share/ca-certificates
          name: ssl-certs-host
  - path: "/etc/kubernetes/manifests/kube-proxy.yaml"
    permissions: "0644"
    owner: "root"
    content: |
      apiVersion: v1
      kind: Pod
      metadata:
        name: kube-proxy
        namespace: kube-system
      spec:
        hostNetwork: true
        containers:
        - name: kube-proxy
          image: registry.aliyuncs.com/archon/hyperkube-amd64:v1.5.1
          command:
          - /hyperkube
          - proxy
          - --master=http://127.0.0.1:8080
          - --proxy-mode=iptables
          securityContext:
            privileged: true
          volumeMounts:
          - mountPath: /etc/ssl/certs
            name: ssl-certs-host
            readOnly: true
        volumes:
        - hostPath:
            path: /usr/share/ca-certificates
          name: ssl-certs-host
  - path: "/etc/kubernetes/manifests/kube-controller-manager.yaml"
    permissions: "0644"
    owner: "root"
    content: |
      apiVersion: v1
      kind: Pod
      metadata:
        name: kube-controller-manager
        namespace: kube-system
      spec:
        hostNetwork: true
        containers:
        - name: kube-controller-manager
          image: registry.aliyuncs.com/archon/hyperkube-amd64:v1.5.1
          command:
          - /hyperkube
          - controller-manager
          - --master=http://127.0.0.1:8080
          - --leader-elect=true
          - --service-account-private-key-file=/etc/kubernetes/ssl/serviceaccount-key.pem
          - --root-ca-file=/etc/kubernetes/ssl/ca.pem
          - --allocate-node-cidrs=true
          - --cluster-cidr=10.2.0.0/16
          - --configure-cloud-routes=false
          livenessProbe:
            httpGet:
              host: 127.0.0.1
              path: /healthz
              port: 10252
            initialDelaySeconds: 15
            timeoutSeconds: 1
          volumeMounts:
          - mountPath: /etc/kubernetes/ssl
            name: ssl-certs-kubernetes
            readOnly: true
          - mountPath: /etc/ssl/certs
            name: ssl-certs-host
            readOnly: true
        volumes:
        - hostPath:
            path: /etc/kubernetes/ssl
          name: ssl-certs-kubernetes
        - hostPath:
            path: /usr/share/ca-certificates
          name: ssl-certs-host
  - path: "/etc/kubernetes/manifests/kube-scheduler.yaml"
    permissions: "0644"
    owner: "root"
    content: |
      apiVersion: v1
      kind: Pod
      metadata:
        name: kube-scheduler
        namespace: kube-system
      spec:
        hostNetwork: true
        containers:
        - name: kube-scheduler
          image: registry.aliyuncs.com/archon/hyperkube-amd64:v1.5.1
          command:
          - /hyperkube
          - scheduler
          - --master=http://127.0.0.1:8080
          - --leader-elect=true
          livenessProbe:
            httpGet:
              host: 127.0.0.1
              path: /healthz
              port: 10251
            initialDelaySeconds: 15
            timeoutSeconds: 1
  - path: "/etc/kubernetes/manifests/kube-aliyun.yaml"
    permissions: "0644"
    owner: "root"
    content: |
      apiVersion: v1
      kind: Pod
      metadata:
        name: aliyun-controller
        namespace: kube-system
      spec:
        hostNetwork: true
        containers:
        - name: aliyun-controller
          image: registry.aliyuncs.com/kubeup/kube-aliyun
          command:
          - /aliyun-controller
          - --server=http://127.0.0.1:8080
          - --leader-elect=true
          - --cluster-cidr=10.2.0.0/16
          env:
          - name: ALIYUN_ACCESS_KEY
            valueFrom:
              secretKeyRef:
                name: aliyun-creds
                key: accessKey
          - name: ALIYUN_ACCESS_KEY_SECRET
            valueFrom:
              secretKeyRef:
                name: aliyun-creds
                key: accessKeySecret
          - name: ALIYUN_REGION
            value: ${YOUR_VPC_REGION}
          - name: ALIYUN_VPC
            value: ${YOUR_VPC_ID}
          - name: ALIYUN_ROUTER
            value: ${YOUR_ROUTER_ID}
          - name: ALIYUN_ROUTE_TABLE
            value: ${YOUR_ROUTE_TABLE_ID}
          - name: ALIYUN_VSWITCH
            value: ${YOUR_VSWITCH_ID}
  - path: "/etc/kubernetes/ssl/ca.pem"
    permissions: "0644"
    owner: "root"
    content: |
      ${CA_PEM}
  - path: "/etc/kubernetes/ssl/apiserver.pem"
    permissions: "0644"
    owner: "root"
    content: |
      ${APISERVER_PEM}
  - path: "/etc/kubernetes/ssl/apiserver-key.pem"
    permissions: "0600"
    owner: "root"
    content: |
      ${APISERVER_KEY}
  - path: "/etc/kubernetes/ssl/serviceaccount.pem"
    permissions: "0644"
    owner: "root"
    content: |
      ${SERVICEACCOUNT_PEM}
  - path: "/etc/kubernetes/ssl/serviceaccount-key.pem"
    permissions: "0600"
    owner: "root"
    content: |
      ${SERVICEACCOUNT_KEY}
  - path: "/etc/kubernetes/ssl/server.pem"
    permissions: "0644"
    owner: "root"
    content: |
      ${SERVER_PEM}
  - path: "/etc/kubernetes/ssl/server-key.pem"
    permissions: "0600"
    owner: "root"
    content: |
      ${SERVER_KEY}

接下来使用cloud-init在新创建的2台服务器上完成服务器初始化。经过一定时间的镜像拉取,所有组件将正常启动,这时可以在主机上用kubectl来验证服务器的启动状态。

创建LoadBalancer

如果直接使用Master服务器组当中的任何一台服务器,会存在单点故障。我们使用阿里云控制台创建一个内网的SLB服务,在两台服务器之上提供一个稳定的负载均衡的apiserver。 具体的操作流程请参考相关阿里云的文档,因为apiserver暴露在443端口,我们只需要配置443端口的负载均衡即可。

搭建Node服务器组

Node服务器组的初始化与Master服务器组的初始化类似。我们可以一次性启动N台服务器,然后在每台服务器上用以下配置进行初始化:

#cloud-config

coreos:
  units:
    - name: "docker.service"
      drop-ins:
        - name: "50-docker-opts.conf"
          content: |
            [Service]
            Environment=DOCKER_OPTS='--registry-mirror="https://${YOUR_MIRROR}.mirror.aliyuncs.com"'
    - name: "kubelet.service"
      command: "start"
      enable: true
      content: |-
        [Service]
        Environment=KUBELET_VERSION=v1.5.1_coreos.0
        Environment=KUBELET_ACI=kubeup.com/aci/coreos/hyperkube
        Environment="RKT_OPTS=--uuid-file-save=/var/run/kubelet-pod.uuid \
          --trust-keys-from-https \
          --volume dns,kind=host,source=/etc/resolv.conf \
          --mount volume=dns,target=/etc/resolv.conf \
          --volume var-log,kind=host,source=/var/log \
          --mount volume=var-log,target=/var/log \
          --volume lib-modules,kind=host,source=/lib/modules \
          --mount volume=lib-modules,target=/lib/modules"
        ExecStartPre=/usr/bin/systemctl stop update-engine
        ExecStartPre=/usr/bin/mkdir -p /etc/kubernetes/manifests
        ExecStartPre=/usr/bin/mkdir -p /var/log/containers
        ExecStartPre=-/usr/bin/rkt rm --uuid-file=/var/run/kubelet-pod.uuid
        ExecStart=/usr/lib/coreos/kubelet-wrapper \
          --api_servers=https://${APISERVER_ENDPOINT}:443 \
          --register-schedulable=true \
          --allow-privileged=true \
          --pod-manifest-path=/etc/kubernetes/manifests \
          --cluster-dns=10.3.0.10 \
          --node-ip=${SERVER_PRIVATE_IP} \
          --hostname-override=${SERVER_PRIVATE_IP} \
          --cluster-domain=cluster.local \
          --network-plugin=kubenet \
          --kubeconfig=/etc/kubernetes/node-kubeconfig.yaml \
          --tls-cert-file=/etc/kubernetes/ssl/server.pem \
          --tls-private-key-file=/etc/kubernetes/ssl/server-key.pem \
          --pod-infra-container-image=registry.aliyuncs.com/archon/pause-amd64:3.0
        ExecStop=-/usr/bin/rkt stop --uuid-file=/var/run/kubelet-pod.uuid
        Restart=always
        RestartSec=10
        User=root
        [Install]
        WantedBy=multi-user.target
write_files:
  - path: "/etc/kubernetes/manifests/kube-proxy.yaml"
    permissions: "0644"
    owner: "root"
    content: |
      apiVersion: v1
      kind: Pod
      metadata:
        name: kube-proxy
        namespace: kube-system
      spec:
        hostNetwork: true
        containers:
        - name: kube-proxy
          image: registry.aliyuncs.com/archon/hyperkube-amd64:v1.5.1
          command:
          - /hyperkube
          - proxy
          - --master=https://${APISERVER_ENDPOINT}:443
          - --kubeconfig=/etc/kubernetes/node-kubeconfig.yaml
          securityContext:
            privileged: true
          volumeMounts:
          - mountPath: /etc/ssl/certs
            name: ssl-certs-host
            readOnly: true
          - mountPath: /etc/kubernetes/node-kubeconfig.yaml
            name: kubeconfig
            readOnly: true
          - mountPath: /etc/kubernetes/ssl
            name: etc-kube-ssl
            readOnly: true
        volumes:
        - hostPath:
            path: /usr/share/ca-certificates
          name: ssl-certs-host
        - hostPath:
            path: /etc/kubernetes/node-kubeconfig.yaml
          name: kubeconfig
        - hostPath:
            path: /etc/kubernetes/ssl
          name: etc-kube-ssl
  - path: "/etc/kubernetes/node-kubeconfig.yaml"
    permissions: "0644"
    owner: "root"
    content: |
      apiVersion: v1
      kind: Config
      clusters:
      - name: local
        cluster:
          certificate-authority: /etc/kubernetes/ssl/ca.pem
      users:
      - name: kubelet
        user:
          client-certificate: /etc/kubernetes/ssl/server.pem
          client-key: /etc/kubernetes/ssl/server-key.pem
      contexts:
      - context:
          cluster: local
          user: kubelet
        name: kubelet-context
      current-context: kubelet-context
  - path: "/etc/kubernetes/ssl/ca.pem"
    permissions: "0644"
    owner: "root"
    content: |
      ${CA_PEM}
  - path: "/etc/kubernetes/ssl/server.pem"
    permissions: "0644"
    owner: "root"
    content: |
      ${SERVER_PEM}
  - path: "/etc/kubernetes/ssl/server-key.pem"
    permissions: "0600"
    owner: "root"
    content: |
      ${SERVER_KEY}

部署kube-aliyun与hostroutes

kube-aliyun的Pod已经在Master服务器组的描述文件中进行了定义,但是这个Pod会因为缺少必要的Secret无法启动。 我们创建这个Secret,来激活kube-aliyun:

kubectl create secret generic aliyun-creds --namespace=kube-system --from-literal=accessKey=${YOUR_ACCESS_KEY} --from-literal=accessKeySecret=${YOUR_ACCESS_KEY_SECRET}

hostroutes是以DaemonSet的形式进行部署的。使用以下定义文件:

apiVersion: extensions/v1beta1
kind: DaemonSet
metadata:
  name: hostrouts
  labels:
    app: hostroutes
spec:
  template:
    metadata:
      name: hostroutes
      labels:
        app: hostroutes
    spec:
      hostNetwork: true
      containers:
        - resources:
            requests:
              cpu: 0.20
          securityContext:
            privileged: true
          image: kubeup/hostroutes
          name: hostroutes
          command: [ "/hostroutes", "--in-cluster" ]

自动化部署和运维

经过前面的手工集群搭建,我们可以发现明显的重复模式。所有的服务器我们均是使用coreos-cloudinit -from-file cloud-init.yaml这个指令完成初始化, 唯一不同的就是不同的服务器组有不同的配置文件模板。这时动手能力强的同学已经可以自己进行简单的编程来简化服务器初始化流程了。

这种初始化模式是我们有意设计的,目的是为了使用程序来实现服务器的自动化运维。我们将以上的实践抽象成了一款叫做Archon的集群管理系统, 方便用户使用描述式的方法来创建和管理集群,这样一个指令就可以完成集群扩容、升级这些日常运维工作,大大降低了运维工程师的工作负担。

关于Archon系统,这里就不详细介绍了。有兴趣的朋友可以访问项目的Github地址https://github.com/kubeup/archon 了解更多的信息。

总结

在本文中,我们首先分析了在阿里云上部署生产级别Kubernetes所具有的优势以及面临的挑战。接着演示了基于CoreOS,使用kube-aliyun以及hostroutes来解决目前存在问题的方法。 最后提出使用archon来进行自动化集群创建以及运维的可能性。整个集群设计充分考虑了生产环境的高可用性需求,同时也使得管理员在运维时可以将出故障的服务器离线进行维护, 或者使用RollingUpdate的方法删除老服务器创建新服务器来下发更新。

由于篇幅所限,部分步骤并没有进行详细的解说,读者在阅读时可以更加注重对原理和思路的理解。在理解了设计思路之后,大可采用自动化工具进行集群的创建和运维来提升工作效率。

本文并没有简单罗列创建集群过程所使用的全部指令,让读者可以一条一条剪切复制来照着操作,而是着重理清思路,帮助读者更好的理解集群搭建的工作细节。 在介绍集群创建流程环节,我们使用了更加底层的用cloud-init的定义文件进行服务器初始化这一演示方式,以期让读者能够清楚看到创建的所有配置文件的内容, 而不是简单依赖工具进行创建,导致出现故障时无法自主解决。文中使用的CoreOS,cfssl等工具可能对部分读者来说比较陌生,在使用过程中大可使用熟悉的工具进行替换,只要整体思路保持一致即可。

实际在生产环境中部署时,还需要部署日志和监控等系统组件,才能有效的对集群进行运维和管理。这些运行在集群之上的系统将留待以后的文章再做分享。

希望本文可以帮助更多的公司更好的在阿里云上利用Kubernetes构建自己的底层架构体系。并在Kubernetes上搭建起自己的DevOps体系,提高整体的研发和运行效率。

原文:https://jishu.io/kubernetes/deploy-production-ready-kubernetes-cluster-on-aliyun/

来源:https://www.kubernetes.org.cn/1667.html

Kubernetes网络原理及方案

大家好,说到容器、Docker,大家一定会想到Kubernetes,确实如此,在2016年ClusterHQ容器技术应用调查报告显示,Kubernetes的使用率已经达到了40%,成为最受欢迎的容器编排工具;那么Kubernetes到底是什么呢?它是一个用于容器集群的自动化部署、扩容以及运维的开源平台;那么通过Kubernetes能干什么呢?它能快速而有预期地部署你的应用,极速地扩展你的应用,无缝对接新的应用功能,节省资源,优化硬件资源的使用。

随着Kubernetes王者时代的到来,计算、网络、存储、安全是Kubernetes绕不开的话题,本次主要分享Kubernetes网络原理及方案,后续还会有Kubernetes其它方面的分享,另外有容云5.22发布了基于Kubernetes的容器云平台产品UFleet,想要获取新品试用,欢迎联系有容云。

一、Kubernetes网络模型

在Kubernetes网络中存在两种IP(Pod IP和Service Cluster IP),Pod IP 地址是实际存在于某个网卡(可以是虚拟设备)上的,Service Cluster IP它是一个虚拟IP,是由kube-proxy使用Iptables规则重新定向到其本地端口,再均衡到后端Pod的。下面讲讲Kubernetes Pod网络设计模型:

1、基本原则:

每个Pod都拥有一个独立的IP地址(IPper Pod),而且假定所有的pod都在一个可以直接连通的、扁平的网络空间中。

2、设计原因:

用户不需要额外考虑如何建立Pod之间的连接,也不需要考虑将容器端口映射到主机端口等问题。

3、网络要求:

所有的容器都可以在不用NAT的方式下同别的容器通讯;所有节点都可在不用NAT的方式下同所有容器通讯;容器的地址和别人看到的地址是同一个地址。

二、Docker网络基础

  • Linux网络名词解释:

1、网络的命名空间:Linux在网络栈中引入网络命名空间,将独立的网络协议栈隔离到不同的命令空间中,彼此间无法通信;docker利用这一特性,实现不容器间的网络隔离。

2、Veth设备对:Veth设备对的引入是为了实现在不同网络命名空间的通信。

3、Iptables/Netfilter:Netfilter负责在内核中执行各种挂接的规则(过滤、修改、丢弃等),运行在内核 模式中;Iptables模式是在用户模式下运行的进程,负责协助维护内核中Netfilter的各种规则表;通过二者的配合来实现整个Linux网络协议栈中灵活的数据包处理机制。

4、网桥:网桥是一个二层网络设备,通过网桥可以将linux支持的不同的端口连接起来,并实现类似交换机那样的多对多的通信。

5、路由:Linux系统包含一个完整的路由功能,当IP层在处理数据发送或转发的时候,会使用路由表来决定发往哪里。

  • Docker生态技术栈

下图展示了Docker网络在整个Docker生态技术栈中的位置:

20170526205252

  • Docker网络实现

1、单机网络模式:Bridge 、Host、Container、None,这里具体就不赘述了。

2、多机网络模式:一类是 Docker 在 1.9 版本中引入Libnetwork项目,对跨节点网络的原生支持;一类是通过插件(plugin)方式引入的第三方实现方案,比如 Flannel,Calico 等等。

三、Kubernetes网络基础

1、容器间通信:

同一个Pod的容器共享同一个网络命名空间,它们之间的访问可以用localhost地址 + 容器端口就可以访问。

20170526205259

2、同一Node中Pod间通信:

同一Node中Pod的默认路由都是docker0的地址,由于它们关联在同一个docker0网桥上,地址网段相同,所有它们之间应当是能直接通信的。

20170526205307

3、不同Node中Pod间通信:

不同Node中Pod间通信要满足2个条件: Pod的IP不能冲突; 将Pod的IP和所在的Node的IP关联起来,通过这个关联让Pod可以互相访问。

20170526205316

4、Service介绍:

Service是一组Pod的服务抽象,相当于一组Pod的LB,负责将请求分发给对应的

Pod;Service会为这个LB提供一个IP,一般称为ClusterIP。

 

20170526205331

20170526205339

5、Kube-proxy介绍:

Kube-proxy是一个简单的网络代理和负载均衡器,它的作用主要是负责Service的实现,具体来说,就是实现了内部从Pod到Service和外部的从NodePort向Service的访问。

实现方式:

  • userspace是在用户空间,通过kuber-proxy实现LB的代理服务,这个是kube-proxy的最初的版本,较为稳定,但是效率也自然不太高。
  • iptables是纯采用iptables来实现LB,是目前kube-proxy默认的方式。

下面是iptables模式下Kube-proxy的实现方式:

 

20170526205348

 

  • 在这种模式下,kube-proxy监视Kubernetes主服务器添加和删除服务和端点对象。对于每个服务,它安装iptables规则,捕获到服务的clusterIP(虚拟)和端口的流量,并将流量重定向到服务的后端集合之一。对于每个Endpoints对象,它安装选择后端Pod的iptables规则。
  • 默认情况下,后端的选择是随机的。可以通过将service.spec.sessionAffinity设置为“ClientIP”(默认为“无”)来选择基于客户端IP的会话关联。
  • 与用户空间代理一样,最终结果是绑定到服务的IP:端口的任何流量被代理到适当的后端,而客户端不知道关于Kubernetes或服务或Pod的任何信息。这应该比用户空间代理更快,更可靠。然而,与用户空间代理不同,如果最初选择的Pod不响应,则iptables代理不能自动重试另一个Pod,因此它取决于具有工作准备就绪探测。

6、Kube-dns介绍

Kube-dns用来为kubernetes service分配子域名,在集群中可以通过名称访问service;通常kube-dns会为service赋予一个名为“service名称.namespace.svc.cluster.local”的A记录,用来解析service的clusterip。

Kube-dns组件:

  • 在Kubernetes v1.4版本之前由“Kube2sky、Etcd、Skydns、Exechealthz”四个组件组成。
  • 在Kubernetes v1.4版本及之后由“Kubedns、dnsmasq、exechealthz”三个组件组成。

20170526205356

 Kubedns

  • 接入SkyDNS,为dnsmasq提供查询服务。
  • 替换etcd容器,使用树形结构在内存中保存DNS记录。
  • 通过K8S API监视Service资源变化并更新DNS记录。
  • 服务10053端口。

Dnsmasq

  • Dnsmasq是一款小巧的DNS配置工具。
  • 在kube-dns插件中的作用是:
  1. 通过kubedns容器获取DNS规则,在集群中提供DNS查询服务
  2. 提供DNS缓存,提高查询性能
  3. 降低kubedns容器的压力、提高稳定性
  • Dockerfile在GitHub上Kubernetes组织的contrib仓库中,位于dnsmasq目录下。
  • 在kube-dns插件的编排文件中可以看到,dnsmasq通过参数–server=127.0.0.1:10053指定upstream为kubedns。

Exechealthz

  • 在kube-dns插件中提供健康检查功能。
  • 源码同样在contrib仓库中,位于exec-healthz目录下。
  • 新版中会对两个容器都进行健康检查,更加完善。

四、Kubernetes网络开源组件

1、技术术语:

IPAM:IP地址管理;这个IP地址管理并不是容器所特有的,传统的网络比如说DHCP其实也是一种IPAM,到了容器时代我们谈IPAM,主流的两种方法: 基于CIDR的IP地址段分配地或者精确为每一个容器分配IP。但总之一旦形成一个容器主机集群之后,上面的容器都要给它分配一个全局唯一的IP地址,这就涉及到IPAM的话题。

Overlay:在现有二层或三层网络之上再构建起来一个独立的网络,这个网络通常会有自己独立的IP地址空间、交换或者路由的实现。

IPSesc:一个点对点的一个加密通信协议,一般会用到Overlay网络的数据通道里。

vxLAN:由VMware、Cisco、RedHat等联合提出的这么一个解决方案,这个解决方案最主要是解决VLAN支持虚拟网络数量(4096)过少的问题。因为在公有云上每一个租户都有不同的VPC,4096明显不够用。就有了vxLAN,它可以支持1600万个虚拟网络,基本上公有云是够用的。

网桥Bridge: 连接两个对等网络之间的网络设备,但在今天的语境里指的是Linux Bridge,就是大名鼎鼎的Docker0这个网桥。

BGP: 主干网自治网络的路由协议,今天有了互联网,互联网由很多小的自治网络构成的,自治网络之间的三层路由是由BGP实现的。

SDN、Openflow: 软件定义网络里面的一个术语,比如说我们经常听到的流表、控制平面,或者转发平面都是Openflow里的术语。

2、容器网络方案:

隧道方案( Overlay Networking )

隧道方案在IaaS层的网络中应用也比较多,大家共识是随着节点规模的增长复杂度会提升,而且出了网络问题跟踪起来比较麻烦,大规模集群情况下这是需要考虑的一个点。

  • Weave:UDP广播,本机建立新的BR,通过PCAP互通
  • Open vSwitch(OVS):基于VxLan和GRE协议,但是性能方面损失比较严重
  • Flannel:UDP广播,VxLan
  • Racher:IPsec

路由方案

路由方案一般是从3层或者2层实现隔离和跨主机容器互通的,出了问题也很容易排查。

  • Calico:基于BGP协议的路由方案,支持很细致的ACL控制,对混合云亲和度比较高。
  • Macvlan:从逻辑和Kernel层来看隔离性和性能最优的方案,基于二层隔离,所以需要二层路由器支持,大多数云服务商不支持,所以混合云上比较难以实现。

3、CNM & CNI阵营:

容器网络发展到现在,形成了两大阵营,就是Docker的CNM和Google、CoreOS、Kuberenetes主导的CNI。首先明确一点,CNM和CNI并不是网络实现,他们是网络规范和网络体系,从研发的角度他们就是一堆接口,你底层是用Flannel也好、用Calico也好,他们并不关心,CNM和CNI关心的是网络管理的问题。

CNM(Docker LibnetworkContainer Network Model):

Docker Libnetwork的优势就是原生,而且和Docker容器生命周期结合紧密;缺点也可以理解为是原生,被Docker“绑架”。

  • Docker Swarm overlay
  • Macvlan & IP networkdrivers
  • Calico
  • Contiv
  • Weave

CNI(Container NetworkInterface):

CNI的优势是兼容其他容器技术(e.g. rkt)及上层编排系统(Kubernetes & Mesos),而且社区活跃势头迅猛,Kubernetes加上CoreOS主推;缺点是非Docker原生。

  • Kubernetes
  • Weave
  • Macvlan
  • Calico
  • Flannel
  • Contiv
  • Mesos CNI

4、Flannel容器网络:

Flannel之所以可以搭建kubernets依赖的底层网络,是因为它可以实现以下两点:

  • 它给每个node上的docker容器分配相互不想冲突的IP地址;
  • 它能给这些IP地址之间建立一个覆盖网络,同过覆盖网络,将数据包原封不动的传递到目标容器内。

Flannel介绍

  • Flannel是CoreOS团队针对Kubernetes设计的一个网络规划服务,简单来说,它的功能是让集群中的不同节点主机创建的Docker容器都具有全集群唯一的虚拟IP地址。
  • 在默认的Docker配置中,每个节点上的Docker服务会分别负责所在节点容器的IP分配。这样导致的一个问题是,不同节点上容器可能获得相同的内外IP地址。并使这些容器之间能够之间通过IP地址相互找到,也就是相互ping通。
  • Flannel的设计目的就是为集群中的所有节点重新规划IP地址的使用规则,从而使得不同节点上的容器能够获得“同属一个内网”且”不重复的”IP地址,并让属于不同节点上的容器能够直接通过内网IP通信。
  • Flannel实质上是一种“覆盖网络(overlaynetwork)”,也就是将TCP数据包装在另一种网络包里面进行路由转发和通信,目前已经支持udp、vxlan、host-gw、aws-vpc、gce和alloc路由等数据转发方式,默认的节点间数据通信方式是UDP转发。

20170526205407

5、Calico容器网络:

Calico介绍

  • Calico是一个纯3层的数据中心网络方案,而且无缝集成像OpenStack这种IaaS云架构,能够提供可控的VM、容器、裸机之间的IP通信。Calico不使用重叠网络比如flannel和libnetwork重叠网络驱动,它是一个纯三层的方法,使用虚拟路由代替虚拟交换,每一台虚拟路由通过BGP协议传播可达信息(路由)到剩余数据中心。
  • Calico在每一个计算节点利用Linux Kernel实现了一个高效的vRouter来负责数据转发,而每个vRouter通过BGP协议负责把自己上运行的workload的路由信息像整个Calico网络内传播——小规模部署可以直接互联,大规模下可通过指定的BGP route reflector来完成。
  • Calico节点组网可以直接利用数据中心的网络结构(无论是L2或者L3),不需要额外的NAT,隧道或者Overlay Network。
  • Calico基于iptables还提供了丰富而灵活的网络Policy,保证通过各个节点上的ACLs来提供Workload的多租户隔离、安全组以及其他可达性限制等功能。

Calico架构图

20170526205416

五、网络开源组件性能对比分析

性能对比分析:

20170526205425

性能对比总结:

CalicoBGP 方案最好,不能用 BGP 也可以考虑 Calico ipip tunnel 方案;如果是 Coreos 系又能开 udp offload,flannel 是不错的选择;Docker 原生Overlay还有很多需要改进的地方。

20170526205431

最后,再提下我们有容云5.22发布了基于Kubernetes的容器云平台产品UFleet,UFleet采用的是Flannel网络,后续我们将支持Calico网络,如需试用,欢迎联系有容云。

 

来源: https://www.kubernetes.org.cn/2059.html

基于Kubeadm的Flannel分析

Flannel概述

Flannel是将多个不同子网(基于主机Node)通过被Flannel维护的Overlay网络拼接成为一张大网来实现互联的,通过官方的一张网络拓扑图我们可以对其基本原理一目了然:

值得探讨的是,flannel的这个overlay网络支持多种后端实现,除上图中的UDP,还有VXLAN和host-gw等。此外,flannel支持通过两种模式来维护隧道端点上FDB的信息,其中一种是通过连接Etcd来实现,另外一种是直接对接K8S,通过K8S添加删除Node来触发更新。

Flannel部署常见问题

1. Node状态显示为“NotReady”

我的K8S环境使用kubeadm来容器化运行K8S的各个组件(除kubelet直接运行在裸机上外),当我使用kubeadm join命令加入新的Minion Node到K8S集群中后,通过kubectl get node会发现所有的node都还是not ready状态,这是因为还没有配置好flannel网络。

2. 使用kube-flannel.yml无法创建DaemonSet

我使用的是K8S的1.6.4的版本,然后按照官方的说明,使用kube-flannel.yml来创建flannel deamon set,结果始终报错。正确的姿势是先使用kube-flannel-rbac.yml来创建flannel的ClusterRole并授权。该yml的主要作用是创建名叫flannel的ClusterRole,然后将该ClusterRole与ServiceAccount(flannel)绑定。接下来,当我们使用kube-flannel.yml来创建flannel daemon set的时候,该daemon set明确指定其Pod的ServiceAccount为flannel,于是通过它启动起来的Pod就具有了flannel ClusterRole中指定的权限。

3.flannel Pod状态为Running,网络不通

我之前在我的Mac Pro上跑了三个VM,为了能够访问公网拉取镜像,我为每个VM分配了一张网卡使用NAT模式,其分配到的IP地址可能重启后发生变化。另外,为了我本机方便管理,我为每台VM又分配了一张网卡使用Host-Only网络模式,每个网卡都有一个固定的IP地址方便SSH。然后,奇怪的事情就这样发生了….

原因在与在kube-flannel.yml中,kube-flannel容器的command被指定为:

command: [ "/opt/bin/flanneld", "--ip-masq", "--kube-subnet-mgr"]

可见,其并没有指定使用哪一张网卡作为flanneld对外通信的物理网卡,于是,可能由于机器上面路由配置的差异,导致三台机器并没有一致通过Host-Only网络模式的网卡来打通Overlay网络。遇到这种情况,如果几台机器的配置一致,可以手动修改kube-flannel.yml文件中kube-flannel的command的值,添加参数–iface=ethX,这里的ethX就为我们希望flanneld通信使用的网卡名称。

4.flannel启动异常,显示install-cni错误

这个现象比较坑,遇到这种情况的第一反应就是去查看install-cni容器到底做了什么。我们打开kube-flannel.yml可以看到,该容器的command字段只有简单的一行Shell:

command: [ "/bin/sh", "-c", "set -e -x; cp -f /etc/kube-flannel/cni-conf.json /etc/cni/net.d/10-flannel.conf; while true; do sleep 3600; done" ]

也就是将镜像中做好的cni-conf.json拷贝到cni的netconf目录下。由于容器的/etc/cni/net.d是挂载主机的对应的目录,所以该操作主要目的是为CNI准备flannel环境,便于启动容器的时候正确从netconf目录中加载到flannel,从而使用flannel网络。

我发现进入主机的netconf目录中能够看到10-flannel.conf:

#ls /etc/cni/net.d/10-flannel.conf
/etc/cni/net.d/10-flannel.conf
# cat /etc/cni/net.d/10-flannel.conf
cat: /etc/cni/net.d/10-flannel.conf: No such file or directory

无法打出其内容,而且文件显示为红色,说明其内容并没有正确从容器中拷贝过来。

之前我怀疑该异常是因为kubelet所带的文件系统参数为systemd,而docker的文件系统参数为cgroupfs所致,结果发现并非如此。当前能够绕开该异常的workaround为手动进入到主机的netconf目录,创建10-flannel.conf目录,并写入以下数据:

{
"name": "cbr0",
 "type": "flannel",
 "delegate": {
 "isDefaultGateway": true
 }
}

5.flannel网络启动正常,能够创建pod,但是网络不通

出现该现象一般会想到debug,而debug的思路当然是基于官方的那张网络拓扑图。比如在我的机器上,通过参看网卡IP地址有如下信息:

6: flannel.1: <BROADCAST,MULTICAST,UP,LOWER_UP> mtu 1450 qdisc noqueue state UNKNOWN
link/ether 22:a0:ce:3c:bf:1f brd ff:ff:ff:ff:ff:ff
 inet 10.244.1.0/32 scope global flannel.1
valid_lft forever preferred_lft forever
inet 10.244.2.0/32 scope global flannel.1
valid_lft forever preferred_lft forever
7:cni0: <NO-CARRIER,BROADCAST,MULTICAST,UP> mtu 1450 qdisc noqueue state DOWN qlen 1000
 link/ether 0a:58:0a:f4:01:01 brd ff:ff:ff:ff:ff:ff
inet 10.244.1.1/24 scope global cni0
valid_lft forever preferred_lft forever
inet6 fe80::4cb6:7fff:fedb:2106/64 scope link
valid_lft forever preferred_lft forever

很明显,cni0为10.244.1.1/24网段,说明该主机应该位于10.244.1.0/16子网内;但是我们看flannel.1网卡,确有两个IP地址,分别为于两个不同的”/16”子网。所以,可以肯定的是,我们一定是在该太主机上部署了多次kubeadm,而kubeadm reset并不会清理flannel创建的flannel.1和cni0接口,这就导致环境上遗留下了上一次部署分配到的IP地址。这些IP地址会导致IP地址冲突,子网混乱,网络通信异常。

解决的方法就是每次执行kubeadm reset的时候,手动执行以下命令来清楚对应的残余网卡信息:

ip link del cni0
ip link del flannel.1

K8S如何使用Flannel网络

Flannel打通Overlay网络

当使用kubeadm来部署k8s,网络组件(如flannel)是通过Add-ons的方式来部署的。我们使用这个yml文件来部署的flannel网络。
我截除了关键的一段内容(containers spec):

containers:
- name: kube-flannel
image: quay.io/coreos/flannel:v0.7.1-amd64
command: [ "/opt/bin/flanneld", "--ip-masq", "--kube-subnet-mgr" ]
securityContext:
privileged: true
env:
- name: POD_NAME
valueFrom:
fieldRef:
 fieldPath: metadata.name
- name: POD_NAMESPACE
valueFrom:
fieldRef:
 fieldPath: metadata.namespace
 volumeMounts:
- name: run
mountPath: /run
- name: flannel-cfg
mountPath: /etc/kube-flannel/
 - name: install-cni
image: quay.io/coreos/flannel:v0.7.1-amd64
 command: [ "/bin/sh", "-c", "set -e -x; cp -f /etc/kube-flannel/cni-conf.json /etc/cni/net.d/10-flannel.conf; while true; do sleep 3600; done" ]
volumeMounts:
- name: cni
mountPath: /etc/cni/net.d
- name: flannel-cfg
 mountPath: /etc/kube-flannel/

通过分析该yaml文件可以知道:该pod内有两个container,分别为install-cni和 kube-flannel。

  • install-cni

主要负责将config-map中适用于该flannel的CNI netconf配置拷贝到主机的CNI netconf 路径下,从而使得K8S在创建pod的时候可以遵照标准的CNI流程挂载网卡。

  • kube-flannel

主要启动flanneld,该command中为flanneld的启动指定了两个参数: –ip-masq, –kube-subnet-mgr。第一个参数–ip-masq代表需要为其配置SNAT;第二个参数–kube-subnet-mgr代表其使用kube类型的subnet-manager。该类型有别于使用etcd的local-subnet-mgr类型,使用kube类型后,flannel上各Node的IP子网分配均基于K8S Node的spec.podCIDR属性。可以参考下图的方式来查看(该示例中,k8s为node-1节点分配的podCIDR为:10.244.8.0/24):

#kubectl edit node node-1

apiVersion: v1
kind: Node
 ...
name: dev-1
resourceVersion: "2452057"
selfLink: /api/v1/nodesdev-1
uid: 31f6e4c3-57b6-11e7-a0a5-00163e122a49
spec:
 externalID: dev-1
podCIDR: 10.244.8.0/24

另外,flannel的subnet-manager通过监测K8S的node变化来维护了一张路由表,这张表里面描述了要到达某个Pod子网需要先到达哪个EndPoint。

CNI挂载容器到隧道端点

如果说flannel为Pod打通了一张跨node的大网,那么CNI就是将各个终端Pod挂载到这张大网上的安装工人。

在刚部署好flannel网络并未在该Node上启动任何Pod时,通过ip link我们只能够看到flannel.1这张网卡,却无法看到cni0。难道是flannel网络运行异常吗?我们接下来就来分析flannel的CNI实现原理,就知道答案了。

通过传统方式来部署flannel都需要通过脚本来修改dockerd的参数,从而使得通过docker创建的容器能够挂载到指定的网桥上。但是flannel的CNI实现并没有采用这种方式。通过分析CNI代码,

我们可以了解flannel CNI的流程:

  • 读取netconf配置文件,并加载/run/flannel/subnet.env环境变量信息。
  • 基于加载的信息,生成适用于其delegate的ipam和CNI bridge的netconf文件;其中指定ipam使用host-local,CNI bridge type为bridge。
  • 调用deletgate(CNI bridge type)对应的二进制文件来挂载容器到网桥上。

这里的环境变量文件/run/flannel/subnet.env是由flanneld生成的,里面包含了该主机所能够使用的IP子网网段,具体内容如下:

# cat /run/flannel/subnet.env
FLANNEL_NETWORK=10.244.0.0/16
FLANNEL_SUBNET=10.244.8.1/24
FLANNEL_MTU=1450
FLANNEL_IPMASQ=true

这些数据生成ipam的配置文件的依据,另外,flannel CNI插件的代码中,默认指定delegate为bridge:

if !hasKey(n.Delegate, "type") {
 n.Delegate["type"] = "bridge"
}

所以,当flannel CNI插件调用delegate,本质上就是调用bridge CNI插件来将容器挂载到网桥上。分析bridge CNI 插件的过程我们可以看到其指定了默认网桥名称为cni0:

const defaultBrName = "cni0"

func loadNetConf(bytes []byte) (*NetConf, error) {
n := &NetConf{
 BrName: defaultBrName,
 }
 ...
 return n, nil
}

因此,现在我们可以将整个流程连起来了:flannel CNI插件首先读取netconf配置和subnet.env信息,生成适用于bridge CNI插件的netconf文件和ipam(host-local)配置,并设置其delegate为bridge CNI插件。然后调用走bridge CNI插件挂载容器到bridge的流程。由于各个Pod的IP地址分配是基于host-local的Ipam,因此整个流程完全是分布式的,不会对API-Server造成太大的负担。

至此,基于kubeadm的flannel分析就大致结束了,希望对您有帮助!

 

来源: https://www.kubernetes.org.cn/2270.html