1. 程式人生 > >管理2000+Docker映象,Kolla是如何做到的

管理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-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 相關配置,使用自己的映象源了。


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