技术雷达——科技宏观趋势

ThoughtWorks每年都会出品两期技术雷达,这是一份关于科技行业技术趋势的报告。是ThoughtWorks对工具、技术、编程语言和平台的详细解读,我们通常会引入一百余个技术条目。编写技术雷达需要与来自ThoughtWorks全球各个办公室的资深技术专家进行深入沟通,在讨论个别现象的过程中,我们也会谈及宏观趋势。本文汇集了我们眼中当前科技行业的大趋势,以飨读者。

区块链不仅仅是炒作

在本文编写之时,一枚比特币的市值已经突破一万美元大关,从年初至今已经翻了十倍。而埃隆·马斯克明确否认自己是中本聪本尊,中本聪是比特币的神秘发行人。比特币炒作带火了加密货币这个混乱的市场,同时名人效应带来的ICO投机也是风生水起,这引发了加密货币存在“巨大泡沫”的担忧。不过在这种过山车式的疯狂炒作下,也孕育了一些很有价值的技术。

我们的许多客户都在试图建立一个运用区块链的分布式账本和智能合约,一些雷达条目显示,区块链相关技术已经趋于成熟,使用多种技术和编程语言实施智能合约的有效方法越来越多。区块链会解决两大问题。首先,这种技术可以让我们摆脱对“大家共同信赖”中间人(如银行或者股票交易所)的依赖,建立分布式信任机制;其次,区块链可以让我们创建一个共享式、不可更改的的可信的账本——是对事实的记录。如今,我们已经见证了基于这两个核心理念的组织的诞生。其中,我们认为以太坊智能合约和Corda分布式账本技术值得持续关注。

企业内部署(on-premise)软件风光不再?

谈及基础设施和部署,暂且把我们的沟通对象变成我们的每一个客户。在组织开始考虑配置服务器、安装软件,并且对软件进行后续打补丁和维护等动作时,第一个问题是“有我可以购买的定制服务吗?”,然后是“我可以从云服务供应商买什么来构建我的云服务?”这个决策流程可以总结为“最后考虑企业内部署(on-premise)软件”。曾几何时,人们在使用云服务时会研究多时;而今使用on-premise式服务时人们才会非常谨慎。过去一年来,云端托管已经成为大家非常感兴趣的话题。

雷达报告中再次印证了这个趋势——本文中谈及的许多工具、技术和平台要么是云服务辅助,要么支持云端服务。我们切实见证了许多组织“默认上云”的趋势,我们这里提到“企业内部署”,但是重点不是服务器在哪里,而是高效获得一项服务或功能,并长期保证其运行和维护所需要的工作量。

虚拟化的“长尾效应”

早在1999年我们开始使用Vmware的虚拟机时,并没有预料到虚拟化将会给软件带来全方位的变革。虚拟机如今已成为软件行业各个环节的必选,无论是开发者工作站还是谷歌这个体量的数据中心,而且虚拟机也是许多系统的“扩展单元”(除非你是谷歌,在谷歌数据中心本身就是扩展单元!)。Docker、Kubernetes以及当前所有重量级云技术都是基于虚拟化来实现的。

虚拟化促成了云服务的繁荣,我们认为,在NIST定义中的云极具价值。NIST的五个“基本特征”中,我们认为两个特征——按需自助服务和弹性——是云服务能够获得宠爱的绝对关键要素。选择云服务时,还有三个特征,而这些优势正是许多“私有云”产品所无法比拟的。

同等特性(feature parity)的误导

我们发现目前科技行业呈现出一种不良趋势,即在实施云迁移、遗留系统升级或产品再开发时以“同等特性(feature parity)”为目标。将一套运行时间达十年或十五年的老系统单纯用新技术重新部署,且不论程序缺陷等等,这绝非好主意。常用的借口是“我们不想给企业带来困扰”,或是担心改变流程或计算,但结果常常是交付遥遥无期、进展缓慢、一次性交付,还潜藏各种风险。在发现项目延期、预算大幅超支且不能给企业带来任何新的利益时,利益相关者往往大失所望。

这些教训值得我们反思。我们认为IT领导者(和企业)应当大胆质疑十年前编写的逻辑能否代表当今企业的运行方式,要相信用户有能力采纳(整体更强大的)新系统。企业应当深入研究自己真正需要的功能,而不是在新平台上重建一套功能完备的特性集。关于如何为云服务重写敏捷项目管理工具Mingle本期技术雷达进行了更多深入的探讨。

中国正在开源世界中崛起

我们发现中国的开源项目在数量和质量上均呈跳跃式增长。百度和阿里巴巴等大企业已经发布自己的开源代码,令全球为之瞩目。在过去的几年里,中国公司对开源代码的认知悄然转变。以前出于保护知识产权的忧虑,不愿意开源。而现在他们看到了Docker、Kubernetes和OpenStack等大型项目的影响力,认识到建立一套生态系统是比闭关更好的选择。只要保持对开源社区的影响力,他们就可以掌握其IP的控制权,同时享受开源的福利。

另外一个因素是中国与发达国家的市场有很大不同,具有独特的文化和视角,由此产生的期望与要求也有所不同,所以中国企业并不一定需要亦步亦趋地追随西方企业的脚步。中国市场的体量巨大,中国企业正在创建、分享开源代码,开发自己特有的软件和生态系统,从而解决中国特有的问题。

