根據 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 鏡像相關的文件都存放在 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 安裝源還是其它依賴的公共組件安裝源,統統在基礎鏡像里固定下來了。
所以在國內網絡不好的情況下,就必須要替換其中的倉庫源。
設置容器啟動命令
定義了默認的 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/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-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' %}/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 相關配置,使用自己的鏡像源了。
如果本文對你有幫助,請 點贊、 關注、分享,謝謝!