源码分析

代码结构

cloud-init的代码结构如下:

cloud-init
├── bash_completion # bash自动补全文件
│   └── cloud-init
├── ChangeLog # 更新日志
├── cloudinit
│   ├── cloud.py # Cloud类
│   ├── cmd # 命令行操作目录
│   │   ├── clean.py # cloud-init clean
│   │   ├── cloud_id.py # cloud-id
│   │   ├── devel # cloud-init devel
│   │   ├── __init__.py
│   │   ├── main.py # cloud-init init/modules
│   │   ├── query.py # cloud-init query
│   │   └── status.py # cloud-init status
│   ├── config # 模块目录
│   ├── distros # 系统发行版目录
│   ├── handlers # 模板渲染处理函数目录
│   ├── helpers.py # 帮助函数
│   ├── __init__.py
│   ├── log.py # 日志处理
│   ├── mergers # 配置合并目录
│   ├── net # 网络目录
│   ├── settings.py # 内置配置
│   ├── sources #数据源目录
│   ├── stages.py # Init类
│   ├── ...
│   └── warnings.py
├── doc # 文档
│   ├── ...
├── packages # 各大Linux发行包制作脚本
│   ├── ...
├── README.md # 简介
├── requirements.txt # 依赖包
├── rhel # 针对redhat系linux发行版的补丁
│   ├── cloud.cfg
│   ├── cloud-init-tmpfiles.conf
│   ├── README.rhel
│   └── systemd
├── setup.py # python模块安装文件
├── tests # 单元测试目录
│   ├── ...
├── tools # 额外工具
│   ├── ...
└── tox.ini # tox配置文件 44 directories, 127 files

cloud-init的函数入口点位于cloudinit/cmd/main.py,该文件包含了所有cloud-init执行阶段的逻辑代码。

cloud-init init [ --local ]

cmd/main.py,main_init:init阶段运行此主函数,分析如下:

def main_init(name, args):
# deps变量,local阶段为NETWORK,network阶段为FILESYSTEM、NETWORK
deps = [sources.DEP_FILESYSTEM, sources.DEP_NETWORK]
if args.local:
deps = [sources.DEP_FILESYSTEM]
# 标准输出log
early_logs = [attempt_cmdline_url(
path=os.path.join("%s.d" % CLOUD_CONFIG,
"91_kernel_cmdline_url.cfg"),
network=not args.local)]
# 决定欢迎语,local阶段为init-local,network阶段为init
if not args.local:
w_msg = welcome_format(name)
else:
# Cloud-init v. 19.4 running 'init-local' at Wed, 15 Dec 2021 03:00:40 +0000. Up 250.20 seconds.
w_msg = welcome_format("%s-local" % (name))
# 实例化stages.Init类
init = stages.Init(ds_deps=deps, reporter=args.reporter)
# Stage 1
# 加载配置文件,优先级从低到高为:内置配置 --> /etc/cloud/cloud.cfg{,.d} --> /run/cloud-init/cloud.cfg --> kernel cmdline
init.read_cfg(extract_fns(args))
# Stage 2
# 重定向输出和错误
outfmt = None
errfmt = None
try:
early_logs.append((logging.DEBUG, "Closing stdin."))
util.close_stdin()
(outfmt, errfmt) = util.fixup_output(init.cfg, name)
except Exception:
msg = "Failed to setup output redirection!"
util.logexc(LOG, msg)
print_exc(msg)
early_logs.append((logging.WARN, msg))
if args.debug:
# Reset so that all the debug handlers are closed out
LOG.debug(("Logging being reset, this logger may no"
" longer be active shortly"))
logging.resetLogging()
logging.setupLogging(init.cfg)
apply_reporting_cfg(init.cfg) # Any log usage prior to setupLogging above did not have local user log
# config applied. We send the welcome message now, as stderr/out have
# been redirected and log now configured.
# 输出欢迎语
welcome(name, msg=w_msg) # re-play early log messages before logging was setup
for lvl, msg in early_logs:
LOG.log(lvl, msg) # Stage 3
try:
# 创建cloud-init相关的目录和文件,包括/var/lib/cloud/目录下的各个子目录,以及日志文件
init.initialize()
except Exception:
util.logexc(LOG, "Failed to initialize, likely bad things to come!")
# Stage 4
# 判断manual_cache_clean配置项,如果为false,cloudinit会通过实例id判断当前运行的实例是否为新实例;否则不作判断,可能导致实例迁移后per-instance模块不运行
# local阶段,删除缓存(boot_finished、no-net)
# network阶段,判断no-net文件是否存在,如存在则提前退出
path_helper = init.paths
mode = sources.DSMODE_LOCAL if args.local else sources.DSMODE_NETWORK if mode == sources.DSMODE_NETWORK:
existing = "trust"
sys.stderr.write("%s\n" % (netinfo.debug_info()))
LOG.debug(("Checking to see if files that we need already"
" exist from a previous run that would allow us"
" to stop early."))
# no-net is written by upstart cloud-init-nonet when network failed
# to come up
stop_files = [
os.path.join(path_helper.get_cpath("data"), "no-net"),
]
existing_files = []
for fn in stop_files:
if os.path.isfile(fn):
existing_files.append(fn) if existing_files:
LOG.debug("[%s] Exiting. stop file %s existed",
mode, existing_files)
return (None, [])
else:
LOG.debug("Execution continuing, no previous run detected that"
" would allow us to stop early.")
else:
existing = "check"
mcfg = util.get_cfg_option_bool(init.cfg, 'manual_cache_clean', False)
if mcfg:
LOG.debug("manual cache clean set from config")
existing = "trust"
else:
mfile = path_helper.get_ipath_cur("manual_clean_marker")
if os.path.exists(mfile):
LOG.debug("manual cache clean found from marker: %s", mfile)
existing = "trust" init.purge_cache()
# Delete the no-net file as well
util.del_file(os.path.join(path_helper.get_cpath("data"), "no-net")) # Stage 5
# 从数据源中获取数据。根据obj.pkl缓存文件是否存在、existing变量、instance_id是否与/run/cloud-init/instance-id一致判断是否从缓存加载数据,
# 否则遍历所有数据源,选择能够第一个能够获取数据的数据源当作本实例数据源
# s.update_metadata([EventType.BOOT_NEW_INSTANCE]),_get_data
try:
init.fetch(existing=existing)
# if in network mode, and the datasource is local
# then work was done at that stage.
# network阶段下,如果数据源的dsmode不为network,则直接结束
if mode == sources.DSMODE_NETWORK and init.datasource.dsmode != mode:
LOG.debug("[%s] Exiting. datasource %s in local mode",
mode, init.datasource)
return (None, [])
except sources.DataSourceNotFoundException:
# In the case of 'cloud-init init' without '--local' it is a bit
# more likely that the user would consider it failure if nothing was
# found. When using upstart it will also mentions job failure
# in console log if exit code is != 0.
if mode == sources.DSMODE_LOCAL:
LOG.debug("No local datasource found")
else:
util.logexc(LOG, ("No instance datasource found!"
" Likely bad things to come!"))
if not args.force:
init.apply_network_config(bring_up=not args.local)
LOG.debug("[%s] Exiting without datasource", mode)
if mode == sources.DSMODE_LOCAL:
return (None, [])
else:
return (None, ["No instance datasource found."])
else:
LOG.debug("[%s] barreling on in force mode without datasource",
mode) # 如果数据源是从缓存恢复的,且instance-data.json文件缺失,则恢复它
_maybe_persist_instance_data(init)
# Stage 6
# 生成/var/lib/cloud/<instance_id>/软链接,创建handlers, scripts, sem目录,写入datasource文件
# 在/var/lib/cloud/data/目录下写入previous-datasource、instance-id、previous-instance-id文件
# 在/run/cloud-init/目录下写入.instance_id文件
# 若manual_cache_clean配置项为true,写入/var/lib/cloud/<instance_id>/manual_clean_marker文件
# 写入obj.pkl
# 刷新init实例的配置
iid = init.instancify()
LOG.debug("[%s] %s will now be targeting instance id: %s. new=%s",
mode, name, iid, init.is_new_instance()) if mode == sources.DSMODE_LOCAL:
# Before network comes up, set any configured hostname to allow
# dhcp clients to advertize this hostname to any DDNS services
# LP: #1746455.
_maybe_set_hostname(init, stage='local', retry_stage='network')
# 应用网络配置,network阶段会拉起网络
# 若存在/var/lib/cloud/data/upgraded-network文件,则直接返回
# netcfg=self.datasource.network_config
# self._apply_netcfg_names(netcfg)
# self.distro.apply_network_config(netcfg, bring_up=bring_up)
init.apply_network_config(bring_up=bool(mode != sources.DSMODE_LOCAL)) # local阶段下,如果数据源的dsmode不为local,则直接返回
if mode == sources.DSMODE_LOCAL:
if init.datasource.dsmode != mode:
LOG.debug("[%s] Exiting. datasource %s not in local mode.",
mode, init.datasource)
return (init.datasource, [])
else:
LOG.debug("[%s] %s is in local mode, will apply init modules now.",
mode, init.datasource) # Give the datasource a chance to use network resources.
# This is used on Azure to communicate with the fabric over network.
# 在uesr-data和vendor-data处理之前调用,用于网络启动后再次更新数据源,目前仅用于azure获取fabric数据并填充进fabric_data
# 调用self.datasource.setup(is_new_instance=self.is_new_instance())
init.setup_datasource()
# update fully realizes user-data (pulling in #include if necessary)
# 存储与渲染userdata和vendor_data
# _store_userdata(),在/var/lib/cloud/instance/目录下写入user-data.txt、user-data.txt.i
# _store_vendordata(),在/var/lib/cloud/instance/目录下写入vendor-data.txt、vendor-data.txt.i
init.update()
_maybe_set_hostname(init, stage='init-net', retry_stage='modules:config')
# Stage 7
try:
# Attempt to consume the data per instance.
# This may run user-data handlers and/or perform
# url downloads and such as needed.
# 消费uesr_data和vendor_data
# allow_userdata不为false的话,执行_consume_userdata(PER_INSTANCE),reading and applying user-data
# 在/var/lib/cloud/instance/目录下写入cloud-config.txt
# 执行_consume_vendordata(PER_INSTANCE),vendor data will be consumed
# 在/var/lib/cloud/instance/目录下写入vendor-cloud-config.txt
# 在/var/lib/cloud/instance/scripts/vendor/目录下写入vendor_data脚本
(ran, _results) = init.cloudify().run('consume_data',
init.consume_data,
args=[PER_INSTANCE],
freq=PER_INSTANCE)
if not ran:
# Just consume anything that is set to run per-always
# if nothing ran in the per-instance code
#
# See: https://bugs.launchpad.net/bugs/819507 for a little
# reason behind this...
init.consume_data(PER_ALWAYS)
except Exception:
util.logexc(LOG, "Consuming user data failed!")
return (init.datasource, ["Consuming user data failed!"])
apply_reporting_cfg(init.cfg) # Stage 8 - re-read and apply relevant cloud-config to include user-data
# 实例化Modules类
# 合并所有cloud-config,包括:/etc/cloud/cloud.cfg{,.d},/run/cloud-init/cloud.cfg,/proc/cmdline,/var/lib/cloud/instance/cloud-config.txt,/var/lib/cloud/instance/vendor-cloud-config.txt
mods = stages.Modules(init, extract_fns(args), reporter=args.reporter)
# Stage 9
try:
# 使用mods对象再次重定向日志输出
outfmt_orig = outfmt
errfmt_orig = errfmt
(outfmt, errfmt) = util.get_output_cfg(mods.cfg, name)
if outfmt_orig != outfmt or errfmt_orig != errfmt:
LOG.warning("Stdout, stderr changing to (%s, %s)",
outfmt, errfmt)
(outfmt, errfmt) = util.fixup_output(mods.cfg, name)
except Exception:
util.logexc(LOG, "Failed to re-adjust output redirection!")
logging.setupLogging(mods.cfg) # give the activated datasource a chance to adjust
# 调用self.datasource.activate,该方法在user-data和vendor-data渲染后,init_modules执行前调用
# 写入/var/lib/cloud/instance/obj.pkl
init.activate_datasource() di_report_warn(datasource=init.datasource, cfg=init.cfg) # Stage 10
# 执行init_modules
return (init.datasource, run_module_section(mods, name, name))