本期技术雷达中,我们重点介绍了阿里巴巴的两大项目AtlasBeehive,可以更好地实现应用程序模块化,有助于分布式或者远程团队协作。借此你可以动态地将物理隔离模块统一装配到单个应用程序中,其具体设计显然考虑到了中国软件市场的情况。

值得注意的是,中国的开源代码首先是为中国编写的,因此不用走出国门就能取得巨大成功。文档将使用中文撰写,如果一个项目进行得足够顺利,后续可能创建翻译版本。中国涌现了一些质量很高的软件,而且非常实用,但需要注意的是其主要受众是中国市场。

Kubernetes统领容器管理生态

一年前,身在ThoughtWorks的我们曾被问道“你们偏爱哪一种容器管理平台,Kubernetes还是Mesos?”如今,这个问题的答案已经不言而喻。Kubernetes俨然已是事实上的默认标准。这是为什么呢?我们认为是各种因素作用下的综合结果。

容器化趋势已经建立了一套生态系统,我们所有的工具都可以在该生态系统内与容器协作(而且经常需要容器),Docker在这一点上尤为突出。在某种程度上,容器就是新POSIX、新通用接口。IT行业在创建软件组件上付出了多年的努力,看来容器可能是目前最好的标准化方式。(然而,因为一个容器里可以插入任何内容,所以目前尚无法保证组件可以很好地共同运行。)微服务、演化架构、默认云等其他重要科技趋势与容器的协作极好,因此也存在自然的共生关系。

几年前,科技行业主要参与者还在探讨GIFFEE——谷歌提供的针对其他所有人的基础架构。“GIFEE”的话题才刚开始,Kubernetes基本已经成了所有人都能用的谷歌式基础架构。谷歌努力推进项目,投入了大量资源,希望把人们吸引到谷歌云产品上。随着时间的推移,Kubernetes已经成了我们与供应商和云提供商打交道的默认容器平台。

除此之外,Kubernetes还进化得更易于大规模运行。经过对Kubernetes核心软件的改进,借助更好的工具和高度活跃的生态系统,运行弹性生产集群的学习曲线已经不再那么陡峭。现在所有主要云提供商都提供基于Kubernetes的托管,所以进入门槛很低。

数据流即是标准

本期技术雷达中,我们探讨了一系列与Kafka相关的问题:Kafka、Kafka Streams、Kafka作为正确数据之源、Kafka作为轻量级ESB。然而我们为什么要强调数据流?

全世界都渴望实时分析。事实上,设计系统时我们必须做出调整适应。我们喜欢基于事件的流式架构所带来的福利——松散耦合、自主组件、高性能和高扩展性——但分析要求推动了对数据流的要求。离开数据流便无法实现实时分析。

与数据流兴起相关的是事件驱动架构的成熟度。人们对这些系统已然司空见惯,也很好理解了。有些新技术还在涌现,例如用数据流作为企业事实/状态的持久化存储。我们并非百分百确定所有这些技术都是好主意(CQRS已经坑了许多不设戒备心的人),但数据流已深入人心,这一点毋庸置疑。

Share

如果有10000台机器,你想怎么玩?(二)高可用

这次聊聊k8s的高可用性是怎么做的。所谓高可用性,就是在一些服务或机器挂掉了之后集群仍然能正常工作的能力。扩展阅读:如果有10000台机器,你想怎么玩?(一)概述

作为背景知识,先介绍一下k8s的架构
01

它分为服务器端(master)和客户端(node)。服务器端主要是3个组件:API Server、Controller Manager和Scheduler。API Server是操作人员和k8s的接口。比如我想看一下当前k8s有几个pod在跑,就需要连接到这个API Server上。Controller Manager顾名思义就是管理各种各样的controller比如先前提到的Replication Controller。Scheduler做的事就是把用户想要启动/删除的pod分发到对应的客户端上。客户端主要是2个组件:Kubelet和Proxy。Kubelet负责响应服务器端的Scheduler分出来的任务。Proxy用来接通服务和对应的机器。举个栗子:如果我们运行这个命令:kubectl -s 192.168.33.10:8080 run nginx —image=nginx来启动一个nginx的rc和pod,API Server(192.168.33.10:8080)就会得到消息并把这些数据存放到etcd里。Controller Manager就会去创建rc,Scheduler则会找个客户端,把启动pod的描述放到客户端上的某个文件夹里。客户端上的Kubelet会监视这个文件夹,一旦发现有了新的pod描述文件,便会将这个pod启动起来。多说一句,Kubelet除了监听文件夹或是某个Url,还有种方式是干脆直接启动一个Http Server让别人来调用。

高可用的情况下,由于用户的命令直接操作的是API Server,所以当API Server挂掉的时候,需要能自动重启。我们可以使用k8s客户端上现成的Kubelet来满足这个需求。Kubelet有一个Standalone模式,把启动API Server的描述文件丢到Kubelet的监视文件夹里就好了。当Kubelet发现API Server挂掉了,就会自动再启动一个API Server,反正新旧API Server连接的存储etcd还是原来那一个。API Server高可用了,要是Kubelet挂了呢?这个…还得监视一下Kubelet…可以用monit之类的东东,这边就不细说了。当然etcd也需要高可用,但是作为分布式存储来说,它的高可用相对而言较为简单并且跟k8s关联不大,这里也不提了。

