从零开始搞定Chef自动化部署,让服务器管理不再头疼
最近公司的服务器数量又增加了不少,每次部署新应用都要重复执行一堆命令,配置文件改来改去的,实在是太麻烦了。我琢磨着得找个自动化工具来解决这个问题,经过一番调研,最终选择了Chef。
说实话,刚开始接触Chef的时候,被它的概念搞得有点晕。什么Cookbook、Recipe、Node、Role的,感觉像是在学做菜一样。不过用了一段时间后发现,这玩意儿还真挺好使的,今天就把我踩过的坑和实践经验分享给大家。
Chef到底是个啥
简单来说,Chef就是一个自动化配置管理工具。你可以把它理解成一个"厨师",按照你写好的"菜谱"(Cookbook),自动在服务器上"做菜"(配置软件和服务)。
我觉得Chef最大的优势就是它用Ruby写配置,代码即配置的理念让运维工作变得更加程序化。不像以前写shell脚本那样,到处都是if-else判断,Chef的DSL(领域特定语言)让配置文件看起来更清晰。
搭建Chef环境
部署Chef主要有三个组件:Chef Server、Chef Workstation和Chef Client。我当时是在Ubuntu 20.04上搭建的,过程还算顺利。
安装Chef其实挺简单的,在工作站上执行:
curl -L https://omnitruck.chef.io/install.sh | sudo bash这个脚本会自动检测你的系统,下载对应版本的Chef。安装完成后,可以用chef --version检查一下版本。
配置Chef Server稍微麻烦点。我是直接用的Chef官方提供的包,大概有200多MB:
wget https://packages.chef.io/files/stable/chef-server/14.0.0/ubuntu/20.04/chef-server-core_14.0.0-1_amd64.deb
sudo dpkg -i chef-server-core_14.0.0-1_amd64.deb
sudo chef-server-ctl reconfigure配置过程需要等一会儿,它会自动安装PostgreSQL、RabbitMQ等依赖服务。配置完成后,创建管理员用户和组织:
sudo chef-server-ctl user-create admin Admin User admin@example.com 'password' --filename admin.pem
sudo chef-server-ctl org-create myorg 'My Organization' --association_user admin --filename myorg-validator.pem这里生成的.pem文件很重要,是后续连接Chef Server的凭证,一定要保存好。
写第一个Cookbook
Cookbook是Chef的核心概念,可以理解为一个配置包,里面包含了Recipe(具体的配置步骤)、Attributes(变量)、Templates(模板文件)等。
创建Cookbook很简单:
chef generate cookbook nginx_setup这会生成一个标准的Cookbook目录结构。我第一次写的是一个安装配置Nginx的Cookbook,在recipes/default.rb里写入:
# 安装nginx包
package 'nginx' do
action :install
end
# 创建网站目录
directory '/var/www/mysite' do
owner 'www-data'
group 'www-data'
mode '0755'
action :create
end
# 配置nginx
template '/etc/nginx/sites-available/mysite' do
source 'mysite.conf.erb'
owner 'root'
group 'root'
mode '0644'
variables(
server_name: node['nginx_setup']['server_name'],
port: node['nginx_setup']['port']
)
notifies :restart, 'service[nginx]', :delayed
end
# 启用站点
link '/etc/nginx/sites-enabled/mysite' do
to '/etc/nginx/sites-available/mysite'
notifies :restart, 'service[nginx]', :delayed
end
# 确保nginx服务运行
service 'nginx' do
action [:enable, :start]
end这个Recipe做了几件事:安装Nginx包、创建网站目录、使用模板生成配置文件、启用站点、确保服务运行。看起来是不是比写shell脚本清晰多了?
模板的妙用
Chef的模板功能特别实用。在templates/default/mysite.conf.erb中:
server {
listen <%= @port %>;
server_name <%= @server_name %>;
root /var/www/mysite;
index index.html index.htm;
location / {
try_files $uri $uri/ =404;
}
access_log /var/log/nginx/<%= @server_name %>_access.log;
error_log /var/log/nginx/<%= @server_name %>_error.log;
}模板中的变量会在运行时被替换,这样同一个模板可以用于不同的环境,只需要改变attributes就行了。
Attributes的灵活运用
Attributes是Chef中用来存储配置数据的地方。在attributes/default.rb中定义默认值:
default['nginx_setup']['server_name'] = 'example.com'
default['nginx_setup']['port'] = 80
default['nginx_setup']['worker_processes'] = 'auto'
default['nginx_setup']['worker_connections'] = 1024这些属性可以在不同层级被覆盖,比如在Role、Environment或者Node级别。这种机制让配置管理变得非常灵活。
我在实际使用中,通常会为不同环境创建不同的Environment文件:
# environments/production.rb
name 'production'
description 'Production environment'
override_attributes(
'nginx_setup' => {
'server_name' => 'prod.example.com',
'worker_processes' => 4,
'worker_connections' => 2048
}
)Data Bags存储敏感信息
处理敏感信息时,Data Bags是个好选择。比如数据库密码、API密钥这些,不适合直接写在代码里。
创建一个加密的Data Bag:
knife data bag create credentials mysql --secret-file /path/to/secret_key然后在Recipe中使用:
mysql_creds = Chef::EncryptedDataBagItem.load('credentials', 'mysql')
mysql_service 'default' do
port '3306'
version '5.7'
initial_root_password mysql_creds['root_password']
action [:create, :start]
end这样密码就不会明文出现在代码中了,安全性大大提高。
实战案例:部署一个完整的Web应用栈
说了这么多理论,来个实际的例子。我曾经用Chef部署过一个典型的LAMP栈应用,包括Nginx作为反向代理、多个PHP-FPM实例、MySQL数据库。
整个Cookbook的结构是这样的:
webapp_stack/
├── recipes/
│ ├── default.rb
│ ├── nginx.rb
│ ├── php.rb
│ ├── mysql.rb
│ └── app_deploy.rb
├── templates/
│ ├── nginx_vhost.conf.erb
│ ├── php_pool.conf.erb
│ └── app_config.php.erb
├── attributes/
│ └── default.rb
└── metadata.rb在recipes/default.rb中,我按顺序调用其他Recipe:
include_recipe 'webapp_stack::mysql'
include_recipe 'webapp_stack::php'
include_recipe 'webapp_stack::nginx'
include_recipe 'webapp_stack::app_deploy'PHP-FPM的配置比较有意思,我用了一个循环来创建多个池:
# recipes/php.rb
node['webapp_stack']['php_pools'].each do |pool_name, pool_config|
template "/etc/php/7.4/fpm/pool.d/#{pool_name}.conf" do
source 'php_pool.conf.erb'
variables(
pool_name: pool_name,
user: pool_config['user'],
group: pool_config['group'],
listen: pool_config['listen'],
pm_max_children: pool_config['pm_max_children']
)
notifies :restart, 'service[php7.4-fpm]', :delayed
end
end这样可以很方便地根据需要创建多个PHP-FPM池,实现应用隔离。
使用Role组织节点
当服务器数量增多后,一个个管理Node会很麻烦。这时候Role就派上用场了。我通常会创建几个标准角色:
# roles/web_server.rb
name 'web_server'
description 'Web server role'
run_list(
'recipe[webapp_stack::nginx]',
'recipe[webapp_stack::php]'
)
default_attributes(
'nginx_setup' => {
'worker_processes' => 2
}
)# roles/db_server.rb
name 'db_server'
description 'Database server role'
run_list(
'recipe[webapp_stack::mysql]'
)
default_attributes(
'mysql' => {
'bind_address' => '0.0.0.0'
}
)然后给节点分配角色:
knife node run_list add web01.example.com 'role[web_server]'
knife node run_list add db01.example.com 'role[db_server]'调试技巧
写Cookbook的过程中难免会遇到问题,分享几个调试技巧。
使用chef-client的--why-run模式可以预览会执行哪些操作,不会真正执行:
sudo chef-client --why-run在Recipe中可以用log资源输出调试信息:
log "Current value of server_name: #{node['nginx_setup']['server_name']}" do
level :info
end如果某个资源执行失败,可以用ignore_failure让Chef继续执行:
package 'some-optional-package' do
ignore_failure true
end但这个要谨慎使用,可能会掩盖真正的问题。
版本管理和测试
Cookbook也是代码,当然要做版本管理。我的做法是每个Cookbook都是一个Git仓库,在metadata.rb中管理版本:
name 'webapp_stack'
maintainer 'Your Name'
maintainer_email 'your@email.com'
license 'All Rights Reserved'
description 'Installs/Configures webapp stack'
version '1.2.3'
depends 'mysql', '~> 8.0'
depends 'php', '~> 4.0'版本号遵循语义化版本规范,这样在升级时能清楚知道变更的影响范围。
测试方面,可以用Test Kitchen进行集成测试:
# .kitchen.yml
driver:
name: vagrant
provisioner:
name: chef_zero
platforms:
- name: ubuntu-20.04
suites:
- name: default
run_list:
- recipe[webapp_stack::default]
attributes:
webapp_stack:
server_name: test.example.com运行kitchen test就能在虚拟机中测试Cookbook了。
一些坑和经验
使用Chef这段时间,也踩了不少坑。比如有一次,我在Recipe中直接修改了配置文件,结果每次运行chef-client都会触发服务重启,即使配置没有变化。后来才知道要用only_if或not_if来添加条件判断:
template '/etc/nginx/nginx.conf' do
source 'nginx.conf.erb'
notifies :restart, 'service[nginx]', :delayed
not_if { ::File.exist?('/etc/nginx/nginx.conf') &&
::File.read('/etc/nginx/nginx.conf').include?('# Managed by Chef') }
end还有一个经验是,不要在Recipe中使用太多Ruby的高级特性。虽然Chef支持完整的Ruby语法,但过于复杂的代码会让其他人难以理解和维护。保持简单直观是最好的。
资源的执行顺序也很重要。Chef默认按照Recipe中的顺序执行,但有时需要用notifies和subscribes来控制资源间的依赖关系:
template '/etc/app/config.yml' do
source 'config.yml.erb'
notifies :restart, 'service[app]', :immediately
end
service 'app' do
action :nothing
subscribes :restart, 'template[/etc/app/config.yml]', :immediately
end性能优化
当管理的节点数量增多后,性能问题就显现出来了。几个优化建议:
- 合理使用
lazy属性评估,避免不必要的计算:
template '/etc/app/config.yml' do
variables lazy {
{
db_host: search(:node, 'role:database').first['ipaddress']
}
}
end- 减少search的使用,search操作比较耗时,可以考虑用Data Bags或Attributes代替。
- 使用
chef-client的定时运行而不是持续运行模式,减少资源消耗。