引言
Paker 是 开源软件公司 Hashicorp 在虚拟机镜像管理上的又一力作,主要通过配置文件来定义镜像的构建过程、适配大部分公有云和私有云厂商(Hashicorp在虚拟机镜像领域的另一个作品Vagrant主要是解决本地开发环境下的虚拟机管理的)。
从Vagrant,Paker,再到Terraform,Hashicorp的多云管理解决方案都离不开通过配置文件去定义基础设施这个“套路”,想要了解Paker就先从基础设施即代码(Infrastructure as Code)这个理念出发吧。
基础设施即代码的概念
在传统机房时代,对于IT基础设施的配置和维护只能通过各硬件提供商的操作面板,需要人工去"点点点",即使有Chef、Puppet、Ansible 这样的配置管理工具,但它们也是在服务器等基础设施建立起来之后,进行一些软件和文件系统层面的配置。
随着云计算的普及,以及云的"可编程"特点(OpenStack、AWS等IaaS产品都有对外提供API来操作资源),使基础设施即代码成为可能。
基础设施即代码通常是云提供商对外提供一种可以使用模板文件声明式地定义云上资源(服务器、安全组、账号等),并且当用户提交模板文件时可以创建、更改或删除相应的云上资源。这样,基础设施就被代码化了,用户可以对这些模板文件进行编排和版本管理来高效地部署和维护云上资源。
适用场景:
适用于部署和维护中大规模的基础设施环境,并且有频繁销毁重建、迁移到其他区域、符合合规性、支持回退等需求。
Iac中的最佳实践以及实现途径
-
可重现(reproducible)
在传统的手工配置基础设施的时代,即使所有服务器共用一个基础镜像,但是在运行过程中也会因为不同管理员在不同服务器上解决特定问题而改变服务器中的库依赖或者其他资源等,造成配置漂移(configuration drift),以至于不同服务器形态各异,而且手工操作没有记录,无法重现。
可重现要求对基础设施的变更都是有记录,可以复现任一阶段的基础设施状态。
具体可以从以下两个点入手:-
自描述(self-documented):
基础设施的模板文件中可以清楚地描述基础设施的创建或变更步骤。比如在 Packer 中,镜像的构建步骤都通过一个 json 文件来描述,只要是通过 Packer 构建的镜像,之后谁都可以通过它的 json 文件再重新构建一个完全相同的镜像,包括镜像中装了哪些版本的软件都可以描述清楚 -
版本管理:
将基础设施对应的模板文件纳入版本管理系统(比如git),就可以实现版本管理。
-
-
测试
在应用软件的开发中,常常要对应用进行功能测试和性能测试。然而在基础设施的发布流水线中,我们主要对安全性、可恢复性、合规性等进行测试。
在 AWS 官方提供的镜像构建工具 AMI Builder 中,在镜像构建结束前还会对正在运行的实例进行安全加固和一些自定义测试:
具体实践
目录规范
Paker支持多云、多平台,如果你用Paker管理不同云厂商的镜像的话,可以像按照云厂商、操作系统、自定义镜像名称的多级目录去编排文件。
Paker使用的配置文件是Json,现在也支持HCL了,这里还是以Json文件为例。
├── alicloud
│ ├── linux
│ │ └── custom_image_name
│ │ ├── files
│ │ ├── main.json
│ │ ├── scripts
样例说明
Paker使用的配置文件是Json,现在也支持HCL了,这里还是以Json文件为例,使用的例子是Paker Github仓库example下的阿里云的模板。
{
"variables": {
"access_key": "{{env `ALICLOUD_ACCESS_KEY`}}",
"secret_key": "{{env `ALICLOUD_SECRET_KEY`}}"
},
"builders": [{
"type":"alicloud-ecs",
"access_key":"{{user `access_key`}}",
"secret_key":"{{user `secret_key`}}",
"region":"cn-beijing",
"image_name":"packer_test",
"source_image":"winsvr_64_dtcC_1809_en-us_40G_alibase_20190318.vhd",
"instance_type":"ecs.n1.tiny",
"io_optimized":"true",
"internet_charge_type":"PayByTraffic",
"image_force_delete":"true",
"communicator": "winrm",
"winrm_port": 5985,
"winrm_username": "Administrator",
"winrm_password": "Test1234",
"user_data_file": "examples/alicloud/basic/winrm_enable_userdata.ps1"
}],
"provisioners": [{
"type": "powershell",
"inline": ["dir c:\\"]
}]
}
Paker构建文件主要包含三部分:
Variables 定义后续模板需要引用的变量,也是可以根据不同场景去灵活调整的;
Builders 是各云厂商提供的构建器,Paker通过与构建器交互来运行实例并创建镜像;
Provisioners 通常是执行一些初始化脚本,使镜像达到你预期的状态。
推荐的实践
安全加固
由AWS的责任共担模式可知,云厂商并不保证镜像市场上所有镜像的安全性,所以在构建生产用的基础镜像时,预先执行一些安全加固脚本可以在系统层面提供更好的安全性。
安全加固脚本参考开源项目 https://github.com/dev-sec/ansible-os-hardening 和 https://github.com/dev-sec/linux-baseline
前者用于加固,后者用于基线检查,都针对 linux 操作系统
主要进行的加固项如下,可在ansible-os-hardening项目的task目录下查看详情:
auditd.yml
安装并配置auditd,用于审计limits.yml
禁止产生coredumplogin_defs.yml
创建并配置login_defs文件,用于对登录用户的一些属性进行限制minimize_access.yml
限制/etc/passwd, /etc/shadow, 二进制目录等文件系统的访问权限modprobe.yml
禁用部分不常用的文件系统pam.yml
删除密码缓存,加强系统用户的安全认证模块rhosts.yml
禁止用户通过 rhost http-equiv netrc 等方式登录securetty.yml
只允许console和tty类型的终端suid_sgid.yml
禁止一些系统命令的提权权限sysctl.yml
调整sysctl系统内核参数user_accounts.yml
设置第三方应用的系统账户无法通过shell登录yum.yml
删除无用仓库,启用GPG检验
以上模块可以根据实际情况进行裁剪。
数据盘挂载
对数据可用性要求较高的镜像,可以把数据单独存储在数据盘并在实例启动时挂载到相应路径。
例如在阿里云和AWS的Packer main.json
文件中,system_disk_mapping
和launch_block_device_mappings
用于设置系统盘初始大小,image_disk_mappings
和ami_block_device_mappings
用于添加多个数据盘,随后可以在 user_data 中写入磁盘的挂载命令,user_data 会在实例启动时执行
"system_disk_mapping": {
"disk_category": "cloud_efficiency",
"disk_size": 50
},
"image_disk_mappings": [
{
"disk_device": "/dev/sdb",
"disk_category": "cloud_efficiency",
"disk_size": 50
}
],
"user_data": "",
"user_data_file": ""
"launch_block_device_mappings": [
{
"device_name": "/dev/sda1",
"volume_size": 50,
"volume_type": "gp2",
"delete_on_termination": true
}
],
"ami_block_device_mappings": [
{
"device_name": "/dev/sdb",
"volume_size": 50,
"volume_type": "gp2"
}
],
"user_data": "",
"user_data_file": ""
最小执行权限
Paker 执行需要云账户的Access Key,在实际构建过程中,推荐单独创建一个子角色,并赋予 Paker构建镜像过程中必要的权限。
AWS的权限参考 packer官方文档,
阿里云的权限如下:
{
"Version": "1",
"Statement": [
{
"Action": [
"ecs:DescribeImages",
"ecs:CreateImage",
"ecs:ModifyImageSharePermission",
"ecs:CreateKeyPair",
"ecs:DeleteKeyPairs",
"ecs:DetachKeyPair",
"ecs:AttachKeyPair",
"ecs:CreateSecurityGroup",
"ecs:DeleteSecurityGroup",
"ecs:AuthorizeSecurityGroupEgress",
"ecs:AuthorizeSecurityGroup",
"ecs:CreateSnapshot",
"ecs:AttachDisk",
"ecs:DetachDisk",
"ecs:DescribeDisks",
"ecs:CreateDisk",
"ecs:DeleteDisk",
"ecs:CreateNetworkInterface",
"ecs:DescribeNetworkInterfaces",
"ecs:AttachNetworkInterface",
"ecs:DetachNetworkInterface",
"ecs:DeleteNetworkInterface",
"ecs:DescribeInstanceAttribute",
"ecs:CreateInstance",
"ecs:DeleteInstance",
"ecs:StartInstance",
"ecs:StopInstance",
"ecs:DescribeInstances",
"ecs:AddTags",
"ecs:CopyImage"
],
"Resource": [
"*"
],
"Effect": "Allow"
},
{
"Action": [
"vpc:CreateVpc",
"vpc:DeleteVpc",
"vpc:DescribeVpcs",
"vpc:CreateVSwitch",
"vpc:DeleteVSwitch",
"vpc:DescribeVSwitches",
"vpc:AllocateEipAddress",
"vpc:AssociateEipAddress",
"vpc:UnassociateEipAddress",
"vpc:DescribeEipAddresses",
"vpc:ReleaseEipAddress"
],
"Resource": [
"*"
],
"Effect": "Allow"
}