刚刚提到的都是进程或容器挂掉的高可用。但是万一整个机器都完蛋了,咋办呢?最直接的做法就是整它好几个服务端,一个挂了还有其他的嘛。好几个服务端就有好几个API Server,其中一个为主,其他为从,简单地挂在一个负载均衡如HAProxy上就可以了。如果还嫌HAProxy上可能有单点故障,那就再做负载均衡集群好了,本文不再赘述。API Server可以跑多份,但是Controller Manager和Scheduler现在不建议跑多份。怎么做到呢?官方提供了一个叫做podmaster的镜像,用它启动的容器可以连接到etcd上。当它从etcd上发现当前机器的API Server为主机的时候,便会把Controller Manager和Scheduler的描述文件丢到Kubelet的监视文件夹里,于是Kubelet就会把这俩启动起来。若当前机器的API Server为从机时,它会把Controller Manager和Scheduler的描述文件从Kubelet的监视文件夹里删掉,这样就可以保证整个集群中Controller Manager和Scheduler各只有一份。上面说的这些画到图里就是这样滴:
02-01

和mesos配合的话,k8s还有另一种高可用方式。这种方式会给Scheduler増加一个叫做–ha的参数,于是Scheduler就能多个同时工作。但是官方也说了,不建议同时起2个以上的Scheduler。这种高可用方式的其它配置还是跟上文所说的一样,照样得使用podmaster,只不过它这回只用管Controller Manager一个而已。

做了这么多,终于把k8s master搞定了。但是还没完,node们还在等着我们呢!如果没用mesos,那就需要把node们的Kubelet重启一下,让它们连接到API Server的负载均衡上去。要是用了mesos就会简单一点儿,因为node们的Kubelet就是由Scheduler帮忙起起来的。记得吗?服务器端我们已经搞定了~

Share

Kubernetes救援 – 教你如何从新技术的坑里爬出来(下)

上文最后讲到,经过一番努力,排除了基础设施的问题,集中精力查找Kubernetes配置错误,现在要抓紧最后一根救命稻草——日志,期待能从日志中找到解决问题的蛛丝马迹。

希望来临

在按下浏览器刷新按钮时,我是非常忐忑的,如果日志没有任何异常提示,我就没有任何其他线索可以继续找下去了。

从master节点开始,etcd.log无异常,flanneld.log无异常,kubelet.log无异常,…,所有日志均无异常。我担心的事情终于发生了。

然后是minion1节点,flanneld.log无异常,kubelet.log无异常,…,所有日志还是无异常。我已经感觉到一丝丝绝望,甚至开始在心里暗骂,Google这群不靠谱的人,竟然错误都不记录到日志中!

最后是minion2节点,flanneld.log无异常,kubelet.log无异常,…,所有日志……等等!kube-proxy.log里那是什么!

E1129 06:06:19.727461    2540 proxysocket.go:104] Dial failed: dial tcp 10.0.2.15:6443: connection refused
E1129 06:06:19.727502    2540 proxysocket.go:104] Dial failed: dial tcp 10.0.2.15:6443: connection refused
E1129 06:06:19.727537    2540 proxysocket.go:104] Dial failed: dial tcp 10.0.2.15:6443: connection refused
E1129 06:06:19.727570    2540 proxysocket.go:104] Dial failed: dial tcp 10.0.2.15:6443: connection refused
E1129 06:06:19.727578    2540 proxysocket.go:138] Failed to connect to balancer: failed to connect to an endpoint.

终于抓到你了!

看到错误,我的第一反应是:一定是kube-proxy配错了。但是到底哪里错我,却没有任何思路。所以要还要仔细看下日志的消息。

盯着这个日志看了3秒钟,猛然发现,这个10.0.2.15根本不该出现在这里!这里对于不熟悉vagrant的读者多解释两句,创建虚拟设施的Vagrantfile在这里,为了让虚拟机之间可以通信,我配置了private_network,其实就是VirtualBox里的host-only网络,但是,vagrant为了管理虚拟机,会默认给每个虚拟机创建一个NAT网卡,这个网卡的IP就是10.0.2.15,所有的虚拟机都是这个IP,互相之间不能通过这块NAT网卡互联。这样每个虚拟机就会有两个网卡,一个是NAT网卡,一个是Private Network网卡。而NAT网卡是eth0,也就是系统默认网卡。Kubernetes集群内部通信是通过Private Network,而对外提供服务,是通过apiserver,用的也是Private Networks。这里出现了10.0.2.5,说明Kubernetes的kube-proxy的服务绑定了eth0网卡,而不是期望的eth1网卡!

为什么官方文档里没有提这件事?因为官方部署Ubuntu的文档针对的目标环境是裸机,裸机通常只有一块网卡,而即使有两块,不同机器之间也应该可以通过默认网卡互联。所以不会出现问题。

想到这,我立即去查如何设置kube-proxy绑定网卡,最直接的,就是在命令行里运行kube-proxy

