每月 Golden Image 构建流程使用 Packer 与 Ansible
Source: Dev.to
(请提供您希望翻译的正文内容,我将为您翻译成简体中文。)
介绍
在 IT 运维中,想象我们在一家云组织工作,每月部署数百个 EC2 实例。每个实例都需要安全且合规。手动配置每个实例简直是噩梦。相反,你希望拥有一个 golden image —— 一个可重复使用的 AMI,已预先加固并配备所有必要工具。
这正是 Packer 发挥作用的时机。
问题陈述
在大多数组织中,EC2 实例会频繁启动以支持各种工作负载。但每个实例必须:
- 安全且合规
- 配备监控和安全代理
- 配置一致
Source: …
实时场景
假设您是一个注重安全的企业的一员。每个 EC2 实例必须:
- 符合 CIS 基准
- 安装 CrowdStrike 和 Qualys 代理
与其在实例启动后再进行配置,您希望创建一个已经加固并预装好的黄金 AMI。该镜像将作为所有未来部署的基础——节省时间并确保一致性。
涉及的工具
- Packer
- AWS EC2
- Ansible
- GitLab CI/CD
- Amazon SSM
架构图
工作流概览
- 从基础镜像启动临时 EC2 实例。
- 运行配置脚本以:
- 应用操作系统加固(CIS 基准、 防火墙规则、SSH 配置)。
- 从已配置的实例创建新的 AMI。
- 终止临时实例。
实施步骤
步骤 1 – 安装 Packer

