根据 DockerHub 上的数据整个 Kolla 项目管理的 镜像有 2000 多个这么多的镜像是怎么定义又是如何构建的呢?
简介
我们一直在说的 Kolla通常情况下泛指包括了 Kolla
和 Kolla-Ansible
两个项目
实际上根据 OpenStack Wiki还有个
Kayobe
项目也是相关的但是这个用的比较少而且我试用后觉得不是特别符合我的需求就不过多介绍了
此外还有一个项目
Kolla-kubernetes
致力于和 Kubernetes 结合但是和另一个项目openstack-helm
重合较多提前退休了
Kolla
项目开始之初只有一个项目从构建 docker 容器到使用 ansible 部署全流程搞定后来把 ansible 这块分离了出来独立为 kolla-ansible
项目原来的 kolla
专门负责 docker 镜像的构建
镜像划分的维度
虽然最终的镜像个数超过 2000 个实际并不是完全独立的 2000 多个服务而是针对不同的场景分别构建多维度全面覆盖的结果
镜像分层
熟悉 Docker 的小伙伴都知道Dockerfile 是可以指定“继承”关系的也就是利用镜像分层的特性逐层构建
OpenStack 中有很多子服务隶属于同一个项目例如nova-api
nova-compute
等都属于 nova
所以很自然地可以先构建一个通用的 nova-base
镜像然后在此基础上分别构建不同的服务
这是一个纵向的划分维度
功能划分
因为 Kolla
项目不仅是把 OpenStack 的服务集成了周边用到的组件和辅助服务也都囊括在内包括 RabbitMQ
MariaDB
等
这是一个横向的划分维度
以上两个是最基础的划分维度也是我们能够很容易想到的
操作系统
每个 Docker 镜像最底层只能是操作系统的基础镜像现在主流的 Linux 发行版有好几家OpenStack 作为一个世界级的开源项目要是只绑定一家其他人可不答应
所以必须要同时支持多个操作系统这个靠 Dockerfile 显然解决不了
如果为每个操作系统单独的定义一份 Dockerfile 显然不够聪明 Kolla 使用了 Jinja 模板文件多做了一层抽象根据指定的参数先由 Dockerfile.j2
生成 Dockerfile
这个维度在 kolla 中对应的参数是 base
目前支持的操作系统有:
['centos', 'rhel', 'ubuntu', 'debian']
Jinja 是 Python 中使用比较广泛的模板引擎(template engine)之所以叫 Jinja是因为日本的神社(Jinja)英文单词是 temple而模板的英文是 template两者发音很相似(什么脑回路)Jinja 项目的 Logo 也是一个神社的图标可能是因为这层关系这个在国内似乎讨论的并不多
安装方式
Kolla 不仅是要作单纯的部署工具还希望能够替代 devstack
为开发助力所以除了从软件源(如 yum
apt
等)直接安装打包好的程序还要能够直接从源码安装
从软件包称为 binary
从源码安装称为 source
这个维度也是在处理 Jinja 模板的阶段完成
实际上还有 2 个安装方式
rdo
和rhos
都是针对 RedHat 系统的一般不怎么会用到
操作系统和安装方式这两个维度决定了镜像名称的前缀:
文件的组织结构
了解了划分维度我们来看一下具体的文件组织结构是怎样的
所有的构建 Docker 镜像相关的文件都存放在 kollahttps://p.download-x.com/docker
目录下这下面的文件夹众多下面把有代表性的列了出来:
docker/
├── base
│ └── Dockerfile.j2
├── horizon
│ └── Dockerfile.j2
├── mariadb
│ └── Dockerfile.j2
├── nova
│ ├── nova-api
│ │ └── Dockerfile.j2
│ ├── nova-base
│ │ └── Dockerfile.j2
│ └── nova-compute
│ └── Dockerfile.j2
└── openstack-base
└── Dockerfile.j2
每个文件夹代表了一个服务除了名字带 base
的其中顶层的有 2 个:
- base 这是所有镜像的初始层
- openstack-base 这是所有 OpenStack 相关服务的初始层
如果一个组件包含多个服务比如 nova
它内部就会又多出一层基础层: nova-base
所有其它的 nova-<xxx>
都是从这层开始如果一个组件只有一个服务则不需要再有子文件夹直接是 Dockerfile.j2
文件
镜像层之间的关系是在 Dockerfile 文件中的 FROM
语句定义的它们在 jinja
模板中是固定的
例如 horizon/Dockerfile.j2
中:
FROM {{ namespace }}/{{ image_prefix }}openstack-base:{{ tag }}
而 openstack-base/Dockerfile.j2
中:
FROM {{ namespace }}/{{ image_prefix }}base:{{ tag }}
它们之间的依赖关系是这样的:
base
├── openstack-base
│ ├── nova-base
│ │ └── nova-api
│ │ └── nova-compute
│ └── horizon
└── mariadb
可以看到最多就 4 层
包含 .j2
文件的文件夹名字最终会成为镜像名的一部分如 <os>-<type>-nova-api
这里的 <os>-<type>-
也就是对应上面的 {{ image_prefix }}
字符串分别对应:
所以最终上面的文件对应的镜像是:
centos-binary-base
centos-binary-openstack-base
centos-binary-nova-base
centos-binary-nova-api
centos-binary-nova-compute
centos-binary-horizon
注意并不是每个镜像都支持任意的类型组合具体需要查看
kolla
源码
base 镜像的作用
所有镜像的源头都是 base
所以它肯定是很重要的其中内容主要有两个地方比较关键:
设置软件仓库源
所有软件包的安装源配置都在 base
中完成不管是 OpenStack 安装源还是其它依赖的公共组件安装源统统在基础镜像里固定下来了
所以在国内网络不好的情况下就必须要替换其中的仓库源
设置容器启动命令
定义了默认的 ENTRYPOINT
和 CMD
也就是把容器的启动方式固定了下来
相信这里大家会有疑惑那么多不同的服务怎么可能在这里把启动命令固定下来呢?其实这里有一点技巧
这里 kolla
固定了一个启动脚本 start.sh
在这个脚本里从固定位置 /run_command
读到真正的执行命令/run_command
则是由 kolla-ansible
在启动容器的时候注入的
还记得在 介绍 Kolla 的配置文件 时看到的 config.json
么其中有一个 command
字段例如 Horizon 服务的配置:
{
"command": "/usr/sbin/httpd -DFOREGROUND",
"config_files": [
{
"source": "/var/lib/kolla/config_files/horizon.conf",
"dest": "/etc/httpd/conf.d/horizon.conf",
"owner": "horizon",
"perm": "0600"
},
]
}
这样做既保证了构建镜像时候的一致性又保证了容器启动的灵活性
处理流程
kolla 构建镜像的流程非常简单大体就是 5 个步骤:
1. 生成 Dockerfile
把 docker
整个目录复制到一个临时的工作目录然后在其中扫描包含有 Dockerfile.j2
文件的文件夹正如在上面分析的那样这样的一个文件夹就对应一个镜像
使用从配置文件中获取的操作系统基础镜像和安装方式等参数渲染生成 Dockerfile
文件
参考源码:
create_dockerfiles
2. 构建镜像列表
将上一步生成的 Dockerfile 都读取到内存处理里面的 FROM
语句可以获得每个镜像的 parent
名字还有其它一些关于安装方式的细节也要处理不用过多关心
这一步完成我们就得到了一个镜像列表这里的镜像指的是 kolla
定义的 Image
类的实例
3. 查找镜像关系
遍历整个镜像列表把它们的依赖关系整理清楚
4. 过滤镜像列表
因为总共镜像数量比较多所以需要根据用户提供的参数做一下过滤
过滤参数有两种方式:
- 预先定义了几组常用的镜像分组称为
profile
指定分组名就可以构建对应的镜像 - 通过正则表达式匹配镜像的名字来选择
5. 执行构建
使用多线程任务队列批量执行构建
构建完镜像后还有一个可选操作将镜像 push
到指定的 registry 中
以上过程有兴趣的可以自行去看 kolla 源码主要内容就集中在 1 个 build.py
文件还是很简单的
使用方法
为避免本文内容失效请关注 Kolla 项目官方文档 获取更新
安装 Python 3
CentOS 7 自带的 Python 版本还是 2.7在 2020 年后不再维护Kolla 项目有的依赖包不再支持
yum install python3
CentOS 7 的安装源提供的 Python 3 版本是 3.6.8
创建虚拟环境(可选)
推荐在 Python 虚拟环境中安装 Kolla
:
python3 -m venv .virtualenvs/kolla-build
source .virtualenvs/kolla-build/bin/activate
(kolla-build) [root@davycloud ~]#
以下操作默认都在虚拟环境下执行
安装 Kolla
有两种方式
- 使用
pip
安装 - 从源码安装
推荐采用后者有助于学习也方便改代码
使用 git 下载源码:
# OpenStack 官方 git 源
git clone https://opendev.org/openstack/kolla
# 上面网速慢的可以使用下面的镜像站地址
git clone http://git.trystack.cn/openstack/kolla
然后使用 pip
安装即可:
(kolla-build) $ pip install kolla/
注意最后的斜杠表示我们安装的是本地目录安装完毕后可以执行:
(kolla-build) [root@davycloud ~]# kolla-build --version
9.1.0
生成配置文件
Kolla 构建镜像有不少配置项但是基本保持默认即可并且缺少配置文件 kolla-build
命令也能执行所以这一步这里就 略过 了
如果你想生成 kolla-build.conf
配置文件可以参考 官方文档
构建 base
镜像
构建最最基础的 base
镜像:
(kolla-build) [root@davycloud ~]# kolla-build ^base
INFO:kolla.common.utils:Found the docker image folder at /root/.virtualenvs/kolla-build/share/kollahttps://p.download-x.com/docker
INFO:kolla.common.utils:Added image base to queue
INFO:kolla.common.utils:Attempt number: 1 to run task: BuildTask(base)
INFO:kolla.common.utils.base:Building started at 2020-01-28 19:54:50.158139
INFO:kolla.common.utils.base:Step 1/37 : FROM centos:7
INFO:kolla.common.utils.base: ---> 5e35e350aded
INFO:kolla.common.utils.base:Step 2/37 : LABEL maintainer="Kolla Project
...
INFO:kolla.common.utils.base:Successfully tagged kolla/centos-binary-base:9.1.0
注意^base
前面的 ^
符号不可省略这是正则表达式中表示句首的符号如果缺少该符号kolla-build
会把 openstack-base
nova-base
等一众名字包含 base
的镜像都匹配上了
构建的镜像标签默认是 kolla 的版本号9.1.0
我们后面会指定自己的版本号
修改 base
镜像
我们在前面分析过了所有的安装源都在 base
镜像中指定了
如果我们直接使用这个 base
镜像后面的镜像构建过程中软件的安装速度没法保证
当然我们可以在下载完 kolla
源码之后就直接修改 base
对应的 Dockerfile.j2
和相关的构建文件但是这样修改是比较麻烦的因为其中夹杂着其它情况的处理代码例如:
{% if base_package_type == 'rpm' %}
# For RPM Variants, enable the correct repositories - this should all be done
# in the base image so repos are consistent throughout the system. This also
# enables to provide repo overrides at a later date in a simple fashion if we
# desire such functionality. I think we will :)
RUN CURRENT_DISTRO_RELEASE=$(awk '{match($0, /[0-9]+/,version)}END{print version[0]}' /etc/system-release); \
if [ $CURRENT_DISTRO_RELEASE != "{{ supported_distro_release }}" ]; then \
echo "Only release '{{ supported_distro_release }}' is supported on {{ base_distro }}"; false; \
fi \
&& cat /tmp/kolla_bashrc >> /etc/bashrc \
&& sed -i 's|^\(override_install_langs=.*\)|# \1|' {% if distro_package_manager == 'dnf' %}/etchttps://p.download-x.com/dnfhttps://p.download-x.com/dnf.conf{% else %}/etc/yum.conf{% endif %}
{% block base_yum_conf %}
{% if distro_package_manager == 'dnf' %}
COPY dnf.conf /etchttps://p.download-x.com/dnfhttps://p.download-x.com/dnf.conf
{% else %}
COPY yum.conf /etc/yum.conf
{% endif %}
修改难度是比较大的要把其中的逻辑捋清楚才能下手而且以后每次这个文件的版本有变化更新都要对照着修改
这里我采取了比较投机取巧的办法等这个 base
镜像构建完成后直接在它之上修改这个时候我们已经确定了操作系统(CentOS)和安装方式(Binary)这样只需要替换 /etc/yum.repos.d/
下面的 .repo
文件即可
先把 kolla/centos-binary-base:9.1.0
镜像内的 /etc/yum.repos.d/
整个文件夹都拷贝出来逐个 .repo
修改把其中的 URL 替换成阿里云镜像站的 URL
然后写了一个超级简单粗暴的 Dockerfile:
FROM kolla/centos-binary-base:9.1.0
RUN mkdir -p /etc/yum.repos.d/bak && mv /etc/yum.repos.d/*.repo /etc/yum.repos.d/bak
COPY CentOS-Base.repo /etc/yum.repos.d/CentOS-Base.repo
COPY CentOS-Ceph-Nautilus.repo /etc/yum.repos.d/CentOS-Ceph-Nautilus.repo
COPY CentOS-CR.repo /etc/yum.repos.d/CentOS-CR.repo
COPY CentOS-Debuginfo.repo /etc/yum.repos.d/CentOS-Debuginfo.repo
COPY CentOS-fasttrack.repo /etc/yum.repos.d/CentOS-fasttrack.repo
COPY CentOS-Media.repo /etc/yum.repos.d/CentOS-Media.repo
COPY CentOS-NFS-Ganesha-28.repo /etc/yum.repos.d/CentOS-NFS-Ganesha-28.repo
COPY CentOS-OpenStack.repo /etc/yum.repos.d/CentOS-OpenStack.repo
COPY CentOS-OpsTools.repo /etc/yum.repos.d/CentOS-OpsTools.repo
COPY CentOS-QEMU-EV.repo /etc/yum.repos.d/CentOS-QEMU-EV.repo
COPY CentOS-Sources.repo /etc/yum.repos.d/CentOS-Sources.repo
COPY CentOS-Storage-common.repo /etc/yum.repos.d/CentOS-Storage-common.repo
COPY CentOS-Vault.repo /etc/yum.repos.d/CentOS-Vault.repo
COPY crmsh.repo /etc/yum.repos.d/crmsh.repo
COPY elasticsearch.repo /etc/yum.repos.d/elasticsearch.repo
COPY epel.repo /etc/yum.repos.d/epel.repo
COPY epel-testing.repo /etc/yum.repos.d/epel-testing.repo
COPY grafana.repo /etc/yum.repos.d/grafana.repo
COPY influxdb.repo /etc/yum.repos.d/influxdb.repo
COPY opendaylight.repo /etc/yum.repos.d/opendaylight.repo
COPY rabbitmq_rabbitmq-server.repo /etc/yum.repos.d/rabbitmq_rabbitmq-server.repo
COPY td.repo /etc/yum.repos.d/td.repo
然后用它来构建一个新的镜像:
(kolla-build) [root@davycloud ~]# docker build . -t kolla/centos-binary-base:davycloud
注意其中的镜像 tag 可以自己随便定义
构建 openstack-base
镜像
有了基础镜像就可以开始构建其它的镜像了可以先挑一个试一试比如 openstack-base
注意上面已经把 tag 修改了所以接下来的命令必须要带两个选项:
--tag davycloud
用来指定自定义的 tag--skip-existing
略过已经创建好的镜像
(kolla-build) [root@davycloud aliyun]# kolla-build --tag davycloud --skip-existing openstack-base
INFO:kolla.common.utils:Found the docker image folder at /root/.virtualenvs/kolla-build/share/kollahttps://p.download-x.com/docker
INFO:kolla.common.utils:===========================
INFO:kolla.common.utils:Images that failed to build
INFO:kolla.common.utils:===========================
ERROR:kolla.common.utils:openstack-base Failed with status: matched
会出现这么一个莫名其妙的错误这其实是 kolla
这里处理的逻辑有点问题找到下面所示代码在 image.status = STATUS_UNMATCHED
上面加一个判断:
@@ -1117,9 +1117,9 @@ class KollaWorker(object):
ancestor_image.status = STATUS_MATCHED
LOG.debug('Image %s matched regex', image.name)
else:
+ # See: https://bugs.launchpad.net/kolla/+bug/1810979
+ if image.status != STATUS_SKIPPED:
+ image.status = STATUS_UNMATCHED
- # we do not care if it is skipped or not as we did not
- # request it
- image.status = STATUS_UNMATCHED
else:
for image in self.images:
if image.status != STATUS_UNBUILDABLE:
我已经给社区提了修改补丁但是没有下文
修改完毕之后就可以重试上面的命令来构建镜像了
构建其它镜像
Kolla 总共支持的镜像比较多不太可能全部需要所以最好事先挑选一番
最简单的是通过 profile
来批量指定然后通过 --list-images
选项在构建之前查看镜像列表做到心中有数:
(kolla-build) [root@davycloud aliyun]# kolla-build -p default --list-images
1 : openstack-base
2 : chrony
3 : barbican-keystone-listener
4 : barbican-base
5 : nova-spicehtml5proxy
6 : nova-conductor
7 : nova-ssh
8 : nova-libvirt
9 : nova-scheduler
10 : nova-compute-ironic
11 : nova-novncproxy
12 : nova-serialproxy
13 : nova-api
14 : nova-compute
15 : nova-base
16 : glance-api
17 : glance-registry
18 : glance-base
19 : kolla-toolbox
20 : neutron-server-opendaylight
21 : neutron-l3-agent
22 : neutron-mlnx-agent
23 : neutron-server
24 : neutron-server-ovn
25 : neutron-metadata-agent
26 : neutron-dhcp-agent
27 : neutron-openvswitch-agent
28 : neutron-bgp-dragent
29 : neutron-linuxbridge-agent
30 : neutron-infoblox-ipam-agent
31 : neutron-base
32 : neutron-metering-agent
33 : neutron-sriov-agent
34 : neutron-metadata-agent-ovn
35 : fluentd
36 : heat-api-cfn
37 : heat-engine
38 : heat-base
39 : heat-api
40 : heat-all
41 : ironic-neutron-agent
42 : mariadb
43 : keystone-ssh
44 : keystone
45 : keystone-fernet
46 : keystone-base
47 : openvswitch-db-server
48 : openvswitch-base
49 : openvswitch-vswitchd
50 : prometheus-haproxy-exporter
51 : prometheus-base
52 : prometheus-memcached-exporter
53 : base
54 : rabbitmq
55 : cron
56 : haproxy
57 : keepalived
58 : memcached
59 : horizon
60 : placement-base
61 : placement-api
也可以查看源码文件:
kolla/common/config.py
中的_PROFILE_OPTS
查看支持哪些 profile 以及包含的镜像列表
(kolla-build) [root@davycloud ~]# kolla-build --tag davycloud --skip-existing -p default
把镜像推送到 registry
可以是本地自建的服务也可以是其它平台提供的比如 阿里云的容器镜像服务
具体的过程就不赘述了
一切完工之后就可以参考我之前的文章在使用 Kolla-Ansible
部署环境的时候在 globals.yml
中修改 registry 相关配置使用自己的镜像源了
如果本文对你有帮助请 点赞、 关注、分享谢谢!