管理2000+Docker鏡像,Kolla是如何做到的


根據 DockerHub 上的數據,整個 Kolla 項目管理的 鏡像有 2000 多個,這么多的鏡像,是怎么定義,又是如何構建的呢?

簡介

我們一直在說的 Kolla,通常情況下泛指,包括了 KollaKolla-Ansible 兩個項目。

實際上,根據 OpenStack Wiki,還有個 Kayobe 項目也是相關的。但是這個用的比較少,而且我試用后覺得不是特別符合我的需求,就不過多介紹了。

此外還有一個項目 Kolla-kubernetes 致力於和 Kubernetes 結合,但是和另一個項目 openstack-helm 重合較多,提前退休了。

Kolla 項目開始之初只有一個項目,從構建 docker 容器,到使用 ansible 部署,全流程搞定。后來把 ansible 這塊分離了出來,獨立為 kolla-ansible 項目,原來的 kolla 專門負責 docker 鏡像的構建。

鏡像划分的維度

雖然最終的鏡像個數超過 2000 個,實際並不是完全獨立的 2000 多個服務。而是針對不同的場景分別構建,多維度全面覆蓋的結果。

鏡像分層

熟悉 Docker 的小伙伴都知道,Dockerfile 是可以指定“繼承”關系的。也就是利用鏡像分層的特性,逐層構建。

OpenStack 中有很多子服務隸屬於同一個項目,例如,nova-apinova-compute 等都屬於 nova,所以,很自然地可以先構建一個通用的 nova-base 鏡像,然后在此基礎上分別構建不同的服務。

這是一個縱向的划分維度。

功能划分

因為 Kolla 項目不僅是把 OpenStack 的服務集成了,周邊用到的組件和輔助服務也都囊括在內。包括 RabbitMQMariaDB 等。

這是一個橫向的划分維度。

以上兩個是最基礎的划分維度,也是我們能夠很容易想到的。

操作系統

每個 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 相關配置,使用自己的鏡像源了。


如果本文對你有幫助,請 點贊關注分享,謝謝!


免責聲明!

本站轉載的文章為個人學習借鑒使用,本站對版權不負任何法律責任。如果侵犯了您的隱私權益,請聯系本站郵箱yoyou2525@163.com刪除。



 
粵ICP備18138465號   © 2018-2025 CODEPRJ.COM