vagrant@master:/opt/bin$ ./kube-proxy --help
Usage of ./kube-proxy:
      --alsologtostderr[=false]: log to standard error as well as files
      --bind-address=0.0.0.0: The IP address for the proxy server to serve on (set to 0.0.0.0 for all interfaces)
      --cleanup-iptables[=false]: If true cleanup iptables rules and exit.
      --healthz-bind-address=127.0.0.1: The IP address for the health check server to serve on, defaulting to 127.0.0.1 (set to 0.0.0.0 for all interfaces)
      --healthz-port=10249: The port to bind the health check server. Use 0 to disable.
      --hostname-override="": If non-empty, will use this string as identification instead of the actual hostname.
      --iptables-sync-period=30s: How often iptables rules are refreshed (e.g. '5s', '1m', '2h22m').  Must be greater than 0.
      --kubeconfig="": Path to kubeconfig file with authorization information (the master location is set by the master flag).
      --log-backtrace-at=:0: when logging hits line file:N, emit a stack trace
      --log-dir="": If non-empty, write log files in this directory
      --log-flush-frequency=5s: Maximum number of seconds between log flushes
      --logtostderr[=true]: log to standard error instead of files
      --masquerade-all[=false]: If using the pure iptables proxy, SNAT everything
      --master="": The address of the Kubernetes API server (overrides any value in kubeconfig)
      --oom-score-adj=-999: The oom-score-adj value for kube-proxy process. Values must be within the range [-1000, 1000]
      --proxy-mode="": Which proxy mode to use: 'userspace' (older, stable) or 'iptables' (experimental). If blank, look at the Node object on the Kubernetes API and respect the 'net.experimental.kubernetes.io/proxy-mode' annotation if provided.  Otherwise use the best-available proxy (currently userspace, but may change in future versions).  If the iptables proxy is selected, regardless of how, but the system's kernel or iptables versions are insufficient, this always falls back to the userspace proxy.
      --proxy-port-range=: Range of host ports (beginPort-endPort, inclusive) that may be consumed in order to proxy service traffic. If unspecified (0-0) then ports will be randomly chosen.
      --resource-container="/kube-proxy": Absolute name of the resource-only container to create and run the Kube-proxy in (Default: /kube-proxy).
      --stderrthreshold=2: logs at or above this threshold go to stderr
      --udp-timeout=250ms: How long an idle UDP connection will be kept open (e.g. '250ms', '2s').  Must be greater than 0. Only applicable for proxy-mode=userspace
      --v=0: log level for V logs
      --version=false: Print version information and quit
      --vmodule=: comma-separated list of pattern=N settings for file-filtered logging

扫了一眼,最有嫌疑的配置就是--bind-address,不过,从说明来看,这个配置默认0.0.0.0,应该是接收所有网卡的请求。有点矛盾,但死马当活马医,试试再说。于是修改/etc/default/kube-proxy,添加一个参数:

KUBE_PROXY_OPTS=" --master=http://172.28.128.3:8080  --logtostderr=true --bind-address=172.28.128.5"

重启kube-proxy。

Finger crossed。

刷新一下页面。shit!还是那样!

看来这个办法不行,改回原来默认的配置,要重新理一下思路。

补充理论

不能再这么“东一耙子西一扫帚”(东北人都懂)了,现在抓到了kube-proxy.log里的关键线索,错误一定跟它有关。这个时候不能急,要冷静,先想清楚到底是怎么回事。

回想一下Kubernetes的架构(是的,在玩Quick Start之前,我当然要基本了解下Kubernetes):

  1. 通过etcd做全局配置和服务发现;
  2. flannel负责给所有docker容器分配虚拟ip给pod,以避免管理port;
  3. 资源被划分为几个层次:应用被打包在docker中,多个docker组成一个pod;多个pod组成一个service;service对外提供服务。

这个kube-proxy到底在这个架构里是一个什么作用?看来又要施展我的Google技能了。

长话短说,搜了几篇没什么营养的入门文章之后,在官方文档中,找到了我想要的答案,总结一下内容:

  1. 每个service会在apiserver监听一个端口,同时还会分配一个service IP,供集群内部访问,service通过selector找到符合要求的pods,然后记录下pods的IP作为Endpoint;
  2. pod的IP和service的IP并不是在同一个IP段,这也是为什么在配置初始化脚本config-default.sh时,有两个IP段需要配置;
  3. kube-proxy监听etcd,并更新iptables,将访问master的service端口,或者集群内部通过service IP的访问请求转发到对应的Endpoint,这个Endpoint的IP是通过flannel控制分配的;

搞清楚kube-proxy的作用了,但是还有点云里雾里,到底是哪里出问题了呢?这时,那个重要的犯罪证据“不该出现的IP”10.0.2.15又一次浮现出来。有没有可能这个IP还出现在其他地方,被我之前漏掉了呢?

我决定在所有日志里搜索一下这个IP,可以用grep这个命令:

root@minion2:/var/log/upstart# grep -ir "10.0.2.15" .
./kube-proxy.log:E1128 11:28:55.091647    2540 proxysocket.go:104] Dial failed: dial tcp 10.0.2.15:6443: connection refused
./kube-proxy.log:E1129 06:06:19.727461    2540 proxysocket.go:104] Dial failed: dial tcp 10.0.2.15:6443: connection refused
./kube-proxy.log:E1129 06:06:19.727502    2540 proxysocket.go:104] Dial failed: dial tcp 10.0.2.15:6443: connection refused
./kube-proxy.log:E1129 06:06:19.727537    2540 proxysocket.go:104] Dial failed: dial tcp 10.0.2.15:6443: connection refused
./kube-proxy.log:E1129 06:06:19.727570    2540 proxysocket.go:104] Dial failed: dial tcp 10.0.2.15:6443: connection refused
./network-interface-eth0.log:DHCPREQUEST of 10.0.2.15 on eth0 to 255.255.255.255 port 67 (xid=0x681829be)
./network-interface-eth0.log:DHCPOFFER of 10.0.2.15 from 10.0.2.2
./network-interface-eth0.log:DHCPACK of 10.0.2.15 from 10.0.2.2
./network-interface-eth0.log:bound to 10.0.2.15 -- renewal in 34886 seconds.
./network-interface-eth0.log:DHCPREQUEST of 10.0.2.15 on eth0 to 255.255.255.255 port 67 (xid=0x18078729)
./network-interface-eth0.log:DHCPOFFER of 10.0.2.15 from 10.0.2.2
./network-interface-eth0.log:DHCPACK of 10.0.2.15 from 10.0.2.2
./network-interface-eth0.log:bound to 10.0.2.15 -- renewal in 37295 seconds.
./flanneld.log:I1128 11:20:37.077746 02534 main.go:188] Using 10.0.2.15 as external interface
./flanneld.log:I1128 11:20:37.078771 02534 main.go:189] Using 10.0.2.15 as external endpoint
./flanneld.log:I1128 11:20:37.094503 02534 etcd.go:129] Found lease (172.16.10.0/24) for current IP (10.0.2.15), reusing

果然在falnneld.log里还有漏网之鱼!看到这个搜索结果,在加上前面看到的kube-proxy原理,把这些线索串到一起,整个事情突然清晰起来:

  1. flanneld在启动时,把默认网卡eth0识别成要监听的网卡;
  2. kube-proxy想要访问某个pod时只有这个pod的虚拟IP,也就是flanneld分配的IP;
  3. 当请求到达kube-proxy时,kube-proxy想通过10.0.2.15这个IP跟flanneld进行通信,但是因为这个IP是vagrant的NAT网卡IP,所以失败了;
  4. 这也就是为什么那个完整的错误日志说的是:Dial failed: dial tcp 10.0.2.15:6443: connection refused

终于定位到你了,flanneld,你这个罪魁祸首!

最后的围剿

既然定位到了flanneld,剩下的事情就是怎么修复这个问题。还是先看看flanneld的参数都有哪些。

root@minion2:/opt/bin# ./flanneld --help
Usage: ./flanneld [OPTION]...
  -alsologtostderr=false: log to standard error as well as files
  -etcd-cafile="": SSL Certificate Authority file used to secure etcd communication
  -etcd-certfile="": SSL certification file used to secure etcd communication
  -etcd-endpoints="http://127.0.0.1:4001,http://127.0.0.1:2379": a comma-delimited list of etcd endpoints
  -etcd-keyfile="": SSL key file used to secure etcd communication
  -etcd-prefix="/coreos.com/network": etcd prefix
  -help=false: print this message
  -iface="": interface to use (IP or name) for inter-host communication
  -ip-masq=false: setup IP masquerade rule for traffic destined outside of overlay network
  -listen="": run as server and listen on specified address (e.g. ':8080')
  -log_backtrace_at=:0: when logging hits line file:N, emit a stack trace
  -log_dir="": If non-empty, write log files in this directory
  -logtostderr=false: log to standard error instead of files
  -networks="": run in multi-network mode and service the specified networks
  -public-ip="": IP accessible by other nodes for inter-host communication
  -remote="": run as client and connect to server on specified address (e.g. '10.1.2.3:8080')
  -remote-cafile="": SSL Certificate Authority file used to secure client/server communication
  -remote-certfile="": SSL certification file used to secure client/server communication
  -remote-keyfile="": SSL key file used to secure client/server communication
  -stderrthreshold=0: logs at or above this threshold go to stderr
  -subnet-dir="/run/flannel/networks": directory where files with env variables (subnet, MTU, ...) will be written to
  -subnet-file="/run/flannel/subnet.env": filename where env variables (subnet, MTU, ... ) will be written to
  -v=0: log level for V logs
  -version=false: print version and exit
  -vmodule=: comma-separated list of pattern=N settings for file-filtered logging

看到-iface=""这个参数的说明,基本上已经可以确认,就是它了!照这个思路开始改:

root@minion2:/opt/bin# vi /etc/default/flanneld
root@minion2:/opt/bin# cat /etc/default/flanneld
FLANNEL_OPTS="-iface=eth1 --etcd-endpoints=http://172.28.128.3:4001"
root@minion2:/opt/bin# service flanneld restart
flanneld stop/waiting
flanneld start/running, process 4495
root@minion2:/opt/bin# tail -f /var/log/upstart/flanneld.log -n 50
I1128 11:20:37.076880 02534 main.go:275] Installing signal handlers
I1128 11:20:37.077355 02534 main.go:130] Determining IP address of default interface
I1128 11:20:37.077746 02534 main.go:188] Using 10.0.2.15 as external interface
I1128 11:20:37.078771 02534 main.go:189] Using 10.0.2.15 as external endpoint
I1128 11:20:37.094503 02534 etcd.go:129] Found lease (172.16.10.0/24) for current IP (10.0.2.15), reusing
I1128 11:20:37.096168 02534 etcd.go:84] Subnet lease acquired: 172.16.10.0/24
I1128 11:20:37.097640 02534 udp.go:222] Watching for new subnet leases
I1129 11:59:35.035222 02534 main.go:292] Exiting...
I1129 11:59:35.075646 04495 main.go:275] Installing signal handlers
I1129 11:59:35.078235 04495 main.go:188] Using 172.28.128.5 as external interface
I1129 11:59:35.078257 04495 main.go:189] Using 172.28.128.5 as external endpoint
I1129 11:59:35.093587 04495 etcd.go:204] Picking subnet in range 172.16.1.0 ... 172.16.255.0
I1129 11:59:35.096558 04495 etcd.go:84] Subnet lease acquired: 172.16.29.0/24
I1129 11:59:35.106917 04495 udp.go:222] Watching for new subnet leases
I1129 11:59:35.108491 04495 udp.go:247] Subnet added: 172.16.10.0/24

