阿里、蚂蚁、晟腾、中科加禾精彩分享 AI 基础设施洞见,现购票可享受 9 折优惠 |AICon 了解详情
写点什么

《Kubernetes 与云原生应用》系列之实践案例“单节点多容器模式”

  • 2016-11-02
  • 本文字数:6906 字

    阅读完需:约 23 分钟

《Kubernetes 与云原生应用》专栏是 InfoQ 向轻元科技首席架构师王昕约稿的系列文章。本专栏包含 8 篇内容,将会从介绍和分析 Kubernetes 系统以及云原生应用入手,逐步推出基于 Kubernetes 的容器设计模式实践案例,希望对计划应用 Kubernetes 的朋友有所帮助。本文是该专栏的第四篇,阅读本系列全部内容请在细说云计算微信公众号(CloudNote)回复 K8s。

1. Kubernetes 系统架构与设计理念
2. 云原生应用的设计理念与挑战
3. Kubernetes 与云原生应用的容器设计模式
4. Kubernetes 容器设计模式实践案例 - 单节点多容器模式
5. Kubernetes 容器设计模式实践案例 - 多节点选举模式
6. Kubernetes 容器设计模式实践案例 - 工作队列模式
7. Kubernetes 容器设计模式实践案例 - 分散收集模式
8. 云原生应用的容器设计模式综述与展望

K8s 与容器设计模式

目前 K8s 社区推出的容器设计模式主要分为三大类:

第一类,单容器管理模式;
第二类,单节点多容器模式;
第三类,多节点多容器模式;

一类比一类更复杂。根据复杂性的不同,本系列文章给出不同篇幅的实践案例介绍。

对于第一类,只在本文中用一小节给与介绍;对于第二类,在本文中,针对每一种典型设计模式分一个小节给与介绍;对于较复杂的第三类,每一种典型设计模式将用一篇文章给与介绍。

单容器管理模式

K8s 的最大特色是支持多容器的微服务实例。当然,单容器的模式也是支持的,只不过这种模式并不能突出 K8s 的特色和强大。很多人对 K8s 一直以来的印象是:功能强大,但入门较难。其实,单单就启动一个单容器微服务实例,K8s 的命令行操作跟 Docker 原生命令一样简单。

运行一个 nginx 容器

复制代码
[root@demo-k8s ~]# kubectl run nginx --image=nginx
deployment "nginx" created
[root@demo-k8s ~]# kubectl get deployment
NAME DESIRED CURRENT UP-TO-DATE AVAILABLE AGE
nginx 1 1 1 1 24s
[root@demo-k8s ~]# kubectl get rs
NAME DESIRED CURRENT AGE
nginx-3137573019 1 1 1m

由上面的例子可以看到,K8s 只要一个命令既可以启动以 nginx 为镜像的一个微服务实例。与此同时,K8s 的强大之处在于,方便用户用一个命令的同时,仍然保证了 K8s 应用体系的完整性和规范性。也就是说,虽然用户只运行了一个命令,但 K8s 为用户自动创建了四种 API 对象,包括:Deployment,ReplicaSet(RS),Pod 和 Container 要想扩展伸缩同一服务的实例个数也非常简单。

复制代码
[root@demo-k8s ~]# kubectl scale deployment nginx --replicas=3
deployment "nginx" scaled
[root@demo-k8s ~]# kubectl get rs
NAME DESIRED CURRENT AGE
nginx-3137573019 3 3 22m

依靠这种兼顾易用性和模型一致性的设计理念,K8s 使自己既适合简单场景也适合复杂场景。

单节点多容器模式

从单节点多容器模式开始的容器设计模式,是真正体现 K8s 设计特点的地方,也就是基于多容器微服务模型的分布式应用模型。在 K8s 体系中,Pod 是一个轻量级的节点,同一个 Pod 中的容器可以共享同一块存储空间和同一个网络地址空间,这使得我们可以实现一些组合多个容器在同一节点工作的模式。既然 Pod 的特点是共享存储空间和网络地址,那么单节点多容器模式一定是利用这两种特性的。

