如何優雅的拋棄 CentOS 7
背景
CentOS 7 自身的生命周期截止到 2024 年 6 月 30 日。在 2020 年底,CentOS 社區宣布修改現有的發布模式,將 CentOS 從作為 RHEL 的下游改為 CentOS Stream,即 RHEL 的上游,更導致 CentOS8 的生命周期短的可憐,這讓社區中原本就對 CentOS 不滿的開發者 / 使用者不滿,從而出現了拋棄 CentOS 轉投其他發行版的情況。
大家選擇使用 CentOS ,雖然都在說穩定,但是我理解更看重的是 RedHat 在身后背書,CentOS 作為 RHEL 的下游,所有的軟件版本都是經過 RedHat 測試驗證的,且后期維護也是有 RedHat 的身影在,不擔心維護的問題。
CentOS 原有的模式也是有問題的,用戶很難參與到 RHEL 的研發周期。用戶發現了 CentOS 某個版本存在問題,想要給 CentOS 進行貢獻,讓 CentOS 下一個版本修復該問題。此時只有一條路,就是貢獻給開源組件自身,但是這樣也只是存在修復的可能,最終是否可能修復還是看 RedHat 開發人員的決定(畢竟 RHEL/CentOS 中存在大量開源組件自身不包含,但是 RHEL/CentOS 通過 rpm spec 中進行 Patch 的方式包含的 Patch)。在引入了 CentOS Stream 之后,用戶就可以通過貢獻給 CentOS 社區,來保證 CentOS 下一個版本包含該 Patch,至于 RHEL 是否包含,用戶并不關心,那是 RedHat 關心的問題。
Fedora 更關注于上游社區最新的代碼,包含最豐富的功能,作為先驅者;CentOS Stream 作為 RHEL 的上游,提供穩定可靠的持續交付版本,保證更多的貢獻者可以參與進來;RHEL 給企業用戶使用,有 RedHat 提供完整的維護服務。
在現有的模式下,CentOS Stream 已經與原有采用 CentOS 的用戶初衷背離,已有的 CentOS7 用戶需要尋找新的替代品,在國產化的浪潮下,選擇的方向也發生了一定的變化。
社區替代品
Rocky Linux
?
Rocky Linux aims to function as a downstream build as CentOS had done previously, building releases after they have been added to the upstream vendor, not before.
AlmaLinux
?
AlmaLinux OS is replacing CentOS as the downstream rebuild of RedHat Enterprise Linux.
在 CentOS 宣布策略改變之后,社區中出現了兩個替代品,分別是 Rocky Linux 和 AlmaLinux,它倆的目的都是一樣的,作為 RHEL 的下游來構建發布,且發布模式和發布周期采用 CentOS 原有模式。
通過 AlmaLinux 官方提供的發行版比較[1] 可以看到,AlmaLinux 和 Rocky Linux 兩者對于用戶來說沒什么差別,如果一定要較真,那就是 AlmaLinux 大部分人員是來自 CloudLinux 公司,而 Rocky Linux 是 Greg 公司。
國產替代品
Anolis OS(阿里巴巴)
?
Anolis OS 8 是 OpenAnolis 社區推出的完全開源、中立、開放的發行版,它支持多計算架構,也面向云端場景優化,兼容 CentOS 軟件生態。Anolis OS 8 旨在為廣大開發者和運維人員提供穩定、高性能、安全、可靠、開源的操作系統服務。
openEuler(華為)
?
openEuler 是一款開源操作系統。當前 openEuler 內核源于 Linux,支持鯤鵬及其它多種處理器,能夠充分釋放計算芯片的潛能,是由全球開源貢獻者構建的高效、穩定、安全的開源操作系統,適用于數據庫、大數據、云計算、人工智能等應用場景。同時,openEuler 是一個面向全球的操作系統開源社區,通過社區合作,打造創新平臺,構建支持多處理器架構、統一和開放的操作系統,推動軟硬件應用生態繁榮發展。
銀河麒麟操作系統
?
銀河麒麟高級服務器操作系統 V10 是針對企業級關鍵業務,適應虛擬化、云計算、大數據、工業互聯網時代對主機系統可靠性、安全性、性能、擴展性和實時性等需求,依據 CMMI5 級標準研制的提供內生本質安全、云原生支持、自主平臺深入優化、 高性能、易管理的新一代自主服務器操作系統
在國產化浪潮下,如果產品需要滿足信創標準,那么操作系統的選擇需要考慮國產替代品,目前(個人了解)符合信創標準的操作系統只有銀河麒麟,openEuler 和 Anolis OS 目前還無法完全通過信創評審。在這一系列的替代品中, Rocky Linux, AlmaLinux, Anolis OS 所采用的發布模式和版本控制方式,都維持 CentOS 原有模式,即 8.1, 8.2, 8.3 發布方式。openEuler 和銀河麒麟操作系統雖然也采用 RPM 作為包管理器并且大部分組件版本與社區中的 CentOS 8 相同,但是不能完全等價,這里需要注意。
比較選擇
如果要滿足信創要求,那么只能選擇銀河麒麟作為替代品;如果從使用角度考慮,選擇 Rocky Linux/AlmaLinux/Anolis OS 是更好的選擇,有良好的社區支持,版本控制也與 CentOS 保持一致,心智負擔更低;如果從國產硬件支持考慮,openEuler 是不錯的選擇。
上述討論的各個發行版,當前所采用的包管理器均為 RPM,所有軟件均已 RPM 為粒度安裝,在 RPM 之上,會存在 Yum/DNF 包含 RPM 依賴管理、沖突管理、升降級等功能的基于 RPM 的包管理器。其中 CentOS 7 系列所采用的基于 RPM 的包管理器是 Yum,其他發行版當前維護版本所采用的基于 RPM 包管理器是 DNF(Dandified Yum)。
升級轉換
在現有使用了 CentOS 7 的環境中,需要使用替代品將 CentOS 7 升級轉換為目標發行版。
如果應用環境都是單體應用,且可以有下線維護時間,進行數據備份然后完整的重裝 OS 是一個穩妥的選擇。如果應用環境是集群,且大部分應用都已經容器化了,那么依次進行單節點重裝 OS 需要認真測試驗證,不同的發行版版本的默認系統參數可能存在差異,哪怕上層基礎平臺保證了版本一致(如 Kubernetes,containerd,runc 的版本一致),也可能導致異常情況。
如果選擇不重裝 OS,原地升級轉換的話有兩種方式:自動和手動。其中 Rocky Linux/AlmaLinux/Anolis OS 提供自動升級轉換方式,openEuler 和銀河麒麟可以采用手動轉換方式。
自動流程
自動升級轉換依賴于 Leapp[2],Leapp 由 Redhat 員工開發的開源工具,Leapp 自身只是一個工作流框架,其中包含 Actor、Model、Message、Workflow 等概念,具體組件關系圖如下,其中 workerflow 包含多個 phase,每個 phase 含有 3 個 stage:Before,Main,After,每個 stage 中包含多個 Actors,其中 Actors 之間沒有嚴格的順序,而是靠 Message 通信,Message 遵循 Model 的定義,如果 ActorA 依賴了 ActorB 產生的 MessageB,那么 ActorA 會在 ActorB 之后執行,沒有 MessageB 依賴的 ActorC 會按照加載順序執行,沒有嚴格順序依賴。
目前 Leapp 主要使用場景是用于 RedHat 系發行版升級、不同發行版之間的升級切換等。
在完整的升級流程中,使用統一定義的 Workflow,不同階段(如預升級、升級、Firstboot)都是調用的同一個 Workflow,只是根據指定的不同的 Tag、參數來決定執行的 Phase 不同。
- 預升級(preupgrade),進行環境信息的收集與檢查,將檢查結果以報告的形式提供給用戶,這里進行的信息收集及檢查項數量很大,包含了很多細節,除了包含一些基礎組件的檢查:CPU 架構、openssh 配置變更、PAM 模塊變更、Driver 支持、NTP 變更等之外,還包含一些第三方應用的檢查:SAP HANA、Memcached、寶塔等。
- 升級(upgrade),升級的主要動作,與預檢查使用的是相同 Workflow。
- configuration_phase
- FactsCollection
- Checks
- TargetTransactionFactsCollection,生成臨時 minimal 環境,包含完整的目標版本的運行環境,用于使用目標版本的工具棧,比如 DNF、RPM 高級特性等,該環境還會用來生成下一步驟所需的 initramfs image
- TargetTransactionCheck,通過上述生成的 minimal 環境,使用其中的 dnf 工具,dnf rhel-upgrade check 來檢查當前節點是否可以進行升級
- Reports
- Download,升級所需軟件包下載步驟, dnf rhel-upgrade download
- InterimPreparation,生成下一步驟所需的 initramfs,在前述步驟中的 minimal 環境中安裝 dracut 相關工具包,使用 dracut[3] 生成 initramfs image,生成完成后調整系統啟動項,將其置為第一個啟動項
- 臨時環境升級(Interim Upgrade),真正執行 RPM 升級的步驟,與預檢查使用的是相同的 Workflow
- 在系統 reboot 后,系統引導到前置步驟生成的 initramfs 中,系統正常引導,dracut hook 中,增加了兩個 hook,分別是 85sys-upgrade-redhat[4] 和 90sys-upgrade[5], 其中 85 是真正執行節點軟件包升級的動作 (leapp upgrade –resume),90 配置 systemd upgrade unit (與重啟相關)
- InitRamStart,移除啟動項設置
- LateTests
- Preparation
- RPMUpgrade,dnf rhel-upgrade upgrade 升級 RPM
- Applications
- ThirdPartyApplications
- Finalization
- 升級后動作(Firstboot),系統升級完成會,會自動 reboot 進入到目標版本系統中,此時會執行 Firstboot 階段,在執行完成后,系統升級完成
- FirstBoot,執行清理動作,修改部分配置(NM)等
完整升級流程共執行 4 次 Workflow,其中采用臨時環境執行升級動作的目的是:升級動作執行工具鏈是目標環境對應版本的工具鏈。
自動實現方式
項目地址列表:
- https://github.com/oamg/leapp
- https://github.com/oamg/leapp-repository
- https://github.com/AlmaLinux/leapp-data
其中 leapp 是框架自身,leapp-repository 是 Leapp 的應用實現,也就是升級中所執行的 Actor 實現,leapp-data 是升級中所用到的基礎配置信息。不同發行版會維護自己的 leapp-repository,比如 Anolis OS 就維護了自己的 Git 倉庫(在 Gitee 上),并針對性的增加了自己的檢查項。在 Leapp 的架構中,因為最終的應用會以獨立的插架形式安裝,所以 Python 的 syspath 可能會發生變化,在查看代碼的時候需要對應的修改一下路徑地址。以 NTP 檢查為例:
NTP 檢查的 Actor 實現:
from leapp.actors import Actor
from leapp.libraries.actor.checkntp import check_ntp
from leapp.models import InstalledRedHatSignedRPM, NtpMigrationDecision, Report
from leapp.tags import ChecksPhaseTag, IPUWorkflowTag
class CheckNtp(Actor):
"""
Check if ntp and/or ntpdate configuration needs to be migrated.
"""
name = 'check_ntp'
consumes = (InstalledRedHatSignedRPM,)
produces = (Report, NtpMigrationDecision)
tags = (ChecksPhaseTag, IPUWorkflowTag)
def process(self):
installed_packages = set()
signed_rpms = self.consume(InstalledRedHatSignedRPM)
for rpm_pkgs in signed_rpms:
for pkg in rpm_pkgs.items:
installed_packages.add(pkg.name)
self.produce(check_ntp(installed_packages))
Actor 中調用的 check_ntp 函數實現:
# Check services from the ntp packages for migration
def check_ntp(installed_packages):
service_data = [('ntpd', 'ntp', '/etc/ntp.conf'),
('ntpdate', 'ntpdate', '/etc/ntp/step-tickers'),
('ntp-wait', 'ntp-perl', None)]
migrate_services = []
migrate_configs = []
for service, package, main_config in service_data:
if package in installed_packages and \
check_service('{}.service'.format(service)) and \
(not main_config or is_file(main_config)):
migrate_services.append(service)
if main_config:
migrate_configs.append(service)
if migrate_configs:
reporting.create_report([
reporting.Title('{} configuration will be migrated'.format(' and '.join(migrate_configs))),
reporting.Summary('{} service(s) detected to be enabled and active'.format(', '.join(migrate_services))),
reporting.Severity(reporting.Severity.LOW),
reporting.Groups([reporting.Groups.SERVICES, reporting.Groups.TIME_MANAGEMENT]),
] + related)
# Save configuration files that will be renamed in the upgrade
config_tgz64 = get_tgz64(files)
else:
api.current_logger().info('ntpd/ntpdate configuration will not be migrated')
migrate_services = []
config_tgz64 = ''
return NtpMigrationDecision(migrate_services=migrate_services, config_tgz64=config_tgz64)
手動流程
對于 Linux 發行版來說,整體是由無數個 RPM 組成的,最終系統中看到的最小粒度就是 RPM,我們可以通過 RPM 的升級來完成整體的發行版的升級變更。但是對于部分 RPM 來說,RPM 之間的依賴阻礙了我們無法通過依次升級部分 RPM 的方式來完成完整的升級替換,其中一些關鍵組件,如 glibc、glib2、openssl 等等都是強依賴的,我們必須要找到一個方式來完成整體的升級。在 Yum 中,存在 distribution-synchronization 命令用來同步當前 OS 中所有的 RPM 到目標 Repository 中的版本,但是用 Yum 可能會存在無法識別 rpmlib 的情況。RPM 作為基礎包管理器,自身會存在部分高級特性以 rpmlib 的依賴形式提供,如果當前系統的包管理器無法識別 rpmlib,那么就會在同步過程中出現無法解決的依賴沖突。
舉例:目標 RPM 為 dnf-4.2.23-6.oe1.noarch.rpm ,升級提示依賴 rpmlib(RichDependencies) <= 4.12.0-1 沖突。這是因為 dnf-4.2.23 這個 RPM 在構建的階段,所使用的 rpm 環境(可能是在 openEuler 20.03 或更高版本)比當前 OS 的 RPM 版本(CentOS 7)高,所以當前 rpm 無法滿足這個依賴條件。
我們可以使用 DNF 的 distro-sync 并配合部分的 RPM 修改,來完成手動升級轉換。流程如下:
- 將當前 CentOS7 升級到 CentOS 7.x 系列最新版本;
- 停止節點上運行的所有應用
- 配置 CentOS7 Repository ,安裝 DNF(DNF 依賴于 glib2 的執行版本,但是未在 spec 中聲明,需要單獨升級 glib2)
- 移除 Yum 管理器,防止與 DNF 產生沖突
- 配置目標發行版 Repository
- 使用 dnf distro-sync 進行升級轉換
- 使用 dnf remove 移除無用 RPM
- 重啟主機生效
手動實現方式
當前 CentOS7 包管理器是 Yum,在目標版本中包管理器是 DNF,在通過 Yum 安裝 DNF ,在保證 Yum(DNF) Repository 配置是目標版本的前提下,使用 dnf distro-sync 命令來進行 RPM 的升級和同步,該命令會將當前 OS 已經安裝的 RPM 與 Yum Repository 中的 RPM 進行匹配。RPM 版本匹配存在以下幾種情況:
- 當前 RPM 版本低于目標 Repository 中包含的 RPM 版本,則會升級;
- 當前 RPM 版本高于目標 Repository 中包含的 RPM 版本,則會降級;
- 當前 RPM 被目標 Repository 中包含的 RPM 所替代(指定 Obsolete),則會安裝新 RPM,原有 RPM 被卸載(替代);
- 當前 RPM 版本與目標 Repository 中包含的 RPM 版本相同,但 dist 等其他 RPM 元數據不同,則會重新安裝;
- 當前 RPM 是被其他 RPM 依賴引入的,但是其他 RPM 已經被替代,則該 RPM 會被卸載;
- 當前 RPM 在目標 Repository 中不包含,則不會進行處理;
總結
通過自動或者手動的方式,我們可以原地將 CentOS 7 升級轉換為我們想要的目標發行版。社區的 Rocky Linux/AlmaLinux/Anolis OS 可以采用自動的方式完成 ,國產非等價替代的 openEuler 可以采用控制 Repository 的方式手動完成,減少發行版變更帶來的工作量。