从这个日志提示看,这个改动应该是成功了,于是把其他两个虚拟机也修改一下。激动人心的时刻到了,终于能见到美丽的kube-ui,想想还有点小激动!

打开浏览器,刷新!

什么都没有发生!还是那个让我抓狂的页面!

Final Fight

这个结果对我确实是一个打击,但是我比较确信这个解决方案的方向是正确的。一个可能的原因是flanneld需要和其他组件配合,虽然flanneld已经改好,但其他配合的组件还需要修改。要验证这个假设,需要去了解Kubernetes启动的具体步骤,有两种方式可以选择:

  1. 看kube-up.sh脚本,找到启动过程中调用的所有配置,看看哪里有和flanneld关联的地方;
  2. Google搜索解决方案。

鉴于前面的经验,估计Google出来的结果可能帮助不大,所以我首选自己先看脚本。花了半个多小时,搞清楚整个启动脚本的结构,kube-up.sh只是一个入口,会根据KUBERNETES_PROVIDER环境变量的值,选择调用不同的配置脚本。kube-up.sh里的方法只是类似虚函数的空实现,具体的逻辑由各个平台对应的脚本重写实现。我用到的脚本都在kubernetes/cluster/ubuntu/路径下。不愧是Google出品,连Shell脚本都能做成面向接口编程。

读脚本的收获很大,但是并没有解决我的问题,因为Shell脚本的可读性实在是不可恭维。所以又要靠Google了。

在Google里输入“kubernetes fanneld iface”等关键字,没什么有价值的发现。既然直接找不行,那就间接找,换个关键字,“kubernetes multinode”,希望能找到一个详细讲手动部署多节点Kubernetes集群的说明。

功夫不负有心人,终于找到一篇,是讲如何用docker部署多节点Kubernetes集群,虽然这个文档也是通过自动化脚本进行配置,但它对每一个脚本都写了个说明文档,比如这个讲worker.sh脚本的。里面比较关键的是这一段:

mutli-node-docker-doc

总结起来就是:

  1. flanneld启动后,会把注册的子网写入到/run/flannel/subnet.env里;
  2. 启动docker时,要把这个子网信息作为参数传进去。

这就是那传说中的“丢失的一步”!照着这个文档修改配置:

root@minion2:/home/vagrant# cat /run/flannel/subnet.env
FLANNEL_NETWORK=172.16.0.0/16
FLANNEL_SUBNET=172.16.29.1/24
FLANNEL_MTU=1472
FLANNEL_IPMASQ=false
root@minion2:/home/vagrant# cat /etc/default/docker
DOCKER_OPTS=" -H tcp://127.0.0.1:4243 -H unix:///var/run/docker.sock --bip=172.16.10.1/24 --mtu=1472"
root@minion2:/home/vagrant# vi /etc/default/docker
root@minion2:/home/vagrant# cat /etc/default/docker
DOCKER_OPTS=" -H tcp://127.0.0.1:4243 -H unix:///var/run/docker.sock --bip=172.16.29.1/24 --mtu=1472"

接着把另外两个节点也修改一下。别忘了重启docker服务。

打开浏览器,刷新!“嘭”!

kubernetes-dashboard

 

总结

看到Dashboard终于出现,内心早已汹涌澎湃,但表面上还是要很平静,毕竟我是专家啊。

回顾前面的整个过程,得到以下两条经验:

  1. 对于新技术爱好者,在Quick Start出问题时,不要放弃,更不要胡乱尝试。要按照下面的步骤来解决问题:
    • 列出所有怀疑对象;
    • 逐一排查,并记录所有可疑点,这个过程中最有力的两个工具:Google和系统日志;
    • 定位问题,尝试解决,如果失败,重新回到第一步;
  2. 对于系统设计者,在设计新系统是,要考虑以下几个方面:
    • 一定要提供完善的日志记录,供用户调试;
    • 即使提供傻瓜操作式工具,也要提供方式让高级用户查看细节,比如让用户查看自动化脚本;
    • 对于复杂的自动化脚本,要提供说明文档,解释原理。

One More Thing

发现这个问题的原因是自动配置脚本没有考虑多网卡的情况,于是给社区提交了issue。看,问题解决了,深入理解Kubernetes了,顺便还给社区做了贡献,一举三得!

Share

Kubernetes救援 – 教你如何从新技术的坑里爬出来(上)