挎斗模式(Sidecar pattern)

第一种单节点多容器模式是挎斗模式。这种模式主要是利用在同一 Pod 中的容器可以共享存储空间的能力。

一个典型的挎斗应用场景如图所示:一个工具容器写文件到共享的文件目录,应用主容器从共享的文件目录读文件。例如,我们可以用 Nginx 构建一个代码发布仓库,简单的将代码放到某个本地目录即可。为了保持同步,我们同时用一个装有 Git 客户端的容器定时到原始代码仓库同步下拉最新的代码。这种模式的好处是,工具容器的镜像,也就是打包有 Git 客户端的镜像可以重用,而不需要跟应用的容器打包在一起。同样的应用,应用主容器不用 Nginx 也可以用 Apache Httpd,都可以跟工具容器组合起来形成微服务。

图:工具容器写文件应用容器读文件

另一个典型的挎斗模式如图所示:一个工具容器读文件,应用容器写文件。例如:一个基于 Nginx 的 Web 应用向系统文件系统写入日志,而一个收集日志的容器从共享目录读出日志,并输出到集群的日志系统。这一模式的好处在于,工具容器的镜像是可以重用的,不需要在每次更新应用容器打包的时候,把工具容器的执行文件打包进去。

图:工具容器读文件应用容器写文件

外交官模式 (Ambassador pattern)

第二种单节点多容器模式是外交官模式。这种模式主要利用同一 Pod 中的容器可以共享网络地址空间的特性。如图所示,在一个 Pod 中给应用容器搭配一个工具容器作为代理服务器。工具容器帮助应用容器访问外部服务,使得应用容器访问服务时不需要使用外网的 IP 地址,而只需要用 localhost 访问本地服务。在这种模式下,作为代理服务器的工具容器好像外部服务派驻在 Pod 中的“外交官”,使得应用容器办理业务时只需要跟本 Pod 的外交官打交道,而不需要出国了,因此而得名。

图:外交官模式的逻辑结构

基于外交官模式的 Redis 访问演示案例

我们这里用一个访问 Redis 服务的简单案例,来实践体验一下 Ambassador 模式和 K8s 单节点多容器模式的应用细节。本案例的文件清单在 Github 上。

图:基于外交官模式的 Redis 访问演示案例架构

1. 创建一个初始的 Redis Master 实例

先创建一个 Redis Master 节点的 Pod 用于初始化 Redis 集群。

复制代码
kubectl create -f examples/redis/redis-master.yaml

2. 创建 redis 的服务

创建一个 redis 的服务,这个服务可以在前段作为多个 redis 的 Pod 节点的负载均衡。

复制代码
kubectl create -f examples/redis/redis-service.yaml

3. 创建 redis 的 replication controller

创建控制多个 redis 服务 Pod 的 RC,当然也可以用 Deployment 或 ReplicaSet 来创建。

复制代码
kubectl create -f examples/redis/redis-controller.yaml
复制代码
[root@demo-k8s ~]# kubectl get rc
NAME DESIRED CURRENT AGE
redis 1 1 19h

创建完后可以用 kubectl get 命令查看 rc 和 Pod,会发现并没有产生新的 Redis Pod,这是因为原来的 Pod redis-master 已经满足了 replica=1 的要求。

4. 创建 redis-sentinel 的服务

创建 redis sentinel 服务。

复制代码
kubectl create -f examples/redis/redis-sentinel-service.yaml

5. 将 redis-sentinel 的 replication controller

创建控制多个 redis sentinel 服务 Pod 的 RC。

复制代码
kubectl create -f examples/redis/redis-sentinel-controller.yaml

6. 将 redis 实例和 redis-sentinel 实例扩展成 3 个

复制代码
kubectl scale rc redis --replicas=3
kubectl scale rc redis-sentinel --replicas=3

7. 删除掉手工启动的 redis 实例 redis-master