cloud-init modules

cmd/main.py, main_modules(),cloud-init在config和final阶段会运行该函数,分析如下:

def main_modules(action_name, args):
# config或final
name = args.mode
# Cloud-init v. 19.4 running 'modules:config' at Wed, 15 Dec 2021 03:01:15 +0000. Up 280.96 seconds.
w_msg = welcome_format("%s:%s" % (action_name, name))
# 实例化Init类
init = stages.Init(ds_deps=[], reporter=args.reporter)
# Stage 1
# 加载配置文件,优先级从低到高为:内置配置 --> /etc/cloud/clouf.cfg{,.d} --> /run/cloud-init/cloud.cfg --> kernel cmdline
init.read_cfg(extract_fns(args))
# Stage 2
try:
# 从数据源中获取数据。当obj.pkl缓存文件存在,则从缓存加载数据,
# 否则遍历所有数据源,选择能够第一个能够获取数据的数据源当作本实例数据源
# s.update_metadata([EventType.BOOT_NEW_INSTANCE]),_get_data
init.fetch(existing="trust")
except sources.DataSourceNotFoundException:
# There was no datasource found, theres nothing to do
msg = ('Can not apply stage %s, no datasource found! Likely bad '
'things to come!' % name)
util.logexc(LOG, msg)
print_exc(msg)
if not args.force:
return [(msg)]
# 如果数据源是从缓存恢复的,且instance-data.json文件缺失,则恢复它
_maybe_persist_instance_data(init)
# Stage 3
# 实例化Modules类
mods = stages.Modules(init, extract_fns(args), reporter=args.reporter)
# Stage 4
# 重定向标准输出到日志文件
try:
LOG.debug("Closing stdin")
util.close_stdin()
util.fixup_output(mods.cfg, name)
except Exception:
util.logexc(LOG, "Failed to setup output redirection!")
if args.debug:
# Reset so that all the debug handlers are closed out
LOG.debug(("Logging being reset, this logger may no"
" longer be active shortly"))
logging.resetLogging()
logging.setupLogging(mods.cfg)
apply_reporting_cfg(init.cfg) # now that logging is setup and stdout redirected, send welcome
welcome(name, msg=w_msg) # Stage 5
# 运行各个模块
return run_module_section(mods, name, name)

