OCI는 장기적인 인프라 관리의 어려움과 장기간 미사용에 대한 서버처리로 인해서
지속적으로 프로비저닝 해야하는 큰 불편이 있습니다.
해당 글은 Terraform를 활용하여 Oracle Cloud 인스턴스 생성하는 과정을 작성해보았습니다.
오라클 클라우드 계정이 없다면 아래 링크를 통하여 가입을 진행합니다.
가입 시 별도의 신용카드가 필요합니다.
https://signup.cloud.oracle.com/
Oracle Cloud Free Tier Signup
signup.cloud.oracle.com
무료 계정이 사용할 수 있는 사양은 다음과 같습니다.
AMD 기반 표준 인스턴스 (최대 2개) - 1/8 OCPU, 1GB RAM
ARM 기반 Ampere A1 인스턴스 - 최대 4 OCPU, 24GB RAM
ARM을 통하여 인스턴스 생성을 다뤄 보도록하겠습니다.
ARM은 스마트폰 기반 아키텍처이므로 호환성 문제와 도커 사용이 무리가 있습니다.
하지만, OpenClaw 같은 오픈소스 활용하기에는 제약은 없습니다.
OpenClaw 이용해서 도커나 다양한 걸 시도하고 싶으신 분은
hostinger 원클릭 서비스를 이용해보시거나 로컬, 맥미니 이용하시길 바랍니다.
https://www.hostinger.com/kr?REFERRALCODE=SN6DNLDP5WL6
Terraform 설치
https://developer.hashicorp.com/terraform/install
Install | Terraform | HashiCorp Developer
Explore Terraform product documentation, tutorials, and examples.
developer.hashicorp.com
공식 홈페이지 혹은 아래 코드를 통하여 Terraform를 설치하도록합니다.
winget install HashiCorp.Terraform
terraform -version
Terraform이 OCI API를 호출하려면 API Signing Key가 필요합니다.
# Windows PS에서 PEM 키 쌍 생성
ssh-keygen -t rsa -b 2048 -f C:\Users\(사용자이름)\.ssh\my_key.pem -N ""



