tools
이 글은 다음 4가지 요구사항을 모두 만족하는 하나의 Packer 템플릿을 예제로 만들어 봅시다.
packer { required_plugins { } }
locals
에서 가공대상 OS는 Amazon Linux 2 기반 AMI이며, 빌드 중 Docker를 설치하고 ECR + Docker Hub 이미지를 미리 받아 이미지 캐시를 AMI에 넣어봅시다.
packer {
required_plugins {
amazon = {
source = "github.com/hashicorp/amazon"
version = ">= 0.0.2"
}
}
}
variable "region" { default = "ap-northeast-2" }
variable "ami_name_prefix" { default = "app-node" }
variable "instance_type" { default = "m6i.large" }
variable "iam_instance_profile" { default = "ec2_role_with_ecr_ssm" }
variable "share_with_accounts" { type = list(string), default = ["111111111111"] }
variable "org_arns" { type = list(string), default = [] } # ex) ["arn:aws:organizations::123456789012:organization/o-abcdefghi"]
variable "ecr_account_id" { default = "123456789012" }
variable "ecr_repo" { default = "myapp/backend" }
variable "ecr_tag" { default = "latest" }
variable "dockerhub_images" { type = list(string), default = ["nginx:1.25-alpine", "busybox:1.36"] }
variable "owner" { default = "platform" }
variable "environment" { default = "STG" }
variable "service" { default = "web" }
variable "description" { default = "AL2 AMI pre-pulls ECR & Docker Hub images for faster cold start" }
locals {
name = "${var.ami_name_prefix}-${timestamp()}"
ecr_registry = "${var.ecr_account_id}.dkr.ecr.${var.region}.amazonaws.com"
ecr_image = "${local.ecr_registry}/${var.ecr_repo}:${var.ecr_tag}"
dockerhub_csv = join(" ", var.dockerhub_images)
# 공통 태그(AMI/런인스턴스)
tags = {
Name = local.name
Owner = var.owner
Environment = var.environment
Service = var.service
Description = var.description
ProvisionedBy = "Packer"
}
}
source "amazon-ebs" "al2" {
region = var.region
ami_name = local.name
instance_type = var.instance_type
ssh_username = "ec2-user"
iam_instance_profile = var.iam_instance_profile
# Amazon Linux 2 최신 AMI 필터
source_ami_filter {
filters = {
name = "amzn2-ami-hvm-*-x86_64-gp2"
root-device-type = "ebs"
virtualization-type = "hvm"
}
owners = ["137112412989"] # Amazon EKS 공식 계정
most_recent = true
}
ami_description = var.description
tags = local.tags # 최종 AMI 태그
run_tags = local.tags # 빌드 시 생성되는 리소스(EC2 등) 태그
ami_users = var.share_with_accounts # AMI를 특정 계정에 공유
ami_org_arns = var.org_arns # 또는 AWS Organization 전체 공유
launch_block_device_mappings {
device_name = "/dev/xvda"
volume_type = "gp3"
volume_size = 30
throughput = 125
delete_on_termination = true
}
aws_polling {
delay_seconds = 20
max_attempts = 200
}
}
build {
name = "al2-ecr-prepull"
sources = ["source.amazon-ebs.al2"]
provisioner "shell" {
environment_vars = [
"REGION=${var.region}",
"ECR_REGISTRY=${local.ecr_registry}",
"ECR_IMAGE=${local.ecr_image}",
"DOCKERHUB_IMAGES=${local.dockerhub_csv}"
]
inline = [
"set -euo pipefail",
# Docker 설치 및 시작 (Amazon Linux 2)
"sudo amazon-linux-extras install -y docker || sudo yum install -y docker",
"sudo systemctl enable docker",
"sudo systemctl start docker",
# ECR 로그인
"aws ecr get-login-password --region \"$REGION\" | sudo docker login --username AWS --password-stdin \"$ECR_REGISTRY\"",
# 이미지 프리풀(ECR + Docker Hub)
"sudo docker pull \"$ECR_IMAGE\"",
"for img in $DOCKERHUB_IMAGES; do sudo docker pull \"$img\"; done",
# 필요 시 부팅 시 자동시작 비활성화(환경에 맞게 결정)
"sudo systemctl disable docker || true"
]
}
post-processor "manifest" {
output = "manifest.json"
strip_path = true
custom_data = {
ecr_image = "${local.ecr_image}"
dockerhub_images = "${local.dockerhub_csv}"
environment = "${var.environment}"
}
}
}
packer { required_plugins { } }
Packer는 VM 이미지를 빌드할 때 "플러그인(빌더, 프로비저너 등)"을 사용합니다. 여기서는 Amazon 빌더(AMI 만드는 플러그인)를 쓰겠다고 선언한 것을 확인 할 수 있습니다. version 조건을 달아두면, Packer가 packer init 할 때 정확한 버전을 받아옵니다. 따라서 사용하는 개발자나 환경에 상관 없이 동일한 동작을 수행합니다. 플러그인 명시를 통해 환경 의존성을 최소화 할 수 있습니다.
locals
가공locals
에서 파생 값(예: ecr_registry
, ecr_image
, 공통 tags
)을 한 번만 계산해 재사용합니다.인스턴스가 처음 부팅될 때 이미지를 당기면 수십 초에서 수분이 걸릴 수 있습니다. AMI 안에 로컬 캐시로 이미지를 갖고 있으면 docker run 시 바로 레이어를 찾아 써서 기동이 훨씬 빨라집니다. 아래의 HCL 블록(provisioner "shell")은 **“AMI 빌드 과정에서 Docker와 컨테이너 이미지를 미리 설치·Pull해 두는 작업”**을 정의한 겁니다.
provisioner "shell" {
environment_vars = [
"REGION=${var.region}",
"ECR_REGISTRY=${local.ecr_registry}",
"ECR_IMAGE=${local.ecr_image}",
"DOCKERHUB_IMAGES=${local.dockerhub_csv}"
]
inline = [
"set -euo pipefail",
# Docker 설치 및 시작 (Amazon Linux 2)
"sudo amazon-linux-extras install -y docker || sudo yum install -y docker",
"sudo systemctl enable docker",
"sudo systemctl start docker",
# ECR 로그인
"aws ecr get-login-password --region \"$REGION\" | sudo docker login --username AWS --password-stdin \"$ECR_REGISTRY\"",
# 이미지 프리풀(ECR + Docker Hub)
"sudo docker pull \"$ECR_IMAGE\"",
"for img in $DOCKERHUB_IMAGES; do sudo docker pull \"$img\"; done",
# 필요 시 부팅 시 자동시작 비활성화(환경에 맞게 결정)
"sudo systemctl disable docker || true"
]
}
REGION
, ECR_REGISTRY
, ECR_IMAGE
, DOCKERHUB_IMAGES
값을 Packer 변수/locals에서 받아 쉘 스크립트에 전달합니다.
Amazon Linux 2 기준으로 amazon-linux-extras install docker
또는 yum install docker
통해 설치합니다. 그리고 systemctl enable/start docker
로 도커 데몬을 켜줍니다.
aws ecr get-login-password
로 얻은 토큰을 docker login
에 전달 → 해당 레지스트리(ECR)에 접근 가능해짐.
docker pull "$ECR_IMAGE"
→ 지정한 ECR 이미지 가져오기for img in $DOCKERHUB_IMAGES; do docker pull "$img"; done
→ Docker Hub 이미지들을 반복해서 가져옵니다.systemctl disable docker
→ 빌드된 AMI가 부팅될 때 자동으로 도커가 켜지지 않도록 설정 (환경에 따라 유지/삭제 선택).정리하면, 이 provisioner "shell" 블록은 Packer가 AMI를 빌드하는 중에 Docker를 설치하고, ECR/Docker Hub에서 필요한 이미지를 미리 가져와서 AMI 안에 캐시로 포함시키는 설정입니다. 그래서 최종적으로 이 AMI로 인스턴스를 띄우면 → 이미 이미지들이 로컬에 있어서 콜드 스타트 지연이 줄어든다는 효과가 있습니다.
주의할 부분은 IAM Instance Profile에 최소 AmazonEC2ContainerRegistryReadOnly
권한이 있어야 합니다. 또한, 프라이빗 Hub를 쓴다면 로그인 토큰/비밀값을 이미지에 남기지 않도록 주의하세요. 프리풀한 이미지를 바꾸면 AMI를 다시 빌드/배포해야 최신이 반영됩니다. 많이 프리풀할수록 AMI 스냅샷이 커져 EBS 스냅샷 비용이 늘 수 있습니다. 따라서 핵심 이미지 위주로 저장하도록 해야합니다.
런타임(빌드 중) 리소스는 run_tags, 결과물(AMI)엔 tags를 사용합니다.
tags
: 최종 결과물인 AMI(이미지) 에 붙는 태그.run_tags
: 빌드 중 잠깐 쓰였다가 사라지는 임시 리소스(EC2, 볼륨, 스냅샷 등) 에 붙는 태그.여러 계정(DEV/STG/PRD)에서 같은 표준 AMI를 쓰게 하려면 ami_users
, ami_org_arns
로 권한을 부여합니다. 여러 환경(DEV/STG/PRD)·여러 계정에 표준 이미지를 안전하게 배포 할 수 있습니다.
AMI를 만들 때 사용할 루트 볼륨을 gp3 로 설정하고, iops 와 throughput 을 원하는 값으로 지정하면, 빌드(프로비저닝) 속도와 부팅 속도를 끌어올리면서 과하게 비싼 타입을 쓰지 않고 비용/성능 밸런스를 맞출 수 있습니다. gp3는 볼륨 크기와 독립적으로 IOPS/Throughput을 조절할 수 있다는 게 있습니다.
*.pkr.hcl
확장자를 가진 파일(예: ami.pkr.hcl) 안에 source / build / provisioner 블록들을 작성합니다. 거기서 variable 블록으로 region, ecr_repo, dockerhub_images 같은 값들을 변수로 선언둡니다. 위에서 작성한 hcl 파일입니다.
# 1) 초기화 & 검증
packer init .
packer validate .
init
: HCL 안에서 필요한 플러그인(예: amazon-ebs) 을 다운로드해서 준비하는 단계입니다.validate
: HCL 문법/변수/구조가 맞는지 확인하는 단계입니다.# 2) 빌드
packer build \
-var "region=ap-northeast-2" \
-var "ecr_account_id=123456789012" \
-var "ecr_repo=myapp/backend" \
-var "ecr_tag=2025-08-01" \
-var 'dockerhub_images=["nginx:1.25-alpine","curlimages/curl:8.9.0"]' \
.
build
: 실제로 HCL에 정의된 source(amazon-ebs) 를 실행해서 AMI 생성합니다.-var
옵션: HCL 안에서 선언된 변수를 실행 시점에 구체적인 값으로 채워줍니다.
region
→ 만들 리전ecr_repo
, ecr_tag → 프리풀 할 ECR 이미지dockerhub_images
→ Docker Hub에서 미리 당겨둘 이미지.
은 “현재 디렉토리의 HCL 파일들을 읽어라”라는 의미Packer Build 명령어를 실행하면 아래의 순서로 작동하게 됩니다.
Jenkins는 CI/CD 파이프라인 툴이고, packer init
/ packer validate
/ packer build
는 로컬에서 사람이 치는 명령어일 뿐입니다. 따라서 Jenkinsfile 안에서는 sh 단계로 넣어서 자동화합니다.
pipeline {
agent any
stages {
stage('Build AMI') {
steps {
sh '''
packer init .
packer validate .
packer build \
-var "region=ap-northeast-2" \
-var "ecr_account_id=123456789012" \
-var "ecr_repo=myapp/backend" \
-var "ecr_tag=2025-08-01" \
-var 'dockerhub_images=["nginx:1.25-alpine","curlimages/curl:8.9.0"]' \
.
'''
}
}
}
}
이번 글에서는 Packer로 Amazon Linux 2 기반의 AMI를 만들면서 플러그인 버전 고정, 변수와 locals 패턴을 통한 가독성 개선, ECR/Docker Hub 이미지 사전 Pull을 통한 콜드 스타트 단축, 태깅과 공유 권한 설정, 스토리지 성능 최적화까지 포함한 베스트 프랙티스를 다뤘습니다. 이렇게 빌드한 AMI는 단순한 스냅샷이 아니라 서비스 구동에 필요한 도커 및 컨테이너 이미지가 준비된 “완성형 베이스 이미지”로서, CI/CD 파이프라인(Jenkins 등)에 packer init → validate → build
단계를 포함시켜 팀 단위로 일관되고 재현 가능한 배포가 가능합니다. 결과적으로 Packer는 단순한 이미지 생성 도구를 넘어 표준화된 인프라 아티팩트를 만드는 핵심 도구이며, 이를 기반으로 ASG, ECS, EKS와 같은 실행 환경에 Immutable Infrastructure 패턴을 적용할 수 있습니다.