월간 골든 이미지 빌드 프로세스 (Packer 및 Ansible 사용)

발행: (2025년 12월 23일 오후 01:43 GMT+9)
9 분 소요
원문: Dev.to

Source: Dev.to

위에 제공된 소스 링크 외에 번역할 텍스트가 포함되어 있지 않습니다. 번역하고자 하는 전체 내용을 제공해 주시면 한국어로 번역해 드리겠습니다.

소개

IT 운영에서, 매달 수백 개의 EC2 인스턴스를 배포하는 클라우드 조직에서 일하고 있다고 상상해 보세요. 각 인스턴스는 보안과 규정 준수를 만족해야 합니다. 각 인스턴스를 수동으로 구성하는 것은 악몽과 같습니다. 대신, 골든 이미지 — 미리 강화되고 필요한 모든 도구가 포함된 재사용 가능한 AMI — 를 원하게 됩니다.

이때 Packer가 관련됩니다.

문제 설명

대부분의 조직에서는 다양한 워크로드를 지원하기 위해 EC2 인스턴스를 자주 시작합니다. 하지만 각 인스턴스는 다음을 만족해야 합니다:

  • 보안 및 규정 준수
  • 모니터링 및 보안 에이전트 설치
  • 일관된 구성

실시간 시나리오

보안에 민감한 기업의 일원이라고 가정해 보세요. 모든 EC2 인스턴스는 다음을 반드시 충족해야 합니다:

  • CIS 벤치마크 준수
  • CrowdStrike 및 Qualys 에이전트 설치

인스턴스를 시작한 뒤 개별적으로 설정하는 대신, 이미 하드닝되고 프로비저닝된 골든 AMI를 만들고자 합니다. 이 이미지는 향후 모든 배포의 기반이 되어 시간 절약과 일관성을 보장합니다.

사용된 도구

  • Packer
  • AWS EC2
  • Ansible
  • GitLab CI/CD
  • Amazon SSM

아키텍처 다이어그램

Architecture Diagram

워크플로우 개요

  1. 기본 이미지에서 임시 EC2 인스턴스를 시작합니다.
  2. 프로비저닝 스크립트를 실행하여:
    • OS 하드닝 적용 (CIS 벤치마크, 방화벽 규칙, SSH 설정).
  3. 구성된 인스턴스에서 새 AMI를 생성합니다.
  4. 임시 인스턴스를 종료합니다.

구현 단계

단계 1 – Packer 설치

Packer 설치 스크린샷

단계 2 – Ansible 프로비저닝이 포함된 Packer 템플릿 만들기

다음 Packer 템플릿은 지정된 SSH 키 페어를 사용해 보안 접근을 수행하면서 특정 AWS VPC 및 서브넷에서 임시 EC2 인스턴스를 시작하고, 맞춤형 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 메인 플레이북

---
- 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
---
- hosts: all
  become: true
  gather_facts: true
  vars_files:
    - ../vars/useradd.yml
    - ../vars/vault.yml
  roles:
    - ../roles/useradd

결과

이 템플릿은 다음과 같이 커스텀 AMI를 생성합니다:

  • 특정 VPC와 서브넷에서 VM을 시작합니다.
  • 접근을 위해 정의된 SSH 키 페어를 사용합니다.
  • 셸 스크립트와 Ansible을 실행하여 인스턴스를 구성합니다.
  • 최종 이미지를 고유한 이름으로 저장하여 이후에 사용할 수 있게 합니다.

참고: 저는 또한 GitLab에 CI/CD 변수를 설정하여 Packer 빌드에서 SSH 접근에 사용되는 프라이빗 키 내용을 안전하게 저장했습니다.

GitLab CI/CD 변수(프라이빗 키)

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 생성 프로세스를 자동화합니다.

  1. CI/CD 변수에서 SSH 개인 키를 안전하게 주입합니다.
  2. Packer 템플릿을 검증하고 빌드합니다.
  3. 특정 VPC/서브넷에 EC2 인스턴스를 프로비저닝합니다.
  4. 최종 이미지를 저장하여 향후 사용합니다.
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

Back to Blog

관련 글

더 보기 »