my_key.pem의 텍스트를 전부 복사합니다.
(-----BEGIN PUBLIC KEY-----부터 -----END PUBLIC KEY-----까지 전부 포함해야 합니다.)
추가 후 표시되는 구성 파일 미리보기에서 다음 값 확인합니다.:
- tenancy (Tenancy OCID)
- user (User OCID)
- fingerprint
- region
최종적으로 필요한 OCID 정보는 다음과 같습니다.
Tenancy OCID: 관리 → 테넌시 세부정보
User OCID: 프로필 → 사용자 설정
Compartment OCID: ID → 구획 (root compartment = tenancy OCID)
Availability Domain: 컴퓨트 → 인스턴스 → 인스턴스 생성 화면에서 확인
OCI CLI 설치
테라폼이 오라클 계정에 로그인할 수 있도록 OCI CLI를 설치하고 설정해야 합니다.
PowerShell를 실행합니다.
# 1. 스크립트 실행 권한 허용
Set-ExecutionPolicy RemoteSigned -Scope CurrentUser
# 2. 설치 스크립트 다운로드 및 실행
powershell -ExecutionPolicy RemoteSigned -Command "iex ([(Array)(New-Object Net.WebClient).DownloadString('https://raw.githubusercontent.com/oracle/oci-cli/master/scripts/install/install.ps1')])"
oci -v
oci setup config
설치 후 oci setup config 명령어를 입력합니다.
이때 오라클 클라우드 콘솔에서 내 계정의 User OCID와 Tenancy OCID
그리고 현재 사용 중인 리전 정보를 입력해야 합니다.
설정 과정에서 API 서명 키는 이미 생성했으므로 스킵합니다.
Terraform 적용
이후 부터는 IDE 사용하여 AI 에이전트를 통해 바이브코딩을 구성하여도 좋습니다.
저는 다음과 같이 적용하였습니다.
oci-project/
├── infra/
│ ├── provider.tf # OCI 프로바이더 인증 설정
│ ├── variables.tf # compartment_id, availability_domain
│ └── main.tf # VCN, 서브넷, 보안 목록, 인스턴스
├── apps/
│ ├── provider.tf
│ └── install_apps.tf # 앱 설치 (openclaw, paperclip 기타 오픈소스)
└── connect.rdp # RDP 접속 파일
provider.tf
provider.tf는 OCI의 계정 정보와 인증 정보를 담습니다.
실제 운영 시에는 OCID, fingerprint를 terraform.tfvars나 환경변수로 분리하는것을 권고합니다.
# infra/provider.tf
provider "oci" {
tenancy_ocid = "ocid1.tenancy.oc1..aaaa..." # Tenancy OCID
user_ocid = "ocid1.user.oc1..aaaa..." # User OCID
fingerprint = "9d:bd:22:ef:f8:b7:45:ff:..." # API 키 fingerprint
private_key_path = "C:/Users/(사용자이름)/.ssh/my_key.pem" # API 서명용 PEM 키
region = "ap-seoul-1" # 홈 리전
}
variables.tf
variables.tf는 입력하는 변수에 대해서 지정합니다.
상시 변화되는 값을 이곳에 분리 하여 저장하도록 관리 하였습니다.
여기서 인스턴스 가용성 도메인(AD)은 인스턴스 생성할 리전을 의미합니다.
국내에서 생성 할 수 있는 무료 인스턴스가 한정 되어있기 때문에 오류가 발생한다면
일본으로 변경하거나 다른 지역으로 변경해야합니다.
variable "compartment_id" {
description = "OCI Compartment OCID (기본값: root tenancy)"
default = "ocid1.tenancy.oc1..aaaa..."
}
variable "availability_domain" {
description = "인스턴스 가용성 도메인"
default = "GQyV:AP-SEOUL-1-AD-1"
}
가용성 도메인 확인 방법
OCI 콘솔 또는 CLI로 확인:
oci iam availability-domain list --compartment-id <테넌시_ocid>
main.rf
main은 VCN, 서브넷, 보안 목록, 인스턴스 항목을 정의 합니다.
1. 프로바이더 설정 (OCI)
2. VCN 및 서브넷 생성
3. 인터넷 게이트웨이 및 라우팅 테이블 연결
4. 보안 목록(Security List) 생성 (SSH, RDP 포트 개방)
5. Compute Instance (ARM) 생성 및 user_data (설치 스크립트) 적용
사용자 요구사항에 맞게 설정 하도록 합니다.

