VM을 만들 때마다 계정을 추가하고 SSH 키를 복사한 뒤 패키지를 설치한다면, 첫 부팅에 필요한 작업을 cloud-init으로 묶어 두면 편해요. 이 글에서는 Ubuntu 24.04 LTS 클라우드 이미지에 접속용 계정과 Nginx를 자동으로 구성하고, 실제 응답까지 확인합니다.
기본 실습은 Linux 관리 PC의 QEMU와 NoCloud 방식입니다. NoCloud는 별도 클라우드 서비스 없이 설정 파일을 작은 디스크로 전달하는 데이터 소스예요. DHCP로 먼저 성공시킨 다음 고정 IP로 바꾸며, AHV·VMware 등에서 설정을 전달할 때 구분할 점도 함께 정리했습니다.
이 글의 목차
먼저 구분할 것: OS 이미지와 첫 부팅 설정
cloud-init은 이미 설치된 OS의 초기 설정을 처리합니다. Ubuntu 설치 ISO의 대화형 설치를 대신하는 도구로 생각하면 출발점이 달라져요. 여기서는 cloud-init이 포함된 noble-server-cloudimg-amd64.img를 사용합니다. Ubuntu 설치 자동화의 autoinstall: 설정은 이 예제에 넣지 않습니다.
| 파일 | 담는 내용 | 이 실습의 값 |
|---|---|---|
user-data | 계정·공개 키·패키지·파일·실행 명령 | #cloud-config로 시작하는 YAML |
meta-data | 인스턴스 식별자·호스트 이름 | instance-id: lab-web-01-v1 |
network-config | NIC·IP·게이트웨이·DNS | 고정 IP 단계에서 추가 |
이 구분은 NoCloud 기준입니다. 다른 가상화 플랫폼은 메타데이터와 네트워크를 플랫폼에서 공급하기도 합니다. YAML 파일 세 개를 모든 플랫폼의 한 입력칸에 붙여 넣는 방식은 아닙니다. NoCloud의 입력 파일 설명에서 전달 구조를 확인할 수 있습니다.
목적별로 골라 쓰는 최소 예제
호스트 이름 하나만 바꾸려는데 계정과 웹 서버 설정까지 가져올 필요는 없어요. 아래 예제는 각각 한 가지 목적의 설정만 담은 독립된 파일입니다. 이미지나 플랫폼이 제공하는 기본 설정과 cloud-init의 기본 동작은 별도로 적용됩니다.
새 VM의 첫 부팅에 전달하는 예제입니다. 운영 중인 VM에 파일을 붙이고 재부팅하는 작업과는 구분합니다. 접속 계정과 공개 키가 없는 예제는 SSH 접속을 준비하지 않으므로, 이미지·플랫폼의 접속 설정을 사용하거나 공개 키 예제를 함께 적용합니다.
예제 1. hostname만 바꾸기
user-data에 아래 내용만 넣습니다. app-01을 원하는 호스트 이름으로 바꾸세요. preserve_hostname: false는 cloud-init이 호스트 이름을 설정하도록 허용합니다.
#cloud-config
preserve_hostname: false
hostname: app-01
이 예제에는 IP·패키지·사용자 설정이 없습니다. meta-data에는 instance-id만 두어 서로 다른 local-hostname 값이 섞이지 않게 합니다. 부팅 후 hostnamectl --static이 app-01인지 확인합니다. /etc/hosts 관리까지 필요하면 별도로 manage_etc_hosts 설정을 검토하세요. 공식 호스트 이름 예제를 참고했습니다.
예제 2. network IP만 고정 IP로 설정하기
NoCloud에서는 고정 IP를 별도 network-config 파일에 넣습니다. 아래는 실제 LAN에 연결하는 Ubuntu 24.04 VM의 예시 주소입니다. MAC 주소와 IP·접두사·게이트웨이·DNS를 할당받은 값으로 바꿉니다. 게이트웨이와 DNS도 넣어 같은 대역 밖의 서버와 패키지 저장소에 접근할 수 있게 했습니다.
version: 2
ethernets:
lan:
match:
macaddress: '52:54:00:12:34:56'
dhcp4: false
addresses:
- 192.168.10.50/24
routes:
- to: default
via: 192.168.10.1
nameservers:
addresses: [192.168.10.1]
파일 맨 위는 version: 2입니다. 일반 Netplan 파일처럼 바깥에 network:를 추가하지 않습니다. 호스트 이름을 지정하지 않도록 meta-data에는 인스턴스 ID만 저장합니다.
instance-id: minimal-02-static-ip-01
계정이나 패키지를 추가하지 않는 user-data는 다음처럼 빈 설정으로 둡니다. {}는 빈 YAML 매핑입니다.
#cloud-config
{}
세 파일을 같은 디렉터리에 놓고 seed를 생성합니다. 네트워크에 관한 파일을 user-data 입력칸에 붙여 넣는 방식은 아닙니다.
cloud-localds --network-config=network-config \
seed-static-only.img user-data meta-data
첫 부팅 전에 생성한 seed 디스크를 VM에 연결합니다. 접속 계정과 SSH 공개 키는 이 예제에서 만들지 않습니다. 부팅 후 ip -br address, ip route, resolvectl status에서 주소·기본 경로·DNS를 확인합니다.
이 LAN 예제의 192.168.10.x는 글 아래 QEMU 기본 user 네트워크에 그대로 쓸 수 없습니다. QEMU NAT 실습은 6번 절의 10.0.2.x 예제를 사용합니다. 네트워크 설정 문서에서도 user-data와 네트워크 입력을 구분합니다.
예제 3. 기본 계정에 SSH 공개 키만 등록하기
계정을 새로 만들지 않고 배포판의 기본 사용자에게 공개 키를 등록합니다. 아래 자리표시자를 본인의 .pub 파일 한 줄 전체로 교체합니다.
#cloud-config
ssh_authorized_keys:
- REPLACE_WITH_YOUR_SSH_PUBLIC_KEY
Ubuntu 클라우드 이미지의 기본 계정은 보통 ubuntu이며 이미지 설정에 따라 다를 수 있습니다. /etc/cloud/cloud.cfg의 system_info.default_user.name을 확인합니다. 이 설정은 SSH 비밀번호 인증이나 sudo 정책을 별도로 바꾸지 않습니다. 공식 SSH 키 예제를 참고했습니다.
예제 4. 시간대만 한국 시간으로 바꾸기
#cloud-config
timezone: Asia/Seoul
부팅 후 timedatectl에서 Time zone: Asia/Seoul인지 확인합니다. 시간대 표시를 정하는 설정이므로 NTP 서버나 시각 동기화 정책은 별도입니다.
예제 5. curl과 jq만 설치하기
#cloud-config
package_update: true
packages:
- curl
- jq
Ubuntu 24.04 기준으로 패키지 목록을 갱신하고 두 패키지를 설치합니다. DNS와 저장소 연결이 필요하며 필요한 의존 패키지도 함께 설치될 수 있습니다. 부팅 후 cloud-init 완료를 기다린 뒤 dpkg-query -W curl jq로 확인합니다.
예제 6. 설정 파일 하나만 만들기
#cloud-config
write_files:
- path: /etc/maruops-lab.conf
owner: root:root
permissions: '0644'
content: |
environment=lab
owner=infra
/etc/maruops-lab.conf는 이 예제에서 정한 파일입니다. OS가 자동으로 읽는 설정 파일은 아니며, 파일 생성 방식을 보여 줍니다. 같은 경로의 기존 파일은 덮어쓰므로 새 파일 경로를 사용합니다. 부팅 후 cat /etc/maruops-lab.conf와 stat -c '%a %U:%G' /etc/maruops-lab.conf로 내용과 644 root:root 권한을 확인합니다.
예제 7. 첫 부팅에 명령 하나만 실행하기
#cloud-config
runcmd:
- [sh, -c, 'date -Iseconds > /var/tmp/first-boot.txt']
첫 실행 시각을 파일에 저장합니다. runcmd는 인스턴스별 한 번 실행되므로 매번 재부팅할 때 기록을 갱신하지 않습니다. cat /var/tmp/first-boot.txt로 확인합니다. /tmp 정리와 겹치지 않도록 여기서는 /var/tmp를 사용했습니다. 시간대·패키지·파일·실행 명령의 설정 키는 모듈별 설정 문서를 기준으로 작성했습니다.
최소 예제를 seed로 만드는 공통 방법
다운로드 ZIP의 minimal/01-hostname부터 minimal/07-first-boot-command까지 각 폴더에 user-data와 고유 ID를 담은 meta-data가 있습니다. 필요한 폴더에서 아래 명령을 실행합니다. 여러 VM에 쓴다면 instance-id를 VM마다 다르게 정합니다.
cloud-localds seed.img user-data meta-data
고정 IP 폴더만 앞에서 설명한 --network-config=network-config 옵션을 추가합니다. 다른 항목과 조합할 때는 user-data의 #cloud-config를 맨 위에 한 번만 쓰고 필요한 키를 같은 YAML 문서에 모읍니다. 네트워크 설정은 계속 별도 파일로 둡니다.
최소 예제 7개는 cloud-init 스키마와 NoCloud seed 생성을 검사했고, 고정 IP 파일은 Netplan 변환·설정 생성까지 확인했습니다. 아래 Nginx 통합 실습과 달리 최소 예제 각각의 첫 부팅은 별도로 실행 검증하지 않았습니다.
1. 관리 PC에 도구와 SSH 키 준비
아래 명령은 Ubuntu 24.04 기반의 실습용 관리 PC에서 실행합니다. x86-64 클라우드 이미지를 부팅하며, 게스트에서 Ubuntu 패키지 저장소에 접근할 수 있어야 합니다. QEMU가 만드는 게스트 디스크에는 12GB의 논리 용량을 지정합니다.
sudo apt-get update
sudo apt-get install -y qemu-system-x86 qemu-utils cloud-image-utils \
curl openssh-client
mkdir -p ~/cloud-init-lab
cd ~/cloud-init-lab
umask 077
ssh-keygen -t ed25519 -f ./lab_ed25519 -C cloud-init-lab
cat ./lab_ed25519.pub
키 생성 중 개인 키 암호를 지정할 수 있습니다. 기존 키와 겹치지 않는 실습용 경로를 사용하세요. 다음 단계에는 lab_ed25519.pub의 한 줄 전체를 넣습니다. lab_ed25519는 개인 키이므로 user-data에 넣거나 배포하지 않습니다.
2. user-data에 계정과 서비스 정의
작업 디렉터리에 user-data 파일을 만들고 아래 내용을 저장합니다. REPLACE_WITH_YOUR_SSH_PUBLIC_KEY는 앞에서 출력한 공개 키 한 줄로 교체합니다. 파일의 첫 줄 #cloud-config와 공백 들여쓰기를 유지하고 탭은 사용하지 않습니다.
#cloud-config
hostname: lab-web-01
preserve_hostname: false
manage_etc_hosts: true
timezone: Asia/Seoul
disable_root: true
ssh_pwauth: false
users:
- name: ops
groups: [adm, sudo]
shell: /bin/bash
lock_passwd: true
sudo: ['ALL=(ALL:ALL) NOPASSWD:ALL']
ssh_authorized_keys:
- REPLACE_WITH_YOUR_SSH_PUBLIC_KEY
package_update: true
packages:
- nginx
- curl
write_files:
- path: /var/www/html/index.html
owner: root:root
permissions: '0644'
content: |
<!doctype html>
<html lang="en">
<meta charset="utf-8">
<title>cloud-init lab</title>
<h1>lab-web-01 ready</h1>
</html>
- path: /usr/local/sbin/bootstrap-web
owner: root:root
permissions: '0750'
content: |
#!/bin/sh
set -eu
nginx -t
systemctl enable --now nginx
curl --retry 5 --retry-connrefused --retry-delay 1 -fsS \
http://127.0.0.1/ | grep -Fq 'lab-web-01 ready'
install -d -m 0755 /var/lib/maruops
date -Iseconds > /var/lib/maruops/bootstrap.done
runcmd:
- [sh, -eu, /usr/local/sbin/bootstrap-web]
이 예제의 ops 계정은 비밀번호가 잠겨 있고 SSH 공개 키로 로그인합니다. ssh_pwauth: false는 SSH 비밀번호 인증을 끄는 설정이고, disable_root: true는 cloud-init의 root SSH 접근 제한을 적용합니다. 기존 이미지에 이미 들어 있는 다른 계정을 삭제하는 설정은 아닙니다.
NOPASSWD:ALL은 실습 관리 계정에 비밀번호 없이 전체 sudo 권한을 주는 선택입니다. 운영 환경에서는 관리자 키 배포와 sudo 정책을 별도로 정합니다. 비밀번호가 잠긴 상태에서 sudo의 비밀번호 요구만 켜면 필요한 관리 명령을 실행하지 못할 수 있습니다.
package_update는 패키지 목록을 갱신하고, packages는 Nginx와 curl을 설치합니다. write_files로 웹 문서와 실행 스크립트를 만들고, runcmd에서는 그 스크립트 하나를 호출합니다. 설정 파일·서비스 시작·응답 검사를 구분해 두면 실패 지점을 찾기 쉬워요.
스크립트는 set -eu로 명령 실패 시 멈춥니다. Nginx 설정 검사와 로컬 HTTP 응답 확인을 통과한 뒤에만 /var/lib/maruops/bootstrap.done을 기록합니다. 이 파일은 이 예제의 확인 표시이며 cloud-init 전체의 성공을 대신 판정하지는 않습니다. 설정 키와 실행 시점은 cloud-init 모듈 문서를 기준으로 작성했습니다.
3. meta-data와 seed 디스크 만들기
meta-data 파일에는 다음 두 줄을 저장합니다. VM을 여러 대 만든다면 instance-id와 호스트 이름을 각 VM에 맞게 정합니다. 이 식별자는 인스턴스 단위 작업을 이미 실행했는지 판단하는 데 사용됩니다.
instance-id: lab-web-01-v1
local-hostname: lab-web-01
두 파일을 같은 디렉터리에 놓고 설정 전달용 디스크를 만듭니다. 이번 단계는 별도의 네트워크 설정을 넣지 않아 cloud-init의 DHCP 기본 구성을 사용합니다.
cloud-localds seed.img user-data meta-data
ls -lh seed.img
seed.img는 OS 부팅 디스크가 아닙니다. NoCloud가 읽을 파일을 담은 별도 디스크이며 파일 이름을 유지합니다. 수동으로 ISO를 만들 때는 볼륨 라벨을 cidata로 지정하고 루트에 user-data, meta-data를 넣어야 합니다. NoCloud 디스크 생성 예제도 같은 구성을 사용합니다.
4. 클라우드 이미지로 VM 부팅
관리 PC에서 이미지를 내려받고 체크섬을 비교합니다. current 경로의 이미지와 체크섬은 갱신될 수 있으므로 같은 시점에 받아야 합니다. 체크섬 검사는 전송 중 손상과 파일 일치를 확인하며, 재현해야 할 환경이라면 받은 이미지와 체크섬을 함께 보관합니다.
curl -fLO https://cloud-images.ubuntu.com/noble/current/noble-server-cloudimg-amd64.img
curl -fLO https://cloud-images.ubuntu.com/noble/current/SHA256SUMS
awk '$2 ~ /^\*?noble-server-cloudimg-amd64\.img$/ {print}' SHA256SUMS | sha256sum -c -
qemu-img create -f qcow2 -F qcow2 \
-b "$PWD/noble-server-cloudimg-amd64.img" boot.qcow2 12G
체크섬 결과가 OK인지 확인한 뒤 진행합니다. boot.qcow2는 원본 이미지를 읽고 변경분만 저장하는 디스크입니다. 원본 파일을 이동하거나 삭제하면 부팅 디스크를 정상적으로 사용할 수 없으므로 함께 보관합니다.
다음 명령을 실행한 터미널은 VM 콘솔로 사용합니다. SSH와 HTTP 검사는 관리 PC의 다른 터미널에서 실행합니다. 관리 PC의 2222, 8080 포트가 비어 있어야 합니다.
qemu-system-x86_64 \
-machine accel=kvm:tcg -cpu max -m 1024 -smp 2 -nographic \
-drive file=boot.qcow2,if=virtio,format=qcow2 \
-drive file=seed.img,if=virtio,format=raw,readonly=on \
-netdev user,id=net0,hostfwd=tcp:127.0.0.1:2222-:22,hostfwd=tcp:127.0.0.1:8080-:80 \
-device virtio-net-pci,netdev=net0,mac=52:54:00:12:34:56
KVM을 사용할 수 없으면 TCG 소프트웨어 에뮬레이션으로 실행하므로 초기 부팅과 패키지 설치가 느릴 수 있습니다. 포트는 관리 PC의 127.0.0.1에만 연결했습니다. SSH는 127.0.0.1:2222 → VM:22, HTTP는 127.0.0.1:8080 → VM:80입니다. 구성 방식은 공식 QEMU 실습 안내를 참고했습니다.
5. SSH 접속 뒤 완료 상태와 HTTP 응답 확인
다른 터미널에서 같은 작업 디렉터리로 이동한 뒤 접속합니다. 처음에는 SSH 연결을 거부할 수 있어요. VM 콘솔에서 부팅이 진행되는지 확인하고 SSH 서비스가 올라온 뒤 다시 접속합니다. 첫 연결의 호스트 키 지문은 VM 콘솔에 출력된 지문과 대조합니다.
cd ~/cloud-init-lab
ssh -i ./lab_ed25519 -p 2222 ops@127.0.0.1
여기부터 다음 코드 블록은 게스트 VM 안에서 실행합니다. SSH 로그인이 성공해도 패키지 설치나 마지막 명령이 끝났다는 뜻은 아닙니다. cloud-init status --wait로 완료를 기다리고 상세 상태를 확인합니다.
sudo cloud-init status --wait
echo "cloud-init exit=$?"
sudo cloud-init status --long
hostnamectl --static
id ops
sudo nginx -t
systemctl is-active nginx
curl -fsS http://127.0.0.1/
sudo cat /var/lib/maruops/bootstrap.done
정상이라면 호스트 이름은 lab-web-01, 서비스 상태는 active이며 HTTP 본문에 lab-web-01 ready가 포함됩니다. 완료 파일에는 검사에 성공한 시간이 기록됩니다. 이어서 관리 PC에서도 curl -fsS http://127.0.0.1:8080/을 실행해 포트 전달까지 확인합니다.
| 종료 코드 | 판단 | 다음 확인 |
|---|---|---|
0 | cloud-init 오류 없이 완료 | 계정·서비스·실제 응답 확인 |
1 | 복구하지 못한 오류 | 실패 단계와 로그 확인 |
2 | 완료했지만 복구 가능한 오류 발생 | recoverable_errors 확인 |
종료 코드와 extended_status도 함께 읽습니다. 화면에 done이 보인다는 이유만으로 오류를 무시하지 않습니다. 이 구분은 실패 상태와 종료 코드 및 상세 상태 설명에 나와 있습니다. 확인 대상 이미지의 버전은 cloud-init --version으로 기록합니다.
6. 고정 IP는 network-config로 전달
DHCP 예제가 성공하면 고정 IP로 바꿔 볼 수 있어요. 네트워크는 user-data의 network:에 섞지 않고 NoCloud의 별도 network-config 파일로 전달합니다. 아래 파일의 맨 위는 version: 2이며 network:로 한 번 더 감싸지 않습니다.
다음 값은 위 QEMU 명령의 기본 user 네트워크 전용입니다. NIC 이름을 추측하는 대신 QEMU에 지정한 MAC 주소로 장치를 찾습니다. 실제 LAN에 연결한 VM에서는 이 주소를 그대로 사용하지 않습니다.
version: 2
ethernets:
labnic:
match:
macaddress: '52:54:00:12:34:56'
dhcp4: false
addresses:
- 10.0.2.15/24
routes:
- to: default
via: 10.0.2.2
nameservers:
addresses: [10.0.2.3]
| 항목 | QEMU 실습 값 | 브리지·가상화 플랫폼에서 바꿀 값 |
|---|---|---|
| MAC | 52:54:00:12:34:56 | 대상 VM NIC의 실제 MAC |
| 주소 | 10.0.2.15/24 | 할당받은 IP와 접두사 길이 |
| 게이트웨이 | 10.0.2.2 | 해당 서브넷의 라우터 |
| DNS | 10.0.2.3 | 환경에서 사용하는 DNS |
게스트에서 sudo poweroff를 실행하고 QEMU가 종료된 뒤, 관리 PC에서 새 시험용 디스크와 seed를 만듭니다. 사용했던 부팅 디스크를 지우지 않고 boot-static.qcow2라는 새 이름으로 만듭니다. meta-data-static에는 별도 식별자를 넣습니다.
cat > meta-data-static <<'EOF'
instance-id: lab-web-01-static-v1
local-hostname: lab-web-01
EOF
cloud-localds --network-config=network-config \
seed-static.img user-data meta-data-static
qemu-img create -f qcow2 -F qcow2 \
-b "$PWD/noble-server-cloudimg-amd64.img" boot-static.qcow2 12G
앞의 QEMU 명령에서 boot.qcow2를 boot-static.qcow2로, seed.img를 seed-static.img로 바꿔 실행합니다. 포트와 MAC 주소는 같은 값을 사용합니다. 새 VM의 호스트 키는 이전 VM과 다르므로 경고가 발생하면 콘솔에서 새 지문을 확인한 뒤 기존 [127.0.0.1]:2222 항목을 교체합니다.
접속한 게스트에서 다음 값을 확인합니다. 주소만 맞아도 기본 경로나 DNS가 빠지면 패키지 설치는 실패할 수 있습니다.
ip -br address
ip route
resolvectl status
getent hosts archive.ubuntu.com
sudo cloud-init status --long
Ubuntu 24.04의 Netplan 기반 환경을 대상으로 한 설정입니다. 다른 배포판에서는 네트워크 렌더러와 지원 항목을 확인합니다. 네트워크 설정의 입력 우선순위와 Version 2 형식를 참고하세요. 이미 운영 중인 Ubuntu의 IP를 바꾸는 작업은 Netplan 고정 IP 설정과 SSH 복구 글에서 별도로 다룹니다.
7. 적용되지 않을 때 확인하는 순서
YAML과 실제 전달된 파일 확인
cloud-init이 설치된 검사 환경에서는 배포 전에 파일 스키마를 검사할 수 있습니다. 첫 명령은 검사 환경에서, 두 번째와 세 번째 명령은 부팅한 게스트에서 실행합니다. 관리 PC에 cloud-init 명령이 없다면 이 검사를 위해 설치된 별도 실습 VM을 사용해도 됩니다.
# 배포 전: cloud-init이 설치된 검사 환경
cloud-init schema --config-file user-data --annotate
# 부팅 후: 게스트 VM
sudo cloud-init schema --system --annotate
sudo less /var/lib/cloud/instance/user-data.txt
스키마 검사는 YAML 구조와 설정 키를 확인합니다. 공개 키가 실제 키와 일치하는지, 저장소에 접속할 수 있는지, 스크립트가 성공하는지까지 보장하지는 않습니다. 수정한 파일과 게스트의 user-data.txt가 다르면 실행 명령보다 seed 디스크와 데이터 소스를 먼저 확인합니다. 공식 user-data 검사 안내도 같은 검사 명령을 제공합니다.
처리 로그와 명령 출력 구분
sudo cloud-init status --long
sudo cloud-id
sudo tail -n 100 /var/log/cloud-init-output.log
sudo tail -n 100 /var/log/cloud-init.log
sudo journalctl -b -u cloud-final.service --no-pager
sudo cat /run/cloud-init/ds-identify.log
cloud-init-output.log에서는 패키지 설치와 스크립트 출력을, cloud-init.log에서는 데이터 소스와 모듈 처리 오류를 살펴봅니다. cloud-id가 nocloud인지 확인하면 의도한 전달 방식이 선택됐는지 빠르게 좁힐 수 있어요. 데이터 소스를 못 찾았다면 ds-identify.log, seed 연결 상태, 볼륨 라벨을 확인합니다.
| 증상 | 먼저 볼 항목 | 조치 방향 |
|---|---|---|
| 공개 키 로그인 실패 | ops 사용자명·키 쌍·게스트 콘솔 | 공개 키 한 줄과 들여쓰기, SSH 포트 확인 |
| 설정이 통째로 무시됨 | #cloud-config·데이터 소스 | 첫 줄, 파일 이름, seed 연결 확인 |
| 패키지 설치 실패 | DNS·기본 경로·저장소 오류 | 네트워크를 고친 후 새 VM에서 재시험 |
| Nginx가 올라오지 않음 | cloud-init-output.log | nginx -t와 서비스 로그 확인 |
| 고정 IP 미적용 | MAC 일치·network-config | 파일 구조와 seed 재생성 여부 확인 |
| 수정 후 재부팅해도 그대로 | 인스턴스 ID·실행 주기 | 새 부팅 디스크로 재시험 |
배포판에 따라 systemd 유닛 이름이 다를 수 있습니다. 여기서 사용한 cloud-final.service는 Ubuntu 24.04 예제의 마지막 처리 단계입니다. 로그 수집 위치는 cloud-init 로그 파일 문서에서도 확인할 수 있습니다. user-data와 로그에는 인증 정보가 남을 수 있으므로 외부 공유 전에 내용을 확인합니다.
8. 재부팅·재실행·템플릿 복제 구분
runcmd는 인스턴스별 한 번 실행되는 작업입니다. user-data를 수정하고 단순히 재부팅하는 것만으로 처음부터 다시 적용되지는 않습니다. 파일을 바꿨다면 seed를 다시 만들고, 원본 클라우드 이미지에서 새 부팅 디스크를 만드는 방식이 이 실습을 반복하기에 편합니다.
VM 재부팅마다 실행해야 하는 장기 작업은 systemd 서비스나 타이머로 관리합니다. bootcmd는 더 이른 부팅 단계에서 실행되므로 패키지 설치가 끝난 뒤의 서비스 작업을 그대로 옮기기에는 맞지 않습니다. runcmd는 스크립트를 작성하고 실제 실행은 마지막 단계의 scripts_user가 처리한다는 점도 기억해 두면 로그를 읽기 편해요.
다음 초기화 명령은 폐기 가능한 실습 VM에서만 사용합니다. 운영 서버에서는 실행하지 않습니다. SSH 호스트 키나 초기 설정이 다시 만들어져 접속과 서비스에 영향을 줄 수 있습니다. 원인 분석에 필요한 로그를 먼저 보관하고, 바꾼 설정이 전달될 seed도 준비한 상태에서 사용합니다.
# 폐기 가능한 실습 VM 내부에서만 실행
sudo cloud-init clean --logs --reboot
이 명령은 cloud-init의 실행 상태를 초기화한 뒤 재부팅합니다. 전에 설치한 패키지·사용자·데이터를 원본 상태로 되돌리지는 않습니다. 따라서 깨끗한 재현 실험은 새 부팅 디스크에서 진행합니다. 공식 재실행 문서에서도 운영 시스템의 재실행을 금지하고 있습니다.
부팅한 VM을 이미지 템플릿으로 복제한다면 인스턴스별 실행 상태와 머신 ID를 정리하는 별도 준비가 필요합니다. cloud-init clean --machine-id는 그때 사용하는 옵션이며, clone별로 새로운 instance-id도 공급해야 합니다. 머신 ID 정리만으로 모든 비밀 값과 계정이 제거되는 것은 아닙니다. 세부 옵션은 clean CLI 문서와 가상화 플랫폼의 이미지 제작 절차를 확인합니다.
AHV·VMware에서 예제를 옮길 때
위 실습에서 공통으로 가져갈 부분은 user-data의 cloud-config입니다. 클라우드 이미지에 cloud-init이 있어야 하고, 해당 이미지가 플랫폼이 제공하는 데이터 소스를 지원해야 합니다. AHV의 게스트 사용자 지정 입력이나 VMware의 GuestInfo·게스트 사용자 지정 등은 전달 경로가 다르므로 VM 이미지와 플랫폼 버전에 맞춰 선택합니다.
처음에는 계정과 공개 키만 넣어 SSH 접속을 확인한 뒤 패키지와 서비스를 추가하는 편이 원인을 좁히기 좋습니다. 플랫폼에서 네트워크를 공급한다면 그 설정과 별도 seed의 설정이 경쟁하지 않게 구성합니다. NoCloud 디스크를 사용하는 경우에만 이 글의 파일 이름과 라벨 규칙을 그대로 적용합니다. 지원되는 데이터 소스는 cloud-init 데이터 소스 목록에서 확인할 수 있습니다.
예제 파일과 확인 범위
목적별 최소 예제 7개와 기존 Nginx 통합 실습의 user-data, meta-data, DHCP·고정 IP 예제를 cloud-init 실습 예제 ZIP으로 묶었습니다. 개인 키와 VM 디스크는 포함하지 않았습니다. 먼저 DHCP로 SSH와 HTTP 응답을 확인하고, 그 구성을 보관한 뒤 고정 IP나 다른 패키지를 추가해 보세요.
문서와 사진 사용 조건은 2026년 10월 22일 확인했습니다. Ubuntu 24.04 클라우드 이미지의 cloud-init 26.1-0ubuntu1~24.04.1을 QEMU TCG로 부팅해 NoCloud 인식, ops 계정의 공개 키 SSH 접속, Nginx 자동 설치, 게스트 내부·포트 전달 HTTP 응답과 완료 파일 생성을 확인했습니다. 고정 IP 예제는 cloud-init 26.2-0ubuntu1~24.04.1로 Netplan 변환과 설정 생성을 검사했습니다. 고정 IP를 적용한 첫 부팅과 AHV·VMware 배포는 실행 검증에 포함하지 않았습니다.



