管理2000+Docker镜像,Kolla是如何作到的

根据 DockerHub 上的数据,整个 Kolla 项目管理的 镜像有 2000 多个,这么多的镜像,是怎么定义,又是如何构建的呢?html

简介

咱们一直在说的 Kolla,一般状况下泛指,包括了 KollaKolla-Ansible 两个项目。html5

实际上,根据 OpenStack Wiki,还有个 Kayobe 项目也是相关的。可是这个用的比较少,并且我试用后以为不是特别符合个人需求,就不过多介绍了。python

此外还有一个项目 Kolla-kubernetes 致力于和 Kubernetes 结合,可是和另外一个项目 openstack-helm 重合较多,提早退休了。linux

Kolla 项目开始之初只有一个项目,从构建 docker 容器,到使用 ansible 部署,全流程搞定。后来把 ansible 这块分离了出来,独立为 kolla-ansible 项目,原来的 kolla 专门负责 docker 镜像的构建。git

镜像划分的维度

虽然最终的镜像个数超过 2000 个,实际并非彻底独立的 2000 多个服务。而是针对不一样的场景分别构建,多维度全面覆盖的结果。正则表达式

镜像分层

熟悉 Docker 的小伙伴都知道,Dockerfile 是能够指定“继承”关系的。也就是利用镜像分层的特性,逐层构建。docker

OpenStack 中有不少子服务隶属于同一个项目,例如,nova-apinova-compute 等都属于 nova,因此,很天然地能够先构建一个通用的 nova-base 镜像,而后在此基础上分别构建不一样的服务。json

这是一个纵向的划分维度。ubuntu

功能划分

由于 Kolla 项目不只是把 OpenStack 的服务集成了,周边用到的组件和辅助服务也都囊括在内。包括 RabbitMQMariaDB 等。centos

这是一个横向的划分维度。

以上两个是最基础的划分维度,也是咱们可以很容易想到的。

操做系统

每一个 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 为开发助力,因此除了从软件源(如 yumapt 等)直接安装打包好的程序,还要可以直接从源码安装。

从软件包称为 binary,从源码安装称为 source

这个维度也是在处理 Jinja 模板的阶段完成。

实际上,还有 2 个安装方式,rdorhos,都是针对 RedHat 系统的,通常不怎么会用到。

操做系统和安装方式这两个维度,决定了镜像名称的前缀:

文件的组织结构

了解了划分维度,咱们来看一下具体的文件组织结构是怎样的。

全部的构建 Docker 镜像相关的文件都存放在 kolla/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

因此最终上面的文件对应的镜像是:

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 安装源仍是其它依赖的公共组件安装源,通通在基础镜像里固定下来了。

因此在国内网络很差的状况下,就必需要替换其中的仓库源。

设置容器启动命令

定义了默认的 ENTRYPOINTCMD,也就是把容器的启动方式固定了下来。

相信这里你们会有疑惑,那么多不一样的服务,怎么可能在这里把启动命令固定下来呢?其实这里有一点技巧。

这里 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/kolla/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-basenova-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' %}/etc/dnf/dnf.conf{% else %}/etc/yum.conf{% endif %}

{% block base_yum_conf %}

{% if distro_package_manager == 'dnf' %}
COPY dnf.conf /etc/dnf/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/kolla/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 相关配置,使用本身的镜像源了。


若是本文对你有帮助,请 点赞关注分享,谢谢!

相关文章
相关标签/搜索