# 1. 가상 네트워크 및 기본 보안 설정
resource "oci_core_vcn" "paperclip_vcn" {
compartment_id = var.compartment_id
cidr_block = "10.0.0.0/16"
display_name = "paperclip-vcn"
dns_label = "paperclip"
}
resource "oci_core_internet_gateway" "ig" {
compartment_id = var.compartment_id
vcn_id = oci_core_vcn.paperclip_vcn.id
display_name = "internet-gateway"
}
resource "oci_core_default_route_table" "rt" {
manage_default_resource_id = oci_core_vcn.paperclip_vcn.default_route_table_id
route_rules {
destination = "0.0.0.0/0"
network_entity_id = oci_core_internet_gateway.ig.id
}
}
resource "oci_core_security_list" "paperclip_sl" {
compartment_id = var.compartment_id
vcn_id = oci_core_vcn.paperclip_vcn.id
display_name = "paperclip-security-list"
ingress_security_rules { # SSH
protocol = "6"
source = "0.0.0.0/0"
tcp_options {
min = 22
max = 22
}
}
ingress_security_rules { # Paperclip UI
protocol = "6"
source = "0.0.0.0/0"
tcp_options {
min = 3100
max = 3100
}
}
# RDP 접속을 위한 3389 포트
ingress_security_rules {
protocol = "6"
source = "0.0.0.0/0"
description = "Allow RDP"
tcp_options {
min = 3389
max = 3389
}
}
ingress_security_rules { # OpenClaw Gateway
protocol = "6"
source = "0.0.0.0/0"
description = "Allow OpenClaw Gateway"
tcp_options {
min = 18789
max = 18789
}
}
ingress_security_rules { # HTTPS — OpenClaw Control UI
protocol = "6"
source = "0.0.0.0/0"
description = "Allow HTTPS (OpenClaw)"
tcp_options {
min = 443
max = 443
}
}
ingress_security_rules { # HTTPS — Paperclip
protocol = "6"
source = "0.0.0.0/0"
description = "Allow HTTPS (Paperclip)"
tcp_options {
min = 3443
max = 3443
}
}
egress_security_rules {
protocol = "all"
destination = "0.0.0.0/0"
}
}
resource "oci_core_subnet" "paperclip_subnet" {
cidr_block = "10.0.1.0/24"
compartment_id = var.compartment_id
vcn_id = oci_core_vcn.paperclip_vcn.id
display_name = "paperclip-subnet"
dns_label = "subnet1"
security_list_ids = [oci_core_security_list.paperclip_sl.id]
}
# 인스턴스 생성 (Ubuntu 24.04 ARM)
resource "oci_core_instance" "paperclip_arm" {
availability_domain = var.availability_domain
compartment_id = var.compartment_id
shape = "VM.Standard.A1.Flex"
shape_config {
ocpus = 4
memory_in_gbs = 24
}
source_details {
source_type = "image"
source_id = "<OCI 콘솔에서 확인한 Ubuntu 24.04 ARM 이미지 OCID>"
}
create_vnic_details {
subnet_id = oci_core_subnet.paperclip_subnet.id
assign_public_ip = true
}
metadata = {
ssh_authorized_keys = file("<SSH 공개키 경로, 예: ~/.ssh/my_key.pem.pub>")
# Ubuntu 24.04 ARM용 자동 설치 스크립트 (오류 대비 강화)
# ubuntu-desktop + xrdp 전용 구성
user_data = base64encode(<<-EOT
#!/bin/bash
# ── 로그 설정: 모든 출력을 파일 + 콘솔에 동시 기록 ──
LOG_FILE="/var/log/setup-script.log"
exec > >(tee -a "$LOG_FILE") 2>&1
echo "=== 설치 시작: $(date '+%Y-%m-%d %H:%M:%S') ==="
# ════════════════════════════════════════════════════════════
# 유틸리티 함수 정의
# ════════════════════════════════════════════════════════════
# apt 재시도 함수 (최대 5회, 실패마다 apt-get update 재실행)
apt_install_with_retry() {
local pkgs="$*"
local max=5
local n=1
while [ $n -le $max ]; do
echo "[apt] 시도 $n/$max: $pkgs"
if apt-get install -y --fix-missing \
-o Dpkg::Options::="--force-confold" \
-o Dpkg::Options::="--force-confdef" \
-o Acquire::Retries=3 \
$pkgs; then
echo "[apt] 성공: $pkgs"
return 0
fi
echo "[apt] 실패($n/$max). 저장소 재갱신 후 60초 대기..."
# broken 패키지 복구 후 재갱신
dpkg --configure -a 2>/dev/null || true
apt-get -f install -y 2>/dev/null || true
apt-get update --fix-missing -o Acquire::Retries=3 || true
sleep 60
n=$((n + 1))
done
echo "[apt] 최종 실패: $pkgs → dpkg 직접 설치 방식으로 전환"
return 1 # 호출부에서 dpkg 폴백 분기 처리
}
# dpkg 직접 설치 함수 (공식 Launchpad/GitHub releases에서 .deb 다운로드)
dpkg_install_from_url() {
local name="$1"
local url="$2"
local deb="/tmp/$${name}.deb"
echo "[dpkg] $name .deb 직접 다운로드: $url"
local max=5
local n=1
while [ $n -le $max ]; do
if curl -fsSL --retry 3 --retry-delay 10 -o "$deb" "$url"; then
echo "[dpkg] 다운로드 완료 → dpkg -i 설치 시도"
dpkg -i "$deb" && echo "[dpkg] 설치 성공: $name" && return 0
echo "[dpkg] dpkg 실패, 의존성 해결 시도..."
apt-get -f install -y || true
dpkg -i "$deb" && echo "[dpkg] 재시도 성공: $name" && return 0
fi
echo "[dpkg] 다운로드 실패($n/$max). 30초 후 재시도..."
sleep 30
n=$((n + 1))
done
echo "[dpkg] 최종 실패: $name"
return 1
}
# systemctl 안전 실행 (서비스 존재 여부 먼저 확인)
safe_systemctl() {
local action="$1"
local svc="$2"
if systemctl list-unit-files --type=service 2>/dev/null | grep -q "^$${svc}"; then
systemctl "$action" "$svc" \
&& echo "[systemctl] $svc $action 완료" \
|| echo "[systemctl] $svc $action 실패 (무시)"
else
echo "[systemctl] '$svc' 서비스 없음 → 건너뜀"
fi
}
# ════════════════════════════════════════════════════════════
# 1. 환경 변수 (팝업 방지)
# ════════════════════════════════════════════════════════════
export DEBIAN_FRONTEND=noninteractive
export UCF_FORCE_CONFOLD=1
export NEEDRESTART_MODE=a
timedatectl set-timezone Asia/Seoul || echo "[tz] 시간대 설정 실패 (무시)"
# ════════════════════════════════════════════════════════════
# 2. 스왑 메모리 (4GB)
# ════════════════════════════════════════════════════════════
echo "=== 스왑 설정 ==="
if [ ! -f /swapfile ]; then
fallocate -l 4G /swapfile \
|| dd if=/dev/zero of=/swapfile bs=1M count=4096 status=progress
chmod 600 /swapfile
mkswap /swapfile
swapon /swapfile
echo '/swapfile none swap sw 0 0' >> /etc/fstab
echo "[swap] 완료"
else
echo "[swap] 스왑파일 이미 존재 → 건너뜀"
fi
sysctl vm.swappiness=10 || true
echo 'vm.swappiness=10' >> /etc/sysctl.conf
# ════════════════════════════════════════════════════════════
# 3. iptables 방화벽 규칙 설정 (OCI 기본 정책: INPUT DROP 대비)
# - OCI Ubuntu 이미지는 기본 iptables 정책이 DROP이므로
# SSH(22), RDP(3389) 포트를 명시적으로 허용해야 함
# ════════════════════════════════════════════════════════════
echo "=== iptables 방화벽 규칙 설정 ==="
# SSH(22) 허용 - 최우선 삽입 (잠기지 않도록 보장)
iptables -I INPUT 1 -p tcp --dport 22 -j ACCEPT \
&& echo "[iptables] SSH(22) 허용 완료" \
|| echo "[iptables] SSH(22) 규칙 추가 실패"
# RDP(3389) 허용
iptables -I INPUT 2 -p tcp --dport 3389 -j ACCEPT \
&& echo "[iptables] RDP(3389) 허용 완료" \
|| echo "[iptables] RDP(3389) 규칙 추가 실패"
# ESTABLISHED/RELATED 허용 (응답 패킷 허용)
iptables -I INPUT 3 -m state --state ESTABLISHED,RELATED -j ACCEPT \
|| true
# loopback 허용
iptables -I INPUT 4 -i lo -j ACCEPT \
|| true
# 규칙 영구 저장 (재부팅 후에도 유지)
mkdir -p /etc/iptables
iptables-save > /etc/iptables/rules.v4 \
&& echo "[iptables] 규칙 영구 저장 완료 (/etc/iptables/rules.v4)" \
|| echo "[iptables] 규칙 저장 실패"
# 재부팅 시 자동 복원을 위한 rc.local 등록
if [ ! -f /etc/rc.local ]; then
echo '#!/bin/bash' > /etc/rc.local
chmod +x /etc/rc.local
fi
grep -q 'iptables-restore' /etc/rc.local \
|| echo 'iptables-restore < /etc/iptables/rules.v4' >> /etc/rc.local
echo "[iptables] 현재 INPUT 규칙:"
iptables -L INPUT -n --line-numbers 2>/dev/null | head -20 || true
# ════════════════════════════════════════════════════════════
# 4. apt-get update (최대 5회 재시도)
# ════════════════════════════════════════════════════════════
echo "=== apt-get update ==="
for i in 1 2 3 4 5; do
if apt-get update --fix-missing -o Acquire::Retries=3; then
echo "[update] 성공"
break
fi
echo "[update] 실패($i/5). 30초 후 재시도..."
sleep 30
done
# 선행 필수 도구 설치
apt_install_with_retry ca-certificates curl gnupg lsb-release apt-transport-https \
|| true
# 기존 패키지 업그레이드
apt-get upgrade -y \
-o Dpkg::Options::="--force-confold" \
-o Dpkg::Options::="--force-confdef" \
|| echo "[upgrade] 경고 발생 (무시하고 계속)"
# ════════════════════════════════════════════════════════════
# 4. ubuntu-desktop 설치
# 실패 시: universe 저장소 추가 후 재시도 → 최종 실패 시 기록
# ════════════════════════════════════════════════════════════
echo "=== ubuntu-desktop 설치 (10~20분 소요) ==="
DESKTOP_OK=false
# 1차: 기본 저장소로 시도
if apt_install_with_retry ubuntu-desktop; then
DESKTOP_OK=true
echo "[desktop] apt(기본 저장소) 설치 성공"
else
echo "[desktop] 기본 저장소 실패 → universe/PPA 저장소 추가 후 재시도"
# 2차: universe 저장소 활성화 후 재시도
add-apt-repository -y universe || true
add-apt-repository -y multiverse || true
apt-get update --fix-missing -o Acquire::Retries=3 || true
if apt_install_with_retry ubuntu-desktop; then
DESKTOP_OK=true
echo "[desktop] apt(universe 저장소) 설치 성공"
else
echo "[desktop] 저장소 방식 모두 실패 → 최종 실패로 기록"
DESKTOP_OK=false
fi
fi
# ════════════════════════════════════════════════════════════
# 5. xrdp 설치
# 실패 시 1: Launchpad PPA(neutrinolabs) 추가 후 재시도
# 실패 시 2: GitHub releases에서 .deb 직접 다운로드 → dpkg 설치
# ════════════════════════════════════════════════════════════
echo "=== xrdp 설치 ==="
XRDP_OK=false
# 1차: 기본 apt 시도
if apt_install_with_retry xrdp; then
XRDP_OK=true
echo "[xrdp] apt(기본) 설치 성공"
else
echo "[xrdp] 기본 apt 실패 → neutrinolabs PPA 추가 후 재시도"
# 2차: 공식 PPA 추가
add-apt-repository -y ppa:neutrinolabs/xrdp || true
apt-get update --fix-missing -o Acquire::Retries=3 || true
if apt_install_with_retry xrdp; then
XRDP_OK=true
echo "[xrdp] PPA 설치 성공"
else
echo "[xrdp] PPA도 실패 → GitHub releases에서 .deb 직접 설치"
# 3차: GitHub releases에서 최신 .deb 직접 다운로드
# ARM64(aarch64) 대상 .deb 파일을 neutrinolabs/xrdp 공식 릴리스에서 획득
XRDP_VER="0.10.1"
XRDP_DEB_URL="https://github.com/neutrinolabs/xrdp/releases/download/v$${XRDP_VER}/xrdp_$${XRDP_VER}-1_arm64.deb"
if dpkg_install_from_url "xrdp" "$XRDP_DEB_URL"; then
XRDP_OK=true
echo "[xrdp] .deb 직접 설치 성공"
else
echo "[xrdp] 모든 설치 방법 실패 → 수동 확인 필요"
XRDP_OK=false
fi
fi
fi
# xrdp 서비스 활성화 (설치 성공 시)
if [ "$XRDP_OK" = true ] && command -v xrdp > /dev/null 2>&1; then
safe_systemctl enable xrdp
safe_systemctl start xrdp
# ssl-cert 그룹에 xrdp 추가 (TLS 인증서 접근 권한)
usermod -aG ssl-cert xrdp 2>/dev/null \
|| adduser xrdp ssl-cert 2>/dev/null \
|| echo "[xrdp] ssl-cert 그룹 추가 실패 (무시)"
echo "[xrdp] 서비스 활성화 완료"
fi
# ════════════════════════════════════════════════════════════
# 6. RDP 접속용 비밀번호 설정
# ════════════════════════════════════════════════════════════
echo "ubuntu:<RDP 접속용 비밀번호>" | chpasswd \
&& echo "[passwd] ubuntu 비밀번호 설정 완료" \
|| echo "[passwd] 비밀번호 설정 실패"
# ════════════════════════════════════════════════════════════
# 7. Docker 설치
# 1차: get.docker.com 공식 스크립트
# 2차: apt docker.io (Ubuntu 공식 패키지)
# 3차: GitHub releases에서 docker-ce .deb 직접 설치
# ════════════════════════════════════════════════════════════
echo "=== Docker 설치 ==="
DOCKER_OK=false
if command -v docker > /dev/null 2>&1; then
DOCKER_OK=true
echo "[docker] 이미 설치됨 → 건너뜀"
else
# 1차: 공식 install 스크립트
echo "[docker] get.docker.com 스크립트 시도..."
if curl -fsSL --retry 5 --retry-delay 10 https://get.docker.com -o /tmp/get-docker.sh \
&& sh /tmp/get-docker.sh; then
DOCKER_OK=true
echo "[docker] 공식 스크립트 설치 성공"
else
echo "[docker] 공식 스크립트 실패 → apt docker.io 시도"
# 2차: Ubuntu 공식 패키지
if apt_install_with_retry docker.io docker-compose-plugin; then
DOCKER_OK=true
echo "[docker] apt(docker.io) 설치 성공"
else
echo "[docker] apt 실패 → Docker CE 공식 저장소 추가 후 재시도"
# 3차: Docker 공식 apt 저장소 직접 구성
install -m 0755 -d /etc/apt/keyrings
curl -fsSL https://download.docker.com/linux/ubuntu/gpg \
| gpg --dearmor -o /etc/apt/keyrings/docker.gpg || true
chmod a+r /etc/apt/keyrings/docker.gpg
DISTRO_CODENAME=$(lsb_release -cs 2>/dev/null || echo "noble")
echo \
"deb [arch=arm64 signed-by=/etc/apt/keyrings/docker.gpg] \
https://download.docker.com/linux/ubuntu $DISTRO_CODENAME stable" \
> /etc/apt/sources.list.d/docker.list
apt-get update --fix-missing -o Acquire::Retries=3 || true
if apt_install_with_retry docker-ce docker-ce-cli containerd.io docker-compose-plugin; then
DOCKER_OK=true
echo "[docker] Docker CE 공식 저장소 설치 성공"
else
echo "[docker] 모든 방법 실패 → 수동 확인 필요"
fi
fi
fi
fi
if [ "$DOCKER_OK" = true ] && command -v docker > /dev/null 2>&1; then
usermod -aG docker ubuntu || true
safe_systemctl enable docker
safe_systemctl start docker
echo "[docker] 완료: $(docker --version)"
fi
# ════════════════════════════════════════════════════════════
# 8. 설치 완료 상태 기록
# ════════════════════════════════════════════════════════════
STATUS_FILE="/home/ubuntu/setup_done.txt"
{
echo "=== 설치 완료: $(date '+%Y-%m-%d %H:%M:%S') ==="
echo ""
echo "[ubuntu-desktop] $(dpkg -l ubuntu-desktop 2>/dev/null | grep '^ii' | awk '{print "installed: "$3}' || echo 'not installed')"
echo "[xrdp] $(systemctl is-active xrdp 2>/dev/null || echo 'not found') / $(dpkg -l xrdp 2>/dev/null | grep '^ii' | awk '{print $3}' || echo 'not installed')"
echo "[docker] $(docker --version 2>/dev/null || echo 'not installed')"
echo ""
echo "전체 로그: $LOG_FILE"
echo ""
echo "접속 확인 명령어 (SSH 접속 후):"
echo " systemctl status xrdp"
echo " tail -f /var/log/setup-script.log"
echo " tail -f /var/log/cloud-init-output.log"
} > "$STATUS_FILE"
chown ubuntu:ubuntu "$STATUS_FILE"
echo "=== 스크립트 완료: $(date '+%Y-%m-%d %H:%M:%S') ==="
EOT
)
}
}
# ════════════════════════════════════════════════════════════
# Outputs
# ════════════════════════════════════════════════════════════
output "instance_public_ip" {
description = "인스턴스 공인 IP (apps/install_apps.tf의 server_ip에 입력)"
value = oci_core_instance.paperclip_arm.public_ip
}
output "instance_id" {
description = "인스턴스 OCID"
value = oci_core_instance.paperclip_arm.id
}
제가 구성했던 포트는 다음과 같습니다.
22 - SSH 접속
443 - OpenClaw HTTPS (nginx 프록시)
3100 - Paperclip HTTP (직접 또는 내부)
3389 - RDP 원격 데스크톱
3443 - Paperclip HTTPS (nginx 프록시)
18789 -OpenClaw Gateway (원본 HTTP)
인스턴스 이미지의 OCID 찾는 방법은 다음과 같습니다.
OCI 콘솔 → 컴퓨트 → 이미지 → 플랫폼 이미지 필터 → Ubuntu 24.04 aarch64 선택 → OCID 복사
또는
oci compute image list --compartment-id <compartment_ocid> `
--operating-system "Canonical Ubuntu" `
--shape "VM.Standard.A1.Flex" `
--sort-by TIMECREATED --sort-order DESC `
--query "data[0].id" --raw-output
cloud-init 서버 초기 설정
핵심 설계 원칙은 모든 설치 단계에 3중 폴백을 적용했습니다.
OCI ARM 인스턴스는 패키지 미러 불안정이 잦아서, 한 가지 방법만 쓰면 실패 확률이 높기 때문입니다.
로그 설정: `/var/log/setup-script.log`에 전체 기록
유틸리티 함수: `apt_install_with_retry` (5회 재시도), `dpkg_install_from_url` (폴백), `safe_systemctl`
환경 변수: `DEBIAN_FRONTEND=noninteractive` (팝업 방지)
스왑: 4GB 스왑파일 생성 (메모리 부족 대비)
iptables: SSH(22), RDP(3389) 포트 열기 + 영구 저장
apt 업데이트: 5회 재시도, 의존성 자동 복구
ubuntu-desktop: GUI 데스크톱 설치 (RDP 접속용)
xrdp: RDP 서버 (1차 apt → 2차 PPA → 3차 .deb 직접 설치)
Docker: 1차 get.docker.com → 2차 apt → 3차 공식 저장소
비밀번호: ubuntu 계정 비밀번호 설정 (RDP 로그인용)
로그 확인:
# SSH 접속 후
tail -f /var/log/setup-script.log # 커스텀 스크립트 로그
tail -f /var/log/cloud-init-output.log # cloud-init 전체 로그
cat /home/ubuntu/setup_done.txt # 설치 완료 요약
Terraform 실행
cd C:\oci-project\infra
# 프로바이더 다운로드
terraform init
# 변경 사항 미리 확인
terraform plan
# 인프라 생성 (VCN + 서브넷 + 인스턴스)
terraform apply
apply 완료까지 약 3-5분 소요되며.
cloud-init은 백그라운드에서 10-20분 추가 실행됩니다.
# 생성된 IP 확인
terraform output instance_public_ip
# SSH 접속 확인
ssh -i C:\Users\jh\.ssh\my_key.pem ubuntu@<IP>
기타 사항
OCI Always Free ARM 인스턴스가 안 만들어질 때
Out of capacity 에러가 뜬다면 해당 리전에 ARM 자원이 없다는 것입니다.
- 다른 Availability Domain 시도 (서울은 1개뿐이라 해당 없음)
- 시간대를 바꿔서 재시도 (새벽 시간에 자원 반납이 많음)
- shape_config에서 OCPU/메모리를 줄여서 시도 (1 OCPU / 6GB로 시작)
- 자동 재시도 스크립트 활용
iptables
OCI Ubuntu 이미지는 기본 iptables 정책이 INPUT DROP 입니다.
Security List에서 포트를 열어도 OS 레벨에서 차단되므로, 반드시 iptables도 설정해야 합니다.
# REJECT 규칙보다 앞에 ACCEPT 삽입
sudo iptables -I INPUT 1 -p tcp --dport 443 -j ACCEPT
sudo iptables-save | sudo tee /etc/iptables.rules
Terraform 상태 파일
terraform.tfstate는 로컬에 저장되므로, 이 파일이 없으면 Terraform은 기존 리소스를 인식하지 못합니다.
# .gitignore에 반드시 추가
*.tfstate
*.tfstate.backup
.terraform/
다음글은 완전 무료로 사용이 가능한
OpenClaw + Github Copilot (GPT5-mini) 모델 설치와 Paperclip 연동에 대해서 작성해보겠습니다.
'Cloud' 카테고리의 다른 글
| codespace 원격 확장 호스트가 지난 5분 동안 3번 예기치 않게 종료되었습니다. (0) | 2026.04.09 |
|---|---|
| 무료 추천 클라우드 활용, 총 700$ 크레딧 4개월 혜택 받는 법 (0) | 2021.11.15 |
| 오라클 클라우드 사용법 (0) | 2021.11.05 |
| Oracle Cloud 가상 머신 생성 및 접속 (0) | 2021.11.02 |