针对redhat系的定制

收录于centos yum仓库的cloud-init是定制的版本,在开源cloud-init的基础上合入了一系列针对redhat系linux的patch。目前收录如下:

  1. Add initial redhat setup。此补丁包含多个补丁,主要包含对默认配置的改动,例如将system_info.distro更改为rhel,添加默认cloud.cfg配置文件,以及添加一系列systemd服务配置文件
  2. Do not write NM_CONTROLLED=no in generated interface config files。
  3. limit permissions on def_log_file。添加日志文件用户权限配置选项def_log_file_mode,且设置其默认值为0600
  4. sysconfig: Don't write BOOTPROTO=dhcp for ipv6 dhcp。
  5. DataSourceAzure.py: use hostnamectl to set hostname。
  6. include 'NOZEROCONF=yes' in /etc/sysconfig/network。云上实例需要使用该配置
  7. Remove race condition between cloud-init and NetworkManager。移除systemd服务中对NetworkManager的竞争,设置ssh_deletekeys为1
  8. net: exclude OVS internal interfaces in get_interfaces。
  9. Fix requiring device-number on EC2 derivatives。
  10. rhel/cloud.cfg: remove ssh_genkeytypes in settings.py and set in cloud.cfg。在cloud.cfg中添加 ssh_genkeytypes,首次开机启动时生成公私钥
  11. write passwords only to serial console, lock down cloud-init-output.log。
  12. ssh-util: allow cloudinit to merge all ssh keys into a custom user file, defined in AuthorizedKeysFile。
  13. Stop copying ssh system keys and check folder permissions。
  14. Fix home permissions modified by ssh module (SC-338)。
  15. ssh_utils.py: ignore when sshd_config options are not key/value pairs。
  16. cc_ssh.py: fix private key group owner and permissions。

针对OpenStack的变更

  1. 基于centos8 cloud-init-21.1-9.el8.src.rpm作变更,此包基于开源cloud-init 21.1版本合入了多个redhat系linux定制的patch。
  2. cloud-init的detect_openstack方法(cloudinit\sources\DataSourceOpenStack.py)检测实例是否位于OpenStack,以判断是否应用openstack数据源。由于裸金属实例无法判断是否属于OpenStack,需修改此方法,直接返回true。
  3. ...