步骤 2 – 使用 Ansible 进行 Packer 模板创建
以下 Packer 模板通过在特定的 AWS VPC 和子网中启动临时 EC2 实例,并使用指定的 SSH 密钥对进行安全访问,自动化创建自定义 Amazon Machine Image(AMI)的过程。
packer {
required_plugins {
amazon = {
version = ">= 1.2.8"
source = "github.com/hashicorp/amazon"
}
ansible = {
version = "~> 1"
source = "github.com/hashicorp/ansible"
}
}
}
variable "ami_prefix" {
type = string
default = ""
}
variable "reference_image" {
type = string
default = ""
}
locals {
timestamp = regex_replace(timestamp(), "[- TZ:]", "")
}
variable "privatekey" {
type = string
default = ""
}
source "amazon-ebs" "amazon_linux" {
ami_name = "${var.ami_prefix}-${local.timestamp}"
instance_type = "t2.micro"
region = "ap-south-1"
vpc_id = "vpc-07b2ce11f9b189f3b"
subnet_id = "subnet-063ebc661edd9fb37"
security_group_id = "sg-04e9ae673095b02e9"
ssh_interface = "private_ip"
associate_public_ip_address = true
ssh_keypair_name = "runner_key"
ssh_private_key_file = var.privatekey
source_ami_filter {
filters = {
name = "${var.reference_image}"
root-device-type = "ebs"
virtualization-type = "hvm"
}
most_recent = true
owners = [""]
}
ssh_username = "ec2-user"
}
build {
name = "learn-packer"
sources = ["source.amazon-ebs.amazon_linux"]
provisioner "shell" {
inline = [
"sleep 20",
"echo '--- Running AMI pre‑check ---'",
"set -e",
"sudo mkdir -p /usr/lib",
"# Ensure SFTP subsystem path exists",
"if [ ! -f /usr/lib/sftp-server ]; then",
" if [ -f /usr/libexec/openssh/sftp-server ]; then",
" sudo ln -s /usr/libexec/openssh/sftp-server /usr/lib/sftp-server",
" echo 'Linked /usr/libexec/openssh/sftp-server -> /usr/lib/sftp-server';",
" else",
" echo 'Warning: sftp-server not found, installing openssh-server...';",
" sudo yum install -y openssh-server || sudo apt-get install -y openssh-server;",
" fi;",
"fi",
"# Basic network sanity check",
"sudo yum clean all || true",
"sudo yum update -y || true",
"echo '--- Pre‑check complete ---'"
]
}
provisioner "shell" {
inline = [
"sudo mkdir -p /tmp/.ansible",
"sudo chmod 777 /tmp/.ansible"
]
}
provisioner "ansible" {
playbook_file = "./playbook/main.yml"
use_proxy = false
extra_arguments = [
"--vault-password-file=/home/gitlab-runner/.vault_pass",
"-e", "ansible_remote_tmp=/tmp/.ansible",
"-e", "ansible_local_tmp=/tmp/.ansible",
"-e", "ansible_scp_if_ssh=True",
"-e", "ansible_python_interpreter=/usr/bin/python3",
"-e", "ansible_ssh_transfer_method=scp"
]
}
}
Ansible 主 Playbook
---
- name: Create users and provide sudo access
hosts: all
become: true
gather_facts: true
vars_files:
- ../vars/useradd.yml
- ../vars/vault.yml
roles:
- ../roles/useradd
- ../roles/sudo
- name: Set hostnames
hosts: all
become: true
gather_facts: false
vars_files:
- ../vars/var.yml
roles:
- ../roles/hostnamectl
- name: Enable or set miscellaneous services
hosts: all
become: true
gather_facts: false
roles:
- ../roles/ssh
- ../roles/login_banner
- ../roles/services
- ../roles/timezone
# - ../roles/fs_integrity
# - ../roles/selinux
# - ../roles/firewalld
# - ../roles/log_management
- ../roles/rsyslog
# - ../roles/cron
./roles/journald
---
- hosts: all
become: true
gather_facts: true
vars_files:
- ../vars/useradd.yml
- ../vars/vault.yml
roles:
- ../roles/useradd
Outcome
此模板通过以下方式构建自定义 AMI:
- 在特定的 VPC 和子网中启动虚拟机。
- 使用已定义的 SSH 密钥对进行访问。
- 运行 Shell 脚本和 Ansible 来配置实例。
- 将最终镜像保存为唯一名称,以便后续使用。
注意: 我还在 GitLab 中配置了一个 CI/CD 变量,用于安全存储用于 SSH 访问的私钥内容,以供 Packer 构建使用。
GitLab CI/CD Variable for Private Key
在 GitLab 中,CI/CD 变量允许我们安全地存储敏感数据(密码、令牌、SSH 密钥)。我创建了一个变量(例如 PRIVATE_KEY),其中包含 完整的 私钥内容(而不仅仅是路径)。该变量在运行时注入到流水线中,使得 Packer 等工具可以使用它,而无需在代码库中硬编码密钥或泄露密钥。
packer validate -var-file="ami.pkrvars.hcl" -var "privatekey=runner_key.pem" aws-linux.pkr.hcl
第 3 步:GitLab 流水线阶段
在此流水线中,GitLab CI/CD 作业通过以下方式自动化 AMI 创建过程:
- 从 CI/CD 变量中安全注入 SSH 私钥。
- 验证并构建 Packer 模板。
- 在特定 VPC/子网中配置 EC2 实例。
- 将最终镜像保存以供后续使用。
default:
tags:
- gitlab_runner
stages:
- image_build
Image Build:
stage: image_build
script:
- echo "$SSH_PRIVATE_KEY" > runner_key.pem
- chmod 400 runner_key.pem
- packer init .
- echo "Validating packer template..."
- packer validate -var-file="ami.pkrvars.hcl" -var "privatekey=runner_key.pem" aws-linux.pkr.hcl
- echo "Building AMI..."
- packer build -var-file="ami.pkrvars.hcl" -var "privatekey=runner_key.pem" aws-linux.pkr.hcl
结论
在本文中,我们解决了云运维中的一个常见挑战——在无需人工干预的情况下,确保每个 EC2 实例 安全、合规且 配置一致。
通过结合 Packer、Ansible 和 GitLab CI/CD,我们构建了一个全自动化流水线,实现了:
- 启动临时 EC2 实例。
- 应用 CIS 加固并安装安全代理。
- 保存用于后续使用的黄金 AMI。
- 使用 GitLab CI/CD 变量来保护凭证。
这种方法不仅提升了安全性和合规性,还节省了大量人工工作时间,降低了人为错误,并确保每次部署都基于可信的基线。
谢谢,
Susseta Bose