删除掉我们已开始手工创建的 redis master 的 Pod,redis 的 rc 会自动启动新的 redis 以满足 replicas=3 的要求。同时,Redis sentinel 节点会选举出一个新的节点作为 master 节点。

复制代码
kubectl delete pods redis-master

8.Redis 集群的验证方法

查询 redis 集群中所有 redis 实例的 IP。

复制代码
[root@demo-k8s ~]# kubectl describe pod -l name=redis | grep IP
IP: 10.120.44.3
IP: 10.120.63.3
IP: 10.120.80.5

我们知道,这 3 个 redis 实例中,只有一个是 Master 节点,是可写的,可以调用 SET 命令和 GET 命令;其他两个节点是 Slave 节点,是只读的,只能调用 GET 命令。我们可以用下面的命令测试三个 redis 节点。

复制代码
[root@demo-k8s ~]# echo -e "SET test1 SET test1 8\r\nQUIT\r\n" | curl telnet://10.120.44.3:6379
-READONLY You can't write against a read only slave.
+OK
[root@demo-k8s ~]# echo -e "SET test1 8\r\nQUIT\r\n" | curl telnet://10.120.63.3:6379
-READONLY You can't write against a read only slave.
+OK
[root@demo-k8s ~]# echo -e "SET test1 8\r\nQUIT\r\n" | curl telnet://10.120.80.5:6379
+OK
+OK

通过上面的测试我们可以知道只有 IP 为 10.120.80.5 的 redis pod 是 Master 节点,其他两个是 slave 节点。

对于只读的操作,我们可以利用 redis 的 service IP,通过 K8s 的 kube-proxy 来访问,如下,我们得到 redis 的 CLUSTER-IP 为 10.123.248.129,可以用这个 IP 来读取 Redis 数据。那么,如果用这个 IP 来写数据将怎么样呢,后面将看到。

复制代码
[root@demo-k8s ~]# kubectl get svc -l name=redis
NAME CLUSTER-IP EXTERNAL-IP PORT(S) AGE
redis 10.123.248.129 <none> 6379/TCP 5d
[root@demo-k8s ~]# echo -e "GET test1\r\nQUIT\r\n" | curl telnet://10.123.248.129:6379
$1
8
+OK

9. 制作 Ambassador 容器镜像

截至目前,我们还没有用到外交官模式。下面我们用 Haproxy 制作一个外交官代理,用来访问 Redis 服务,使得跟该容器在同一个 Pod 里的容器,在访问 Redis 读写服务的时候,都只需要访问本地 localhost 服务。本例中的文件在 Github 上可以找到。

Dockerfile 文件清单:Dockerfile

复制代码
FROM haproxy:1.5
COPY tmpl-haproxy.cfg /
COPY starthaproxy.sh /
CMD ["sh"Node.js "-c"Node.js "/starthaproxy.sh"]

启动 haproxy 的文件清单:starthaproxy.sh

复制代码
echo
"Proxying localhost ${INPUT_PORT} to ${TARGET_IP}:
${TARGET_PORT}"
复制代码
cat tmpl-haproxy.cfg |
sed -e "s/INPUT_PORT/${INPUT_PORT}/" -e "s/TARGET_IP/${TARGET_IP}/" -
e "s/TARGET_PORT/${TARGET_PORT}/" > /haproxy.cfg
haproxy -f /haproxy.cfg

通过这里的 Dockerfile 构建的代理服务器容器,根据输入的环境变量 INPUT_PORT、TARGET_IP 和 TARGET_PORT,可以将发向本地 INPUT_PORT 的服务请求转发到目标 TARGET_IP:TARGET_PORT 上去。
因此这个容器可以作为一个简单方便的本地代理服务器使用。

10. 使用 Ambassador 代理服务器的 pod

使用 Ambassador 代理服务的 Pod 文件清单:demo-redis-amb-centos.yaml

