我正在使用 Puppet 通过 GlusterFS 文件系统管理一些在服务器之间共享的文件。(具体细节并不重要,但在这种情况下,/etc/httpd/conf.d 和 /var/www/html 等内容通过 GlusterFS 在网络上安装。这是在 RHEL 6 服务器上,带有 Puppet 3.8 和 Gluster 3.5。)
Puppet 处理给定服务器的本地文件没有问题,但当我尝试在此共享文件系统上创建或更新文件时,它几乎从不工作。Puppet 发现需要进行更改,但随后文件无法通过后续的校验和检查。以下是 Puppet 尝试(并失败)创建文件的示例:
从不存在到文件的更改失败:写入磁盘的文件与校验和不匹配;放弃更改({md5} 990680e579211b74e3a8b58a3f4d9814 vs {md5} d41d8cd98f00b204e9800998ecf8427e)
以下是文件编辑的类似示例:
从 {md5}216751de84e40fc247cb02da3944b415 更改为 {md5}261e86c60ce62a99e4b1b91611c1af0e 失败:写入磁盘的文件与校验和不匹配;丢弃更改({md5}261e86c60ce62a99e4b1b91611c1af0e vs {md5}d41d8cd98f00b204e9800998ecf8427e)
这种情况并不总是发生,但在我的 Gluster 文件系统上,我认为它至少 90% 的时间都会发生。
后一个校验和 (d41d8...) 是空文件的校验和。所以我认为情况就是这样的:Puppet 发现需要进行更改,并进行了更改。但它在提交写入之前再次对文件进行校验和,因此它看不到更改已成功完成,因此它会回滚。
那么有两个问题。首先:这看起来合理吗?我该如何测试/确认这种情况?其次:假设这是正在发生的事情,我该如何防止它发生?首先想到的是在文件更改操作后简单地休眠几百毫秒,但我不知道这是否可行,更不用说明智了。
答案1
简洁的
将检查文件的校验和,然后刷新。此校验和将与要写入的文件进行比较。如果存在差异,则写入将失败。
详细
该错误是由以下方法引发的,该方法定义在文件.rb:
# Make sure the file we wrote out is what we think it is.
def fail_if_checksum_is_wrong(path, content_checksum)
newsum = parameter(:checksum).sum_file(path)
return if [:absent, nil, content_checksum].include?(newsum)
self.fail "File written to disk did not match checksum; discarding changes (#{content_checksum} vs #{newsum})"
end
并且该方法包含以下方法,该方法位于校验和.rb:
def sum_file(path)
type = digest_algorithm()
method = type.to_s + "_file"
"{#{type}}" + send(method, path).to_s
end
校验和是如何计算的?
负责此任务的方法也位于 file.rb 中:
def write(property)
remove_existing(:file)
mode = self.should(:mode) # might be nil
mode_int = mode ? symbolic_mode_to_int(mode, Puppet::Util::DEFAULT_POSIX_MODE) : nil
if write_temporary_file?
Puppet::Util.replace_file(self[:path], mode_int) do |file|
file.binmode
content_checksum = write_content(file)
file.flush
fail_if_checksum_is_wrong(file.path, content_checksum) if validate_checksum?
if self[:validate_cmd]
output = Puppet::Util::Execution.execute(self[:validate_cmd].gsub(self[:validate_replacement], file.path), :failonfail => true, :combine => true)
output.split(/\n/).each { |line|
self.debug(line)
}
end
end
else
umask = mode ? 000 : 022
Puppet::Util.withumask(umask) { ::File.open(self[:path], 'wb', mode_int ) { |f| write_content(f) } }
end
# make sure all of the modes are actually correct
property_fix
end
检查校验和的代码片段content_checksum = write_content(file)
:
# write the current content. Note that if there is no content property
# simply opening the file with 'w' as done in write is enough to truncate
# or write an empty length file.
def write_content(file)
(content = property(:content)) && content.write(file)
end
以下代码片段:
content_checksum = write_content(file)
file.flush
fail_if_checksum_is_wrong(file.path, content_checksum) if validate_checksum?
表示将要写入的文件与实际写入的文件有差异。
讨论
后面的校验和(d41d8...)是空文件的校验和。
您如何检查这一点?
所以我认为事情是这样的:Puppet 发现需要进行更改,并进行了更改。但它在提交写入之前再次对文件进行校验和,因此它看不到更改已成功完成,因此它回滚。
如上所述的代码总是像解释的那样工作,并且根据我的经验,校验和检查是有效的。
结论
看起来 GlusterFS 存在问题,例如,使用 Puppet 部署的文件由于某种原因被 GlusterFS 更改了。
建议
我建议按如下方式调试该问题:
- 在 Puppet 上部署包含内容 X 的文件 1
- 使用 Puppet 在 GlusterFS 上部署此文件
- 手动检查位于 puppetserver 上的文件 1 的校验和
- 手动检查 GlusterFS 上文件 1 的校验和
- 在 GlusterFS 上运行 Puppet 并检查问题是否发生