当下新技术层出不穷,为了降低开发者的学习成本,很多新技术都会提供“Quick Start”,初学者只需要非常简单的几步,就可以把这个新技术用起来。“Quick Start”的初衷是好的,隐藏复杂性,让用户第一时间体验产品。但是,正因为复杂性被隐藏了,很多初学者在跟着“Quick Start”成功操作一遍后,会产生“我已经会了”的假象。而在引入到具体项目后,遇到问题,束手无策,只能求助于StackOverflow一知半解的答案,或者陷入到茫茫多的官方文档之中。

为什么我的眼里常含泪水?因为这坑真是又深又沉。每当被坑,我的心里就会浮现出这张图:

 

how-to-draw-a-horse

最近摆弄Kubernetes,不免又被摆了一道。

一不小心掉进坑里

作为一个发布不过两三年的新技术,Kubernetes的文档可谓做的不错。对于如何创建试用集群,Kubernetes提供了非常丰富的说明,包括基于三大云平台(AWS,GCE,Azure),Mesos集群,CoreOS集群,vagrant虚拟机,以及裸机等等。一开始,我选择vagrant多机部署,一切都是自动化的,等了大概半个多小时,显示配置成功,然后按照文档说的,执行了几个kubectl命令,看起来挺简单的。

“Quick Start”基本上都是提供一个傻瓜式命令,想变魔术一样,“嘭”,什么都有了。 因为vagrant集群是基于fedora的,而且用SaltStack做部署,每次启动都要重新下载一大堆东西,慢的要死,不适合快速创建演示环境。所以我决定稍微改变一下,用vagrant创建几个Ubuntu的虚拟机,用基于Ubuntu的多机部署方案来做演示集群。

用vagrant创建Ubuntu集群环境,done; 配置一下几台虚拟机之间的ssh key,done; 参照文档,运行几条命令,done; 这次就快多了,运行kubectl命令,没问题。一切都那么美好,我感到自己已经掌握了Kubernetes了,要不怎么说我是DevOps专家呢!看到文档里说,安装kube-ui可以看到图形界面,只需要运行./deployAddons.sh就够了。敲完命令,不断用kubectl get pods –namespace=kube-system看到对应的pod状态,变成running表示已经启动成功。我迫不及待的就去浏览器里看结果,结果就是这样:

fail-to-visit-kube-ui

看到这,我就傻眼了,说好的美图呢?不得不说,此时我的内心是崩溃的。

急救

当然,作为DevOps专家,内心的崩溃是不能让外人看出来的。所以我淡定的开始修复工作:

复制网页上的报错信息; 打开Google(是的,你没看错,是Google); 按下回车 不愧是Google,瞬间就返回了结果,有几个遇到的错误信息和我一样,但是要么是没解决,要么是重启就解决了,等于没说。恩,干得漂亮。

清点处境

既然知道没有人能帮我,我也就放心了。基于我的经历,我发现了一个定律:

Quick Start如果出了问题,是没有Quick Fix的。 深吸一口气,现在只能靠自己了。首先,清点一下我的处境。

我自己配置了三台Ubuntu虚拟机; 根据官方文档里的配置,把config-default.sh中的目标IP修改成真实的虚拟机IP,然后运行kube-up.sh,然后根据按照文档,安装了kube-ui; 网页上报错信息是:Kubernetes healthz sidecar container。 既然只有三条线索,就一个一个排查好了。首先,检查vagrant虚拟机是不是出错了:

三台虚拟机互相ping可以通,宿主机分别ping三台机器也可以通,看来虚拟机网络没问题; 通过网页控制台信息可以看到,没有任何错误,说明8080端口是工作的,而且响应正常; 用service –status-all命令列出所有服务,所有看起来跟Kubernetes有关的服务状态都是running,说明服务都运行良好。 因此暂时可以排除是基础设施的问题,再来看第二个线索,看看是不是漏掉了文档中的关键配置。于是我对着文档,一字一句的看,文档写的比较简洁,因为大部分工作都自动化了,需要配置的也不多,逐字逐句的对比也没发现什么问题。不过倒是在文档里找到一节之前没注意到文字:

kubernetes-ubuntu-doc-troubleshooting

照文档的说法,出问题,看etcd,大喜,赶紧打开日志查看。不出所料,没什么异常。不过根据这个提示,找到了Kubernetes日志输出路径和配置信息路径,也算不小的收获。按照原计划,进行第三步问题排查,看看这条出错信息能帮我找到什么。先Google下kubernetes healthz,发现在Kubernetes 201里提到,这是用来做节点健康检查的。再细想一下,本来应该显示kube-ui的页面,结果却显示了健康检查相关的内容,这说明了什么呢?暂时还不太清楚,但是这个疑问先记下。好了,再清点一下第一轮排查的收获:

找到了Kubernetes的日志,在/var/log/upstart/下面; 找到了Kubernetes的配置,在/etc/default/下面; 找到一个疑点:本该显示kube-ui的页面,却显示了健康检查相关的内容。 缩小排查范围

看起来问题还是没什么头绪,但至少又给自己找了些事情可做。在/var/log/upstart/下面,有很多日志,我可以一个一个看下去。除此之外,别无他法。有时候,没有选择也是一种幸福。先列一下都有哪些日志要查:

master minion1 minion2 docker ✓ ✓ ✓ etcd ✓ – – flanneld ✓ ✓ ✓ kubelet ✓ ✓ ✓ kube-proxy ✓ ✓ ✓ kube-apiserver ✓ – – kube-controller-manager ✓ – – kube-proxy ✓ ✓ ✓ kube-scheduler ✓ – – kube-apiserver ✓ – – 用iterm做分屏非常容易,于是把所有的log都用tail -f /var/log/upstart/.log命令打印出来:

kubernetes-log

看起来不错!当然这只是其中一个节点,另外两个没有展示。现在,我只要再次访问那个让我抓狂的页面,然后从这些漂亮的黑底白字中,找出任何蛛丝马迹,就可以直捣黄龙,解救我于水火之中了。

休息一下

总结一下目前的进展:

Kubernetes集群看似跑起来了,但是却并不能正常工作,比如kube-ui就打不开; 通过排查,基础设施的问题已经排除,将注意力集中在查找Kubernetes配置是否有问题; 监控所有日志,期待出现解决问题的线索。

休息一下,欲知后事,且听下回分解。

Share

如果有10000台机器,你想怎么玩?(一)概述

这一系列文章主要是关于kubernetes和mesos集群管理的内容,里面不会说用啥命令,怎么操作,而是了解一些基本概念,理清思路。

不管这些机器是虚拟还是实体,是啥操作系统,实际上我拥有的是一堆的资源,如cpu、内存、硬盘等。当有人需要某个服务的时候,我从这堆资源中启动某个服务给对方即可。在单机环境中,操作系统有能力帮我们做这样的事情。当我们需要一个服务时,我们就启动一个应用,这个应用使用了操作系统的一些资源,为我们提供服务。剩下的资源可以为我们提供其他的服务。在集群环境中,mesos有能力帮我们做这样的事情。它就像一个操作系统,告诉我们现在集群中有多少的资源。当我们需要一个服务时,我们就启动一个任务,这个任务使用了集群环境的一些资源,为我们提供服务。剩下的资源可以为我们提供其他的服务。一般情况下我们看到的mesos主页是这样子滴:

mesos

我们不希望各个任务太不一样,因为那管理起来很麻烦。神一般的docker把各种任务都抽象成一个容器,这样启动一个任务就变成启动一个容器了,大大解放了我们的双手,让我还有时间在这里码码字。尽管如此,我们还是需要管理我们的容器。Kubernetes就是这样一个容器编排工具。大家叫它k8s,听起来就像i18n那么的亲切。它有自己的一些概念:首先是pod,它里头可以含着多个容器的实例,是k8s调度的原子单元。其次是Replication Controller简称rc,它关联一个pod和一个pod数量。最后是service,它通过rc暴露出来。这三个概念听起来没啥,混合起来使用威力十足。举个栗子:pod里面有一个nginx容器,有一个rc关联到这个pod,并暴露出服务以使外界可以访问这个nginx。当访问量很大的时候,运维人员可以把rc的pod数量这个值从1调整成10,k8s会自动把pod变成10份,从而让nginx容器也启动10份,而服务则会自动在这10份pod中做负载均衡(截稿为止,这个负载均衡的算法是随机)。一条命令就能轻易实现扩容,当然前提是mesos那头有足够的资源。Kubernetes有一个kube-ui的插件可以可视化当前的主机、资源、pod、rc、服务等:

kube-ui

集群操作系统和容器编排工具都有了,假设我们需要一个mysql服务。用k8s启动一个docker hub下的官方镜像,于是它就会被mesos分配在某台有资源的机器上。用户并不关心到底被分配到哪台机器上,只关心服务能不能用,好不好用。现在问题来了:要是服务挂掉,数据会不会丢失?那么应该怎么做持久化?这里需要引入k8s的另外两个概念:PersistentVolume(PV)和PersistentVolumeClaim(PVC)。简单说来,PV就是存储资源,它表示一块存储区域。比如:nfs上的、可读写的、10G空间。PVC就是对PV的请求,比如需要–可读写的1G空间。我们的mysql直接挂载在需要的PVC上就可以了,k8s自己会帮这个PVC寻找适配的PV。就算mysql挂掉或者是被停掉不用了,PVC仍然存在并可被其他pod使用,数据不会丢失。

现在数据库也有了,需要一个tomcat服务来使用刚才创建的mysql服务并把自己暴露到公网上。传统上说,要使用数据库那就得在自己应用的xml或config文件中配置一下数据库的链接,java平台上一般是酱紫滴:

jdbc:mysql://localhost:3306/dbname

可是mysql服务并不在localhost上,我们也不知道它被分配到哪台机器上去了,怎么写这个链接呢?这里边就涉及到k8s服务发现的概念了。一种方法是,k8s在新启动一个pod的时候,会把当前所有的服务都写到这个pod的容器的环境变量里去。于是就可以使用环境变量来“发现”这个服务。但是这种做法并不推荐,因为它要求在启动pod的时候,它所需要的服务已经存在。是啊,如果服务不存在,怎么知道往环境变量写什么呢?由于环境变量大法严重依赖于启动顺序,所以一般使用DNS大法。k8s提供了kube2sky和skydns的插件,当mysql服务启动后,这哥俩就会监听到mysql服务,并为之提供dns服务。所以只要这么配:

jdbc:mysql://mysql.default.svc.cluster.local:3306/dbname

便可以解决服务发现的问题了。

接着往下走,还会涉及到外部负载均衡、高可用、多租户、监控、安全等一系列挑战,你想怎么玩?

Share