本节书摘来自华章出版社《开源容器云OpenShift:构建基于Kubernetes的企业应用云平台》一书中的第3章,第3.2节,作者 陈耿 ,更多章节内容可以访问云栖社区“华章计算机”公众号查看
3.2 核心组件详解
OpenShift的核心组件及其之间的关联关系如图3-2所示。OpenShift在容器编排层使用了Kubernetes,所以OpenShift在架构上和Kubernetes十分接近。其内部的许多组件和概念是从Kubernetes衍生而来,但是也存在一些在容器编排层之上,OpenShift特有的组件和概念。下面将详细介绍OpenShift内部的核心组件和概念。
3.2.1 Master节点
在介绍Master节点前,我们先补充一些内容。OpenShift集群可以由一台或多台主机组成。这些主机可以是物理机或虚拟机,同时可以运行在私有云、公有云,或混合云上。在OpenShift的集群成员有两种角色。
Master节点:即主控节点。集群内的管理组件均运行于Master节点之上。Master节点负责管理和维护OpenShift集群的状态。
Node节点:即计算节点。集群内的容器实例均运行于Node节点之上。
如图3-2所示,在Master节点上运行了众多集群的服务组件:
API Server。负责提供集群的Web Console以及RESTful API服务。集群内的所有Node节点都会访问API Server更新各节点的状态及其上运行的容器的状态。
数据源(Data Store)。集群所有动态的状态信息都会存储在后端的一个etcd分布式数据库中。默认的etcd实例安装在Master节点上。如有需要,也可以将etcd节点部署在集群之外。
调度控制器(Scheduler)。调度控制器在容器部署时负责按照用户输入的要求寻找合适的计算节点。例如,在前面章节我们部署的Router组件需要占用计算节点主机的80、443及1936端口。部署Router时,调度控制器会寻找端口没有被占用的计算节点并分配给Router进行部署。除了端口外,用户还能指定如CPU、内存及标签匹配等多种调度条件。
复制控制器(Replication Controller)。对容器云而言,一个很重要的特性是异常自恢复。复制控制器负责监控当前容器实例的数量和用户部署指定的数量是否匹配。如果容器异常退出,复制控制器将会发现实际的容器实例数少于部署定义的数量,从而触发部署新的容器实例,以恢复原有的状态。
3.2.2 Node节点
在Node节点上没有这么多系统组件,其主要职责就是接收Master节点的指令,运行和维护Docker容器。
这里要指出的是,Master节点本身也是一个Node节点,只是在一般环境中会将其运行容器的功能关闭。但是在我们这个实验集群中,由于只有一个节点,所以这个Master节点也必须承担运行容器的责任。
通过执行oc get node命令可以查看系统中的所有节点。
[root@master ~]# oc get nodes
NAME STATUS AGE
master.example.com Ready 1d
查看集群信息需要集群管理员的权限,请先登录为system:admin。具体方法请查看之前的章节介绍。
可以看到,目前集群中只有一个节点,状态是Ready。通过oc describe node master.example.com命令查看节点的详细信息。
[root@master ~]# oc describe node master.example.com
Name: master.example.com
Labels: beta.kubernetes.io/arch=amd64
beta.kubernetes.io/os=linux
kubernetes.io/hostname=master.example.com
Taints: <none>
CreationTimestamp: Sat, 17 Sep 2016 18:24:40 -0400
Phase:
Conditions:
Type Status LastHeartbeatTime LastTransitionTime Reason Message
---- ------ ----------------- ------------------ ------ -------
OutOfDisk False Sat, 17 Sep 2016... Sat, 17 Sep 2016... KubeletHasSufficient...
MemoryPressure False Sat, 17 Sep 2016... Sat, 17 Sep 2016... KubeletHasSufficient...
Ready True Sat, 17 Sep 2016... Sat, 17 Sep 2016... KubeletReadykubelet...
Addresses: 192.168.172.167,192.168.172.167
Capacity:
alpha.kubernetes.io/nvidia-gpu: 0
cpu: 1
memory: 1868664Ki
pods: 110
Allocatable:
alpha.kubernetes.io/nvidia-gpu: 0
cpu: 1
memory: 1868664Ki
pods: 110
System Info:
Machine ID: 68b21a5dd46c4a34a8b7f66cd66b3b73
System UUID: 564D2779-5B04-6978-470E-5796F8DF3ECB
Boot ID: 0b3c164b-6d0b-4082-b5da-bac31b8f61a2
Kernel Version: 3.10.0-327.el7.x86_64
OS Image: CentOS Linux 7 (Core)
Operating System: linux
Architecture: amd64
Container Runtime Version: docker://1.10.3
Kubelet Version: v1.3.0+52492b4
Kube-Proxy Version: v1.3.0+52492b4
ExternalID: master.example.com
......
从上面的输出可以看到该节点详细的系统信息、节点上运行的容器资源使用情况、网络地址等。
3.2.3 Project与Namespace
在Kubernetes中使用命名空间的概念来分隔资源。在同一个命名空间中,某一个对象的名称在其分类中必须是唯一的,但是分布在不同命名空间中的对象则可以同名。OpenShift中继承了Kubernetes命名空间的概念,而且在其之上定义了Project对象的概念。每一个Project会和一个Namespace相关联,甚至可以简单地认为,Project就是Namespace。所以在OpenShift中进行操作时,首先要确认当前执行的上下文是哪一个Project。
通过oc project命令可以查看用户当前所在的Project。
[root@master ~]# oc project
Using project "default" on server "https://192.168.172.167:8443".
通过oc project 可以切换到指定的项目。现在请切换到上一章创建的helloworld-php项目,接下来我们会以这个项目为基础进行讲解。
[root@master ~]# oc project hello-world-php
Now using project "hello-world-php" on server "https://192.168.172.167:8443".
3.2.4 Pod
在OpenShift上运行的容器会被一种叫Pod的对象所“包裹”,用户不会直接看到Docker容器本身。从技术上来说,Pod其实也是一种特殊的容器。执行oc get pods命令可以看到当前所在项目的Pod。
[root@master ~]# oc get pod
NAME READY STATUS RESTARTS AGE
cakephp-mysql-example-1-build 0/1 Completed 0 1h
cakephp-mysql-example-1-u63y2 1/1 Running 0 1h
mysql-1-jovdm 1/1 Running 0 1h
执行oc describe pod命令可以查看容器的详细信息,如Pod部署的Node节点名、所处的Project、IP地址等。
[root@master ~]# oc describe pod mysql-1-jovdm
Name: mysql-1-jovdm
Namespace: hello-world-php
Security Policy: restricted
Node: master.example.com/192.168.172.167
Start Time: Sat, 17 Sep 2016 21:00:28 -0400
Labels: deployment=mysql-1
deploymentconfig=mysql
name=mysql
Status: Running
IP: 172.17.0.6
Controllers: ReplicationController/mysql-1
……
用户可以近似认为实际部署的容器会运行在Pod内部。一个Pod内部可以运行一个或多个容器,运行在一个Pod内的多个容器共享这个Pod的网络及存储资源。从这个层面上,可以将Pod理解为一个虚拟主机,在这个虚拟主机中,用户可以运行一个或多个容器。虽然一个Pod内可以有多个容器,但是在绝大多数情况下,一个Pod内部只运行一个容器实例。Pod其实也是一个Docker容器,通过dockerps命令可以查看Pod的实例信息。
因为大多数情况下都是一个容器运行在一个Pod内,很多时候可以将Pod等同于我们所要运行的容器本身。
[root@master ~]# dockerps |grep php
17c5e7154790 172.30.73.49:5000/hello-world-php/cakephp-mysql-example@sha256:e3f4705aac3e718c22e4d8d1bf12ab3041d0f417752289ea132e503cf5adc91d "container-entrypoint" About an hour ago Up About an hour k8s_cakephp-mysql-example.9fcda1c6_cakephp-mysql-example-1-u63y2_hello-world-php_382e1e18-7d3c-11e6-a285-000c29df3ecb_ced53322
3eb08d061fa9 openshift/origin-pod:v1.3.0 "/pod" About an hour ago Up About an hour
……
上述代码是查找PHP的容器实例。一共有两个输出,一个是实际的PHP应用的容器实例,另一个是镜像类型为openshift/origin-pod:v1.3.0的容器,即Pod容器实例。
容器像盒子一样为应用提供一个独立的运行环境,但它并不是一个黑盒子。用户可以实时地查看容器的输出,也可以进入容器内部执行操作。
执行oc logs 命令,可以查看Pod的输出。
[root@master ~]# oc logs mysql-1-jovdm
---> 16:22:33 Processing MySQL configuration files ...
---> 16:22:33 Initializing database ...
……
执行ocrsh命令,可以进入容器内部执行命令进行调试。
[root@master ~]# ocrsh mysql-1-jovdm
sh-4.2$ hostname
mysql-1-jovdm
sh-4.2$ ps ax
PID TTY STAT TIME COMMAND
1 ?Ssl 0:51 /opt/rh/rh-mysql56/root/usr/libexec/mysqld --default
43096 ?Ss 0:00 /bin/sh
43104 ? R+ 0:00 ps ax
oc logs及ocrsh是两个非常有用的命令,是排查容器相关问题的重要手段。
3.2.5 Service
容器是一个一个非持久化的对象。所有对容器的更改在容器销毁后默认都会丢失。同一个Docker镜像实例化形成容器后,会恢复到这个镜像定义的状态,并且获取一个新的IP地址。容器的这种特性在某些场景下非常难能可贵,但是每个新容器的IP地址都在不断变化,这对应用来说不是一件好事。拿前文部署的PHP和MySQL应用来说,后端MySQL容器在重启后IP地址改变了,就意味着必须更新PHP应用的数据库地址指向。如果不修改应用地址指向,就需要有一种机制使得前端的PHP应用总是能连接到后端的MySQL数据库。
为了克服容器变化引发的连接信息的变化,Kubernetes提供了一种叫Service(服务)的组件。当部署某个应用时,我们会为该应用创建一个Service对象。Service对象会与该应用的一个或多个Pod关联。同时每个Service会被分配到一个IP地址,这个IP地址是相对恒定的。通过访问这个IP地址及相应的端口,请求就会被转发到对应Pod的相应端口。这意味着,无论后端的Pod实例的数量或地址如何变化,前端的应用只需要访问Service的IP地址,就能连接到正确的后端容器实例。Service起到了代理的作用,在相互依赖的容器应用之间实现了解耦。
通过oc get svc命令,可以获取当前项目下所有Service对象的列表。
[root@master ~]# oc get svc
NAME CLUSTER-IP EXTERNAL-IP PORT(S) AGE
cakephp-mysql-example 172.30.1.84 <none> 8080/TCP 17h
mysql 172.30.166.12 <none> 3306/TCP 17h
通过CakePHP的Service的IP地址加端口172.30.1.84:8080,可以访问到Cake-PHP的服务。
[root@master ~]# curl -q 172.30.1.84:8080
如果尝试ping一下Service的IP地址,结果是不会成功的。因为Service的IP地址是虚拟的IP地址,而且这个地址只有集群内的节点和容器可以识别。
除了通过IP地址访问Service所指向的服务外,还可以通过域名访问某一个Service。监听在Master上的集群内置DNS服务器会负责解析这个DNS请求。Service域名的格式是..svc.cluster.local。比如上面例子中的PHP应用的Service域名将会是cakephp-mysql-example.helloworld-ng.svc.cluster.local:8080。可以在Master节点上用ping检查域名解析。
[root@master ~]# pingcakephp-mysql-example.helloworld-ng.svc.cluster.local
PING cakephp-mysql-example.helloworld-ng.svc.cluster.local (172.30.1.84) 56(84) bytes of data.
如果发现内部域名解析失败,可以在/etc/resolve.conf中添加一条指向本机的域名服务器的记录。
nameserver 127.0.0.1
3.2.6 Router与Route
Service提供了一个通往后端Pod集群的稳定的入口,但是Service的IP地址只是集群内部的节点及容器可见。对于外部的应用或者用户来说,这个地址是不可达的。那么外面的用户想要访问Service指向的服务应该怎么办呢?OpenShift提供了Router(路由器)来解决这个问题。上一章中介绍了Router组件的部署。其实Router组件就是一个运行在容器内的Haproxy,是一个特殊定制的Haproxy。用户可以创建一种叫Route的对象,笔者称为路由规则。一个Route会与一个Service相关联,并且绑定一个域名。Route规则会被Router加载。当用户通过指定域名访问应用时,域名会被解析并指向Router所在的计算节点上。Router获取这个请求,然后根据Route规则定义转发给与这个域名对应的Service后端所关联的Pod容器实例。在上一章部署CakePHP应用时,我们将Route域名修改为php.apps.example.com。当访问域php.apps.example.com时,请求到达Router,并由其向后端分发。当Pod的数量或者状态变化时,OpenShift负责更新Router内的配置,确保请求总是能被正确路由到对应的Pod。
通过命令oc get routes,可以查看项目中的所有Route。
[root@master ~]# oc get route -n hello-world-php
NAME HOST/PORT PATH SERVICES PORT TERMINATION
cakephp-mysql-example php.apps.example.com cakephp-mysql-example <all>
经常会有用户混淆了Router和Service的作用。Router负责将集群外的请求转发到集群的容器。Service则负责把来自集群内部的请求转发到指定的容器中。一个是对外,一个是对内。
3.2.7 Persistent Storage
容器默认是非持久化的,所有的修改在容器销毁时都会丢失。但现实是传统的应用大多都是有状态的,因此要求某些容器内的数据必须持久化,容器云平台必须为容器提供持久化储存(persistent storage)。Docker本身提供了持久化卷挂载的能力。相对于单机容器的场景,在容器云集群的场景中,持久化的实现有更多细节需要考虑。OpenShift除了支持Docker持久化卷的挂载方式外,还提供了一种持久化供给模型,即Persistent Volume(持久化卷,PV)及Persistent Volume Claim(持久化卷请求,PVC)模型。在PV和PVC模型中,集群管理员会创建大量不同大小和不同特性的PV。用户在部署应用时,显式声明对持久化的需求,创建PVC。用户在PVC中定义所需存储的大小、访问方式(只读或可读可写;独占或共享)。OpenShift集群会自动寻找符合要求的PV与PVC自动对接。通过PV和PVC模型,OpenShift为用户提供了一种灵活的方式来消费存储资源。
OpenShift对持久化后端的支持比较广泛,除了NFS及iSCSI外,还支持如Ceph、GluterFS等的分布式储存,以及Amazon WebService和Google Compute Engine的云硬盘。关于存储相关的话题,在后续章节会有更详细的探讨。
3.2.8 Registry
OpenShift提供了一个内部的Docker镜像仓库(Registry),该镜像仓库用于存放用户通过内置的Source to Image镜像构建流程所产生的镜像。Registry组件默认以容器的方式提供,在上一章中,我们手工部署了Registry组件。
通过oc get pod -n default命令可以查看Registry容器的状态。
[root@master ~]# oc get pod -n default
NAME READY STATUS RESTARTS AGE
docker-registry-1-xm3un 1/1 Running 1 7h
router-1-e95qa 1/1 Running 1 7h
通过oc get svc -n default命令可以查看Registry容器对应的Service信息。
[root@master ~]# oc get svc -n default
NAME CLUSTER-IP EXTERNAL-IP PORT(S) AGE
docker-registry 172.30.73.49 <none> 5000/TCP 7h
kubernetes 172.30.0.1 <none> 443/TCP,53/UDP,53/TCP 9h
router 172.30.58.19 <none> 80/TCP,443/TCP,1936/TCP 7h
每当S2I完成镜像构建,就会向内部的镜像仓库推送构建完成的镜像。在上面的输出示例中,镜像仓库的访问点为172.30.73.49:5000。如果查看上一章CakePHP的S2I构建日志,就会看到最后有成功推送镜像的日志输出:Push successful。
[root@master ~]# oc logs cakephp-mysql-example-1-build -n hello-world-php
Cloning "https://github.com/openshift/cakephp-ex.git" ...
Commit: 701d706b7f2b50ee972d0bf76990042f6c0cda5c (Merge pull request #42 from
bparees/recreate)
Author: Ben Parees<bparees@users.noreply.github.com>
Date: Mon Aug 22 14:44:49 2016 -0400
---> Installing application source...
Pushing image 172.30.73.49:5000/hello-world-php/cakephp-mysql-example:latest ...
Pushed 0/10 layers, 1% complete
Pushed 1/10 layers, 50% complete
Pushed 2/10 layers, 50% complete
Pushed 3/10 layers, 50% complete
Pushed 4/10 layers, 50% complete
Pushed 5/10 layers, 50% complete
Pushed 6/10 layers, 61% complete
Pushed 7/10 layers, 71% complete
Pushed 7/10 layers, 78% complete
Pushed 8/10 layers, 85% complete
Pushed 8/10 layers, 91% complete
Pushed 8/10 layers, 97% complete
Pushed 9/10 layers, 99% complete
Pushed 10/10 layers, 100% complete
Push successful
一个常见的疑问是“是不是OpenShift用到的镜像都要存放到内置的仓库?”答案是否定的。内部的镜像仓库存放的只是S2I产生的镜像。其他镜像可以存放在集群外部的镜像仓库,如企业的镜像仓库或社区的镜像仓库。只要保证OpenShift的节点可以访问到这些镜像所在的镜像仓库即可。
3.2.9 Source to Image
前文多次提及Source to Image(S2I),因为S2I的确是OpenShit的一个重要功能。容器镜像是容器云的应用交付格式。容器镜像中包含了应用及其所依赖的运行环境。可以从社区或者第三方厂商获取基础的操作系统或者中间件的镜像。但是这些外部获取的操作系统或中间件的镜像并不包含企业内部开发和定制的应用。企业内部的开发人员必须自行基于外部的基础镜像构建包含企业自身开发的应用。这个镜像的构建过程是必须的,要么由企业的IT人员手工完成,要么使用某种工具实现自动化。
作为一个面向应用的平台,OpenShift提供了S2I的流程,使得企业内容器的构建变得标准化和自动化,从而提高了软件从开发到上线的效率。一个典型的S2I流程包含了以下几个步骤。
1)用户输入源代码仓库的地址。
2)用户选择S2I构建的基础镜像(又称为Builder镜像)。Builder镜像中包含了操作系统、编程语言、框架等应用所需的软件及配置。OpenShift默认提供了多种编程语言的Builder镜像,如Java、PHP、Ruby、Python、Perl等。用户也可以根据自身需求定制自己的Builder镜像,并发布到服务目录中供用户选用。
3)用户或系统触发S2I构建。OpenShift将实例化S2I构建执行器。
4)S2I构建执行器将从用户指定的代码仓库下载源代码。
5)S2I构建执行器实例化Builder镜像。代码将会被注入Builder镜像中。
6)Builder镜像将根据预定义的逻辑执行源代码的编译、构建并完成部署。
7)S2I构建执行器将完成操作的Builder镜像并生成新的Docker镜像。
8)S2I构建执行器将新的镜像推送到OpenShift内部的镜像仓库。
9)S2I构建执行器更新该次构建相关的Image Stream信息。
S2I构建完成后,根据用户定义的部署逻辑,OpenShift将把镜像实例化部署到集群中。
除了接受源代码仓库地址作为输入外,S2I还接受Dockerf?ile以及二进制文件作为构建的输入。用户甚至可以完全自定义构建逻辑来满足特殊的需求。
3.2.10 开发及管理工具集
OpenShift提供了不同的工具集为开发和运维的用户提供良好的体验,也为持续集成和打通DevOps流程提供便利。例如,OpenShift提供了Eclipse插件,开发工程师可以在Eclipse中完成应用及服务的创建和部署、远程调试、实时日志查询等功能。