云实例初始化工具cloud-init源码分析的更多相关文章

  1. 云实例初始化工具cloud-init简介

    项目简介 cloud-init是一款用于初始化云服务器的工具,它拥有丰富的模块,能够为云服务器提供的能力有:初始化密码.扩容根分区.设置主机名.注入公钥.执行自定义脚本等等,功能十分强大. 目前为止c ...

  2. [Android实例] Scroll原理-附ScrollView源码分析

    想象一下你拿着放大镜贴很近的看一副巨大的清明上河图, 那放大镜里可以看到的内容是很有限的, 而随着放大镜的上下左右移动,就可以看到不同的内容了 android中手机屏幕就相当于这个放大镜, 而看到的内 ...

  3. [Android实例] Scroll原理-附ScrollView源码分析 (转载)

    想象一下你拿着放大镜贴很近的看一副巨大的清明上河图, 那放大镜里可以看到的内容是很有限的, 而随着放大镜的上下左右移动,就可以看到不同的内容了 android中手机屏幕就相当于这个放大镜, 而看到的内 ...

  4. Spring Cloud Eureka源码分析 --- client 注册流程

    Eureka Client 是一个Java 客户端,用于简化与Eureka Server的交互,客户端同时也具备一个内置的.使用轮询负载算法的负载均衡器. 在应用启动后,将会向Eureka Serve ...

  5. Spring Cloud Eureka源码分析之服务注册的流程与数据存储设计!

    Spring Cloud是一个生态,它提供了一套标准,这套标准可以通过不同的组件来实现,其中就包含服务注册/发现.熔断.负载均衡等,在spring-cloud-common这个包中,org.sprin ...

  6. Spring Cloud Ribbon 源码分析---负载均衡算法

    上一篇分析了Ribbon如何发送出去一个自带负载均衡效果的HTTP请求,本节就重点分析各个算法都是如何实现. 负载均衡整体是从IRule进去的: public interface IRule{ /* ...

  7. 【spring cloud】源码分析(一)

    概述 从服务发现注解 @EnableDiscoveryClient入手,剖析整个服务发现与注册过程 一,spring-cloud-common包 针对服务发现,本jar包定义了 DiscoveryCl ...

  8. 云风协程库coroutine源码分析

    前言 前段时间研读云风的coroutine库,为了加深印象,做个简单的笔记.不愧是大神,云风只用200行的C代码就实现了一个最简单的协程,代码风格精简,非常适合用来理解协程和用来提升编码能力. 协程简 ...

  9. Spring之WebContext不使用web.xml启动 初始化重要的类源码分析(Servlet3.0以上的)

    入口: org.springframework.web.SpringServletContainerInitializer implements ServletContainerInitializer ...

随机推荐

  1. 简易的DragDropCarousel 拖拽轮播控件

    上一篇文章有写到 自动轮播的控件  简易的AutoPlayCarousel 轮播控件 - 黄高林 - 博客园 (cnblogs.com) 本章是基于自动轮播的一种衍生,通过拖拽鼠标进切换 直接上代码 ...

  2. 三 单例模式【Singleton Pattern】  来自CBF4LIFE 的设计模式

    这个模式是很有意思,而且比较简单,但是我还是要说因为它使用的是如此的广泛,如此的有人缘,单例就是单一.独苗的意思,那什么是独一份呢?你的思维是独一份,除此之外还有什么不能山寨的呢?我们举个比较难复制的 ...

  3. API接口签名校验(C#版)

    我们在提供API服务的时候,为了防止数据传输过程被篡改,通常的做法是对传输的内容进行摘要签名,把签名串同参数一起请求API,API服务接收到请求后以同样的方式生成签名串,然后进行对比,如果签名串不一致 ...

  4. LFS(Linux From Scratch)构建过程全记录(三):下载所需的软件包

    写在前面 本文将记录构建LFS的过程中,下载软件包的全过程 准备下载的路径 注意请确保$LFS已经设置完毕 我们需要创建一个文件夹,地址为$LFS/sources,用于保存对应的源码 输入的指令如下: ...

  5. OKR之剑(理念篇)01—— OKR带给我们的改变

    作者:vivo互联网平台产品研发团队 一.前言 OKR即目标与关键成果法,起源于英特尔,在谷歌发扬光大.近几年在国内比较火,很多企业都相继引入了OKR的管理方式,小到2-3人的小微初创公司,大到十几万 ...

  6. Win32简单图形界面程序逆向

    Win32简单图形界面程序逆向 前言 为了了解与学习底层知识,从 汇编开始 -> C语言 -> C++ -> PE文件 ,直至今天的Win32 API,着实学的令我头皮发麻(笑哭). ...

  7. Django 使用cmd 创建工程

    一.Django 安装 1 通过pip安装 Django 如果你是通过升级的方式安装Django,那么你需要先卸载旧的版本. Django 提供3种发行版本,推荐使用官方的稳定版本: 你的操作系统提供 ...

  8. 修改Windows10 命令终端cmd的编码为UTF-8 && IDEA控制台输出中文乱码

    临时修改 进入cmd窗口后,直接执行 chcp 65001 永久修改 在运行中输入regedit,找到HKEY_LOCAL_MACHINE\SOFTWARE\Microsoft\Command Pro ...

  9. Netty 学习(八):新连接接入源码说明

    Netty 学习(八):新连接接入源码说明 作者: Grey 原文地址: 博客园:Netty 学习(八):新连接接入源码说明 CSDN:Netty 学习(八):新连接接入源码说明 新连接的接入分为3个 ...

  10. 分布式存储系统之Ceph集群存储池、PG 与 CRUSH

    前文我们了解了ceph集群状态获取常用命令以及通过ceph daemon.ceph tell动态配置ceph组件.ceph.conf配置文件相关格式的说明等,回顾请参考https://www.cnbl ...