复制代码
apiVersion: v1
kind: Pod
metadata:
labels:
name: demo-redis-amb-centos
name: demo-redis-amb-centos
spec:
containers:
- name: app-main-container
image: centos:7
args:
- sleep
- "1000000"
- name: redis-amb-read
image: xwangqingyuan/port-forward-haproxy
env:
- name: INPUT_PORT
value: "6379"
- name: TARGET_IP
value: "10.123.248.129"
- name: TARGET_PORT
value: "6379"
ports:
- containerPort: 6379
- name: redis-amb-write
image: xwangqingyuan/port-forward-haproxy
env:
- name: INPUT_PORT
value: "16379"
- name: TARGET_IP
value: "10.120.80.5"
- name: TARGET_PORT
value: "6379"
ports:
- containerPort: 16379
volumes:
- name: data
emptyDir: {}

在这个 Pod 中,我们用一个 CentOS 容器作为应用的主容器来使用 Ambassador 工具容器,当然也可以用实际的应用容器 Tomcat、Node.js、PHP 等等,因为这里只是为了演示测试 Ambassador 代理,我们用 CentOS 作为应用容器。其中我们有两个外交官容器,一个用来从 Redis 集群读数据,容器的名字为 redis-amb-read,它将发向本地 6379 端口的请求转发到 redis 服务的 CLUSTER-IP,最终会轮训地发送给任意一个 redis 实例;另一个用来向 Redis 集群写数据,容器的名字为 redis-amb-write,它将发向本地 16379 端口的请求转发到 redis master Pod 的 IP。

11. 测试一个使用 Ambassador 模式的 pod

创建一个使用 Ambassador 代理的 Pod,并登陆到主容器进行测试。

复制代码
kubectl create -f /home/centos/worktemp/redis/demo-redis-amb-centos.yaml
kubectl exec -it demo-redis-amb-centos /bin/bash

测试对读容器本地 Ambassador 的访问。

复制代码
echo -e "SET test1 10\r\nQUIT\r\n" | curl telnet://localhost:6379
-READONLY You can't write against a read only slave.
+OK
echo -e "SET test1 10\r\nQUIT\r\n" | curl telnet://localhost:6379
-READONLY You can't write against a read only slave.
+OK
echo -e "SET test1 10\r\nQUIT\r\n" | curl telnet://localhost:6379
+OK
+OK
echo -e "GET test1\r\nQUIT\r\n" | curl telnet://localhost:6379
$2
10
+OK
echo -e "GET test1\r\nQUIT\r\n" | curl telnet://localhost:6379
$2
10
+OK
echo -e "GET test1\r\nQUIT\r\n" | curl telnet://localhost:6379
$2
10
+OK

从测试结果可以发现,对读容器执行 GET 操作时,操作总是成功的,说明 3 个 redis pod 都可以读取数据。而对读容器执行 SET 操作时,3 个操作只有一个是成功的,也就是说只有负载均衡将请求发给 redis master pod 时,操作能够成功。

测试对写容器本地 Ambassador 的访问。

复制代码
echo -e "SET test1 10\r\nQUIT\r\n" | curl telnet://localhost:16379
+OK
+OK
echo -e "SET test1 10\r\nQUIT\r\n" | curl telnet://localhost:16379
+OK
+OK
echo -e "SET test1 10\r\nQUIT\r\n" | curl telnet://localhost:16379
+OK
+OK

从测试结果可以发现,对写容器执行 SET 操作时,操作总是成功的,说明 redis-amb-write 容器将所有请求都转发给了 redis master pod。

适配器模式(Adapter pattern)

第三种单节点多容器模式是适配器模式。这种模式对于监控和管理分布式系统尤为重要。对分布式系统的一种理想设计目标,就是能够实现“分布地执行和存储,统一的监控和管理”。要想实现“统一的监控和管理”,应用和监控管理交互的接口需要是统一的,而且其接口是依照“统一的监控服务”的接口模式来实现。这和面向对象设计模式中的“适配器模式”也非常相似。

图:统一监控服务和监控适配器模式

