近些年来,容器技术迅速席卷全球,颠覆了应用的开发、交付和运行模式。容器技术作为云原生技术领域的技术基石,也是现今最热门的一种服务器端技术。容器以及容器编排技术成为基础设施领域最炙手可热的关键词,随着容器及周边生态技术的蓬勃发展,容器社区当仁不让成为开源社区最活跃的生态圈之一。同时,以容器技术为核心的容器生态圈在云计算、互联网等领域得到了广泛应用。
01容器的概念
早在2000年,容器技术的概念就已经出现,当初是用在chroot环境中做进程隔离。2013年,Docker项目正式发布,让Linux容器技术逐步席卷天下。2014 年,Kubernetes项目正式发布,容器技术开始和编排系统齐头并进。
容器的本质,是一个试图被隔离,资源受限制的进程。所使用的隔离技术都是基于Linux内核提供,其中namespace用来做资源隔离,cgroups用来做资源限制(CPU、内存、存储、网络的使用限制)。隔离技术中不得不提的是虚拟机。通过如下的虚拟机和容器的对比图,可以看出,虚拟机工作原理中最重要的是Hypervisor层——进行硬件虚拟化功能,模拟出运行操作系统需要的各种硬件(如 CPU、内存、I/O 设备等),然后,在这些虚拟硬件上安装操作系统Guest OS。而容器没有自己的OS,通过Container Daemon直接共享宿主机内核,所有对容器进程的限制都是基于宿主机操作系统本身能力进行。故而,容器也显得很轻量化。
02Pod概念
2.1 操作系统中的例子
首先,在虚拟机中运行一个简单的java程序,然后通过pstree指令,将会看到这个java程序实际上是由一组(linux中)线程组成,共享资源,共同协作完成java程序的业务工作。
2.2 Pod实现原理
在被誉为云时代的操作系统Kubernetes 中,容器被类比为了进程,那么Pod呢?其实 Pod 只是一个抽象的逻辑概念,它可以类比为前面例子中的java程序一样,是一组(一个或者多个)容器的集合,这些容器之间共享同一份网络(Network Namespace)和存储(Volume)等资源。
2.2.1 网络共享
Pod中首先会创建一个基础容器(Infra container),然后以join network namespace的方式,将其它容器都与基础容器的网络关联起来,这样就实现了pod内容器间共享网络资源的功能。所以基础容器永远都是Pod 里面第一个被创建的容器,等待其他容器的加入。Pod的生命周期只跟基础容器一致。基础容器是一个非常小的镜像,叫做 k8s.gcr.io/pause,大概 100~200KB 左右,是一个汇编语言写的、永远处于“暂停”状态的容器。
2.2.2 存储共享
Pod是通过Volume实现共享存储的。Pod先绑定好Volume,然后Pod里的容器只要声明挂载这个Volume,就可以共享这个Volume对应的宿主机目录,所有容器看到的这个Volume目录内容都是一样的。以下yaml提供了单Pod内启两个容器的示例。
apiVersion: v1
kind: Pod
metadata:
name: one-pod-two-container-demo
spec:
volumes: #Pod先绑定好Volume
- name: shared-logs
emptyDir: {}
containers:
- name: web-nginx
image: nginx
volumeMounts: #容器再挂载这个volume
- name: shared-logs
mountPath: /var/log/nginx
- name: sidecar-container
image: busybox
command: ["sh","-c","while true; do cat /var/log/nginx/access.log /var/log/nginx/error.log; sleep 30; done"]
volumeMounts: #容器再挂载这个volume
- name: shared-logs
mountPath: /var/log/nginx
03容器设计模式
Kubernetes 社区结合了Kubernetes 集群的微服务模型提出了一系列解决典型分布式系统问题的容器设计模式,主要可以分三大类:
- 单容器管理模式;
- 单节点多容器模式;
- 多节点多容器模式。
3.1 单容器管理模式
Kubernetes 容器设计模式中,单容器管理模式是最简单的,单单启动一个单容器微服务实例命令与Docker原生命令类似。
$ kubectl run nginx --image=nginx:latest -n qiuqiu
pod/nginx created
$ kubectl get all -n qiuqiu
NAME READY STATUS RESTARTS AGE
pod/nginx 1/1 Running 0 9s
3.2 单节点多容器模式
在秉持关注点分离原则(SOC)下,容器化应用程序是容器之间的职责分离,每个容器都应该只承担它最擅长业务,其他额外的任务,应该交给其他容器(Sidecar 容器),单节点多容器模式便是利用了Kubernetes 中Pod内所有容器可以共享同一个存储空间(Volume)和网络地址空间(Network Namespace)特性,来充分体现SOC原则。
3.2.1 挎斗模式(Sidecar Pattern)
挎斗模式主要是利用在同一个Pod中的容器可以共享存储的能力。如下图应用日志收集服务场景:对于应用服务A(如:Nginx)来说,重心并非日志的持久化存储与管理(access.log等),而运维人员有时会需要通过日志排查服务异常问题。因此,日志收集器服务是一个很有必要的辅助工具。秉持关注点分离原则(SOC),可以为应用服务的Pod中加入一个日志收集器服务的Sidecar 容器,应用服务容器作为主容器只需关注业务,日志信息由Sidecar 容器进行收集处理。生产环境中可以使用Filebeat 或者 Logstash 等工具收集日志并分发给ElasticSearch集群。挎斗模式下,通常作为辅助工具的Sidecar 容器镜像是标准化可重用的,应用服务不需要每次都带着辅助工具的执行文件一起打包迭代升级。
以下yaml文件定义了一个Pod内,启动2个容器(不包含基础容器),并使用emptyDir卷作为共享存储。Sidecar 容器通常在定义中排在第二位,因此在执行 kubectl 命令时,默认目标是主容器。Nginx 服务作为主容器,产生的日志写入共享存储卷挂载的/var/log/nginx目录。这样可以阻止原本将Nginx 服务日志以容器标准化输出形式输出(即阻止被软连接到/dev/stdout和/dev/stderr,输出到了容器的前台日志),并写入access.log 与 error.log 文件。最后由Sidecar容器(busybox)进行相关日志处理。
apiVersion: v1
kind: Pod
metadata:
name: app-log-aggregation-server
spec:
volumes:
- name: shared-logs
emptyDir: {}
containers:
- name: nginx
image: nginx
volumeMounts:
- name: shared-logs
mountPath: /var/log/nginx
- name: sidecar-container
image: busybox
command: ["sh","-c","while true; do cat /var/log/nginx/access.log /var/log/nginx/error.log; sleep 30; done"]
volumeMounts:
- name: shared-logs
mountPath: /var/log/nginx
3.2.2 外交官模式(Ambassador Pattern)
外交官模式主要是利用同一个Pod中的容器可以共享网络地址空间的特性。外交官模式中存在一个负责代理从应用程序容器到其他服务的连接的边车容器(SideCar container ),也称为外交官容器。例如,几乎所有应用程序在某个阶段都需要数据库连接。针对应用开发迭代中会存在多种环境数据库——一个开发数据库、一个测试数据库和一个生产数据库。在编写应用程序的 Pod 时,开发/实施人员需要可以通过环境变量或 configMap 更改数据库连接配置项达到数据库的切换。而这种情况下还可以通过外交官模式,根据运行的环境将数据库连接代理到相应的数据库。开发/实施人员无需修改配置项(和使用开发环境数据库配置项一样),当部署到测试或生产环境时,外交官容器会检测它在哪个环境上运行(例如反射方式),并连接到正确的数据库上。
以下yaml给出一个Redis客户端访问不同环境Redis库样例。主容器是由redis客户端服务,外交官容器是基于malexer/twemproxy 镜像启动的容器,提供中继链接Redis数据服务。需要说明的malexer/twemproxy 镜像需要将Redis 实例以环境变量传递(13-15行),格式为address:port:weight。默认情况下,该容器侦听端口 6380(其他高级配置可查阅github:https://github.com/malexer/docker-twemproxy)。
# Redis client yaml
apiVersion: v1
kind: Pod
metadata:
namespace: qiuqiu
name: ambassador-example
spec:
containers:
- name: redis-client
image: redis
- name: ambassador
image: malexer/twemproxy
env:
- name: REDIS_SERVERS
value: redis-st-0.redis-svc.qiuqiu.svc.cluster.local:6379:1
ports:
- containerPort: 6380
以下图,演示了应用程序(由 Redis 客户端模拟)向 localhost:6380 发起请求,外交官容器接收请求并将其中转到其配置中定义的 Redis 服务器。
3.2.3 适配器模式(Adapter Pattern)
适配器模式主要是以标准化和规范化应用对外暴露服务接口。每个应用程序可能会以各种形式(如JSON、XML、StatsD等),对外输出监控指标数据。然而,监控系统却希望收到的是统一数据模型的数据。这时就可以通过使用适配器模式,将应用程序容器,同相应的监控指标数据转换的适配器容器创建在同一个Pod内,就可做到将不同应用的异构监控指标数据转换为单个统一模型数据。
以下yaml是以Prometheus 监控Nginx 服务状态指标为例。Nginx 有一个用于查询 Web 服务器状态的接口,只要更改 default.conf 文件配置项即可启用。接着添加一个适配器容器将此接口的输出转换为 Prometheus 所需的数据格式即可。通过configMap 配置 default.conf 文件内容,其中(20-24行)定义了一个接口(/nginx_status),它利用 stub_status 模块来显示 Nginx 状态信息;接着是创建包含Nginx 容器和适配器容器的Pod。适配器容器使用 nginx/nginx-prometheus-exporter镜像创建,它可以按照 Prometheus 格式转换 Nginx 在 /nginx_status 接口暴露的指标数据格式。
# Nginx config
apiVersion: v1
kind: ConfigMap
metadata:
namespace: qiuqiu
name: nginx-conf
data:
default.conf: |
server {
listen 80;
server_name localhost;
location / {
root /usr/share/nginx/html;
index index.html index.htm;
}
error_page 500 502 503 504 /50x.html;
location = /50x.html {
root /usr/share/nginx/html;
}
location /nginx_status {
stub_status;
allow 127.0.0.1; #only allow requests from localhost
deny all; #deny all other hosts
}
}
# Nginx Pod and the adapter container
apiVersion: v1
kind: Pod
metadata:
namespace: qiuqiu
name: webserver
spec:
volumes:
- name: nginx-conf
configMap:
name: nginx-conf
items:
- key: default.conf
path: default.conf
containers:
- name: webserver
image: nginx
ports:
- containerPort: 80
volumeMounts:
- mountPath: /etc/nginx/conf.d
name: nginx-conf
readOnly: true
- name: adapter
image: nginx/nginx-prometheus-exporter:0.4.2
args: ["-nginx.scrape-uri","http://localhost/nginx_status"]
ports:
- containerPort: 9113
以下图是验证操作:执行yaml创建服务后,首先登录到 webserver Pod,安装 curl 指令(便于发起 HTTP 请求),接着,检查 /nginx_status 接口和适配器容器的转换接口(9113/metrics )。
3.3 多节点多容器模式
3.3.1 选举模式(Election Pattern)
选举模式是一种多节点多容器组合模式,在分布式系统中尤为重要。在分布式系统中,有状态应用服务来要解决高可用和水平伸缩问题时,需要依赖外部系统存储每个实例与状态分片数据的对应关系(全局信息)。而引入选举主控节点机制,就不需要依赖外部的系统来维护自己的状态了,由主控节点完成保存和分发全局信息任务——将应用服务容器与可复用的选举器容器组合起来使用——即选举模式。
以下yaml提供了一个用来作为应用容器检测选举结果的nodejs服务(具体参考Kubernetes社区contrib项目election(https://github.com/kubernetes/contrib/tree/master/election))
apiVersion: apps/v1
kind: Deployment
metadata:
name: demo-elector-nodejs
namespace: qiuqiu
spec:
replicas: 3
selector:
matchLabels:
name: leader-elector
template:
metadata:
labels:
name: leader-elector
spec:
containers:
- image: k8s.gcr.io/nodejs-election-client:0.1
imagePullPolicy: IfNotPresent
name: nodejs
ports:
- containerPort: 8080
protocol: TCP
resources:
requests:
cpu: 100m
- image: k8s.gcr.io/leader-elector:0.5
imagePullPolicy: Always
name: elector
args:
- --election=elect-master
- --http=localhost:4040
ports:
- containerPort: 4040
protocol: TCP
resources:
requests:
cpu: 100m
04综述
以上简单介绍了容器和Pod的概念,并基于容器和Pod的关系,引入容器设计模式。并结合实例介绍了跨斗模式、外交官模式、适配器模式、选举模式。以下列表是对以上介绍模式的一个简单综述。
鉴于篇幅限制,诸如工作队列模式(Work queue pattern)、分散收集模式(Scatter/gather pattern)等其他优秀的设计模式,读者可以按需查询社区文档(https://kubernetes.io/blog)。