图:利用 Prometheus 作为监控服务的分布式系统

一个典型的可以采用适配器模式的系统,是利用 Prometheus 作为监控服务的分布式系统。在 Prometheus 周边项目中,有诸多适用于不同应用系统的监控数据输出器(Exporter),负责收集跟特定应用相关的监控数据,使得 Prometheus 服务可以以统一的数据模式收集不同应用系统的监控数据,每个 Exporter 同时也都是一个适配器模式的实现。

总结

本文主要介绍了 K8s 集群中,单节点单容器模式和基于 Pod 模型所支持的几种单节点多容器模式,包括挎斗模式、外交官模式和适配器模式。并且对于外交官模式,利用一个 redis 集群案例演示了如何利用外交官模式以访问本地服务的模式访问网络服务。

后续,K8s 社区肯定还会发展出其他的容器设计模式,但不论如何,单节点多容器模式主要是利用同一 Pod 中的容器可以共享存储空间和网络地址空间的特点。本文案例中的代码只能用来演示,还比较简单,有很多的地方可以增强。例如:用 Replication Controller 的地方,可以用 Deployment 或 Replica Set 来部署,这是 K8s 社区更为推荐的方式;对于案例中的外交官容器,Master Pod 的 IP 和 Redis 服务的 IP 未必需要写死,而是可以通过脚本从指定的服务读取;此外,演示中所用的代理服务器容器也可以增强为支持多个端口映射的代理服务器。

答疑环节

问:考虑到性能,分布式文件存储可能不会适合,K8s 有没有针对 database 的略微完美的部署方案?

王昕:完美的应该没有。有两个 K8s 功能可能跟 database 的存储有关,一个是 nodeAffinity 一个是 PetSet。

nodeAffinity 的作用是让你可以为 Pod 选定特定的节点运行,例如你想要的高性能的挂载了 SSD 的节点。PetSet 的作用是让 Pod 可用于有状态服务,可以固定跟某个存储绑定。这两个功能都处于非常早的阶段还很不成熟,但可以肯定这些功能是 K8s 社区专门解决有状态服务和存储问题的方案。

在 nodeAffinity 和 PetSet 设计稳定之前,还有一个 nodeSelector 也是可以利用的。它可以让用户选定特定的 node 部署 Pod,同样,你可以选定挂有高性能存储的节点。

问:希望更深层次了解一下 Overlay 的网络原理,能否介绍一下?

王昕:Overlay 也就是覆盖网络,跟隧道技术是紧密相关的,通过网络封装技术模拟二层网络,跟 VPN 所采用的技术是类似的。

简单说,比如 A 和 B 之间是没有网络直接用以太网连接的,就是所谓的二层不连通。但是通过三层的 IP 层可以连通,也就可以建立四层的连接,比如 UDP 可以连通,也就是四层连通,二层不连通。但是我可以,通过四层的连通传输二层以太网的数据包,模拟出一个二层连通的以太网来。这个模拟出来的连通的二层网络就叫 Overlay,下面真实的物理网络叫做 Underlay。

所以说,Overlay 的技术每一层封装会增加一些开销,会影响一定性能。此外,各个层次对网络丢包延迟的问题处理不好,也会影响性能。K8s 比较流行的基于 Overlay 的组网技术有 Flannel 和 Weave,Weave 目前看性能是比较差的。Flannel 有两种隧道封装技术,UDP 和 VxLAN,一般来说 VxLAN 性能比 UDP 好很多。Overlay 有一定的性能劣势,也有优势,主要是降低对下面物理网络的依赖,很容易搭建。


感谢魏星对本文的策划和审校。

给InfoQ 中文站投稿或者参与内容翻译工作,请邮件至 editors@cn.infoq.com 。也欢迎大家通过新浪微博( @InfoQ @丁晓昀),微信(微信号: InfoQChina )关注我们。

2016-11-02 17:562892

评论

发布
暂无评论
发现更多内容

金三银四了!必知必会,HTTP面试题!漫画图解超硬核!

小白debug

面试 网络编程 网络 HTTP 网络层

v01.12 鸿蒙内核源码分析(双向链表) | 谁是内核最重要结构体 | 百篇博客分析 HarmonyOS 源码

鸿蒙研究站

鸿蒙 HarmonyOS 鸿蒙内核源码分析 百篇博客分析鸿蒙 百万汉字注解鸿蒙

智能化软件开发微访谈·第十六期:低代码/无代码开发

吴盛

低代码 快速开发 sql 无代码开发

要求输出事故报告,线上日志文件却不见了!!

陈皮的JavaLib

Java 运维 日志框架

智慧公安一键扫描二维码报警定位系统

13828808769

智慧交通

关于热力图数据上报清洗,我们做了一个有意思的尝试

阿里巴巴中间件

云端数智新引擎,腾讯云原生数据湖计算重磅发布

腾讯云大数据

大数据 数据湖

初识Golang之函数及方法的多返回值

Kylin

3月日更

另类数据:投资中的怪咖

博文视点Broadview

大“食”代来临,后厨重地可以更“聪明”点儿

IoT云工坊

人工智能 物联网 PaaS 智慧厨房 智慧餐饮

EGG Network阿凡提的模式是怎么样的?早点了解别错失这个机会!

币圈那点事

区块链

是谁拖(慢)了 Redis 的后腿?

escray

redis 学习 极客时间 3月日更 Redis 核心技术与实战

17张图带你搞懂ZooKeeper一致性原理!

Java小咖秀

程序员 TCP udp 传输协议

聊聊LiteOS中生成的Bin、HEX、ELF三种文件格式

华为云开发者联盟

编译器 LiteOS Bin HEX ELF

区块链电子合同签署平台,助力企业数字化转型

13828808769

区块链+ #区块链#

MindSpore实践:对篮球运动员目标的检测

华为云开发者联盟

深度学习 mindspore 图像检测 yolo 篮球运动

基于NB-IoT的智慧路灯监控系统(NB-IoT专栏—实战篇5:手机应用开发)

不脱发的程序猿

物联网 28天写作 3月日更 NB-IoT智慧路灯 手机应用开发

Knativa 基于流量的灰度发布和自动弹性实践

Serverless Devs

Serverless Kubernetes 运维 云原生 Knative

35岁了,还不知道,TCP为什么会粘包?【硬核图解】

小白debug

TCP 网络 协议栈 TCP/IP 网络层

未来几年,低代码开发平台会如何发展?

优秀

低代码

6大创新技术及2亿美元投入计划,这个活动有点料

华为云开发者联盟

人工智能 数据库 华为 云原生 HDC.Cloud

Python OpenCV 图像平移,取经之旅第 10 天

梦想橡皮擦

3月日更

金三银四,冰河为你整理了这份20万字134页的面试圣经!!

冰河

面试 面经 offer 金三银四 我要进大厂

降维打击:数据可视化降本增效,传统制造业价值即将扭转!

一只数据鲸鱼

物联网 数据可视化 工业物联网 数字化运维 3D

数字化进入深水区

鲸品堂

方法论 数字化 企业数字化转型

智能安防监控系统的发展与应用

anyRTC开发者

android 监控 音视频 WebRTC RTC

Python基础之:Python中的IO

程序那些事

Python 人工智能 数据分析 程序那些事

风暴眼中的“以太坊”堪比堵车的北京东三环,NA公链(Nirvana)NAC公链对垒胜算几何?

区块链第一资讯

区块链

区块链产品宗谱链,一款记录族谱的APP

13828808769

区块链+ #区块链#

关于企业容器安全问题的思考

阿里巴巴中间件

基于深度学习的端到端通信系统模型

华为云开发者联盟

深度学习 端到端 编码器 通信系统 信道模型

《Kubernetes与云原生应用》系列之实践案例“单节点多容器模式”_语言 & 开发_王昕_InfoQ精选文章